programgarden-core 1.25.3__tar.gz → 1.27.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.25.3 → programgarden_core-1.27.0}/PKG-INFO +11 -1
  2. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/README.md +10 -0
  3. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/bases/listener.py +89 -0
  4. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/base.py +19 -0
  5. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/pyproject.toml +1 -1
  6. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/__init__.py +0 -0
  7. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/bases/__init__.py +0 -0
  8. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/bases/client.py +0 -0
  9. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/bases/components.py +0 -0
  10. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/bases/mixins.py +0 -0
  11. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/bases/products.py +0 -0
  12. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/bases/sql.py +0 -0
  13. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/bases/storage.py +0 -0
  14. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/code_node.py +0 -0
  15. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/exceptions.py +0 -0
  16. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/expression/__init__.py +0 -0
  17. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/expression/evaluator.py +0 -0
  18. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/i18n/__init__.py +0 -0
  19. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/i18n/locales/en.json +0 -0
  20. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/i18n/locales/ko.json +0 -0
  21. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/i18n/translator.py +0 -0
  22. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/korea_alias.py +0 -0
  23. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/__init__.py +0 -0
  24. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/connection_rule.py +0 -0
  25. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/credential.py +0 -0
  26. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/edge.py +0 -0
  27. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/event.py +0 -0
  28. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/exchange.py +0 -0
  29. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/field_binding.py +0 -0
  30. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/job.py +0 -0
  31. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/order_diagnostics.py +0 -0
  32. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/plugin_resource.py +0 -0
  33. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/resilience.py +0 -0
  34. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/resource.py +0 -0
  35. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/validation.py +0 -0
  36. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/models/workflow.py +0 -0
  37. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/__init__.py +0 -0
  38. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/account_futures.py +0 -0
  39. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/account_korea_stock.py +0 -0
  40. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/account_stock.py +0 -0
  41. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/ai.py +0 -0
  42. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/backtest.py +0 -0
  43. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/backtest_futures.py +0 -0
  44. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/backtest_korea_stock.py +0 -0
  45. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/backtest_stock.py +0 -0
  46. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/broker.py +0 -0
  47. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/calculation.py +0 -0
  48. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/code.py +0 -0
  49. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/condition.py +0 -0
  50. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/data.py +0 -0
  51. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/data_futures.py +0 -0
  52. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/data_korea_stock.py +0 -0
  53. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/data_stock.py +0 -0
  54. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/display.py +0 -0
  55. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/event.py +0 -0
  56. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/fundamental_korea_stock.py +0 -0
  57. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/fundamental_stock.py +0 -0
  58. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/infra.py +0 -0
  59. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/market_external.py +0 -0
  60. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/market_status.py +0 -0
  61. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/open_orders_futures.py +0 -0
  62. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/open_orders_korea_stock.py +0 -0
  63. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/open_orders_stock.py +0 -0
  64. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/order.py +0 -0
  65. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/portfolio.py +0 -0
  66. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/realtime_futures.py +0 -0
  67. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/realtime_korea_stock.py +0 -0
  68. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/realtime_stock.py +0 -0
  69. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/risk.py +0 -0
  70. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/symbol.py +0 -0
  71. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/symbol_futures.py +0 -0
  72. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/symbol_korea_stock.py +0 -0
  73. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/symbol_stock.py +0 -0
  74. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/nodes/trigger.py +0 -0
  75. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/presets/__init__.py +0 -0
  76. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/presets/news_analyst.json +0 -0
  77. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/presets/risk_manager.json +0 -0
  78. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/presets/strategist.json +0 -0
  79. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/presets/technical_analyst.json +0 -0
  80. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/registry/__init__.py +0 -0
  81. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/registry/credential_registry.py +0 -0
  82. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/registry/node_registry.py +0 -0
  83. {programgarden_core-1.25.3 → programgarden_core-1.27.0}/programgarden_core/registry/plugin_registry.py +0 -0
  84. {programgarden_core-1.25.3 → programgarden_core-1.27.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.25.3
3
+ Version: 1.27.0
4
4
  Summary: ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Author: 프로그램동산
@@ -113,3 +113,13 @@ is inferred from these estimates. Actual zero and negative amounts are retained.
113
113
  `currency` is null for mixed or unavailable currencies. Consumers must handle
114
114
  nullable monetary fields and must not coerce them to zero.
115
115
 
116
+ `WorkflowPnLEvent.personal_metrics` is an optional versioned envelope for retained
117
+ local workflow executions. Version 1 includes `scope` (kind, product, provider,
118
+ trading mode), timezone-aware `as_of`, `basis`, executed-order count/status,
119
+ per-symbol/exchange/currency realized amounts/status, and unavailable MDD.
120
+ It is unverified personal evidence, independent of whole-account contest results.
121
+ Stock realized amounts are stored long-only FIFO gross values excluding fees;
122
+ unknown currency stays null. Futures monetary values and portfolio MDD require
123
+ accounting evidence absent from this ledger and remain null. Consumers must
124
+ preserve null/status and use the latest cumulative observation, not sum dates.
125
+
@@ -97,3 +97,13 @@ monetary/rate scalars remain null when accounting evidence is unavailable.
97
97
  is inferred from these estimates. Actual zero and negative amounts are retained.
98
98
  `currency` is null for mixed or unavailable currencies. Consumers must handle
99
99
  nullable monetary fields and must not coerce them to zero.
100
+
101
+ `WorkflowPnLEvent.personal_metrics` is an optional versioned envelope for retained
102
+ local workflow executions. Version 1 includes `scope` (kind, product, provider,
103
+ trading mode), timezone-aware `as_of`, `basis`, executed-order count/status,
104
+ per-symbol/exchange/currency realized amounts/status, and unavailable MDD.
105
+ It is unverified personal evidence, independent of whole-account contest results.
106
+ Stock realized amounts are stored long-only FIFO gross values excluding fees;
107
+ unknown currency stays null. Futures monetary values and portfolio MDD require
108
+ accounting evidence absent from this ledger and remain null. Consumers must
109
+ preserve null/status and use the latest cumulative observation, not sum dates.
@@ -343,9 +343,18 @@ 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
356
+ # Unverified retained workflow executions; separate from account/contest PnL.
357
+ personal_metrics: Optional[Dict[str, Any]] = None
349
358
 
350
359
  # ========== NEW FIELDS (v2.0) ==========
351
360
 
@@ -583,6 +592,67 @@ class RestartEvent:
583
592
  timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
584
593
 
585
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=datetime.utcnow)
654
+
655
+
586
656
  @runtime_checkable
587
657
  class ExecutionListener(Protocol):
588
658
  """
@@ -737,6 +807,21 @@ class ExecutionListener(Protocol):
737
807
  """
738
808
  ...
739
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
+
740
825
 
741
826
  class BaseExecutionListener:
742
827
  """
@@ -820,6 +905,10 @@ class BaseExecutionListener:
820
905
  """Default implementation: do nothing"""
821
906
  pass
822
907
 
908
+ async def on_order_fill(self, event: 'OrderFillEvent') -> None:
909
+ """Default implementation: do nothing"""
910
+ pass
911
+
823
912
 
824
913
  class ConsoleExecutionListener(BaseExecutionListener):
825
914
  """
@@ -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.25.3"
8
+ version = "1.27.0"
9
9
  license = "AGPL-3.0-or-later"
10
10
  description = "ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의"
11
11
  readme = "README.md"