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.
Files changed (73) hide show
  1. {programgarden-2.6.6 → programgarden-2.6.7}/PKG-INFO +14 -3
  2. {programgarden-2.6.6 → programgarden-2.6.7}/README.md +12 -1
  3. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/executor.py +138 -48
  4. {programgarden-2.6.6 → programgarden-2.6.7}/pyproject.toml +2 -2
  5. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/__init__.py +0 -0
  6. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/binding_provenance.py +0 -0
  7. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/binding_validator.py +0 -0
  8. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/client.py +0 -0
  9. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/code_worker.py +0 -0
  10. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/context.py +0 -0
  11. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/__init__.py +0 -0
  12. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/adjustment_delivery.py +0 -0
  13. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/broker_evidence.py +0 -0
  14. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/broker_order_totals.py +0 -0
  15. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/broker_snapshot.py +0 -0
  16. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/checkpoint_manager.py +0 -0
  17. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/db_naming.py +0 -0
  18. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/execution_storage.py +0 -0
  19. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/fill_reconciler.py +0 -0
  20. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/order_cancellation.py +0 -0
  21. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/order_recovery.py +0 -0
  22. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/owned_order_cancellation.py +0 -0
  23. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/position_reconciliation.py +0 -0
  24. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/query_builder.py +0 -0
  25. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/sqlite_scope.py +0 -0
  26. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/workflow_position_tracker.py +0 -0
  27. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/database/workflow_risk_tracker.py +0 -0
  28. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/deep_fixtures.py +0 -0
  29. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/futures_contract_info.py +0 -0
  30. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/futures_exchange.py +0 -0
  31. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/futures_history.py +0 -0
  32. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/futures_orderable.py +0 -0
  33. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/futures_pnl.py +0 -0
  34. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/futures_read_evidence.py +0 -0
  35. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/incremental_build.py +0 -0
  36. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/managed_order_control.py +0 -0
  37. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/node_runner.py +0 -0
  38. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/order_lifecycle.py +0 -0
  39. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/paper_futures_business_date.py +0 -0
  40. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/plugin/__init__.py +0 -0
  41. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/plugin/sandbox.py +0 -0
  42. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/providers/__init__.py +0 -0
  43. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/providers/llm_errors.py +0 -0
  44. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/providers/llm_provider.py +0 -0
  45. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/ranking_nodes.py +0 -0
  46. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/reconnect_handler.py +0 -0
  47. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_contracts.py +0 -0
  48. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_events.py +0 -0
  49. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_external.py +0 -0
  50. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_order_adapter.py +0 -0
  51. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_orders.py +0 -0
  52. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_scenarios.py +0 -0
  53. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_semantics.py +0 -0
  54. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_sources.py +0 -0
  55. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_sqlite.py +0 -0
  56. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/replay_triggers.py +0 -0
  57. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/resolver.py +0 -0
  58. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/resource/__init__.py +0 -0
  59. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/resource/context.py +0 -0
  60. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/resource/limiter.py +0 -0
  61. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/resource/monitor.py +0 -0
  62. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/resource/throttle.py +0 -0
  63. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/semantic_rules.py +0 -0
  64. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/__init__.py +0 -0
  65. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/credential_tools.py +0 -0
  66. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/definition_tools.py +0 -0
  67. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/event_tools.py +0 -0
  68. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/job_tools.py +0 -0
  69. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/registry_tools.py +0 -0
  70. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/tools/sqlite_tools.py +0 -0
  71. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/validation_recommender.py +0 -0
  72. {programgarden-2.6.6 → programgarden-2.6.7}/programgarden/validation_replay.py +0 -0
  73. {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.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.3,<3.0.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. Unknown or blank side codes are logged and skipped
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. Unknown or blank side codes are logged and skipped
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
- TC2 필드명:
5165
- - svc_id: HO02=주문확인, HO03=주문거부
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', resp)
5178
- header = getattr(resp, 'header', None)
5179
- # TC2에서는 svc_id가 body에 있음
5180
- svc_id = getattr(body, 'svc_id', '') or (getattr(header, 'svc_id', '') if header else '')
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.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.3"
40
+ programgarden-finance = "^2.0.5"
41
41
  programgarden-community = "^2.3.1"
42
42
 
43
43
  [tool.poetry.group.dev.dependencies]