programgarden 2.5.4__tar.gz → 2.5.5__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.4 → programgarden-2.5.5}/PKG-INFO +1 -1
  2. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/context.py +1 -0
  3. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/executor.py +23 -6
  4. {programgarden-2.5.4 → programgarden-2.5.5}/pyproject.toml +1 -1
  5. {programgarden-2.5.4 → programgarden-2.5.5}/README.md +0 -0
  6. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/__init__.py +0 -0
  7. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/binding_validator.py +0 -0
  8. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/client.py +0 -0
  9. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/code_worker.py +0 -0
  10. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/__init__.py +0 -0
  11. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/adjustment_delivery.py +0 -0
  12. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/broker_evidence.py +0 -0
  13. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/broker_order_totals.py +0 -0
  14. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/broker_snapshot.py +0 -0
  15. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/checkpoint_manager.py +0 -0
  16. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/db_naming.py +0 -0
  17. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/execution_storage.py +0 -0
  18. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/fill_reconciler.py +0 -0
  19. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/order_cancellation.py +0 -0
  20. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/order_recovery.py +0 -0
  21. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/owned_order_cancellation.py +0 -0
  22. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/position_reconciliation.py +0 -0
  23. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/query_builder.py +0 -0
  24. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/workflow_position_tracker.py +0 -0
  25. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/database/workflow_risk_tracker.py +0 -0
  26. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/deep_fixtures.py +0 -0
  27. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/futures_orderable.py +0 -0
  28. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/futures_pnl.py +0 -0
  29. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/futures_read_evidence.py +0 -0
  30. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/incremental_build.py +0 -0
  31. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/managed_order_control.py +0 -0
  32. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/node_runner.py +0 -0
  33. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/order_lifecycle.py +0 -0
  34. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/plugin/__init__.py +0 -0
  35. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/plugin/sandbox.py +0 -0
  36. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/providers/__init__.py +0 -0
  37. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/providers/llm_errors.py +0 -0
  38. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/providers/llm_provider.py +0 -0
  39. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/reconnect_handler.py +0 -0
  40. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/replay_contracts.py +0 -0
  41. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/replay_events.py +0 -0
  42. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/replay_external.py +0 -0
  43. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/replay_order_adapter.py +0 -0
  44. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/replay_orders.py +0 -0
  45. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/replay_scenarios.py +0 -0
  46. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/replay_semantics.py +0 -0
  47. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/replay_sources.py +0 -0
  48. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/replay_sqlite.py +0 -0
  49. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/replay_triggers.py +0 -0
  50. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/resolver.py +0 -0
  51. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/resource/__init__.py +0 -0
  52. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/resource/context.py +0 -0
  53. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/resource/limiter.py +0 -0
  54. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/resource/monitor.py +0 -0
  55. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/resource/throttle.py +0 -0
  56. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/semantic_rules.py +0 -0
  57. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/tools/__init__.py +0 -0
  58. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/tools/credential_tools.py +0 -0
  59. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/tools/definition_tools.py +0 -0
  60. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/tools/event_tools.py +0 -0
  61. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/tools/job_tools.py +0 -0
  62. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/tools/registry_tools.py +0 -0
  63. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/tools/sqlite_tools.py +0 -0
  64. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/validation_recommender.py +0 -0
  65. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/validation_replay.py +0 -0
  66. {programgarden-2.5.4 → programgarden-2.5.5}/programgarden/valuation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programgarden
3
- Version: 2.5.4
3
+ Version: 2.5.5
4
4
  Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Author: 프로그램동산
@@ -260,6 +260,7 @@ class ExecutionContext:
260
260
  # Stores trackers/connections that should stay alive between flow executions
261
261
  self._persistent_nodes: Dict[str, Any] = {} # node_id -> tracker/connection
262
262
  self._persistent_tasks: Dict[str, asyncio.Task] = {} # node_id -> background task
263
+ self._schedule_tick_source: Optional[str] = None # Only this timer may trigger the current cycle.
263
264
  self._persistent_metadata: Dict[str, Dict[str, Any]] = {} # node_id -> {key: value}
264
265
 
265
266
  # === New: Order Event Dispatcher ===
@@ -2227,12 +2227,12 @@ class ScheduleNodeExecutor(NodeExecutorBase):
2227
2227
  ) -> Dict[str, Any]:
2228
2228
  from datetime import datetime
2229
2229
  from zoneinfo import ZoneInfo
2230
- from croniter import croniter
2230
+ from croniter import croniter, CroniterBadDateError
2231
2231
 
2232
2232
  # 이미 스케줄러가 실행 중이면 재등록하지 않음 (schedule_tick 이벤트로 인한 재실행 시)
2233
2233
  if node_id in context._persistent_tasks:
2234
2234
  context.log("debug", f"Scheduler already running for {node_id}, skip", node_id)
2235
- return {"trigger": True}
2235
+ return {"trigger": bool(context.is_dry_run or context._schedule_tick_source == node_id)}
2236
2236
 
2237
2237
  # ⑭ cron is required — NO silent "*/5 * * * *" fallback. A missing cron
2238
2238
  # used to make the schedule quietly run every 5 minutes ("looks fine,
@@ -2305,7 +2305,7 @@ class ScheduleNodeExecutor(NodeExecutorBase):
2305
2305
  try:
2306
2306
  # second_at_beginning=True로 초 단위 cron도 지원
2307
2307
  try:
2308
- itr = croniter(cron_expr, datetime.now(tz), second_at_beginning=True)
2308
+ itr = croniter(cron_expr, datetime.now(tz), second_at_beginning=True, max_years_between_matches=130)
2309
2309
  except TypeError:
2310
2310
  itr = croniter(cron_expr, datetime.now(tz))
2311
2311
 
@@ -2340,7 +2340,12 @@ class ScheduleNodeExecutor(NodeExecutorBase):
2340
2340
  )
2341
2341
  break
2342
2342
  # 다음 실행 시간 계산
2343
- next_dt = itr.get_next(datetime)
2343
+ try:
2344
+ next_dt = itr.get_next(datetime)
2345
+ except CroniterBadDateError:
2346
+ # A calendar date/year can be exhausted. It is not an
2347
+ # execution failure and must never roll into another year.
2348
+ break
2344
2349
  now = datetime.now(tz)
2345
2350
  delay = (next_dt - now).total_seconds()
2346
2351
 
@@ -2421,8 +2426,9 @@ class ScheduleNodeExecutor(NodeExecutorBase):
2421
2426
  task = asyncio.create_task(scheduler_task())
2422
2427
  context.register_persistent_task(node_id, task)
2423
2428
 
2424
- # 초기 트리거 반환 (첫 플로우 실행용)
2425
- return {"trigger": True}
2429
+ # Register the timer without executing downstream trading work early.
2430
+ # Explicit dry-run simulation still exercises one synthetic cycle.
2431
+ return {"trigger": bool(context.is_dry_run)}
2426
2432
 
2427
2433
 
2428
2434
  class WatchlistNodeExecutor(NodeExecutorBase):
@@ -23557,6 +23563,14 @@ class WorkflowJob:
23557
23563
  event = await self.context.wait_for_event(timeout=1.0)
23558
23564
 
23559
23565
  if event is None:
23566
+ has_realtime_source = any(
23567
+ self.workflow.nodes[n].node_type in REALTIME_NODE_TYPES
23568
+ for n in self._stay_connected_nodes
23569
+ )
23570
+ if (self._has_schedule_node and not has_realtime_source
23571
+ and all(task.done() for task in self.context._persistent_tasks.values())):
23572
+ # Finite calendar/count schedules finish after queued ticks drain.
23573
+ break
23560
23574
  continue
23561
23575
 
23562
23576
  # Wait if paused
@@ -23595,6 +23609,7 @@ class WorkflowJob:
23595
23609
  # RuntimeError 등)가 스케줄 잡 전체를 종료시키지 않도록 한다.
23596
23610
  # 실패 사이클은 silent 하지 않게 'cycle_failed' 로 통지하고
23597
23611
  # 다음 tick 에서 재시도한다 (24시간 무인 운영 보호).
23612
+ self.context._schedule_tick_source = event.source_node_id
23598
23613
  try:
23599
23614
  await self._execute_main_flow()
23600
23615
  except Exception as cycle_err:
@@ -23606,6 +23621,8 @@ class WorkflowJob:
23606
23621
  await self.context.notify_job_state("cycle_failed", self.stats)
23607
23622
  else:
23608
23623
  await self.context.notify_job_state("cycle_completed", self.stats)
23624
+ finally:
23625
+ self.context._schedule_tick_source = None
23609
23626
 
23610
23627
  logger.info("Event loop ended")
23611
23628
 
@@ -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.4"
8
+ version = "2.5.5"
9
9
  license = "AGPL-3.0-or-later"
10
10
  description = "ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진"
11
11
  readme = "README.md"
File without changes