programgarden-core 1.26.0__tar.gz → 1.28.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/PKG-INFO +1 -1
  2. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/bases/listener.py +93 -0
  3. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/base.py +19 -0
  4. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/pyproject.toml +1 -1
  5. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/README.md +0 -0
  6. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/__init__.py +0 -0
  7. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/bases/__init__.py +0 -0
  8. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/bases/client.py +0 -0
  9. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/bases/components.py +0 -0
  10. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/bases/mixins.py +0 -0
  11. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/bases/products.py +0 -0
  12. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/bases/sql.py +0 -0
  13. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/bases/storage.py +0 -0
  14. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/code_node.py +0 -0
  15. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/exceptions.py +0 -0
  16. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/expression/__init__.py +0 -0
  17. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/expression/evaluator.py +0 -0
  18. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/i18n/__init__.py +0 -0
  19. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/i18n/locales/en.json +0 -0
  20. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/i18n/locales/ko.json +0 -0
  21. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/i18n/translator.py +0 -0
  22. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/korea_alias.py +0 -0
  23. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/__init__.py +0 -0
  24. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/connection_rule.py +0 -0
  25. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/credential.py +0 -0
  26. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/edge.py +0 -0
  27. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/event.py +0 -0
  28. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/exchange.py +0 -0
  29. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/field_binding.py +0 -0
  30. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/job.py +0 -0
  31. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/order_diagnostics.py +0 -0
  32. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/plugin_resource.py +0 -0
  33. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/resilience.py +0 -0
  34. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/resource.py +0 -0
  35. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/validation.py +0 -0
  36. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/models/workflow.py +0 -0
  37. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/__init__.py +0 -0
  38. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/account_futures.py +0 -0
  39. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/account_korea_stock.py +0 -0
  40. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/account_stock.py +0 -0
  41. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/ai.py +0 -0
  42. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/backtest.py +0 -0
  43. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/backtest_futures.py +0 -0
  44. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/backtest_korea_stock.py +0 -0
  45. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/backtest_stock.py +0 -0
  46. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/broker.py +0 -0
  47. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/calculation.py +0 -0
  48. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/code.py +0 -0
  49. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/condition.py +0 -0
  50. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/data.py +0 -0
  51. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/data_futures.py +0 -0
  52. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/data_korea_stock.py +0 -0
  53. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/data_stock.py +0 -0
  54. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/display.py +0 -0
  55. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/event.py +0 -0
  56. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/fundamental_korea_stock.py +0 -0
  57. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/fundamental_stock.py +0 -0
  58. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/infra.py +0 -0
  59. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/market_external.py +0 -0
  60. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/market_status.py +0 -0
  61. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/open_orders_futures.py +0 -0
  62. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/open_orders_korea_stock.py +0 -0
  63. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/open_orders_stock.py +0 -0
  64. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/order.py +0 -0
  65. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/portfolio.py +0 -0
  66. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/realtime_futures.py +0 -0
  67. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/realtime_korea_stock.py +0 -0
  68. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/realtime_stock.py +0 -0
  69. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/risk.py +0 -0
  70. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/symbol.py +0 -0
  71. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/symbol_futures.py +0 -0
  72. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/symbol_korea_stock.py +0 -0
  73. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/symbol_stock.py +0 -0
  74. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/nodes/trigger.py +0 -0
  75. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/presets/__init__.py +0 -0
  76. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/presets/news_analyst.json +0 -0
  77. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/presets/risk_manager.json +0 -0
  78. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/presets/strategist.json +0 -0
  79. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/presets/technical_analyst.json +0 -0
  80. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/registry/__init__.py +0 -0
  81. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/registry/credential_registry.py +0 -0
  82. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/registry/node_registry.py +0 -0
  83. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/registry/plugin_registry.py +0 -0
  84. {programgarden_core-1.26.0 → programgarden_core-1.28.0}/programgarden_core/retry_executor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programgarden-core
3
- Version: 1.26.0
3
+ Version: 1.28.0
4
4
  Summary: ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Author: 프로그램동산
@@ -343,6 +343,13 @@ class WorkflowPnLEvent:
343
343
  monetary_status: Optional[str] = None
344
344
  monetary_basis: Optional[str] = None
345
345
  monetary_unavailable_reason: Optional[str] = None
346
+ # 워크플로우 **비율**을 못 낸 사유. 값을 비우는 것만으로는 "안 샀다" 와 "증권사가
347
+ # 평단·현재가를 안 보냈다" 가 구분되지 않는다 — 서버가 이 사유를 그대로 적재한다.
348
+ # 어휘: no_workflow_positions | basis_unreported | price_unreported | no_tracker |
349
+ # computation_failed (dsl-api RATE_UNAVAILABLE_REASONS 와 공용).
350
+ # 🔴 monetary_* 를 재사용하지 않는다 — 그건 선물 전용 계약이고, 주식 이벤트에서
351
+ # monetary_status is None 임을 고정하는 테스트가 있다.
352
+ workflow_rate_unavailable_reason: Optional[str] = None
346
353
  pnl_by_currency: Dict[str, Any] = field(default_factory=dict)
347
354
  monetary_positions: Dict[str, Any] = field(default_factory=dict)
348
355
  unavailable_position_count: int = 0
@@ -585,6 +592,67 @@ class RestartEvent:
585
592
  timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
586
593
 
587
594
 
595
+ # ============================================================
596
+ # 체결 확정 이벤트
597
+ # ============================================================
598
+
599
+ @dataclass
600
+ class OrderFillEvent:
601
+ """
602
+ Event emitted when an order fill is confirmed and recorded to the ledger.
603
+
604
+ Facts only — no monetary computation. This mirrors what the broker reported
605
+ for one execution plus how the workflow ledger classified it. It fires once
606
+ per newly recorded fill, including a fill whose classification is only
607
+ resolved after the arrival-buffering timeout.
608
+
609
+ A fill whose ``classification`` is not "workflow" did not match one of our
610
+ recorded workflow orders, so ``node_id`` is None. Return/P&L interpretation
611
+ belongs to WorkflowPnLEvent and to broker-verified accounting, never to this
612
+ raw record — quantity/price are the reported execution facts, nothing is
613
+ summed or converted here.
614
+
615
+ Attributes:
616
+ job_id: Job identifier
617
+ node_id: OrderNode ID when this fill matches a recorded workflow order;
618
+ None for manual/unknown_api/other fills
619
+ order_no: Broker order number
620
+ order_date: Order date (YYYYMMDD)
621
+ execution_id: Broker execution identity when the frame reported one; else None
622
+ symbol: Symbol code
623
+ exchange: Exchange code
624
+ side: "buy" | "sell"
625
+ quantity: Filled quantity (reported, not aggregated)
626
+ price: Fill price (reported)
627
+ fill_time: Fill time (HHMMSSsss) as reported by the broker
628
+ product: "overseas_stock" | "overseas_futures" | "korea_stock"
629
+ provider: Broker provider (e.g. "ls")
630
+ classification: "workflow" | "manual" | "unknown_api" | "other"
631
+ commda_code: Broker communication-media code, preserved as reported
632
+ trading_mode: "live" | "paper"
633
+ received_at: ISO-8601 timestamp when the tracker received the fill
634
+ timestamp: Event emission timestamp
635
+ """
636
+ job_id: str
637
+ order_no: str
638
+ order_date: str
639
+ symbol: str
640
+ exchange: str
641
+ side: str # "buy" | "sell"
642
+ quantity: Union[Decimal, float, int]
643
+ price: Union[Decimal, float]
644
+ fill_time: str
645
+ product: str
646
+ provider: str
647
+ classification: str # "workflow" | "manual" | "unknown_api" | "other"
648
+ commda_code: str
649
+ trading_mode: str
650
+ received_at: str
651
+ node_id: Optional[str] = None
652
+ execution_id: Optional[Union[str, int]] = None
653
+ timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
654
+
655
+
588
656
  @runtime_checkable
589
657
  class ExecutionListener(Protocol):
590
658
  """
@@ -739,6 +807,21 @@ class ExecutionListener(Protocol):
739
807
  """
740
808
  ...
741
809
 
810
+ async def on_order_fill(self, event: 'OrderFillEvent') -> None:
811
+ """
812
+ Called when an order fill is confirmed and recorded to the ledger.
813
+
814
+ Facts only — no monetary computation. Fires once per newly recorded
815
+ fill, including a fill whose classification resolves after the
816
+ arrival-buffering timeout. Servers may durably record the fill (symbol,
817
+ side, quantity, price, classification) without trusting it as a return.
818
+
819
+ Args:
820
+ event: OrderFillEvent with order/execution identifiers, symbol,
821
+ side, quantity, price, classification, and trading mode.
822
+ """
823
+ ...
824
+
742
825
 
743
826
  class BaseExecutionListener:
744
827
  """
@@ -822,6 +905,10 @@ class BaseExecutionListener:
822
905
  """Default implementation: do nothing"""
823
906
  pass
824
907
 
908
+ async def on_order_fill(self, event: 'OrderFillEvent') -> None:
909
+ """Default implementation: do nothing"""
910
+ pass
911
+
825
912
 
826
913
  class ConsoleExecutionListener(BaseExecutionListener):
827
914
  """
@@ -959,3 +1046,9 @@ class ConsoleExecutionListener(BaseExecutionListener):
959
1046
  print(f"{color}{emoji} NOTIFY {cat.upper()}{node_tag} {event.title}{reset}")
960
1047
  if event.message:
961
1048
  print(f" {event.message}")
1049
+
1050
+ async def on_order_fill(self, event: 'OrderFillEvent') -> None:
1051
+ """Print a confirmed order fill (facts only, no P&L)."""
1052
+ node_tag = f" [{event.node_id}]" if event.node_id else ""
1053
+ print(f"💵 FILL{node_tag} {event.symbol} {event.side} {event.quantity}@{event.price} "
1054
+ f"[{event.classification}] order={event.order_no} ({event.trading_mode})")
@@ -361,6 +361,16 @@ OVERSEAS_FUTURES_POSITION_FIELDS: List[Dict[str, str]] = [
361
361
  {"name": "pnl_amount", "type": "number", "description": "평가 손익 (금액)"},
362
362
  {"name": "pnl_rate", "type": "number", "description": "명목가 대비 수익률 (%) — 진입가/현재가로 산출 (승수 무관)"},
363
363
  {"name": "currency", "type": "string", "description": "통화 코드"},
364
+ # ── 손익 증거 메타 (v1.34.0 선물 monetary 작업에서 런타임에 추가됐는데 **선언이 안 따라왔다**) ──
365
+ # 선언에 없으면 `{{ nodes.account.positions[0].pnl_status }}` 같은 바인딩이
366
+ # INVALID_EXPRESSION_REF 로 **부당하게 거부된다** — 노드가 실제로 내보내는 값인데도.
367
+ # 이 값들이 있어야 "손익을 모른다" 와 "손익이 0" 을 사용자가 구분할 수 있다.
368
+ {"name": "pnl_status", "type": "string", "description": "손익 산출 가능 여부 (available / unavailable)"},
369
+ {"name": "pnl_basis", "type": "string", "description": "손익 산출 근거 (예: estimated_gross_price_change)"},
370
+ {"name": "pnl_currency", "type": "string", "description": "손익 금액의 통화 코드"},
371
+ {"name": "pnl_unavailable_reason", "type": "string", "description": "손익을 못 낸 사유 (pnl_status=unavailable 일 때)"},
372
+ {"name": "broker_pnl_amount", "type": "number", "description": "증권사가 보고한 평가손익 원문 (검증 전 값)"},
373
+ {"name": "broker_pnl_basis", "type": "string", "description": "증권사 보고 손익의 근거 표기 (예: broker_reported_unconfirmed)"},
364
374
  ]
365
375
 
366
376
  # ── 실시간 계좌 포지션 (RealAccountNode.positions) ──
@@ -416,6 +426,15 @@ OVERSEAS_FUTURES_REAL_POSITION_FIELDS: List[Dict[str, str]] = [
416
426
  {"name": "pnl_rate", "type": "number", "description": "수익률 (%)"},
417
427
  {"name": "currency", "type": "string", "description": "통화 코드"},
418
428
  {"name": "product", "type": "string", "description": "상품 구분 (항상 overseas_futures)"},
429
+ # ── 손익 증거 메타 — REST 계좌(OVERSEAS_FUTURES_POSITION_FIELDS)와 **같은 세트**다.
430
+ # 두 노드가 같은 직렬화 헬퍼(`_serialize_futures_tracker_position`)를 쓰므로 선언도
431
+ # 어긋나면 안 된다. v1.34.0 에서 런타임만 늘고 선언이 안 따라와 바인딩이 거부됐다.
432
+ {"name": "pnl_status", "type": "string", "description": "손익 산출 가능 여부 (available / unavailable)"},
433
+ {"name": "pnl_basis", "type": "string", "description": "손익 산출 근거 (예: estimated_gross_price_change)"},
434
+ {"name": "pnl_currency", "type": "string", "description": "손익 금액의 통화 코드"},
435
+ {"name": "pnl_unavailable_reason", "type": "string", "description": "손익을 못 낸 사유 (pnl_status=unavailable 일 때)"},
436
+ {"name": "broker_pnl_amount", "type": "number", "description": "증권사가 보고한 평가손익 원문 (검증 전 값)"},
437
+ {"name": "broker_pnl_basis", "type": "string", "description": "증권사 보고 손익의 근거 표기 (예: broker_reported_unconfirmed)"},
419
438
  ]
420
439
 
421
440
  ORDER_RESULT_FIELDS: List[Dict[str, str]] = [
@@ -5,7 +5,7 @@ authors = [
5
5
  homepage = "https://programgarden.com"
6
6
  requires-python = ">=3.12"
7
7
  name = "programgarden-core"
8
- version = "1.26.0"
8
+ version = "1.28.0"
9
9
  license = "AGPL-3.0-or-later"
10
10
  description = "ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의"
11
11
  readme = "README.md"