programgarden 2.3.0__tar.gz → 2.4.0__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.3.0 → programgarden-2.4.0}/PKG-INFO +2 -2
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/executor.py +10 -6
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/replay_events.py +7 -1
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/replay_semantics.py +7 -4
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/replay_triggers.py +7 -2
- {programgarden-2.3.0 → programgarden-2.4.0}/pyproject.toml +2 -2
- {programgarden-2.3.0 → programgarden-2.4.0}/README.md +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/__init__.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/binding_validator.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/client.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/code_worker.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/context.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/__init__.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/adjustment_delivery.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/broker_evidence.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/broker_order_totals.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/broker_snapshot.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/checkpoint_manager.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/db_naming.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/execution_storage.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/fill_reconciler.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/order_cancellation.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/order_recovery.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/owned_order_cancellation.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/position_reconciliation.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/query_builder.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/workflow_position_tracker.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/workflow_risk_tracker.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/deep_fixtures.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/futures_orderable.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/futures_pnl.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/futures_read_evidence.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/incremental_build.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/managed_order_control.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/node_runner.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/order_lifecycle.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/plugin/__init__.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/plugin/sandbox.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/providers/__init__.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/providers/llm_errors.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/providers/llm_provider.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/reconnect_handler.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/replay_contracts.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/replay_external.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/replay_order_adapter.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/replay_orders.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/replay_scenarios.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/replay_sources.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/replay_sqlite.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/resolver.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/resource/__init__.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/resource/context.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/resource/limiter.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/resource/monitor.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/resource/throttle.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/semantic_rules.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/tools/__init__.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/tools/credential_tools.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/tools/definition_tools.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/tools/event_tools.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/tools/job_tools.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/tools/registry_tools.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/tools/sqlite_tools.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/validation_recommender.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/validation_replay.py +0 -0
- {programgarden-2.3.0 → programgarden-2.4.0}/programgarden/valuation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: programgarden
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
|
|
5
5
|
License-Expression: AGPL-3.0-or-later
|
|
6
6
|
Author: 프로그램동산
|
|
@@ -16,7 +16,7 @@ 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
18
|
Requires-Dist: programgarden-community (>=2.2.0,<3.0.0)
|
|
19
|
-
Requires-Dist: programgarden-core (>=2.
|
|
19
|
+
Requires-Dist: programgarden-core (>=2.4.0,<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)
|
|
@@ -2253,8 +2253,10 @@ class ScheduleNodeExecutor(NodeExecutorBase):
|
|
|
2253
2253
|
cron_expr = cron_expr.strip()
|
|
2254
2254
|
tz_name = config.get("timezone", "America/New_York")
|
|
2255
2255
|
enabled = config.get("enabled", True)
|
|
2256
|
-
|
|
2257
|
-
|
|
2256
|
+
# None/absent = unbounded: run until the workflow is stopped (owner
|
|
2257
|
+
# 2026-09-26). A provided value is enforced as a cap in the loop below.
|
|
2258
|
+
count = config.get("count") # None → no cycle cap
|
|
2259
|
+
max_duration_hours = config.get("max_duration_hours") # None → no wall-clock cap
|
|
2258
2260
|
|
|
2259
2261
|
if not enabled:
|
|
2260
2262
|
context.log("info", f"Schedule disabled: {cron_expr}", node_id)
|
|
@@ -2289,7 +2291,8 @@ class ScheduleNodeExecutor(NodeExecutorBase):
|
|
|
2289
2291
|
import time as _time
|
|
2290
2292
|
cnt = 0
|
|
2291
2293
|
start_mono = _time.monotonic()
|
|
2292
|
-
|
|
2294
|
+
# None = unbounded; only compute a wall-clock cap when a bound was set.
|
|
2295
|
+
max_duration_sec = max_duration_hours * 3600 if max_duration_hours is not None else None
|
|
2293
2296
|
try:
|
|
2294
2297
|
# second_at_beginning=True로 초 단위 cron도 지원
|
|
2295
2298
|
try:
|
|
@@ -2297,7 +2300,7 @@ class ScheduleNodeExecutor(NodeExecutorBase):
|
|
|
2297
2300
|
except TypeError:
|
|
2298
2301
|
itr = croniter(cron_expr, datetime.now(tz))
|
|
2299
2302
|
|
|
2300
|
-
while cnt < count and context.is_running:
|
|
2303
|
+
while (count is None or cnt < count) and context.is_running:
|
|
2301
2304
|
# dry_run: 루프 진입 즉시 1회 emit 후 종료
|
|
2302
2305
|
if context.is_dry_run:
|
|
2303
2306
|
cnt += 1
|
|
@@ -2317,8 +2320,9 @@ class ScheduleNodeExecutor(NodeExecutorBase):
|
|
|
2317
2320
|
},
|
|
2318
2321
|
)
|
|
2319
2322
|
break
|
|
2320
|
-
# M-6: max_duration_hours 초과 시 스케줄 종료
|
|
2321
|
-
|
|
2323
|
+
# M-6: max_duration_hours 초과 시 스케줄 종료 (한도가 설정된 경우에만;
|
|
2324
|
+
# None 이면 사용자가 워크플로우를 멈출 때까지 계속 실행)
|
|
2325
|
+
if max_duration_sec is not None and (_time.monotonic() - start_mono) >= max_duration_sec:
|
|
2322
2326
|
context.log(
|
|
2323
2327
|
"warning",
|
|
2324
2328
|
f"Schedule max_duration_hours={max_duration_hours}h 초과 - "
|
|
@@ -128,7 +128,13 @@ async def replay_events(job, runner, fixture, outcome):
|
|
|
128
128
|
if now != expected:
|
|
129
129
|
raise ContractViolation(source_id, "Recorded tick is not the next configured cron instant")
|
|
130
130
|
count = schedule_counts.get(source_id, 0) + 1
|
|
131
|
-
|
|
131
|
+
# count / max_duration_hours are optional (None/absent = unbounded,
|
|
132
|
+
# run until the workflow is stopped). Only enforce a bound that was set.
|
|
133
|
+
cfg_count = config.get("count")
|
|
134
|
+
cfg_max_dur = config.get("max_duration_hours")
|
|
135
|
+
if (cfg_count is not None and count > cfg_count) or (
|
|
136
|
+
cfg_max_dur is not None and (now - origin).total_seconds() > cfg_max_dur * 3600
|
|
137
|
+
):
|
|
132
138
|
raise ContractViolation(source_id, "Recorded ticks exceed schedule safety limits")
|
|
133
139
|
schedule_instants[source_id], schedule_counts[source_id] = now, count
|
|
134
140
|
else:
|
|
@@ -349,8 +349,9 @@ def _time_rules(node_type: str, role: str) -> List[Dict[str, Any]]:
|
|
|
349
349
|
{"id": "timezone_iana", "field": "timezone",
|
|
350
350
|
"default": "America/New_York", "invalid": "replay_rejects"},
|
|
351
351
|
{"id": "disabled_emits_nothing", "field": "enabled"},
|
|
352
|
-
{"id": "limits", "
|
|
353
|
-
"
|
|
352
|
+
{"id": "limits", "optional": True, "absent_means": "unbounded",
|
|
353
|
+
"count": {"default": None, "min": 1},
|
|
354
|
+
"max_duration_hours": {"default": None, "gt": 0}, "pin": "range_not_const"},
|
|
354
355
|
]
|
|
355
356
|
if node_type == "SessionGateNode":
|
|
356
357
|
return [{"id": "evaluate_at_fixture_instant"}] # validation_replay.py:276-283
|
|
@@ -607,8 +608,10 @@ def _schedule_time_lines(lines: List[str]) -> None:
|
|
|
607
608
|
lines.append("A recorded tick must equal the cron's next firing instant after "
|
|
608
609
|
"the prior frame in this timezone: a whole minute, at least one "
|
|
609
610
|
"minute later.")
|
|
610
|
-
lines.append("count and max_duration_hours are safety limits
|
|
611
|
-
"
|
|
611
|
+
lines.append("count and max_duration_hours are optional safety limits; omit both "
|
|
612
|
+
"and the schedule runs until the workflow is stopped. When provided, "
|
|
613
|
+
"count must be >= 1 and max_duration_hours > 0, and a contract bounds "
|
|
614
|
+
"them with a range, never a const.")
|
|
612
615
|
lines.append("Set the cron time in the target market's timezone. Market "
|
|
613
616
|
"operating hours by product:")
|
|
614
617
|
pe = product_execution()
|
|
@@ -49,8 +49,13 @@ async def schedule_startup(node, config, context):
|
|
|
49
49
|
ZoneInfo(node.timezone)
|
|
50
50
|
except (ValueError, KeyError):
|
|
51
51
|
raise ContractViolation(node.id, "Invalid schedule timezone") from None
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
# count / max_duration_hours are optional (None = unbounded, run until the
|
|
53
|
+
# workflow is stopped). Only a provided value is bounded (count >= 1,
|
|
54
|
+
# max_duration_hours > 0).
|
|
55
|
+
if (node.count is not None and node.count < 1) or (
|
|
56
|
+
node.max_duration_hours is not None and node.max_duration_hours <= 0
|
|
57
|
+
):
|
|
58
|
+
raise ContractViolation(node.id, "Schedule safety limits must be positive when set")
|
|
54
59
|
proxy = _StartupContext(context)
|
|
55
60
|
try:
|
|
56
61
|
return await ScheduleNodeExecutor().execute(node.id, node.type, config, proxy)
|
|
@@ -5,7 +5,7 @@ authors = [
|
|
|
5
5
|
homepage = "https://programgarden.com"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
name = "programgarden"
|
|
8
|
-
version = "2.
|
|
8
|
+
version = "2.4.0"
|
|
9
9
|
license = "AGPL-3.0-or-later"
|
|
10
10
|
description = "ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진"
|
|
11
11
|
readme = "README.md"
|
|
@@ -36,7 +36,7 @@ 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.
|
|
39
|
+
programgarden-core = "^2.4.0"
|
|
40
40
|
programgarden-finance = "^2.0.1"
|
|
41
41
|
programgarden-community = "^2.2.0"
|
|
42
42
|
|
|
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.3.0 → programgarden-2.4.0}/programgarden/database/owned_order_cancellation.py
RENAMED
|
File without changes
|
{programgarden-2.3.0 → programgarden-2.4.0}/programgarden/database/position_reconciliation.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden-2.3.0 → programgarden-2.4.0}/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
|