programgarden 2.6.6__tar.gz → 2.6.7__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.
- {programgarden-2.6.6 → programgarden-2.6.7}/PKG-INFO +14 -3
- {programgarden-2.6.6 → programgarden-2.6.7}/README.md +12 -1
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/executor.py +138 -48
- {programgarden-2.6.6 → programgarden-2.6.7}/pyproject.toml +2 -2
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/__init__.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/binding_provenance.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/binding_validator.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/client.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/code_worker.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/context.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/__init__.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/adjustment_delivery.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/broker_evidence.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/broker_order_totals.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/broker_snapshot.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/checkpoint_manager.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/db_naming.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/execution_storage.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/fill_reconciler.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/order_cancellation.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/order_recovery.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/owned_order_cancellation.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/position_reconciliation.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/query_builder.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/sqlite_scope.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/workflow_position_tracker.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/workflow_risk_tracker.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/deep_fixtures.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/futures_contract_info.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/futures_exchange.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/futures_history.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/futures_orderable.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/futures_pnl.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/futures_read_evidence.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/incremental_build.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/managed_order_control.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/node_runner.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/order_lifecycle.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/paper_futures_business_date.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/plugin/__init__.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/plugin/sandbox.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/providers/__init__.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/providers/llm_errors.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/providers/llm_provider.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/ranking_nodes.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/reconnect_handler.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_contracts.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_events.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_external.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_order_adapter.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_orders.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_scenarios.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_semantics.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_sources.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_sqlite.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_triggers.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/resolver.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/resource/__init__.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/resource/context.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/resource/limiter.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/resource/monitor.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/resource/throttle.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/semantic_rules.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/__init__.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/credential_tools.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/definition_tools.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/event_tools.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/job_tools.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/registry_tools.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/sqlite_tools.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/validation_recommender.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/validation_replay.py +0 -0
- {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/valuation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: programgarden
|
|
3
|
-
Version: 2.6.
|
|
3
|
+
Version: 2.6.7
|
|
4
4
|
Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
|
|
5
5
|
License-Expression: AGPL-3.0-or-later
|
|
6
6
|
Author: 프로그램동산
|
|
@@ -17,7 +17,7 @@ Requires-Dist: litellm (>=1.40.0)
|
|
|
17
17
|
Requires-Dist: lxml (>=6.0.2,<7.0.0)
|
|
18
18
|
Requires-Dist: programgarden-community (>=2.3.1,<3.0.0)
|
|
19
19
|
Requires-Dist: programgarden-core (>=2.6.3,<3.0.0)
|
|
20
|
-
Requires-Dist: programgarden-finance (>=2.0.
|
|
20
|
+
Requires-Dist: programgarden-finance (>=2.0.5,<3.0.0)
|
|
21
21
|
Requires-Dist: psutil (>=6.0.0,<7.0.0)
|
|
22
22
|
Requires-Dist: psycopg2-binary (>=2.9.11,<3.0.0)
|
|
23
23
|
Requires-Dist: pydantic (>=2.0.1,<3.0.0)
|
|
@@ -31,6 +31,13 @@ The 2.6.2 release candidate [futures minute alias correction](../../docs/futures
|
|
|
31
31
|
|
|
32
32
|
# ProgramGarden
|
|
33
33
|
|
|
34
|
+
The 2.6.7 release [futures TC2 confirmation handling](../../docs/release-2.6.7-validation.md)
|
|
35
|
+
requires programgarden-finance 2.0.5, which reads overseas futures modify and
|
|
36
|
+
cancel confirmations (TC2) as LS sends them and re-registers account events
|
|
37
|
+
after a reconnect. The engine finds the workflow's order through
|
|
38
|
+
`orgn_ordr_no`, uses the guide's side codes and skips frames the SDK could
|
|
39
|
+
not read.
|
|
40
|
+
|
|
34
41
|
The 2.6.6 release candidate [ranking nodes and runtime-values-as-data correction](../../docs/release-2.6.6-validation.md)
|
|
35
42
|
adds KoreaStockRankingNode and OverseasStockRankingNode, evaluates only
|
|
36
43
|
expression templates authored in the workflow, routes Gemini and DeepSeek
|
|
@@ -205,7 +212,11 @@ exchange execution numbers and broker execution numbers are not interchangeable.
|
|
|
205
212
|
This ledger API alone does not reconcile finite workflows after their shutdown.
|
|
206
213
|
|
|
207
214
|
Standalone futures TC3 callbacks interpret `s_b_ccd="1"` as sell and `"2"` as buy,
|
|
208
|
-
matching the SDK contract.
|
|
215
|
+
matching the SDK contract. TC2 confirmations use the same codes and find the
|
|
216
|
+
workflow's order through `orgn_ordr_no`: a futures modify or cancel gets its own
|
|
217
|
+
new order number (LS paper, 2026-10-15), so the cancel confirmation cancels the
|
|
218
|
+
original order in the ledger and a modify confirmation updates the successor row
|
|
219
|
+
recorded by ModifyOrderNodeExecutor. Unknown or blank side codes are logged and skipped
|
|
209
220
|
before any inventory write is scheduled. With an app order lifecycle handler,
|
|
210
221
|
TC3 still bypasses this legacy fill path: canonical REST reconciliation remains
|
|
211
222
|
the sole writer. This side correction does not establish an alias between TC3
|
|
@@ -2,6 +2,13 @@ The 2.6.2 release candidate [futures minute alias correction](../../docs/futures
|
|
|
2
2
|
|
|
3
3
|
# ProgramGarden
|
|
4
4
|
|
|
5
|
+
The 2.6.7 release [futures TC2 confirmation handling](../../docs/release-2.6.7-validation.md)
|
|
6
|
+
requires programgarden-finance 2.0.5, which reads overseas futures modify and
|
|
7
|
+
cancel confirmations (TC2) as LS sends them and re-registers account events
|
|
8
|
+
after a reconnect. The engine finds the workflow's order through
|
|
9
|
+
`orgn_ordr_no`, uses the guide's side codes and skips frames the SDK could
|
|
10
|
+
not read.
|
|
11
|
+
|
|
5
12
|
The 2.6.6 release candidate [ranking nodes and runtime-values-as-data correction](../../docs/release-2.6.6-validation.md)
|
|
6
13
|
adds KoreaStockRankingNode and OverseasStockRankingNode, evaluates only
|
|
7
14
|
expression templates authored in the workflow, routes Gemini and DeepSeek
|
|
@@ -176,7 +183,11 @@ exchange execution numbers and broker execution numbers are not interchangeable.
|
|
|
176
183
|
This ledger API alone does not reconcile finite workflows after their shutdown.
|
|
177
184
|
|
|
178
185
|
Standalone futures TC3 callbacks interpret `s_b_ccd="1"` as sell and `"2"` as buy,
|
|
179
|
-
matching the SDK contract.
|
|
186
|
+
matching the SDK contract. TC2 confirmations use the same codes and find the
|
|
187
|
+
workflow's order through `orgn_ordr_no`: a futures modify or cancel gets its own
|
|
188
|
+
new order number (LS paper, 2026-10-15), so the cancel confirmation cancels the
|
|
189
|
+
original order in the ledger and a modify confirmation updates the successor row
|
|
190
|
+
recorded by ModifyOrderNodeExecutor. Unknown or blank side codes are logged and skipped
|
|
180
191
|
before any inventory write is scheduled. With an app order lifecycle handler,
|
|
181
192
|
TC3 still bypasses this legacy fill path: canonical REST reconciliation remains
|
|
182
193
|
the sole writer. This side correction does not establish an alias between TC3
|
|
@@ -244,6 +244,127 @@ def _stock_position_fill_currency(position: Any, symbol: str, market_code: str)
|
|
|
244
244
|
return unit if re.fullmatch(r"[A-Z]{3}", unit) else None
|
|
245
245
|
|
|
246
246
|
|
|
247
|
+
# LS guide apis[30] TC1/TC2/TC3 ``s_b_ccd`` (매도매수유형): "1" = 매도, "2" = 매수.
|
|
248
|
+
# Observed the same way on LS paper overseas futures 2026-10-15: buys (CIDBT00100
|
|
249
|
+
# BnsTpCode "2") arrived as "2", the closing sell (BnsTpCode "1") as "1".
|
|
250
|
+
_FUTURES_EVENT_SIDE = {"1": "sell", "2": "buy"}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _futures_event_order_number(value: Any) -> Optional[str]:
|
|
254
|
+
"""TC frame order number → the integer form, or None for blank / "0".
|
|
255
|
+
|
|
256
|
+
New orders carry ``orgn_ordr_no "0"``; CIDBQ01800 pads the same numbers to 10
|
|
257
|
+
digits ("0000000027") while TC frames and order TRs say "27".
|
|
258
|
+
"""
|
|
259
|
+
text = str(value if value is not None else "").strip()
|
|
260
|
+
if not text:
|
|
261
|
+
return None
|
|
262
|
+
if text.isascii() and text.isdigit():
|
|
263
|
+
return text.lstrip("0") or None
|
|
264
|
+
return text
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
async def _apply_futures_tc2_confirmation(context: Any, body: Any) -> str:
|
|
268
|
+
"""Apply one overseas futures TC2 frame to the workflow order ledger.
|
|
269
|
+
|
|
270
|
+
Returns a short outcome code (logged at debug level and asserted by tests).
|
|
271
|
+
|
|
272
|
+
What LS sends (LS paper overseas futures, 2026-10-15, 8 orders; frames in
|
|
273
|
+
``tests/fixtures/tc_frames_paper_futures_20261015.json``):
|
|
274
|
+
|
|
275
|
+
- New orders get **no** TC2 (TC1 HO01/HO04, then TC3 when filled).
|
|
276
|
+
- A modify (CIDBT00900) gets a **new** order number ``ordr_no``; the TC2 HO02
|
|
277
|
+
carries ``ordr_ccd "2"``, ``orgn_ordr_no`` = the order that was modified
|
|
278
|
+
(direct predecessor), ``mthr_ordr_no`` = the first order of the chain, and
|
|
279
|
+
``cnfr_q`` = the quantity moved to the new price. A partial move leaves the
|
|
280
|
+
rest working under the predecessor's number.
|
|
281
|
+
- A cancel (CIDBT01000) also gets its **own** order number; the TC2 HO02 has
|
|
282
|
+
``ordr_ccd "3"``, ``orgn_ordr_no`` = the order cancelled and ``cnfr_q`` = the
|
|
283
|
+
quantity cancelled.
|
|
284
|
+
|
|
285
|
+
So the order this workflow owns is found through ``orgn_ordr_no``, never
|
|
286
|
+
``ordr_no`` (that number is the modify/cancel's own) and never
|
|
287
|
+
``mthr_ordr_no`` (after a partial modify the chain head can still be
|
|
288
|
+
working, and a cancel of the successor must not touch it).
|
|
289
|
+
|
|
290
|
+
- Cancel: the owned original order is cancelled in the ledger.
|
|
291
|
+
- Modify: when this engine issued the modify, ModifyOrderNodeExecutor already
|
|
292
|
+
recorded the successor (inheriting side/symbol); the confirmed quantity and
|
|
293
|
+
price are written to that row. When the successor is unknown (modified in
|
|
294
|
+
HTS/MTS or the board order window), nothing is recorded — recognising
|
|
295
|
+
outside modifies of owned orders is the separately gated E1 item; its
|
|
296
|
+
fills keep the existing classification.
|
|
297
|
+
- The side code and symbol must match the owned row; otherwise nothing changes.
|
|
298
|
+
- HO03 (reject) and frames the SDK could not read (``body is None``) change
|
|
299
|
+
nothing.
|
|
300
|
+
"""
|
|
301
|
+
if body is None:
|
|
302
|
+
return "unreadable"
|
|
303
|
+
service = str(getattr(body, "svc_id", "") or "").strip()
|
|
304
|
+
if service != "HO02":
|
|
305
|
+
return "not_a_confirmation"
|
|
306
|
+
action = str(getattr(body, "ordr_ccd", "") or "").strip()
|
|
307
|
+
if action not in ("2", "3"):
|
|
308
|
+
return "not_modify_or_cancel"
|
|
309
|
+
side = _FUTURES_EVENT_SIDE.get(str(getattr(body, "s_b_ccd", "") or "").strip())
|
|
310
|
+
if side is None:
|
|
311
|
+
logger.warning("Ignoring TC2 confirmation with unknown side code")
|
|
312
|
+
return "unknown_side"
|
|
313
|
+
order_date = str(getattr(body, "ordr_dt", "") or "").strip()
|
|
314
|
+
if len(order_date) != 8 or not order_date.isdigit():
|
|
315
|
+
# The frame's own date only; never the local clock (night sessions).
|
|
316
|
+
return "no_order_date"
|
|
317
|
+
original = _futures_event_order_number(getattr(body, "orgn_ordr_no", None))
|
|
318
|
+
own_number = _futures_event_order_number(getattr(body, "ordr_no", None))
|
|
319
|
+
symbol = str(getattr(body, "is_cd", "") or "").strip()
|
|
320
|
+
if original is None or own_number is None or not symbol:
|
|
321
|
+
return "no_order_numbers"
|
|
322
|
+
|
|
323
|
+
owned = context.find_workflow_order(original, order_date, symbol=symbol)
|
|
324
|
+
if owned is None:
|
|
325
|
+
return "not_a_workflow_order"
|
|
326
|
+
if (str(owned.get("symbol") or "").strip().upper() != symbol.upper()
|
|
327
|
+
or owned.get("side") != side):
|
|
328
|
+
logger.warning(
|
|
329
|
+
"TC2 confirmation for workflow order %s does not match its ledger row "
|
|
330
|
+
"(symbol/side); ledger unchanged", original,
|
|
331
|
+
)
|
|
332
|
+
return "mismatch"
|
|
333
|
+
|
|
334
|
+
if action == "3":
|
|
335
|
+
await context.cancel_workflow_order(
|
|
336
|
+
order_no=owned["order_no"], order_date=owned["order_date"],
|
|
337
|
+
)
|
|
338
|
+
logger.debug("TC2 cancel %s confirmed for workflow order %s", own_number, original)
|
|
339
|
+
return "cancelled"
|
|
340
|
+
|
|
341
|
+
successor = context.find_workflow_order(own_number, order_date, symbol=symbol)
|
|
342
|
+
if successor is None:
|
|
343
|
+
logger.info(
|
|
344
|
+
"Workflow futures order %s was modified outside this workflow (new order %s); "
|
|
345
|
+
"the new order number is not recorded", original, own_number,
|
|
346
|
+
)
|
|
347
|
+
return "modified_outside"
|
|
348
|
+
if (str(successor.get("symbol") or "").strip().upper() != symbol.upper()
|
|
349
|
+
or successor.get("side") != side):
|
|
350
|
+
logger.warning(
|
|
351
|
+
"TC2 modify %s does not match its ledger row (symbol/side); ledger unchanged", own_number,
|
|
352
|
+
)
|
|
353
|
+
return "mismatch"
|
|
354
|
+
quantity = _whole_unit_quantity(_qty_num(getattr(body, "cnfr_q", None)) or _qty_num(getattr(body, "ordr_q", None)))
|
|
355
|
+
try:
|
|
356
|
+
price = float(str(getattr(body, "ordr_prc", "") or "0").strip() or 0)
|
|
357
|
+
except ValueError:
|
|
358
|
+
price = 0.0
|
|
359
|
+
await context.modify_workflow_order(
|
|
360
|
+
order_no=successor["order_no"],
|
|
361
|
+
order_date=successor["order_date"],
|
|
362
|
+
new_quantity=quantity if quantity and quantity > 0 else None,
|
|
363
|
+
new_price=price if price > 0 else None,
|
|
364
|
+
)
|
|
365
|
+
return "modified"
|
|
366
|
+
|
|
367
|
+
|
|
247
368
|
def _usable_execution_identity(order_no: Any, order_date: Any) -> bool:
|
|
248
369
|
"""체결 프레임의 (주문번호, 주문일자)가 원장 execution identity 키로 쓸 수 있나.
|
|
249
370
|
|
|
@@ -5159,59 +5280,18 @@ class BrokerNodeExecutor(NodeExecutorBase):
|
|
|
5159
5280
|
loop = asyncio.get_running_loop()
|
|
5160
5281
|
|
|
5161
5282
|
def on_tc2_event(resp):
|
|
5162
|
-
"""TC2
|
|
5283
|
+
"""TC2 HO02(정정/취소 확인)를 원장에 반영한다 — 판정은 _apply_futures_tc2_confirmation.
|
|
5163
5284
|
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
- ordr_no: 주문번호
|
|
5167
|
-
- ordr_dt: 주문일자
|
|
5168
|
-
- is_cd: 종목코드
|
|
5169
|
-
- s_b_ccd: 매매구분 (1=매수, 2=매도)
|
|
5170
|
-
- ordr_ccd: 주문구분 (1=신규, 2=정정, 3=취소)
|
|
5171
|
-
- ordr_q: 주문수량
|
|
5172
|
-
- ordr_prc: 주문가격
|
|
5285
|
+
SDK 는 동기 리스너를 워커 스레드에서 부르므로 원장 조회·갱신은 전부
|
|
5286
|
+
이벤트 루프로 넘긴다(다른 원장 경로와 같은 규약).
|
|
5173
5287
|
"""
|
|
5174
5288
|
try:
|
|
5175
5289
|
if context.is_shutdown:
|
|
5176
5290
|
return
|
|
5177
|
-
body = getattr(resp, 'body',
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
order_no = str(getattr(body, 'ordr_no', ''))
|
|
5183
|
-
order_date = getattr(body, 'ordr_dt', datetime.now().strftime('%Y%m%d'))
|
|
5184
|
-
symbol = getattr(body, 'is_cd', '')
|
|
5185
|
-
side_code = getattr(body, 's_b_ccd', '') # 매매구분
|
|
5186
|
-
quantity = int(getattr(body, 'ordr_q', 0) or getattr(body, 'cnfr_q', 0) or 0)
|
|
5187
|
-
price = float(getattr(body, 'ordr_prc', 0) or 0)
|
|
5188
|
-
ordr_ccd = getattr(body, 'ordr_ccd', '') # 1=신규, 2=정정, 3=취소
|
|
5189
|
-
|
|
5190
|
-
# 매매구분
|
|
5191
|
-
side = 'buy' if side_code == '1' else 'sell'
|
|
5192
|
-
|
|
5193
|
-
logger.debug(f"[TC2] svc_id={svc_id}, ordr_ccd={ordr_ccd}, order_no={order_no}, symbol={symbol}")
|
|
5194
|
-
|
|
5195
|
-
if svc_id == 'HO02': # 주문 확인
|
|
5196
|
-
if ordr_ccd == '2': # 정정 완료
|
|
5197
|
-
asyncio.run_coroutine_threadsafe(
|
|
5198
|
-
context.modify_workflow_order(
|
|
5199
|
-
order_no=order_no,
|
|
5200
|
-
order_date=order_date,
|
|
5201
|
-
new_quantity=quantity if quantity > 0 else None,
|
|
5202
|
-
new_price=price if price > 0 else None,
|
|
5203
|
-
),
|
|
5204
|
-
loop
|
|
5205
|
-
)
|
|
5206
|
-
elif ordr_ccd == '3': # 취소 완료
|
|
5207
|
-
asyncio.run_coroutine_threadsafe(
|
|
5208
|
-
context.cancel_workflow_order(
|
|
5209
|
-
order_no=order_no,
|
|
5210
|
-
order_date=order_date,
|
|
5211
|
-
),
|
|
5212
|
-
loop
|
|
5213
|
-
)
|
|
5214
|
-
|
|
5291
|
+
body = getattr(resp, 'body', None)
|
|
5292
|
+
asyncio.run_coroutine_threadsafe(
|
|
5293
|
+
_apply_futures_tc2_confirmation(context, body), loop
|
|
5294
|
+
)
|
|
5215
5295
|
except Exception as e:
|
|
5216
5296
|
logger.warning(f"Error processing TC2 event: {e}")
|
|
5217
5297
|
|
|
@@ -9072,6 +9152,16 @@ class RealOrderEventNodeExecutor(NodeExecutorBase):
|
|
|
9072
9152
|
return
|
|
9073
9153
|
|
|
9074
9154
|
body = getattr(resp, 'body', resp)
|
|
9155
|
+
if body is None:
|
|
9156
|
+
# The SDK could not read this frame (it then hands over
|
|
9157
|
+
# body=None with error_msg). Parsing None produced an
|
|
9158
|
+
# empty 'accepted' event that still triggered the next
|
|
9159
|
+
# nodes — up to finance 2.0.3 that was every TC2 frame
|
|
9160
|
+
# (LS sends `user_id`, the model required `userid`).
|
|
9161
|
+
# error_msg is not logged: it echoes the raw frame,
|
|
9162
|
+
# including the account number and login id.
|
|
9163
|
+
logger.warning(f"[{_node_id}] {tr_cd} order event could not be read; skipped")
|
|
9164
|
+
return
|
|
9075
9165
|
|
|
9076
9166
|
if tr_cd == "TC1":
|
|
9077
9167
|
event_data = self._parse_tc1_data(body)
|
|
@@ -5,7 +5,7 @@ authors = [
|
|
|
5
5
|
homepage = "https://programgarden.com"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
name = "programgarden"
|
|
8
|
-
version = "2.6.
|
|
8
|
+
version = "2.6.7"
|
|
9
9
|
license = "AGPL-3.0-or-later"
|
|
10
10
|
description = "ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진"
|
|
11
11
|
readme = "README.md"
|
|
@@ -37,7 +37,7 @@ litellm = ">=1.40.0"
|
|
|
37
37
|
# (1.26.0 이후로도 context.py 는 WorkflowPnLEvent(**event_data) 에 personal_metrics
|
|
38
38
|
# 를 무조건 실으므로 그 하한 근거도 이 상한에 포함된다.)
|
|
39
39
|
programgarden-core = "^2.6.3"
|
|
40
|
-
programgarden-finance = "^2.0.
|
|
40
|
+
programgarden-finance = "^2.0.5"
|
|
41
41
|
programgarden-community = "^2.3.1"
|
|
42
42
|
|
|
43
43
|
[tool.poetry.group.dev.dependencies]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/owned_order_cancellation.py
RENAMED
|
File without changes
|
{programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/position_reconciliation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/workflow_position_tracker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|