programgarden 1.37.5__tar.gz → 1.37.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.
Files changed (40) hide show
  1. {programgarden-1.37.5 → programgarden-1.37.7}/PKG-INFO +12 -1
  2. {programgarden-1.37.5 → programgarden-1.37.7}/README.md +11 -0
  3. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/executor.py +55 -7
  4. {programgarden-1.37.5 → programgarden-1.37.7}/pyproject.toml +1 -1
  5. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/__init__.py +0 -0
  6. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/binding_validator.py +0 -0
  7. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/client.py +0 -0
  8. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/code_worker.py +0 -0
  9. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/context.py +0 -0
  10. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/database/__init__.py +0 -0
  11. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/database/checkpoint_manager.py +0 -0
  12. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/database/fill_reconciler.py +0 -0
  13. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/database/query_builder.py +0 -0
  14. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/database/workflow_position_tracker.py +0 -0
  15. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/database/workflow_risk_tracker.py +0 -0
  16. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/deep_fixtures.py +0 -0
  17. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/futures_pnl.py +0 -0
  18. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/node_runner.py +0 -0
  19. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/order_lifecycle.py +0 -0
  20. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/plugin/__init__.py +0 -0
  21. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/plugin/sandbox.py +0 -0
  22. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/providers/__init__.py +0 -0
  23. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/providers/llm_errors.py +0 -0
  24. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/providers/llm_provider.py +0 -0
  25. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/reconnect_handler.py +0 -0
  26. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/resolver.py +0 -0
  27. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/resource/__init__.py +0 -0
  28. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/resource/context.py +0 -0
  29. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/resource/limiter.py +0 -0
  30. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/resource/monitor.py +0 -0
  31. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/resource/throttle.py +0 -0
  32. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/semantic_rules.py +0 -0
  33. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/tools/__init__.py +0 -0
  34. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/tools/credential_tools.py +0 -0
  35. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/tools/definition_tools.py +0 -0
  36. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/tools/event_tools.py +0 -0
  37. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/tools/job_tools.py +0 -0
  38. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/tools/registry_tools.py +0 -0
  39. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/tools/sqlite_tools.py +0 -0
  40. {programgarden-1.37.5 → programgarden-1.37.7}/programgarden/validation_recommender.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programgarden
3
- Version: 1.37.5
3
+ Version: 1.37.7
4
4
  Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Author: 프로그램동산
@@ -96,12 +96,23 @@ Broker account trackers belong to the workflow job. Normal completion, `stop()`,
96
96
  account polling, and close each tracker's dedicated WebSocket. Late account PnL
97
97
  callbacks do not restart work after shutdown. This cleanup also covers partially
98
98
  initialized trackers and leaves other jobs running.
99
+ Scheduled cycles reuse the same broker account tracker, including pending startup.
100
+ A failed startup stops its tracker and closes its connection before a later cycle
101
+ may try again; no immediate retry or extra workflow node is introduced.
99
102
  Already queued or in-flight PnL notifications have up to one second to finish
100
103
  after account trackers stop, before listeners close. On timeout, cleanup logs the
101
104
  pending count and cancels those tasks, allowing another 0.25 seconds for cancellation.
102
105
  A listener that suppresses cancellation is reported and remains tracked; it cannot
103
106
  hold job shutdown indefinitely. Delivery beyond this bounded grace is not guaranteed.
104
107
 
108
+ Overseas-stock OpenOrdersNode treats the observed terminal COSAQ00102 `02679`
109
+ empty envelope as `open_orders=[]` / `count=0` without an error. The echoed date,
110
+ market and filters must match its current-day pending query, the empty detail
111
+ array must be explicitly present, and continuation must be terminal. This follows
112
+ the finance package's observed-response reference and StockAccountTracker;
113
+ `02679` is not a generic success code. Missing, malformed, mismatched and failed
114
+ reads retain `error` / `reason=fetch_failed` so workflows can block new entries.
115
+
105
116
  For one-shot workflows, an unhandled `order_result.success=False` returned directly
106
117
  by a main-flow node makes the final job status `failed` and emits `WORKFLOW_FAILED`.
107
118
  The original broker message remains in node diagnostics and the failure statistics.
@@ -67,12 +67,23 @@ Broker account trackers belong to the workflow job. Normal completion, `stop()`,
67
67
  account polling, and close each tracker's dedicated WebSocket. Late account PnL
68
68
  callbacks do not restart work after shutdown. This cleanup also covers partially
69
69
  initialized trackers and leaves other jobs running.
70
+ Scheduled cycles reuse the same broker account tracker, including pending startup.
71
+ A failed startup stops its tracker and closes its connection before a later cycle
72
+ may try again; no immediate retry or extra workflow node is introduced.
70
73
  Already queued or in-flight PnL notifications have up to one second to finish
71
74
  after account trackers stop, before listeners close. On timeout, cleanup logs the
72
75
  pending count and cancels those tasks, allowing another 0.25 seconds for cancellation.
73
76
  A listener that suppresses cancellation is reported and remains tracked; it cannot
74
77
  hold job shutdown indefinitely. Delivery beyond this bounded grace is not guaranteed.
75
78
 
79
+ Overseas-stock OpenOrdersNode treats the observed terminal COSAQ00102 `02679`
80
+ empty envelope as `open_orders=[]` / `count=0` without an error. The echoed date,
81
+ market and filters must match its current-day pending query, the empty detail
82
+ array must be explicitly present, and continuation must be terminal. This follows
83
+ the finance package's observed-response reference and StockAccountTracker;
84
+ `02679` is not a generic success code. Missing, malformed, mismatched and failed
85
+ reads retain `error` / `reason=fetch_failed` so workflows can block new entries.
86
+
76
87
  For one-shot workflows, an unhandled `order_result.success=False` returned directly
77
88
  by a main-flow node makes the final job status `failed` and emits `WORKFLOW_FAILED`.
78
89
  The original broker message remains in node diagnostics and the failure statistics.
@@ -5491,6 +5491,11 @@ class BrokerNodeExecutor(NodeExecutorBase):
5491
5491
  로그인이라, 더미 secret 으로 도는 기기(트레이앱 검증 잡·서버 발급 모드)의 실제 실행에서
5492
5492
  추적기만 403 으로 죽고 error 로그 뒤 추적 없이 진행됐다.
5493
5493
  """
5494
+ tracker_key = f"{context.job_id}_{node_id}"
5495
+ # Scheduled cycles revisit the broker. Keep its existing tracker (also
5496
+ # while startup awaits connect) instead of orphaning polling/WS resources.
5497
+ if context.is_shutdown or tracker_key in self._active_trackers:
5498
+ return
5494
5499
  try:
5495
5500
  from programgarden_finance import LS
5496
5501
 
@@ -5527,6 +5532,18 @@ class BrokerNodeExecutor(NodeExecutorBase):
5527
5532
 
5528
5533
  except Exception as e:
5529
5534
  context.log("error", f"Failed to start account tracking: {e}", node_id)
5535
+ # Failed startup must not leave an unusable entry that prevents a
5536
+ # later scheduled cycle from trying again. No immediate retry.
5537
+ info = self._active_trackers.get(tracker_key)
5538
+ if info is not None:
5539
+ for resource, method in ((info.get("tracker"), "stop"), (info.get("real"), "close")):
5540
+ if resource is not None:
5541
+ try:
5542
+ await getattr(resource, method)()
5543
+ except Exception as cleanup_error:
5544
+ logger.warning("Account tracker startup cleanup failed: %s", type(cleanup_error).__name__)
5545
+ if self._active_trackers.get(tracker_key) is info:
5546
+ self._active_trackers.pop(tracker_key)
5530
5547
 
5531
5548
  async def _start_overseas_stock_tracker(
5532
5549
  self,
@@ -6541,14 +6558,39 @@ class OpenOrdersNodeExecutor(NodeExecutorBase):
6541
6558
 
6542
6559
  rows = response.block3 or []
6543
6560
  usable_rows = [item for item in rows if item.OrdNo and item.OrdNo > 0]
6544
- # The SDK defaults a missing detail block to []; it is not empty-result
6545
- # evidence. Unknown codes remain unclassified, even with echo blocks.
6546
- valid_empty = (
6561
+ # Match StockAccountTracker's observed pending-query envelope. The SDK
6562
+ # defaults missing details to []; only an explicit empty array is evidence.
6563
+ complete_empty = (
6547
6564
  not rows
6548
- and diagnostics["rsp_cd"] == "00000"
6549
- and getattr(response, "block1", None) is not None
6550
- and getattr(response, "block2", None) is not None
6565
+ and "block3" in response.model_fields_set
6566
+ and response.block1 is not None
6567
+ and response.block2 is not None
6568
+ )
6569
+ echo = response.block1
6570
+ header = response.header
6571
+ # 02679 is not a generic success code. This exact terminal response was
6572
+ # observed for market 00 (echoed as %) and this current-day pending query;
6573
+ # see finance/docs/observed_broker_responses.md (2026-09-09).
6574
+ observed_no_data = (
6575
+ complete_empty and status == 200 and diagnostics["rsp_cd"] == "02679"
6576
+ and echo.OrdDt == today and echo.OrdMktCode == "%"
6577
+ and echo.ThdayBnsAppYn == "1" and echo.BnsTpCode == "0"
6578
+ and echo.CrcyCode == "000"
6579
+ and all(
6580
+ str(getattr(echo, name)) == expected
6581
+ for name, expected in (
6582
+ ("QryTpCode", "1"), ("BkseqTpCode", "1"),
6583
+ ("IsuNo", ""), ("SrtOrdNo", "999999999"),
6584
+ ("ExecYn", "2"), ("LoanBalHldYn", "0"),
6585
+ )
6586
+ if name in echo.model_fields_set
6587
+ )
6588
+ and header is not None and header.tr_cont == "N"
6589
+ and not header.tr_cont_key
6551
6590
  )
6591
+ valid_empty = complete_empty and (diagnostics["rsp_cd"] == "00000" or observed_no_data)
6592
+ if diagnostics["rsp_cd"] == "02679" and not observed_no_data:
6593
+ return unavailable()
6552
6594
  if not usable_rows and not valid_empty:
6553
6595
  return unavailable()
6554
6596
 
@@ -24533,9 +24575,15 @@ class WorkflowJob:
24533
24575
 
24534
24576
  ls = entry["ls"]
24535
24577
  node_id = "fill_reconciler"
24578
+ # 🔴 체결 조회는 `NewOrderNodeExecutor` 의 메서드다 — WorkflowJob 의 것이 아니다.
24579
+ # `self._query_...` 로 부르면 AttributeError 로 매 주기 죽는다(2026-09-14 실관측).
24580
+ # 같은 부류로 `_active_trackers`(BrokerNodeExecutor) 도 한 번 틀렸다. 이 파일은
24581
+ # 한 모듈에 여러 클래스가 있어 `self.` 이 어느 클래스인지 눈으로는 안 보인다 —
24582
+ # test_fill_reconcile_wiring.py 의 소유자 검증 테스트가 이걸 잠근다.
24583
+ order_executor = NewOrderNodeExecutor()
24536
24584
 
24537
24585
  async def _fetch(order_date: str) -> Dict[str, Dict[str, Any]]:
24538
- return await self._query_overseas_stock_fills_by_date(
24586
+ return await order_executor._query_overseas_stock_fills_by_date(
24539
24587
  ls, order_date, self.context, node_id
24540
24588
  )
24541
24589
 
@@ -5,7 +5,7 @@ authors = [
5
5
  homepage = "https://programgarden.com"
6
6
  requires-python = ">=3.12"
7
7
  name = "programgarden"
8
- version = "1.37.5"
8
+ version = "1.37.7"
9
9
  license = "AGPL-3.0-or-later"
10
10
  description = "ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진"
11
11
  readme = "README.md"