programgarden 1.33.4__tar.gz → 1.34.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 (41) hide show
  1. programgarden-1.34.0/PKG-INFO +294 -0
  2. programgarden-1.34.0/README.md +264 -0
  3. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/context.py +90 -3
  4. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/database/workflow_position_tracker.py +301 -56
  5. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/executor.py +693 -326
  6. programgarden-1.34.0/programgarden/futures_pnl.py +98 -0
  7. programgarden-1.34.0/programgarden/order_lifecycle.py +117 -0
  8. {programgarden-1.33.4 → programgarden-1.34.0}/pyproject.toml +7 -3
  9. programgarden-1.33.4/PKG-INFO +0 -179
  10. programgarden-1.33.4/README.md +0 -149
  11. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/__init__.py +0 -0
  12. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/binding_validator.py +0 -0
  13. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/client.py +0 -0
  14. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/code_worker.py +0 -0
  15. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/database/__init__.py +0 -0
  16. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/database/checkpoint_manager.py +0 -0
  17. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/database/query_builder.py +0 -0
  18. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/database/workflow_risk_tracker.py +0 -0
  19. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/deep_fixtures.py +0 -0
  20. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/node_runner.py +0 -0
  21. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/plugin/__init__.py +0 -0
  22. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/plugin/sandbox.py +0 -0
  23. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/providers/__init__.py +0 -0
  24. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/providers/llm_errors.py +0 -0
  25. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/providers/llm_provider.py +0 -0
  26. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/reconnect_handler.py +0 -0
  27. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/resolver.py +0 -0
  28. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/resource/__init__.py +0 -0
  29. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/resource/context.py +0 -0
  30. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/resource/limiter.py +0 -0
  31. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/resource/monitor.py +0 -0
  32. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/resource/throttle.py +0 -0
  33. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/semantic_rules.py +0 -0
  34. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/tools/__init__.py +0 -0
  35. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/tools/credential_tools.py +0 -0
  36. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/tools/definition_tools.py +0 -0
  37. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/tools/event_tools.py +0 -0
  38. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/tools/job_tools.py +0 -0
  39. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/tools/registry_tools.py +0 -0
  40. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/tools/sqlite_tools.py +0 -0
  41. {programgarden-1.33.4 → programgarden-1.34.0}/programgarden/validation_recommender.py +0 -0
@@ -0,0 +1,294 @@
1
+ Metadata-Version: 2.4
2
+ Name: programgarden
3
+ Version: 1.34.0
4
+ Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
5
+ License-Expression: AGPL-3.0-or-later
6
+ Author: 프로그램동산
7
+ Author-email: coding@programgarden.com
8
+ Requires-Python: >=3.12
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Dist: aiohttp (>=3.9.0,<4.0.0)
14
+ Requires-Dist: aiosqlite (>=0.20.0,<0.21.0)
15
+ Requires-Dist: croniter (>=6.0.0,<7.0.0)
16
+ Requires-Dist: litellm (>=1.40.0)
17
+ Requires-Dist: lxml (>=6.0.2,<7.0.0)
18
+ Requires-Dist: programgarden-community (>=1.15.0,<2.0.0)
19
+ Requires-Dist: programgarden-core (>=1.26.0,<2.0.0)
20
+ Requires-Dist: programgarden-finance (>=1.9.5,<2.0.0)
21
+ Requires-Dist: psutil (>=6.0.0,<7.0.0)
22
+ Requires-Dist: psycopg2-binary (>=2.9.11,<3.0.0)
23
+ Requires-Dist: pydantic (>=2.0.0,<3.0.0)
24
+ Requires-Dist: python-dotenv (>=1.1.0,<2.0.0)
25
+ Requires-Dist: pytickersymbols (>=1.17.5) ; python_version >= "3.12" and python_version < "4.0"
26
+ Requires-Dist: tzdata (>=2025.2,<2026.0)
27
+ Requires-Dist: yfinance (>=0.2.0,<0.3.0)
28
+ Description-Content-Type: text/markdown
29
+
30
+ # ProgramGarden
31
+
32
+ ProgramGarden은 AI 시대에 맞춰 파이썬을 모르는 투자자도 개인화된 시스템 트레이딩을 자동으로 수행할 수 있게 돕는 노드 기반 자동매매 DSL(Domain Specific Language) 오픈소스입니다.
33
+
34
+ 노드를 조합하여 워크플로우를 정의하고, 실행 엔진이 이를 자동으로 처리합니다. LS증권 OpenAPI를 메인으로 해외 주식/선물 거래 자동화를 지원합니다.
35
+
36
+ ## 공식 문서 및 커뮤니티
37
+
38
+ - 비개발자 빠른 시작: https://programgarden.gitbook.io/docs/invest/non_dev_quick_guide
39
+ - 개발자 커스텀 가이드: https://programgarden.gitbook.io/docs/develop/custom_dsl
40
+ - 유튜브: https://www.youtube.com/@programgarden
41
+ - 실시간 오픈톡방: https://open.kakao.com/o/gKVObqUh
42
+
43
+ ## 설치
44
+
45
+ ```bash
46
+ pip install programgarden
47
+
48
+ # Poetry 사용 시 (개발 환경)
49
+ poetry add programgarden
50
+ ```
51
+
52
+ 요구 사항: Python 3.12+
53
+
54
+ ## 빠른 시작
55
+
56
+ ### 동기 실행
57
+
58
+ ```python
59
+ from programgarden import ProgramGarden
60
+
61
+ pg = ProgramGarden()
62
+
63
+ # 워크플로우 검증
64
+ result = pg.validate(workflow_definition)
65
+
66
+ # 워크플로우 실행 (완료 대기)
67
+ job_state = pg.run(
68
+ definition=workflow_definition,
69
+ context={"param": "value"},
70
+ secrets={"appkey": "...", "appsecret": "..."},
71
+ wait=True,
72
+ timeout=60.0,
73
+ )
74
+ ```
75
+
76
+ ### 비동기 실행
77
+
78
+ ```python
79
+ from programgarden import ProgramGarden
80
+
81
+ pg = ProgramGarden()
82
+
83
+ # 워크플로우 비동기 실행 (리스너 연결)
84
+ job = await pg.run_async(
85
+ definition=workflow_definition,
86
+ context={"param": "value"},
87
+ listeners=[MyExecutionListener()],
88
+ )
89
+
90
+ # 실행 중 제어
91
+ await job.stop()
92
+ ```
93
+
94
+ Broker account trackers belong to the workflow job. Normal completion, `stop()`,
95
+ `cancel()`, and `force_stop()` cancel pending broker startup/history tasks, stop
96
+ account polling, and close each tracker's dedicated WebSocket. Late account PnL
97
+ callbacks do not restart work after shutdown. This cleanup also covers partially
98
+ initialized trackers and leaves other jobs running.
99
+ Already queued or in-flight PnL notifications have up to one second to finish
100
+ after account trackers stop, before listeners close. On timeout, cleanup logs the
101
+ pending count and cancels those tasks, allowing another 0.25 seconds for cancellation.
102
+ A listener that suppresses cancellation is reported and remains tracked; it cannot
103
+ hold job shutdown indefinitely. Delivery beyond this bounded grace is not guaranteed.
104
+
105
+ For one-shot workflows, an unhandled `order_result.success=False` returned directly
106
+ by a main-flow node makes the final job status `failed` and emits `WORKFLOW_FAILED`.
107
+ The original broker message remains in node diagnostics and the failure statistics.
108
+ Successful downstream nodes do not erase that rejection. `reason="no_signal"` stays
109
+ a normal no-op. Scheduled/resident workflows continue after a rejected cycle;
110
+ SplitNode and automatic iteration retain their existing item-error continuation
111
+ policies. Final failure is not inferred from cumulative `errors_count` or old logs.
112
+ Raised main-node exceptions retain the existing fail-fast behavior.
113
+
114
+ New-order nodes for overseas stocks, overseas futures, and Korea stocks expose
115
+ the catalog's `result` port as a list of outcome rows: `{{ nodes.order.result }}`
116
+ can feed TableDisplayNode directly. Each row copies the final `order_result`
117
+ and includes `order_id` from the existing top-level or nested order number.
118
+ Legacy `order_result` and `order_id` outputs are unchanged. The projection occurs
119
+ after fill confirmation, fractional-remainder annotation, and replay marking;
120
+ an accepted order is not promoted to a fill without confirmation. Rejections and
121
+ `no_signal` keep their original diagnostics/reason as an outcome row, with no
122
+ fabricated order number. Dry runs add a `simulated` row while retaining the legacy
123
+ flat simulation envelope; request/credential data is not copied into the row.
124
+ Automatic iteration merges these rows into one list. Implicit SplitNode collection
125
+ of a new-order result retains its legacy `order_result` row shape; explicit
126
+ `nodes.order.result` bindings always read the list. Modify/cancel nodes retain their
127
+ separately declared `modify_result`/`cancel_result` ports and are outside this change.
128
+
129
+ ## Explicit execution identities in local ledgers
130
+
131
+ `WorkflowPositionTracker.record_fill(..., execution_id=...)` can preserve a
132
+ broker-provided execution identity. Within one ledger/product/provider/mode,
133
+ order date and normalized order number, an identical replay returns the first
134
+ classification without changing FIFO or history. Conflicting facts raise
135
+ `ExecutionIdentityConflictError`. Positive numeric identifiers ignore padding;
136
+ opaque identifiers retain case. Missing, blank and zero identifiers retain legacy
137
+ behavior and are never inferred from time, price or quantity. Every pending
138
+ partial fill is retained while its order acknowledgement is being recorded.
139
+
140
+ The migration is additive and does not invent identities for existing rows.
141
+ Callers must establish consistent identifier and timestamp semantics first;
142
+ exchange execution numbers and broker execution numbers are not interchangeable.
143
+ This ledger API alone does not reconcile finite workflows after their shutdown.
144
+
145
+ Standalone futures TC3 callbacks interpret `s_b_ccd="1"` as sell and `"2"` as buy,
146
+ matching the SDK contract. Unknown or blank side codes are logged and skipped
147
+ before any inventory write is scheduled. With an app order lifecycle handler,
148
+ TC3 still bypasses this legacy fill path: canonical REST reconciliation remains
149
+ the sole writer. This side correction does not establish an alias between TC3
150
+ and REST execution identities, dates, or times.
151
+
152
+ ## Futures monetary evidence
153
+
154
+ Futures workflow PnL events preserve native gross estimates separately from
155
+ accounting results. `workflow_*`, `other_*`, `total_*`, account and competition
156
+ monetary/rate scalars remain null when accounting evidence is unavailable.
157
+ `pnl_by_currency` contains gross price-change subtotals by contract currency;
158
+ `monetary_positions` retains their basis/status and unmodified, unconfirmed
159
+ `broker_pnl_amount`. No fee, FX, margin/equity return or verified contest score
160
+ is inferred from these estimates. Actual zero and negative amounts are retained.
161
+ `currency` is null for mixed or unavailable currencies. Consumers must handle
162
+ nullable monetary fields and must not coerce them to zero.
163
+
164
+ ## Personal workflow execution metrics
165
+
166
+ `WorkflowPositionTracker.personal_metrics()` reads retained SQLite `trade_history`
167
+ for the current product, provider and paper/live mode. Its independent
168
+ `WorkflowPnLEvent.personal_metrics` envelope retains stock realized PnL after all
169
+ positions close and counts distinct positive executed orders by valid stored
170
+ order date and normalized order number. Partial fills of one dated order count
171
+ once. This is the reporting runtime's retained workflow ledger, not a complete
172
+ account history or verified contest score. No credential/account generation is
173
+ claimed by this legacy local storage.
174
+
175
+ Stored stock realized PnL is gross long-only FIFO before fees. The reader
176
+ validates sufficient recorded buys and consistency with stored results; mixed
177
+ manual/product/provider/exchange FIFO ownership makes the affected symbol
178
+ unavailable. Amounts remain separate by symbol/exchange, with currency null
179
+ because the historical table has no currency evidence. No FX conversion or return
180
+ denominator is invented. Futures executed-order counts are available under the
181
+ same identity rules, but futures monetary results and portfolio MDD remain null:
182
+ the retained FIFO lacks contract accounting and risk price windows are not equity
183
+ curves. Invalid order identities also yield a null count with its reason.
184
+
185
+ The listener refreshes this evidence at most once per 10 seconds to avoid scanning
186
+ retained history on every price tick; reused observations retain their original
187
+ `as_of`. The cache is isolated to the actual tracker/product/provider/mode, and
188
+ other broker-node callbacks do not receive it. A newly recorded fill may appear
189
+ at the next refresh. Existing open-position PnL fields are unchanged. Core and
190
+ engine consumers must ship together to support the additive event field.
191
+
192
+ ## Stock read failures
193
+
194
+ Stock open-order queries include current-day orders (`ThdayBnsAppYn="1"`).
195
+ `OpenOrdersNode` reports unusable `COSAQ00102` responses with `error`,
196
+ `reason="fetch_failed"`, and the original TR, HTTP status and broker response
197
+ fields in `diagnostics`. Its legacy empty payload/count remains for compatibility;
198
+ an error result is unavailable, not evidence of zero orders. A successful empty
199
+ response requires documented success plus parsed echo and aggregate blocks.
200
+ Unknown broker codes receive no inferred meaning.
201
+
202
+ Stock `MarketDataNode` preserves failed `g3101` attempts under `failures`.
203
+ An entirely failed fetch returns `error` and `reason="fetch_failed"`; mixed
204
+ results retain usable `values` with `_partial_failure` and `_failure_reason`.
205
+ The existing exchange fallback and empty upstream no-signal behavior remain.
206
+ Futures master errors likewise report the observed catalogue and original broker
207
+ fields without inferring account entitlement, token or quota causes.
208
+
209
+ ### Dry Run (워크플로우 검증용 모의 실행)
210
+
211
+ 실제 주문/알림/Realtime WebSocket 연결 없이 워크플로우를 검증합니다.
212
+
213
+ - ScheduleNode / TradingHoursFilterNode → 1 cycle 후 즉시 종료
214
+ - 주문 노드 → LS API 미호출, `{"order_id": "DRYRUN-<uuid>", "status": "simulated", ...}` 반환
215
+ - Realtime 노드 → WebSocket 미개방, `{"status": "skipped_dry_run"}` 반환
216
+ - Messaging 노드(Telegram 등) → no-op, `{"status": "simulated"}` 반환
217
+ - 조회/백테스트 노드 → 기존 동작 유지 (실제 API 경로)
218
+
219
+ ```python
220
+ job = await pg.run_async(
221
+ definition=workflow_definition,
222
+ context={"dry_run": True},
223
+ secrets={...},
224
+ )
225
+ ```
226
+
227
+ ### 워크플로우 정의 (JSON)
228
+
229
+ ```json
230
+ {
231
+ "nodes": [
232
+ {"id": "broker", "type": "OverseasStockBrokerNode", "credential_id": "cred-1"},
233
+ {"id": "account", "type": "OverseasStockAccountNode"},
234
+ {"id": "rsi", "type": "ConditionNode", "plugin": "RSI", "data": "{{ nodes.historical.values }}"}
235
+ ],
236
+ "edges": [
237
+ {"from": "broker", "to": "account"},
238
+ {"from": "account", "to": "rsi"}
239
+ ],
240
+ "credentials": [
241
+ {
242
+ "credential_id": "cred-1",
243
+ "type": "broker_ls_overseas_stock",
244
+ "data": [
245
+ {"key": "appkey", "value": "", "type": "password", "label": "App Key"},
246
+ {"key": "appsecret", "value": "", "type": "password", "label": "App Secret"}
247
+ ]
248
+ }
249
+ ]
250
+ }
251
+ ```
252
+
253
+ ## 주요 특징
254
+
255
+ - **노드 기반 DSL**: 72개 내장 노드를 조합하여 코딩 없이 자동매매 전략 구성
256
+ - **실시간 처리**: WebSocket 기반 실시간 시세, 계좌, 체결 이벤트 수신
257
+ - **AI Agent 통합**: LLMModelNode + AIAgentNode로 LLM 기반 분석/의사결정
258
+ - **플러그인 확장**: 67개 내장 전략 플러그인 (RSI, MACD, 볼린저밴드, 이치모쿠, 듀얼모멘텀, 터틀브레이크아웃 등)
259
+ - **ExecutionListener**: 10개 이상의 콜백으로 실행 상태 실시간 모니터링
260
+ - **위험 관리**: WorkflowRiskTracker로 HWM/drawdown 추적, 포지션 사이징
261
+ - **동적 노드 주입**: 런타임에 커스텀 노드 등록 및 실행
262
+
263
+ ## 아키텍처
264
+
265
+ ```
266
+ 5-Layer Architecture:
267
+ 1. Registry Layer - 노드/플러그인 메타데이터 (73개 노드, 77개 플러그인)
268
+ 2. Credential Layer - 인증 정보 관리
269
+ 3. Definition Layer - JSON 워크플로우 정의 (노드, 엣지, 크레덴셜)
270
+ 4. Job Layer - 상태 유지 실행 인스턴스 (최대 24시간 장기 실행)
271
+ 5. Event Layer - ExecutionListener 콜백 이벤트
272
+ ```
273
+
274
+ ## ExecutionListener 콜백
275
+
276
+ | 콜백 | 설명 |
277
+ |------|------|
278
+ | `on_node_state_change` | 노드 실행 상태 변경 |
279
+ | `on_edge_state_change` | 엣지 실행 상태 변경 |
280
+ | `on_log` | 로그 이벤트 |
281
+ | `on_job_state_change` | Job 생명주기 |
282
+ | `on_display_data` | 차트/테이블 출력 데이터 |
283
+ | `on_workflow_pnl_update` | 실시간 수익률 (FIFO 기반) |
284
+ | `on_retry` | 노드 재시도 이벤트 |
285
+ | `on_token_usage` | AI 토큰 사용량 |
286
+ | `on_ai_tool_call` | AI Agent 도구 호출 |
287
+ | `on_llm_stream` | LLM 스트리밍 출력 |
288
+ | `on_risk_event` | 위험 임계값 이벤트 |
289
+ | `on_notification` | 투자자 알림 (시그널, 리스크, 워크플로우 상태, 스케줄, 재시도 소진) |
290
+
291
+ ## 변경 로그
292
+
293
+ 자세한 변경 사항은 `CHANGELOG.md`를 참고하세요.
294
+
@@ -0,0 +1,264 @@
1
+ # ProgramGarden
2
+
3
+ ProgramGarden은 AI 시대에 맞춰 파이썬을 모르는 투자자도 개인화된 시스템 트레이딩을 자동으로 수행할 수 있게 돕는 노드 기반 자동매매 DSL(Domain Specific Language) 오픈소스입니다.
4
+
5
+ 노드를 조합하여 워크플로우를 정의하고, 실행 엔진이 이를 자동으로 처리합니다. LS증권 OpenAPI를 메인으로 해외 주식/선물 거래 자동화를 지원합니다.
6
+
7
+ ## 공식 문서 및 커뮤니티
8
+
9
+ - 비개발자 빠른 시작: https://programgarden.gitbook.io/docs/invest/non_dev_quick_guide
10
+ - 개발자 커스텀 가이드: https://programgarden.gitbook.io/docs/develop/custom_dsl
11
+ - 유튜브: https://www.youtube.com/@programgarden
12
+ - 실시간 오픈톡방: https://open.kakao.com/o/gKVObqUh
13
+
14
+ ## 설치
15
+
16
+ ```bash
17
+ pip install programgarden
18
+
19
+ # Poetry 사용 시 (개발 환경)
20
+ poetry add programgarden
21
+ ```
22
+
23
+ 요구 사항: Python 3.12+
24
+
25
+ ## 빠른 시작
26
+
27
+ ### 동기 실행
28
+
29
+ ```python
30
+ from programgarden import ProgramGarden
31
+
32
+ pg = ProgramGarden()
33
+
34
+ # 워크플로우 검증
35
+ result = pg.validate(workflow_definition)
36
+
37
+ # 워크플로우 실행 (완료 대기)
38
+ job_state = pg.run(
39
+ definition=workflow_definition,
40
+ context={"param": "value"},
41
+ secrets={"appkey": "...", "appsecret": "..."},
42
+ wait=True,
43
+ timeout=60.0,
44
+ )
45
+ ```
46
+
47
+ ### 비동기 실행
48
+
49
+ ```python
50
+ from programgarden import ProgramGarden
51
+
52
+ pg = ProgramGarden()
53
+
54
+ # 워크플로우 비동기 실행 (리스너 연결)
55
+ job = await pg.run_async(
56
+ definition=workflow_definition,
57
+ context={"param": "value"},
58
+ listeners=[MyExecutionListener()],
59
+ )
60
+
61
+ # 실행 중 제어
62
+ await job.stop()
63
+ ```
64
+
65
+ Broker account trackers belong to the workflow job. Normal completion, `stop()`,
66
+ `cancel()`, and `force_stop()` cancel pending broker startup/history tasks, stop
67
+ account polling, and close each tracker's dedicated WebSocket. Late account PnL
68
+ callbacks do not restart work after shutdown. This cleanup also covers partially
69
+ initialized trackers and leaves other jobs running.
70
+ Already queued or in-flight PnL notifications have up to one second to finish
71
+ after account trackers stop, before listeners close. On timeout, cleanup logs the
72
+ pending count and cancels those tasks, allowing another 0.25 seconds for cancellation.
73
+ A listener that suppresses cancellation is reported and remains tracked; it cannot
74
+ hold job shutdown indefinitely. Delivery beyond this bounded grace is not guaranteed.
75
+
76
+ For one-shot workflows, an unhandled `order_result.success=False` returned directly
77
+ by a main-flow node makes the final job status `failed` and emits `WORKFLOW_FAILED`.
78
+ The original broker message remains in node diagnostics and the failure statistics.
79
+ Successful downstream nodes do not erase that rejection. `reason="no_signal"` stays
80
+ a normal no-op. Scheduled/resident workflows continue after a rejected cycle;
81
+ SplitNode and automatic iteration retain their existing item-error continuation
82
+ policies. Final failure is not inferred from cumulative `errors_count` or old logs.
83
+ Raised main-node exceptions retain the existing fail-fast behavior.
84
+
85
+ New-order nodes for overseas stocks, overseas futures, and Korea stocks expose
86
+ the catalog's `result` port as a list of outcome rows: `{{ nodes.order.result }}`
87
+ can feed TableDisplayNode directly. Each row copies the final `order_result`
88
+ and includes `order_id` from the existing top-level or nested order number.
89
+ Legacy `order_result` and `order_id` outputs are unchanged. The projection occurs
90
+ after fill confirmation, fractional-remainder annotation, and replay marking;
91
+ an accepted order is not promoted to a fill without confirmation. Rejections and
92
+ `no_signal` keep their original diagnostics/reason as an outcome row, with no
93
+ fabricated order number. Dry runs add a `simulated` row while retaining the legacy
94
+ flat simulation envelope; request/credential data is not copied into the row.
95
+ Automatic iteration merges these rows into one list. Implicit SplitNode collection
96
+ of a new-order result retains its legacy `order_result` row shape; explicit
97
+ `nodes.order.result` bindings always read the list. Modify/cancel nodes retain their
98
+ separately declared `modify_result`/`cancel_result` ports and are outside this change.
99
+
100
+ ## Explicit execution identities in local ledgers
101
+
102
+ `WorkflowPositionTracker.record_fill(..., execution_id=...)` can preserve a
103
+ broker-provided execution identity. Within one ledger/product/provider/mode,
104
+ order date and normalized order number, an identical replay returns the first
105
+ classification without changing FIFO or history. Conflicting facts raise
106
+ `ExecutionIdentityConflictError`. Positive numeric identifiers ignore padding;
107
+ opaque identifiers retain case. Missing, blank and zero identifiers retain legacy
108
+ behavior and are never inferred from time, price or quantity. Every pending
109
+ partial fill is retained while its order acknowledgement is being recorded.
110
+
111
+ The migration is additive and does not invent identities for existing rows.
112
+ Callers must establish consistent identifier and timestamp semantics first;
113
+ exchange execution numbers and broker execution numbers are not interchangeable.
114
+ This ledger API alone does not reconcile finite workflows after their shutdown.
115
+
116
+ Standalone futures TC3 callbacks interpret `s_b_ccd="1"` as sell and `"2"` as buy,
117
+ matching the SDK contract. Unknown or blank side codes are logged and skipped
118
+ before any inventory write is scheduled. With an app order lifecycle handler,
119
+ TC3 still bypasses this legacy fill path: canonical REST reconciliation remains
120
+ the sole writer. This side correction does not establish an alias between TC3
121
+ and REST execution identities, dates, or times.
122
+
123
+ ## Futures monetary evidence
124
+
125
+ Futures workflow PnL events preserve native gross estimates separately from
126
+ accounting results. `workflow_*`, `other_*`, `total_*`, account and competition
127
+ monetary/rate scalars remain null when accounting evidence is unavailable.
128
+ `pnl_by_currency` contains gross price-change subtotals by contract currency;
129
+ `monetary_positions` retains their basis/status and unmodified, unconfirmed
130
+ `broker_pnl_amount`. No fee, FX, margin/equity return or verified contest score
131
+ is inferred from these estimates. Actual zero and negative amounts are retained.
132
+ `currency` is null for mixed or unavailable currencies. Consumers must handle
133
+ nullable monetary fields and must not coerce them to zero.
134
+
135
+ ## Personal workflow execution metrics
136
+
137
+ `WorkflowPositionTracker.personal_metrics()` reads retained SQLite `trade_history`
138
+ for the current product, provider and paper/live mode. Its independent
139
+ `WorkflowPnLEvent.personal_metrics` envelope retains stock realized PnL after all
140
+ positions close and counts distinct positive executed orders by valid stored
141
+ order date and normalized order number. Partial fills of one dated order count
142
+ once. This is the reporting runtime's retained workflow ledger, not a complete
143
+ account history or verified contest score. No credential/account generation is
144
+ claimed by this legacy local storage.
145
+
146
+ Stored stock realized PnL is gross long-only FIFO before fees. The reader
147
+ validates sufficient recorded buys and consistency with stored results; mixed
148
+ manual/product/provider/exchange FIFO ownership makes the affected symbol
149
+ unavailable. Amounts remain separate by symbol/exchange, with currency null
150
+ because the historical table has no currency evidence. No FX conversion or return
151
+ denominator is invented. Futures executed-order counts are available under the
152
+ same identity rules, but futures monetary results and portfolio MDD remain null:
153
+ the retained FIFO lacks contract accounting and risk price windows are not equity
154
+ curves. Invalid order identities also yield a null count with its reason.
155
+
156
+ The listener refreshes this evidence at most once per 10 seconds to avoid scanning
157
+ retained history on every price tick; reused observations retain their original
158
+ `as_of`. The cache is isolated to the actual tracker/product/provider/mode, and
159
+ other broker-node callbacks do not receive it. A newly recorded fill may appear
160
+ at the next refresh. Existing open-position PnL fields are unchanged. Core and
161
+ engine consumers must ship together to support the additive event field.
162
+
163
+ ## Stock read failures
164
+
165
+ Stock open-order queries include current-day orders (`ThdayBnsAppYn="1"`).
166
+ `OpenOrdersNode` reports unusable `COSAQ00102` responses with `error`,
167
+ `reason="fetch_failed"`, and the original TR, HTTP status and broker response
168
+ fields in `diagnostics`. Its legacy empty payload/count remains for compatibility;
169
+ an error result is unavailable, not evidence of zero orders. A successful empty
170
+ response requires documented success plus parsed echo and aggregate blocks.
171
+ Unknown broker codes receive no inferred meaning.
172
+
173
+ Stock `MarketDataNode` preserves failed `g3101` attempts under `failures`.
174
+ An entirely failed fetch returns `error` and `reason="fetch_failed"`; mixed
175
+ results retain usable `values` with `_partial_failure` and `_failure_reason`.
176
+ The existing exchange fallback and empty upstream no-signal behavior remain.
177
+ Futures master errors likewise report the observed catalogue and original broker
178
+ fields without inferring account entitlement, token or quota causes.
179
+
180
+ ### Dry Run (워크플로우 검증용 모의 실행)
181
+
182
+ 실제 주문/알림/Realtime WebSocket 연결 없이 워크플로우를 검증합니다.
183
+
184
+ - ScheduleNode / TradingHoursFilterNode → 1 cycle 후 즉시 종료
185
+ - 주문 노드 → LS API 미호출, `{"order_id": "DRYRUN-<uuid>", "status": "simulated", ...}` 반환
186
+ - Realtime 노드 → WebSocket 미개방, `{"status": "skipped_dry_run"}` 반환
187
+ - Messaging 노드(Telegram 등) → no-op, `{"status": "simulated"}` 반환
188
+ - 조회/백테스트 노드 → 기존 동작 유지 (실제 API 경로)
189
+
190
+ ```python
191
+ job = await pg.run_async(
192
+ definition=workflow_definition,
193
+ context={"dry_run": True},
194
+ secrets={...},
195
+ )
196
+ ```
197
+
198
+ ### 워크플로우 정의 (JSON)
199
+
200
+ ```json
201
+ {
202
+ "nodes": [
203
+ {"id": "broker", "type": "OverseasStockBrokerNode", "credential_id": "cred-1"},
204
+ {"id": "account", "type": "OverseasStockAccountNode"},
205
+ {"id": "rsi", "type": "ConditionNode", "plugin": "RSI", "data": "{{ nodes.historical.values }}"}
206
+ ],
207
+ "edges": [
208
+ {"from": "broker", "to": "account"},
209
+ {"from": "account", "to": "rsi"}
210
+ ],
211
+ "credentials": [
212
+ {
213
+ "credential_id": "cred-1",
214
+ "type": "broker_ls_overseas_stock",
215
+ "data": [
216
+ {"key": "appkey", "value": "", "type": "password", "label": "App Key"},
217
+ {"key": "appsecret", "value": "", "type": "password", "label": "App Secret"}
218
+ ]
219
+ }
220
+ ]
221
+ }
222
+ ```
223
+
224
+ ## 주요 특징
225
+
226
+ - **노드 기반 DSL**: 72개 내장 노드를 조합하여 코딩 없이 자동매매 전략 구성
227
+ - **실시간 처리**: WebSocket 기반 실시간 시세, 계좌, 체결 이벤트 수신
228
+ - **AI Agent 통합**: LLMModelNode + AIAgentNode로 LLM 기반 분석/의사결정
229
+ - **플러그인 확장**: 67개 내장 전략 플러그인 (RSI, MACD, 볼린저밴드, 이치모쿠, 듀얼모멘텀, 터틀브레이크아웃 등)
230
+ - **ExecutionListener**: 10개 이상의 콜백으로 실행 상태 실시간 모니터링
231
+ - **위험 관리**: WorkflowRiskTracker로 HWM/drawdown 추적, 포지션 사이징
232
+ - **동적 노드 주입**: 런타임에 커스텀 노드 등록 및 실행
233
+
234
+ ## 아키텍처
235
+
236
+ ```
237
+ 5-Layer Architecture:
238
+ 1. Registry Layer - 노드/플러그인 메타데이터 (73개 노드, 77개 플러그인)
239
+ 2. Credential Layer - 인증 정보 관리
240
+ 3. Definition Layer - JSON 워크플로우 정의 (노드, 엣지, 크레덴셜)
241
+ 4. Job Layer - 상태 유지 실행 인스턴스 (최대 24시간 장기 실행)
242
+ 5. Event Layer - ExecutionListener 콜백 이벤트
243
+ ```
244
+
245
+ ## ExecutionListener 콜백
246
+
247
+ | 콜백 | 설명 |
248
+ |------|------|
249
+ | `on_node_state_change` | 노드 실행 상태 변경 |
250
+ | `on_edge_state_change` | 엣지 실행 상태 변경 |
251
+ | `on_log` | 로그 이벤트 |
252
+ | `on_job_state_change` | Job 생명주기 |
253
+ | `on_display_data` | 차트/테이블 출력 데이터 |
254
+ | `on_workflow_pnl_update` | 실시간 수익률 (FIFO 기반) |
255
+ | `on_retry` | 노드 재시도 이벤트 |
256
+ | `on_token_usage` | AI 토큰 사용량 |
257
+ | `on_ai_tool_call` | AI Agent 도구 호출 |
258
+ | `on_llm_stream` | LLM 스트리밍 출력 |
259
+ | `on_risk_event` | 위험 임계값 이벤트 |
260
+ | `on_notification` | 투자자 알림 (시그널, 리스크, 워크플로우 상태, 스케줄, 재시도 소진) |
261
+
262
+ ## 변경 로그
263
+
264
+ 자세한 변경 사항은 `CHANGELOG.md`를 참고하세요.