programgarden 1.40.0__tar.gz → 1.41.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-1.40.0 → programgarden-1.41.0}/PKG-INFO +6 -2
- {programgarden-1.40.0 → programgarden-1.41.0}/README.md +4 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/code_worker.py +16 -3
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/executor.py +49 -12
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/semantic_rules.py +3 -1
- {programgarden-1.40.0 → programgarden-1.41.0}/pyproject.toml +2 -2
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/__init__.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/binding_validator.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/client.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/context.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/__init__.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/adjustment_delivery.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/broker_evidence.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/broker_order_totals.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/broker_snapshot.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/checkpoint_manager.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/db_naming.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/execution_storage.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/fill_reconciler.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/order_cancellation.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/order_recovery.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/owned_order_cancellation.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/position_reconciliation.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/query_builder.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/workflow_position_tracker.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/workflow_risk_tracker.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/deep_fixtures.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/futures_orderable.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/futures_pnl.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/futures_read_evidence.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/managed_order_control.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/node_runner.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/order_lifecycle.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/plugin/__init__.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/plugin/sandbox.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/providers/__init__.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/providers/llm_errors.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/providers/llm_provider.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/reconnect_handler.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/resolver.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/resource/__init__.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/resource/context.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/resource/limiter.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/resource/monitor.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/resource/throttle.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/tools/__init__.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/tools/credential_tools.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/tools/definition_tools.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/tools/event_tools.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/tools/job_tools.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/tools/registry_tools.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/tools/sqlite_tools.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/validation_recommender.py +0 -0
- {programgarden-1.40.0 → programgarden-1.41.0}/programgarden/valuation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: programgarden
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.41.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 (>=1.15.3,<2.0.0)
|
|
19
|
-
Requires-Dist: programgarden-core (>=1.
|
|
19
|
+
Requires-Dist: programgarden-core (>=1.31.0,<2.0.0)
|
|
20
20
|
Requires-Dist: programgarden-finance (>=1.10.5,<2.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)
|
|
@@ -308,3 +308,7 @@ job = await pg.run_async(
|
|
|
308
308
|
|
|
309
309
|
자세한 변경 사항은 `CHANGELOG.md`를 참고하세요.
|
|
310
310
|
|
|
311
|
+
See [Session gates and guarded Split](../../docs/session-gates-and-guarded-split.md)
|
|
312
|
+
for immediate timezone decisions, per-item conditional routing, effective sequential
|
|
313
|
+
execution of guarded/order branches, and finite CodeNode input bindings.
|
|
314
|
+
|
|
@@ -278,3 +278,7 @@ job = await pg.run_async(
|
|
|
278
278
|
## 변경 로그
|
|
279
279
|
|
|
280
280
|
자세한 변경 사항은 `CHANGELOG.md`를 참고하세요.
|
|
281
|
+
|
|
282
|
+
See [Session gates and guarded Split](../../docs/session-gates-and-guarded-split.md)
|
|
283
|
+
for immediate timezone decisions, per-item conditional routing, effective sequential
|
|
284
|
+
execution of guarded/order branches, and finite CodeNode input bindings.
|
|
@@ -472,12 +472,25 @@ def run_code_node_sandboxed(
|
|
|
472
472
|
{"ok": True, "value": <raw return>}
|
|
473
473
|
{"ok": False, "error_code": ..., "message": ..., "suggestion": ..., "line": ..., "traceback": ...}
|
|
474
474
|
"""
|
|
475
|
+
# Expressions can accidentally return a bound helper (e.g. `.count`
|
|
476
|
+
# instead of `.count()`). Never pickle such objects into the child: their
|
|
477
|
+
# proxy state can recurse during unpickling and crash the worker process.
|
|
478
|
+
# Normalize the input boundary to the same finite JSON domain as outputs.
|
|
479
|
+
try:
|
|
480
|
+
inputs = json.loads(json.dumps(
|
|
481
|
+
{"data": data, "params": params or {}, "ctx_snapshot": ctx_snapshot or {}},
|
|
482
|
+
allow_nan=False,
|
|
483
|
+
))
|
|
484
|
+
except (TypeError, ValueError, RecursionError):
|
|
485
|
+
return _error_envelope(
|
|
486
|
+
"CODE_NODE_EXEC_ERROR",
|
|
487
|
+
"CodeNode input must contain only finite JSON-safe values.",
|
|
488
|
+
suggestion="Check data/params bindings; call array helpers such as count() instead of passing a method.",
|
|
489
|
+
)
|
|
475
490
|
task: Dict[str, Any] = {
|
|
476
491
|
"code": code,
|
|
477
492
|
"node_id": node_id,
|
|
478
|
-
|
|
479
|
-
"params": params or {},
|
|
480
|
-
"ctx_snapshot": ctx_snapshot or {},
|
|
493
|
+
**inputs,
|
|
481
494
|
"allowed_imports": list(allowed_imports) if allowed_imports else None,
|
|
482
495
|
}
|
|
483
496
|
pool = get_code_worker_pool()
|
|
@@ -21837,6 +21837,16 @@ class WorkflowJob:
|
|
|
21837
21837
|
)
|
|
21838
21838
|
continue
|
|
21839
21839
|
|
|
21840
|
+
# === IfNode: 비활성 브랜치 스킵 ===
|
|
21841
|
+
if node_id in if_skipped_nodes:
|
|
21842
|
+
_safe_print(f" ⏭ Skipping node: {node_id} (IfNode branch not taken)")
|
|
21843
|
+
await self.context.notify_node_state(
|
|
21844
|
+
node_id=node_id,
|
|
21845
|
+
node_type=node.node_type,
|
|
21846
|
+
state=NodeState.SKIPPED,
|
|
21847
|
+
)
|
|
21848
|
+
continue
|
|
21849
|
+
|
|
21840
21850
|
# === Item-based execution: Skip branch nodes (handled by SplitNode) ===
|
|
21841
21851
|
if node_id in branch_nodes and node.node_type not in ("SplitNode", "AggregateNode"):
|
|
21842
21852
|
_safe_print(f" ⏭ Skipping branch node: {node_id} (handled by SplitNode)")
|
|
@@ -21853,16 +21863,6 @@ class WorkflowJob:
|
|
|
21853
21863
|
_safe_print(f" ⏭ Skipping AggregateNode: {node_id} (already executed by SplitNode)")
|
|
21854
21864
|
continue
|
|
21855
21865
|
|
|
21856
|
-
# === IfNode: 비활성 브랜치 스킵 ===
|
|
21857
|
-
if node_id in if_skipped_nodes:
|
|
21858
|
-
_safe_print(f" ⏭ Skipping node: {node_id} (IfNode branch not taken)")
|
|
21859
|
-
await self.context.notify_node_state(
|
|
21860
|
-
node_id=node_id,
|
|
21861
|
-
node_type=node.node_type,
|
|
21862
|
-
state=NodeState.SKIPPED,
|
|
21863
|
-
)
|
|
21864
|
-
continue
|
|
21865
|
-
|
|
21866
21866
|
_safe_print(f" ▶ Executing node: {node_id} ({node.node_type})")
|
|
21867
21867
|
|
|
21868
21868
|
# 🆕 노드 실행 시작 알림
|
|
@@ -22265,7 +22265,7 @@ class WorkflowJob:
|
|
|
22265
22265
|
"OverseasStockAccountNode", "OverseasFuturesAccountNode", # 계좌 노드
|
|
22266
22266
|
"OverseasStockRealAccountNode", "OverseasFuturesRealAccountNode",
|
|
22267
22267
|
"KoreaStockAccountNode", "KoreaStockRealAccountNode",
|
|
22268
|
-
"SQLiteNode", "HTTPRequestNode", # 데이터 노드
|
|
22268
|
+
"SQLiteNode", "HTTPRequestNode", "SessionGateNode", # 데이터 노드
|
|
22269
22269
|
"CodeNode", # custom code: receives the whole array in `data`, loops in-code (one subprocess call)
|
|
22270
22270
|
"BacktestEngineNode", "BenchmarkCompareNode", # 분석 노드
|
|
22271
22271
|
}
|
|
@@ -22979,6 +22979,24 @@ class WorkflowJob:
|
|
|
22979
22979
|
parallel = config.get("parallel", False)
|
|
22980
22980
|
delay_ms = config.get("delay_ms", 0)
|
|
22981
22981
|
continue_on_error = config.get("continue_on_error", True)
|
|
22982
|
+
# Branch expressions share ExecutionContext outputs. Until contexts
|
|
22983
|
+
# are isolated per parallel item, an await can replace item A's gate
|
|
22984
|
+
# evidence with item B's data. Serialize guarded/order branches even
|
|
22985
|
+
# when parallel was requested; do not race an authorization decision.
|
|
22986
|
+
guarded_branch = any(
|
|
22987
|
+
self.workflow.nodes[n].node_type == "IfNode"
|
|
22988
|
+
or self.workflow.nodes[n].node_type.endswith(
|
|
22989
|
+
("NewOrderNode", "ModifyOrderNode", "CancelOrderNode")
|
|
22990
|
+
)
|
|
22991
|
+
for n in branch_order if n in self.workflow.nodes
|
|
22992
|
+
)
|
|
22993
|
+
if parallel and guarded_branch:
|
|
22994
|
+
parallel = False
|
|
22995
|
+
self.context.log(
|
|
22996
|
+
"warning",
|
|
22997
|
+
"SplitNode guarded/order branches execute sequentially to preserve per-item decisions.",
|
|
22998
|
+
split_id,
|
|
22999
|
+
)
|
|
22982
23000
|
|
|
22983
23001
|
# Resolve the array to split — the single source of truth for this
|
|
22984
23002
|
# branch: per-item item/index/total AND the items/_array output ports
|
|
@@ -23181,11 +23199,26 @@ class WorkflowJob:
|
|
|
23181
23199
|
# 종목들이 한 노드를 공유하면 대기시간까지 공유돼 한 번에 한 종목만 통과한다.
|
|
23182
23200
|
branch_scope = f"{split_id}#{index}"
|
|
23183
23201
|
|
|
23202
|
+
# Routing decisions belong to this item, never the preceding item.
|
|
23203
|
+
if_skipped_nodes: Set[str] = set()
|
|
23204
|
+
terminal_nodes = {
|
|
23205
|
+
edge.from_node_id for edge in self.workflow.edges
|
|
23206
|
+
if edge.from_node_id in branch_order
|
|
23207
|
+
and self.workflow.nodes.get(edge.to_node_id)
|
|
23208
|
+
and self.workflow.nodes[edge.to_node_id].node_type == "AggregateNode"
|
|
23209
|
+
}
|
|
23210
|
+
|
|
23184
23211
|
# Execute each branch node
|
|
23185
23212
|
for node_id in branch_order:
|
|
23186
23213
|
node = self.workflow.nodes.get(node_id)
|
|
23187
23214
|
if not node:
|
|
23188
23215
|
continue
|
|
23216
|
+
if node_id in if_skipped_nodes:
|
|
23217
|
+
# Do not expose a previous item's gated output through a later
|
|
23218
|
+
# expression or collect it as this item's result.
|
|
23219
|
+
self.context._outputs.pop(node_id, None)
|
|
23220
|
+
self.context._outputs.pop(f"_input_{node_id}", None)
|
|
23221
|
+
continue
|
|
23189
23222
|
|
|
23190
23223
|
is_realtime = node.node_type in REALTIME_NODE_TYPES
|
|
23191
23224
|
subscribed: List[str] = []
|
|
@@ -23239,6 +23272,10 @@ class WorkflowJob:
|
|
|
23239
23272
|
order_iteration_index=index,
|
|
23240
23273
|
)
|
|
23241
23274
|
|
|
23275
|
+
if node.node_type == "IfNode" and outputs:
|
|
23276
|
+
taken = outputs.pop("_if_branch", "true")
|
|
23277
|
+
if_skipped_nodes.update(self._compute_if_skip_nodes(node_id, taken))
|
|
23278
|
+
|
|
23242
23279
|
# Store outputs
|
|
23243
23280
|
for port_name, value in outputs.items():
|
|
23244
23281
|
self.context.set_output(node_id, port_name, value)
|
|
@@ -23310,7 +23347,7 @@ class WorkflowJob:
|
|
|
23310
23347
|
# 을 throttling 중이라 내부 메타만 반환) 이 아이템은 기여할 실데이터가 없다 →
|
|
23311
23348
|
# skip 하여 "실시간 체결가" 표에 가격 없는/이질적인 행이 끼지 않게 한다. branch
|
|
23312
23349
|
# 노드가 아예 없으면(bare Split→Aggregate) item 을 그대로 수집한다(정상 패턴).
|
|
23313
|
-
if branch_order and last_had_public is False:
|
|
23350
|
+
if branch_order and (last_had_public is False or (terminal_nodes and terminal_nodes <= if_skipped_nodes)):
|
|
23314
23351
|
return _SKIP_BRANCH_ITEM
|
|
23315
23352
|
|
|
23316
23353
|
return result
|
|
@@ -198,8 +198,10 @@ def _is_aiagent_node(node_type: Any) -> bool:
|
|
|
198
198
|
# workflow at the strict save gate.
|
|
199
199
|
_RESERVED_NODE_KEYS: frozenset = frozenset(
|
|
200
200
|
{"id", "type", "category", "position", "config", "description", "plugin",
|
|
201
|
-
"fields", "credential_id"}
|
|
201
|
+
"fields", "credential_id", "customLabel", "size"}
|
|
202
202
|
)
|
|
203
|
+
# The editor persists these two presentation fields on nodes. They are not
|
|
204
|
+
# execution settings; keep them intact while rejecting unknown configuration.
|
|
203
205
|
|
|
204
206
|
|
|
205
207
|
def _node_allowed_keys(node_type: Any) -> Optional[frozenset]:
|
|
@@ -5,7 +5,7 @@ authors = [
|
|
|
5
5
|
homepage = "https://programgarden.com"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
name = "programgarden"
|
|
8
|
-
version = "1.
|
|
8
|
+
version = "1.41.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 = "^1.
|
|
39
|
+
programgarden-core = "^1.31.0"
|
|
40
40
|
programgarden-finance = "^1.10.5"
|
|
41
41
|
programgarden-community = "^1.15.3"
|
|
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
|
{programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/owned_order_cancellation.py
RENAMED
|
File without changes
|
{programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/position_reconciliation.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/workflow_position_tracker.py
RENAMED
|
File without changes
|
{programgarden-1.40.0 → programgarden-1.41.0}/programgarden/database/workflow_risk_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
|