programgarden 2.5.0__tar.gz → 2.5.2__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 (66) hide show
  1. {programgarden-2.5.0 → programgarden-2.5.2}/PKG-INFO +7 -3
  2. {programgarden-2.5.0 → programgarden-2.5.2}/README.md +4 -0
  3. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/executor.py +38 -3
  4. {programgarden-2.5.0 → programgarden-2.5.2}/pyproject.toml +3 -3
  5. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/__init__.py +0 -0
  6. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/binding_validator.py +0 -0
  7. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/client.py +0 -0
  8. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/code_worker.py +0 -0
  9. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/context.py +0 -0
  10. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/__init__.py +0 -0
  11. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/adjustment_delivery.py +0 -0
  12. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/broker_evidence.py +0 -0
  13. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/broker_order_totals.py +0 -0
  14. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/broker_snapshot.py +0 -0
  15. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/checkpoint_manager.py +0 -0
  16. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/db_naming.py +0 -0
  17. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/execution_storage.py +0 -0
  18. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/fill_reconciler.py +0 -0
  19. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/order_cancellation.py +0 -0
  20. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/order_recovery.py +0 -0
  21. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/owned_order_cancellation.py +0 -0
  22. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/position_reconciliation.py +0 -0
  23. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/query_builder.py +0 -0
  24. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/workflow_position_tracker.py +0 -0
  25. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/database/workflow_risk_tracker.py +0 -0
  26. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/deep_fixtures.py +0 -0
  27. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/futures_orderable.py +0 -0
  28. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/futures_pnl.py +0 -0
  29. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/futures_read_evidence.py +0 -0
  30. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/incremental_build.py +0 -0
  31. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/managed_order_control.py +0 -0
  32. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/node_runner.py +0 -0
  33. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/order_lifecycle.py +0 -0
  34. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/plugin/__init__.py +0 -0
  35. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/plugin/sandbox.py +0 -0
  36. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/providers/__init__.py +0 -0
  37. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/providers/llm_errors.py +0 -0
  38. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/providers/llm_provider.py +0 -0
  39. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/reconnect_handler.py +0 -0
  40. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/replay_contracts.py +0 -0
  41. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/replay_events.py +0 -0
  42. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/replay_external.py +0 -0
  43. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/replay_order_adapter.py +0 -0
  44. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/replay_orders.py +0 -0
  45. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/replay_scenarios.py +0 -0
  46. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/replay_semantics.py +0 -0
  47. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/replay_sources.py +0 -0
  48. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/replay_sqlite.py +0 -0
  49. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/replay_triggers.py +0 -0
  50. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/resolver.py +0 -0
  51. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/resource/__init__.py +0 -0
  52. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/resource/context.py +0 -0
  53. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/resource/limiter.py +0 -0
  54. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/resource/monitor.py +0 -0
  55. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/resource/throttle.py +0 -0
  56. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/semantic_rules.py +0 -0
  57. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/tools/__init__.py +0 -0
  58. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/tools/credential_tools.py +0 -0
  59. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/tools/definition_tools.py +0 -0
  60. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/tools/event_tools.py +0 -0
  61. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/tools/job_tools.py +0 -0
  62. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/tools/registry_tools.py +0 -0
  63. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/tools/sqlite_tools.py +0 -0
  64. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/validation_recommender.py +0 -0
  65. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/validation_replay.py +0 -0
  66. {programgarden-2.5.0 → programgarden-2.5.2}/programgarden/valuation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programgarden
3
- Version: 2.5.0
3
+ Version: 2.5.2
4
4
  Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Author: 프로그램동산
@@ -15,8 +15,8 @@ Requires-Dist: aiosqlite (>=0.20.0,<0.21.0)
15
15
  Requires-Dist: croniter (>=6.0.0,<7.0.0)
16
16
  Requires-Dist: litellm (>=1.40.0)
17
17
  Requires-Dist: lxml (>=6.0.2,<7.0.0)
18
- Requires-Dist: programgarden-community (>=2.3.0,<3.0.0)
19
- Requires-Dist: programgarden-core (>=2.5.0,<3.0.0)
18
+ Requires-Dist: programgarden-community (>=2.3.1,<3.0.0)
19
+ Requires-Dist: programgarden-core (>=2.5.2,<3.0.0)
20
20
  Requires-Dist: programgarden-finance (>=2.0.1,<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)
@@ -29,6 +29,10 @@ Description-Content-Type: text/markdown
29
29
 
30
30
  # ProgramGarden
31
31
 
32
+ The REST overseas-stock account output preserves broker-reported `sellable_qty`
33
+ separately from holdings; absent capacity remains null. See
34
+ [the sell-capacity contract](../../docs/overseas-stock-sellable-quantity.md).
35
+
32
36
  Unreleased execution reconciliation work is documented in
33
37
  [execution-reconciliation.md](../../docs/execution-reconciliation.md). Its
34
38
  optional host storage identity is not yet enabled in deployed worker/tray hosts;
@@ -1,5 +1,9 @@
1
1
  # ProgramGarden
2
2
 
3
+ The REST overseas-stock account output preserves broker-reported `sellable_qty`
4
+ separately from holdings; absent capacity remains null. See
5
+ [the sell-capacity contract](../../docs/overseas-stock-sellable-quantity.md).
6
+
3
7
  Unreleased execution reconciliation work is documented in
4
8
  [execution-reconciliation.md](../../docs/execution-reconciliation.md). Its
5
9
  optional host storage identity is not yet enabled in deployed worker/tray hosts;
@@ -2257,7 +2257,15 @@ class ScheduleNodeExecutor(NodeExecutorBase):
2257
2257
  # 2026-09-26). A provided value is enforced as a cap in the loop below.
2258
2258
  count = config.get("count") # None → no cycle cap
2259
2259
  max_duration_hours = config.get("max_duration_hours") # None → no wall-clock cap
2260
-
2260
+ # Schedule jitter (owner 2026-09-28): stagger each fire 0..jitter_seconds past
2261
+ # the cron instant so many same-cron workflows do not stampede a shared egress
2262
+ # IP. Clamp to [0, 300]; a non-numeric value falls back to 0 (no jitter).
2263
+ try:
2264
+ jitter_seconds = int(config.get("jitter_seconds", 0) or 0)
2265
+ except (TypeError, ValueError):
2266
+ jitter_seconds = 0
2267
+ jitter_seconds = max(0, min(jitter_seconds, 300))
2268
+
2261
2269
  if not enabled:
2262
2270
  context.log("info", f"Schedule disabled: {cron_expr}", node_id)
2263
2271
  return {"trigger": False}
@@ -2289,6 +2297,7 @@ class ScheduleNodeExecutor(NodeExecutorBase):
2289
2297
  # 백그라운드 스케줄러 태스크
2290
2298
  async def scheduler_task():
2291
2299
  import time as _time
2300
+ import random as _random
2292
2301
  cnt = 0
2293
2302
  start_mono = _time.monotonic()
2294
2303
  # None = unbounded; only compute a wall-clock cap when a bound was set.
@@ -2349,10 +2358,30 @@ class ScheduleNodeExecutor(NodeExecutorBase):
2349
2358
  sleep_time = min(delay, 1.0)
2350
2359
  await asyncio.sleep(sleep_time)
2351
2360
  delay -= sleep_time
2352
-
2361
+
2353
2362
  if not context.is_running:
2354
2363
  break
2355
-
2364
+
2365
+ # 스케줄 지터 (오너 2026-09-28): 같은 cron 순간을 공유하는 여러
2366
+ # 워크플로우가 공유 egress IP 로 동시에 몰리는 것을 막으려, 실제 실행을
2367
+ # cron 순간 이후 0~jitter 초 랜덤 지연한다. cron 케이던스 앵커는 그대로다.
2368
+ # dry_run 은 위에서 단일 틱 emit 후 break 하므로 여기 도달하지 않는다
2369
+ # (빌드 예산·replay 의미에 영향 없음).
2370
+ if jitter_seconds > 0 and context.is_running:
2371
+ jitter_delay = _random.uniform(0, jitter_seconds)
2372
+ context.log(
2373
+ "debug",
2374
+ f"Schedule jitter: delaying fire by {jitter_delay:.1f}s "
2375
+ f"(0..{jitter_seconds}s)",
2376
+ node_id,
2377
+ )
2378
+ while jitter_delay > 0 and context.is_running:
2379
+ sleep_time = min(jitter_delay, 1.0)
2380
+ await asyncio.sleep(sleep_time)
2381
+ jitter_delay -= sleep_time
2382
+ if not context.is_running:
2383
+ break
2384
+
2356
2385
  # 스케줄 시간 도달 - 이벤트 발생
2357
2386
  cnt += 1
2358
2387
  context.log("info", f"Schedule tick #{cnt}: {cron_expr}", node_id)
@@ -5937,6 +5966,12 @@ class AccountNodeExecutor(NodeExecutorBase):
5937
5966
  "name": item.JpnMktHanglIsuNm.strip() if item.JpnMktHanglIsuNm else symbol,
5938
5967
  "qty": item.AstkBalQty,
5939
5968
  "quantity": item.AstkBalQty, # NewOrderNode 호환
5969
+ # SDK defaults are not evidence that LS returned this quantity.
5970
+ "sellable_qty": (
5971
+ item.AstkSellAbleQty
5972
+ if "AstkSellAbleQty" in getattr(item, "model_fields_set", set())
5973
+ else None
5974
+ ),
5940
5975
  "direction": "long", # 주식은 보유=매수(long)
5941
5976
  "close_side": "sell", # 청산=매도
5942
5977
  "avg_price": item.FcstckUprc,
@@ -5,7 +5,7 @@ authors = [
5
5
  homepage = "https://programgarden.com"
6
6
  requires-python = ">=3.12"
7
7
  name = "programgarden"
8
- version = "2.5.0"
8
+ version = "2.5.2"
9
9
  license = "AGPL-3.0-or-later"
10
10
  description = "ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진"
11
11
  readme = "README.md"
@@ -36,9 +36,9 @@ litellm = ">=1.40.0"
36
36
  # 로 통째 실패한다(1.35.1 의 조용한 TypeError 보다 강한 모듈 로드 실패).
37
37
  # (1.26.0 이후로도 context.py 는 WorkflowPnLEvent(**event_data) 에 personal_metrics
38
38
  # 를 무조건 실으므로 그 하한 근거도 이 상한에 포함된다.)
39
- programgarden-core = "^2.5.0"
39
+ programgarden-core = "^2.5.2"
40
40
  programgarden-finance = "^2.0.1"
41
- programgarden-community = "^2.3.0"
41
+ programgarden-community = "^2.3.1"
42
42
 
43
43
  [tool.poetry.group.dev.dependencies]
44
44
  pytest = "^8.0.0"