programgarden 1.2.1__tar.gz → 1.3.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.2.1 → programgarden-1.3.0}/PKG-INFO +2 -2
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/executor.py +60 -16
- {programgarden-1.2.1 → programgarden-1.3.0}/pyproject.toml +2 -2
- {programgarden-1.2.1 → programgarden-1.3.0}/README.md +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/__init__.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/binding_validator.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/client.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/context.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/database/__init__.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/database/query_builder.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/database/workflow_position_tracker.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/database/workflow_risk_tracker.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/plugin/__init__.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/plugin/sandbox.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/providers/__init__.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/providers/llm_errors.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/providers/llm_provider.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/reconnect_handler.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/resolver.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/resource/__init__.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/resource/context.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/resource/limiter.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/resource/monitor.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/resource/throttle.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/__init__.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/credential_tools.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/definition_tools.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/event_tools.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/job_tools.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/registry_tools.py +0 -0
- {programgarden-1.2.1 → programgarden-1.3.0}/programgarden/tools/sqlite_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: programgarden
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
|
|
5
5
|
Author: 프로그램동산
|
|
6
6
|
Author-email: coding@programgarden.com
|
|
@@ -13,7 +13,7 @@ Requires-Dist: aiosqlite (>=0.20.0,<0.21.0)
|
|
|
13
13
|
Requires-Dist: croniter (>=6.0.0,<7.0.0)
|
|
14
14
|
Requires-Dist: litellm (>=1.40.0)
|
|
15
15
|
Requires-Dist: lxml (>=6.0.2,<7.0.0)
|
|
16
|
-
Requires-Dist: programgarden-community (>=1.
|
|
16
|
+
Requires-Dist: programgarden-community (>=1.3.0,<2.0.0)
|
|
17
17
|
Requires-Dist: programgarden-core (>=1.2.0,<2.0.0)
|
|
18
18
|
Requires-Dist: programgarden-finance (>=1.2.0,<2.0.0)
|
|
19
19
|
Requires-Dist: psutil (>=6.0.0,<7.0.0)
|
|
@@ -5445,16 +5445,17 @@ class ConditionNodeExecutor(NodeExecutorBase):
|
|
|
5445
5445
|
context.log("warning", f"External binding evaluation failed for {target_field}: {e}", node_id)
|
|
5446
5446
|
external_values[target_field] = source_expr
|
|
5447
5447
|
|
|
5448
|
+
# 기본 컨텍스트 dict (row 제외)
|
|
5449
|
+
base_dict = expr_context.to_dict()
|
|
5450
|
+
|
|
5448
5451
|
# 각 row 처리
|
|
5449
5452
|
for row in from_data:
|
|
5450
5453
|
record = {}
|
|
5451
5454
|
|
|
5452
|
-
# row 컨텍스트 추가
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
}
|
|
5457
|
-
row_evaluator = ExpressionEvaluator(row_context)
|
|
5455
|
+
# row 컨텍스트 추가: 기본 dict에 row 추가
|
|
5456
|
+
row_ctx = ExpressionContext()
|
|
5457
|
+
row_ctx.variables = {**base_dict, "row": row}
|
|
5458
|
+
row_evaluator = ExpressionEvaluator(row_ctx)
|
|
5458
5459
|
|
|
5459
5460
|
for target_field, source_expr in extract.items():
|
|
5460
5461
|
if target_field in external_values:
|
|
@@ -5578,11 +5579,12 @@ class ConditionNodeExecutor(NodeExecutorBase):
|
|
|
5578
5579
|
# === 표현식 평가 (items 제외 - items는 _process_items_with_extract에서 처리) ===
|
|
5579
5580
|
context.log("debug", f"ConditionNode '{node_id}' 실행: plugin={plugin_id}, is_positions_based={is_positions_based}", node_id)
|
|
5580
5581
|
|
|
5581
|
-
# items를 제외한 나머지 필드만 평가 (items
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5582
|
+
# items를 제외한 나머지 필드만 평가 (items는 _process_items_with_extract에서 별도 처리)
|
|
5583
|
+
items_orig = config.get("items")
|
|
5584
|
+
config_without_items = {k: v for k, v in config.items() if k != "items"}
|
|
5585
|
+
config = evaluate_all_bindings(config_without_items, context, node_id)
|
|
5586
|
+
if items_orig is not None:
|
|
5587
|
+
config["items"] = items_orig
|
|
5586
5588
|
|
|
5587
5589
|
# === positions 기반 플러그인 처리 (ProfitTarget, StopLoss) ===
|
|
5588
5590
|
if is_positions_based:
|
|
@@ -5827,9 +5829,17 @@ class ConditionNodeExecutor(NodeExecutorBase):
|
|
|
5827
5829
|
"fields": fields, # 플러그인 파라미터
|
|
5828
5830
|
"field_mapping": field_mapping, # 필드 매핑
|
|
5829
5831
|
"symbols": normalized_symbols, # [{exchange, symbol}, ...]
|
|
5830
|
-
"context": context, # ExecutionContext (risk_tracker 등)
|
|
5831
5832
|
}
|
|
5832
5833
|
|
|
5834
|
+
# context는 시그니처에 있는 플러그인에만 전달 (trailing_stop 등)
|
|
5835
|
+
import inspect
|
|
5836
|
+
try:
|
|
5837
|
+
sig = inspect.signature(plugin)
|
|
5838
|
+
if "context" in sig.parameters:
|
|
5839
|
+
plugin_kwargs["context"] = context
|
|
5840
|
+
except (ValueError, TypeError):
|
|
5841
|
+
pass
|
|
5842
|
+
|
|
5833
5843
|
# 추가 포트 데이터
|
|
5834
5844
|
if held_symbols:
|
|
5835
5845
|
plugin_kwargs["held_symbols"] = held_symbols
|
|
@@ -12165,6 +12175,19 @@ class WorkflowJob:
|
|
|
12165
12175
|
if edge.to_node_id == node_id:
|
|
12166
12176
|
# symbols 포트 우선 확인 (WatchlistNode 출력)
|
|
12167
12177
|
input_data = self.context.get_output(edge.from_node_id, "symbols")
|
|
12178
|
+
|
|
12179
|
+
# symbols가 문자열 배열이면 (merge 후) value 포트로 폴백
|
|
12180
|
+
# - WatchlistNode symbols: [{exchange, symbol}, ...] → dict 배열 → 그대로 사용
|
|
12181
|
+
# - HistoricalDataNode merged symbols: ["TSLA"] → string 배열 → value 포트로 전환
|
|
12182
|
+
if (isinstance(input_data, list) and input_data
|
|
12183
|
+
and not isinstance(input_data[0], dict)):
|
|
12184
|
+
value_data = self.context.get_output(edge.from_node_id, "value")
|
|
12185
|
+
if value_data is not None:
|
|
12186
|
+
if isinstance(value_data, list):
|
|
12187
|
+
input_data = value_data
|
|
12188
|
+
elif isinstance(value_data, dict):
|
|
12189
|
+
input_data = [value_data]
|
|
12190
|
+
|
|
12168
12191
|
if input_data is None:
|
|
12169
12192
|
# 기본 출력 확인
|
|
12170
12193
|
input_data = self.context.get_output(edge.from_node_id, None)
|
|
@@ -12814,17 +12837,38 @@ class WorkflowJob:
|
|
|
12814
12837
|
- {{ input.xxx }}: 워크플로우 inputs 파라미터
|
|
12815
12838
|
- {{ nodeId.port }}: 이전 노드 출력값
|
|
12816
12839
|
- {{ context.xxx }}: 실행 컨텍스트 값
|
|
12840
|
+
|
|
12841
|
+
Note: items 키는 제외 (ConditionNode의 _process_items_with_extract에서 별도 처리).
|
|
12842
|
+
items 내부에 {{ row.xxx }} 같은 지연 평가 표현식이 있어 여기서 평가하면 실패함.
|
|
12817
12843
|
"""
|
|
12818
12844
|
from programgarden_core.expression import ExpressionEvaluator
|
|
12819
|
-
|
|
12845
|
+
|
|
12846
|
+
# items는 별도 처리 대상이므로 항상 제외
|
|
12847
|
+
items_orig = config.get("items")
|
|
12848
|
+
config_copy = {k: v for k, v in config.items() if k != "items"}
|
|
12849
|
+
|
|
12850
|
+
# iteration context 없으면 {{ item }} 표현식도 지연 (auto-iterate 전)
|
|
12851
|
+
deferred = {}
|
|
12852
|
+
if self.context._iteration_item is None:
|
|
12853
|
+
for k in list(config_copy.keys()):
|
|
12854
|
+
v = config_copy[k]
|
|
12855
|
+
if isinstance(v, str) and "{{ item" in v:
|
|
12856
|
+
deferred[k] = config_copy.pop(k)
|
|
12857
|
+
|
|
12820
12858
|
try:
|
|
12821
12859
|
expr_context = self.context.get_expression_context()
|
|
12822
12860
|
evaluator = ExpressionEvaluator(expr_context)
|
|
12823
|
-
|
|
12861
|
+
resolved = evaluator.evaluate_fields(config_copy)
|
|
12824
12862
|
except Exception as e:
|
|
12825
|
-
# 표현식 평가 실패 시 원본 반환 (graceful degradation)
|
|
12826
12863
|
self.context.log("warning", f"Expression resolve failed: {e}")
|
|
12827
|
-
|
|
12864
|
+
resolved = config_copy
|
|
12865
|
+
|
|
12866
|
+
# 지연 평가 필드 복원
|
|
12867
|
+
resolved.update(deferred)
|
|
12868
|
+
if items_orig is not None:
|
|
12869
|
+
resolved["items"] = items_orig
|
|
12870
|
+
|
|
12871
|
+
return resolved
|
|
12828
12872
|
|
|
12829
12873
|
async def _event_loop(self) -> None:
|
|
12830
12874
|
"""
|
|
@@ -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.3.0"
|
|
9
9
|
description = "ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
|
|
@@ -30,7 +30,7 @@ aiosqlite = "^0.20.0"
|
|
|
30
30
|
litellm = ">=1.40.0"
|
|
31
31
|
programgarden-core = "^1.2.0"
|
|
32
32
|
programgarden-finance = "^1.2.0"
|
|
33
|
-
programgarden-community = "^1.
|
|
33
|
+
programgarden-community = "^1.3.0"
|
|
34
34
|
|
|
35
35
|
[tool.poetry.group.dev.dependencies]
|
|
36
36
|
pytest = "^8.0.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden-1.2.1 → programgarden-1.3.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
|