programgarden-core 1.18.0__tar.gz → 1.20.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_core-1.18.0 → programgarden_core-1.20.0}/PKG-INFO +1 -1
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/account_futures.py +2 -2
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/account_korea_stock.py +2 -2
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/account_stock.py +2 -2
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/ai.py +5 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/base.py +224 -27
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/data_futures.py +9 -7
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/data_korea_stock.py +11 -6
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/data_stock.py +15 -10
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/display.py +14 -4
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/infra.py +6 -1
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/realtime_futures.py +10 -7
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/realtime_korea_stock.py +10 -7
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/realtime_stock.py +10 -7
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/symbol.py +4 -2
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/symbol_futures.py +2 -1
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/symbol_korea_stock.py +2 -2
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/symbol_stock.py +2 -2
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/pyproject.toml +1 -1
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/README.md +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/__init__.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/bases/__init__.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/bases/client.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/bases/components.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/bases/listener.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/bases/mixins.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/bases/products.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/bases/sql.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/bases/storage.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/code_node.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/exceptions.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/expression/__init__.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/expression/evaluator.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/i18n/__init__.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/i18n/locales/en.json +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/i18n/locales/ko.json +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/i18n/translator.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/korea_alias.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/__init__.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/connection_rule.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/credential.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/edge.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/event.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/exchange.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/field_binding.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/job.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/order_diagnostics.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/plugin_resource.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/resilience.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/resource.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/validation.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/workflow.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/__init__.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/backtest.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/backtest_futures.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/backtest_korea_stock.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/backtest_stock.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/broker.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/calculation.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/code.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/condition.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/data.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/event.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/fundamental_korea_stock.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/fundamental_stock.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/market_external.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/market_status.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/open_orders_futures.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/open_orders_korea_stock.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/open_orders_stock.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/order.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/portfolio.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/risk.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/trigger.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/presets/__init__.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/presets/news_analyst.json +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/presets/risk_manager.json +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/presets/strategist.json +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/presets/technical_analyst.json +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/registry/__init__.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/registry/credential_registry.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/registry/node_registry.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/registry/plugin_registry.py +0 -0
- {programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/retry_executor.py +0 -0
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/account_futures.py
RENAMED
|
@@ -19,7 +19,7 @@ from programgarden_core.nodes.base import (
|
|
|
19
19
|
ProductScope,
|
|
20
20
|
BrokerProvider,
|
|
21
21
|
OVERSEAS_FUTURES_BALANCE_FIELDS,
|
|
22
|
-
|
|
22
|
+
OVERSEAS_FUTURES_POSITION_FIELDS,
|
|
23
23
|
SYMBOL_LIST_FIELDS,
|
|
24
24
|
)
|
|
25
25
|
|
|
@@ -171,7 +171,7 @@ class OverseasFuturesAccountNode(BaseNode):
|
|
|
171
171
|
_outputs: List[OutputPort] = [
|
|
172
172
|
OutputPort(name="held_symbols", type="symbol_list", description="i18n:ports.held_symbols", fields=SYMBOL_LIST_FIELDS),
|
|
173
173
|
OutputPort(name="balance", type="balance_data", description="i18n:ports.balance", fields=OVERSEAS_FUTURES_BALANCE_FIELDS),
|
|
174
|
-
OutputPort(name="positions", type="position_data", description="i18n:ports.positions", fields=
|
|
174
|
+
OutputPort(name="positions", type="position_data", description="i18n:ports.positions", fields=OVERSEAS_FUTURES_POSITION_FIELDS),
|
|
175
175
|
]
|
|
176
176
|
|
|
177
177
|
_version: ClassVar[str] = "1.0.0"
|
|
@@ -18,7 +18,7 @@ from programgarden_core.nodes.base import (
|
|
|
18
18
|
ProductScope,
|
|
19
19
|
BrokerProvider,
|
|
20
20
|
KOREA_STOCK_BALANCE_FIELDS,
|
|
21
|
-
|
|
21
|
+
KOREA_STOCK_POSITION_FIELDS,
|
|
22
22
|
SYMBOL_LIST_FIELDS,
|
|
23
23
|
)
|
|
24
24
|
|
|
@@ -170,7 +170,7 @@ class KoreaStockAccountNode(BaseNode):
|
|
|
170
170
|
_outputs: List[OutputPort] = [
|
|
171
171
|
OutputPort(name="held_symbols", type="symbol_list", description="i18n:ports.held_symbols", fields=SYMBOL_LIST_FIELDS),
|
|
172
172
|
OutputPort(name="balance", type="balance_data", description="i18n:ports.balance", fields=KOREA_STOCK_BALANCE_FIELDS),
|
|
173
|
-
OutputPort(name="positions", type="position_data", description="i18n:ports.positions", fields=
|
|
173
|
+
OutputPort(name="positions", type="position_data", description="i18n:ports.positions", fields=KOREA_STOCK_POSITION_FIELDS),
|
|
174
174
|
]
|
|
175
175
|
|
|
176
176
|
_version: ClassVar[str] = "1.0.0"
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/account_stock.py
RENAMED
|
@@ -19,7 +19,7 @@ from programgarden_core.nodes.base import (
|
|
|
19
19
|
ProductScope,
|
|
20
20
|
BrokerProvider,
|
|
21
21
|
OVERSEAS_STOCK_BALANCE_FIELDS,
|
|
22
|
-
|
|
22
|
+
OVERSEAS_STOCK_POSITION_FIELDS,
|
|
23
23
|
SYMBOL_LIST_FIELDS,
|
|
24
24
|
)
|
|
25
25
|
|
|
@@ -200,7 +200,7 @@ class OverseasStockAccountNode(BaseNode):
|
|
|
200
200
|
name="positions",
|
|
201
201
|
type="position_data",
|
|
202
202
|
description="i18n:ports.positions",
|
|
203
|
-
fields=
|
|
203
|
+
fields=OVERSEAS_STOCK_POSITION_FIELDS,
|
|
204
204
|
example=[
|
|
205
205
|
{"symbol": "AAPL", "exchange": "NASDAQ", "quantity": 10, "avg_price": 175.20, "pnl_rate": 6.99},
|
|
206
206
|
{"symbol": "TSLA", "exchange": "NASDAQ", "quantity": 5, "avg_price": 240.00, "pnl_rate": -2.50},
|
|
@@ -360,6 +360,11 @@ class AIAgentNode(BaseNode):
|
|
|
360
360
|
"max_total_tokens (default 100,000) caps total token spend per execution cycle",
|
|
361
361
|
]
|
|
362
362
|
_anti_patterns: ClassVar[List[Dict[str, str]]] = [
|
|
363
|
+
{
|
|
364
|
+
"pattern": "Adding an AIAgentNode without connecting an LLMModelNode via an 'ai_model' edge",
|
|
365
|
+
"reason": "AIAgentNode CANNOT run without an LLM — the ai_model edge injects the LLM connection. With no LLMModelNode wired via an ai_model edge, the node fails at runtime (raises). The workflow validator now blocks this with an ERROR at save time.",
|
|
366
|
+
"alternative": "Always add an LLMModelNode and wire it to the AIAgentNode with an ai_model edge: {\"from\": \"<llm_node_id>\", \"to\": \"<agent_id>\", \"type\": \"ai_model\"}. A plain 'main' edge does NOT inject the LLM.",
|
|
367
|
+
},
|
|
363
368
|
{
|
|
364
369
|
"pattern": "Connecting a real-time node (RealMarketDataNode) directly to AIAgentNode without ThrottleNode",
|
|
365
370
|
"reason": "Real-time nodes fire on every tick. Without throttling, AIAgentNode would call the LLM API thousands of times per minute, causing massive cost and rate limit errors. The workflow validator blocks this with an ERROR.",
|
|
@@ -112,11 +112,81 @@ class OutputPort(BaseModel):
|
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
# === OutputPort.fields 공통 상수 ===
|
|
115
|
+
#
|
|
116
|
+
# 🔴 **상수 공유는 노드가 같은 키를 내보낼 때만 한다.**
|
|
117
|
+
# resolver 는 이 선언으로 `nodes.<id>.<port>.<field>` 의 필드 존재를 **정적 검증**한다.
|
|
118
|
+
# 그래서 선언이 런타임과 어긋나면 검증이 양방향으로 다 틀린다:
|
|
119
|
+
# 실제 있는 필드를 바인딩 → INVALID_EXPRESSION_REF 로 **부당 거부** (동작하는 워크플로우를 막음)
|
|
120
|
+
# 실제 없는 필드를 바인딩 → **통과** 시킨 뒤 런타임에 조용히 None (표에 '-' 만 찍힘)
|
|
121
|
+
# 노드 하나를 맞추려고 공유 상수에 필드를 더하면 **다른 노드가 반대 방향으로 거짓말**한다.
|
|
122
|
+
# → 런타임 키가 다르면 **노드별 상수로 쪼갠다.** 증명은 `tests/test_output_schema_contract.py`
|
|
123
|
+
# (executor 의 런타임 dict 리터럴을 AST 로 뽑아 선언과 대조).
|
|
124
|
+
|
|
125
|
+
# WatchlistNode / SymbolFilterNode / ExclusionListNode / 계좌 노드의 held_symbols —
|
|
126
|
+
# 정말로 exchange+symbol 둘만 내보내는 포트 전용.
|
|
115
127
|
SYMBOL_LIST_FIELDS: List[Dict[str, str]] = [
|
|
116
128
|
{"name": "exchange", "type": "string", "description": "거래소 코드 (NASDAQ, NYSE, CME 등)"},
|
|
117
129
|
{"name": "symbol", "type": "string", "description": "종목코드"},
|
|
118
130
|
]
|
|
119
131
|
|
|
132
|
+
# ── ScreenerNode.symbols (조건 검색 결과) ──
|
|
133
|
+
# LS(g3101 enrich) 분기와 yfinance 분기가 **같은 키 집합**을 내보낸다.
|
|
134
|
+
SCREENER_SYMBOL_FIELDS: List[Dict[str, str]] = [
|
|
135
|
+
{"name": "exchange", "type": "string", "description": "거래소 코드"},
|
|
136
|
+
{"name": "symbol", "type": "string", "description": "종목코드"},
|
|
137
|
+
{"name": "name", "type": "string", "description": "종목명 (상류 종목 리스트가 줬거나 yfinance 조회로 채움)"},
|
|
138
|
+
{"name": "market", "type": "string", "description": "시장 구분 (상류가 준 값 통과 — 예: KOSPI/KOSDAQ)"},
|
|
139
|
+
{"name": "price", "type": "number", "description": "현재가 (필터 시점)"},
|
|
140
|
+
{"name": "market_cap", "type": "number", "description": "시가총액"},
|
|
141
|
+
{"name": "volume", "type": "number", "description": "거래량"},
|
|
142
|
+
{"name": "sector", "type": "string", "description": "섹터 (yfinance 분기에서만 채워짐 — LS 분기는 빈 문자열)"},
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
# ── MarketUniverseNode.symbols (지수 구성종목) ──
|
|
146
|
+
MARKET_UNIVERSE_SYMBOL_FIELDS: List[Dict[str, str]] = [
|
|
147
|
+
{"name": "exchange", "type": "string", "description": "거래소 코드"},
|
|
148
|
+
{"name": "symbol", "type": "string", "description": "종목코드"},
|
|
149
|
+
{"name": "name", "type": "string", "description": "종목명"},
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
# ── OverseasStockSymbolQueryNode.symbols (g3190 마스터) ──
|
|
153
|
+
OVERSEAS_STOCK_SYMBOL_QUERY_FIELDS: List[Dict[str, str]] = [
|
|
154
|
+
{"name": "exchange", "type": "string", "description": "거래소 이름 (NASDAQ, NYSE 등)"},
|
|
155
|
+
{"name": "exchange_code", "type": "string", "description": "LS 거래소 코드"},
|
|
156
|
+
{"name": "symbol", "type": "string", "description": "종목코드"},
|
|
157
|
+
{"name": "name", "type": "string", "description": "종목명 (한글 우선, 없으면 영문)"},
|
|
158
|
+
{"name": "isin", "type": "string", "description": "ISIN 코드"},
|
|
159
|
+
{"name": "price", "type": "number", "description": "전일 종가"},
|
|
160
|
+
{"name": "market_cap", "type": "number", "description": "시가총액"},
|
|
161
|
+
{"name": "shares_outstanding", "type": "number", "description": "상장 주식 수"},
|
|
162
|
+
{"name": "currency", "type": "string", "description": "통화 코드"},
|
|
163
|
+
{"name": "suspend", "type": "string", "description": "거래정지 여부 (Y/N)"},
|
|
164
|
+
{"name": "sellonly", "type": "string", "description": "매도전용 여부 (Y/N)"},
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
# ── KoreaStockSymbolQueryNode.symbols (t9945 마스터) ──
|
|
168
|
+
KOREA_STOCK_SYMBOL_QUERY_FIELDS: List[Dict[str, str]] = [
|
|
169
|
+
{"name": "exchange", "type": "string", "description": "거래소 코드 (항상 KRX)"},
|
|
170
|
+
{"name": "market", "type": "string", "description": "시장 구분 (KOSPI / KOSDAQ)"},
|
|
171
|
+
{"name": "symbol", "type": "string", "description": "종목코드 (6자리)"},
|
|
172
|
+
{"name": "name", "type": "string", "description": "종목명"},
|
|
173
|
+
{"name": "is_etf", "type": "boolean", "description": "ETF 여부"},
|
|
174
|
+
{"name": "product", "type": "string", "description": "상품 구분 (항상 korea_stock)"},
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
# ── OverseasFuturesSymbolQueryNode.symbols (o3101 마스터) ──
|
|
178
|
+
OVERSEAS_FUTURES_SYMBOL_QUERY_FIELDS: List[Dict[str, str]] = [
|
|
179
|
+
{"name": "exchange", "type": "string", "description": "거래소 코드 (CME, HKEX 등 — 주문에 쓰는 값)"},
|
|
180
|
+
{"name": "exchange_code", "type": "string", "description": "LS 거래소 코드 (exchange 와 동일)"},
|
|
181
|
+
{"name": "exchange_name", "type": "string", "description": "거래소 한글명 (표시용 — 주문에 쓰면 안 된다)"},
|
|
182
|
+
{"name": "symbol", "type": "string", "description": "종목코드 (월물 포함)"},
|
|
183
|
+
{"name": "name", "type": "string", "description": "종목명"},
|
|
184
|
+
{"name": "base_product", "type": "string", "description": "기초상품 코드 (예: ES)"},
|
|
185
|
+
{"name": "base_product_name", "type": "string", "description": "기초상품명"},
|
|
186
|
+
{"name": "currency", "type": "string", "description": "통화 코드"},
|
|
187
|
+
{"name": "contract_month", "type": "string", "description": "월물 (YYYYMM)"},
|
|
188
|
+
]
|
|
189
|
+
|
|
120
190
|
# ── Partial-failure metadata (internal) ──
|
|
121
191
|
# Balance dicts may include these underscore-prefixed keys when the
|
|
122
192
|
# upstream API call partially fails. Consumers (PositionSizingNode,
|
|
@@ -187,16 +257,22 @@ KOREA_STOCK_REAL_BALANCE_FIELDS: List[Dict[str, str]] = [
|
|
|
187
257
|
]
|
|
188
258
|
|
|
189
259
|
# ── 국내주식 시세 전용 (t1102 기반) ──
|
|
260
|
+
# 런타임 정본: executor.py `MarketDataNodeExecutor._fetch_korea_stock`
|
|
261
|
+
# (해외와 같은 이름 규칙을 쓴다 — 예전 선언의 current_price/open_price/market_cap 은 런타임에 없었다.)
|
|
190
262
|
KOREA_STOCK_PRICE_DATA_FIELDS: List[Dict[str, str]] = [
|
|
191
263
|
{"name": "symbol", "type": "string", "description": "종목코드 (6자리)"},
|
|
264
|
+
{"name": "exchange", "type": "string", "description": "거래소 코드 (KRX)"},
|
|
192
265
|
{"name": "name", "type": "string", "description": "종목명"},
|
|
193
|
-
{"name": "
|
|
266
|
+
{"name": "price", "type": "number", "description": "현재가 (원)"},
|
|
267
|
+
{"name": "change", "type": "number", "description": "전일대비 (하락이면 음수)"},
|
|
268
|
+
{"name": "change_pct", "type": "number", "description": "등락률 (%)"},
|
|
194
269
|
{"name": "volume", "type": "number", "description": "거래량"},
|
|
195
|
-
{"name": "
|
|
196
|
-
{"name": "
|
|
197
|
-
{"name": "
|
|
198
|
-
{"name": "
|
|
199
|
-
{"name": "
|
|
270
|
+
{"name": "open", "type": "number", "description": "시가"},
|
|
271
|
+
{"name": "high", "type": "number", "description": "고가"},
|
|
272
|
+
{"name": "low", "type": "number", "description": "저가"},
|
|
273
|
+
{"name": "close", "type": "number", "description": "종가 (현재가와 동일)"},
|
|
274
|
+
{"name": "per", "type": "number", "description": "PER"},
|
|
275
|
+
{"name": "pbr", "type": "number", "description": "PBR"},
|
|
200
276
|
]
|
|
201
277
|
|
|
202
278
|
# ── 국내주식 펀더멘털 전용 ──
|
|
@@ -216,14 +292,114 @@ KOREA_STOCK_FUNDAMENTAL_FIELDS: List[Dict[str, str]] = [
|
|
|
216
292
|
{"name": "industry", "type": "string", "description": "업종명"},
|
|
217
293
|
]
|
|
218
294
|
|
|
219
|
-
|
|
295
|
+
# ── 보유 포지션 (positions 포트) ──
|
|
296
|
+
# 🔴 옛 공유 상수 `POSITION_FIELDS` 는 `pnl` / `pnl_percent` 를 선언했으나 **런타임에 그런 키는 없다**
|
|
297
|
+
# (실제는 `pnl_amount` / `pnl_rate`). 챗봇이 카탈로그를 보고 `pnl` 을 바인딩하면 정적 검증은
|
|
298
|
+
# 통과하고 런타임엔 조용히 None 이 됐다. 상품별로 키가 다르므로 노드별로 쪼갠다.
|
|
299
|
+
|
|
300
|
+
# 해외주식 REST 계좌 (COSOQ00201 block4)
|
|
301
|
+
OVERSEAS_STOCK_POSITION_FIELDS: List[Dict[str, str]] = [
|
|
220
302
|
{"name": "exchange", "type": "string", "description": "거래소 코드"},
|
|
221
303
|
{"name": "symbol", "type": "string", "description": "종목코드"},
|
|
304
|
+
{"name": "name", "type": "string", "description": "종목명"},
|
|
305
|
+
{"name": "qty", "type": "number", "description": "보유 수량"},
|
|
306
|
+
{"name": "quantity", "type": "number", "description": "보유 수량 (주문 노드 호환 별칭)"},
|
|
307
|
+
{"name": "direction", "type": "string", "description": "포지션 방향 (주식은 항상 long)"},
|
|
308
|
+
{"name": "close_side", "type": "string", "description": "청산 주문 방향 (주식은 항상 sell)"},
|
|
309
|
+
{"name": "avg_price", "type": "number", "description": "평균 매입가"},
|
|
310
|
+
{"name": "current_price", "type": "number", "description": "현재가"},
|
|
311
|
+
{"name": "pnl_amount", "type": "number", "description": "평가 손익 (금액)"},
|
|
312
|
+
{"name": "pnl_rate", "type": "number", "description": "수익률 (%)"},
|
|
313
|
+
{"name": "eval_amount", "type": "number", "description": "평가 금액"},
|
|
314
|
+
{"name": "purchase_amount", "type": "number", "description": "매입 금액"},
|
|
315
|
+
{"name": "currency", "type": "string", "description": "통화 코드"},
|
|
316
|
+
{"name": "market", "type": "string", "description": "시장 구분명"},
|
|
317
|
+
]
|
|
318
|
+
|
|
319
|
+
# 국내주식 REST 계좌 (t0424)
|
|
320
|
+
KOREA_STOCK_POSITION_FIELDS: List[Dict[str, str]] = [
|
|
321
|
+
{"name": "exchange", "type": "string", "description": "거래소 코드 (항상 KRX)"},
|
|
322
|
+
{"name": "symbol", "type": "string", "description": "종목코드"},
|
|
323
|
+
{"name": "name", "type": "string", "description": "종목명"},
|
|
222
324
|
{"name": "quantity", "type": "number", "description": "보유 수량"},
|
|
325
|
+
{"name": "price", "type": "number", "description": "현재가 (주문 노드 호환 별칭)"},
|
|
223
326
|
{"name": "avg_price", "type": "number", "description": "평균 매입가"},
|
|
224
327
|
{"name": "current_price", "type": "number", "description": "현재가"},
|
|
225
|
-
{"name": "
|
|
226
|
-
{"name": "
|
|
328
|
+
{"name": "pnl_amount", "type": "number", "description": "평가 손익 (금액)"},
|
|
329
|
+
{"name": "pnl_rate", "type": "number", "description": "수익률 (%)"},
|
|
330
|
+
{"name": "sellable_qty", "type": "number", "description": "매도 가능 수량"},
|
|
331
|
+
{"name": "eval_amount", "type": "number", "description": "평가 금액"},
|
|
332
|
+
{"name": "product", "type": "string", "description": "상품 구분 (항상 korea_stock)"},
|
|
333
|
+
]
|
|
334
|
+
|
|
335
|
+
# 해외선물 REST 계좌 (CIDBQ01500 block2)
|
|
336
|
+
# 주의: 이 TR 응답에는 수익률(pnl_rate)이 없다 — 손익 금액(pnl_amount)만 온다.
|
|
337
|
+
OVERSEAS_FUTURES_POSITION_FIELDS: List[Dict[str, str]] = [
|
|
338
|
+
{"name": "exchange", "type": "string", "description": "거래소 코드"},
|
|
339
|
+
{"name": "symbol", "type": "string", "description": "종목코드 (월물 포함)"},
|
|
340
|
+
{"name": "name", "type": "string", "description": "종목명"},
|
|
341
|
+
{"name": "direction", "type": "string", "description": "포지션 방향 (long / short)"},
|
|
342
|
+
{"name": "close_side", "type": "string", "description": "청산 주문 방향 (sell / buy)"},
|
|
343
|
+
{"name": "quantity", "type": "number", "description": "보유 계약 수"},
|
|
344
|
+
{"name": "price", "type": "number", "description": "현재가 (주문 노드 호환 별칭)"},
|
|
345
|
+
{"name": "entry_price", "type": "number", "description": "진입 가격"},
|
|
346
|
+
{"name": "current_price", "type": "number", "description": "현재가"},
|
|
347
|
+
{"name": "pnl_amount", "type": "number", "description": "평가 손익 (금액)"},
|
|
348
|
+
{"name": "currency", "type": "string", "description": "통화 코드"},
|
|
349
|
+
]
|
|
350
|
+
|
|
351
|
+
# ── 실시간 계좌 포지션 (RealAccountNode.positions) ──
|
|
352
|
+
# 실시간 노드는 두 갈래로 positions 를 내보낸다: ① REST 스냅샷 직렬화 ② WebSocket tracker.
|
|
353
|
+
# 둘의 키가 다르면 **같은 포트인데 순간마다 다른 필드**가 나온다 → 두 갈래를 같은 키로 통일했다.
|
|
354
|
+
OVERSEAS_STOCK_REAL_POSITION_FIELDS: List[Dict[str, str]] = [
|
|
355
|
+
{"name": "exchange", "type": "string", "description": "거래소 코드"},
|
|
356
|
+
{"name": "market_code", "type": "string", "description": "LS 시장 코드"},
|
|
357
|
+
{"name": "symbol", "type": "string", "description": "종목코드"},
|
|
358
|
+
{"name": "name", "type": "string", "description": "종목명"},
|
|
359
|
+
{"name": "qty", "type": "number", "description": "보유 수량"},
|
|
360
|
+
{"name": "quantity", "type": "number", "description": "보유 수량 (주문 노드 호환 별칭)"},
|
|
361
|
+
{"name": "price", "type": "number", "description": "현재가 (주문 노드 호환 별칭)"},
|
|
362
|
+
{"name": "avg_price", "type": "number", "description": "평균 매입가"},
|
|
363
|
+
{"name": "current_price", "type": "number", "description": "현재가"},
|
|
364
|
+
{"name": "pnl_amount", "type": "number", "description": "평가 손익 (금액)"},
|
|
365
|
+
{"name": "pnl_rate", "type": "number", "description": "수익률 (%)"},
|
|
366
|
+
{"name": "eval_amount", "type": "number", "description": "평가 금액"},
|
|
367
|
+
{"name": "currency", "type": "string", "description": "통화 코드"},
|
|
368
|
+
{"name": "product", "type": "string", "description": "상품 구분 (항상 overseas_stock)"},
|
|
369
|
+
]
|
|
370
|
+
|
|
371
|
+
KOREA_STOCK_REAL_POSITION_FIELDS: List[Dict[str, str]] = [
|
|
372
|
+
{"name": "exchange", "type": "string", "description": "거래소 코드 (항상 KRX)"},
|
|
373
|
+
{"name": "market_code", "type": "string", "description": "시장 구분 코드"},
|
|
374
|
+
{"name": "symbol", "type": "string", "description": "종목코드"},
|
|
375
|
+
{"name": "name", "type": "string", "description": "종목명"},
|
|
376
|
+
{"name": "qty", "type": "number", "description": "보유 수량"},
|
|
377
|
+
{"name": "quantity", "type": "number", "description": "보유 수량 (주문 노드 호환 별칭)"},
|
|
378
|
+
{"name": "price", "type": "number", "description": "현재가 (주문 노드 호환 별칭)"},
|
|
379
|
+
{"name": "avg_price", "type": "number", "description": "평균 매입가"},
|
|
380
|
+
{"name": "current_price", "type": "number", "description": "현재가"},
|
|
381
|
+
{"name": "pnl_amount", "type": "number", "description": "평가 손익 (금액)"},
|
|
382
|
+
{"name": "pnl_rate", "type": "number", "description": "수익률 (%)"},
|
|
383
|
+
{"name": "eval_amount", "type": "number", "description": "평가 금액"},
|
|
384
|
+
{"name": "currency", "type": "string", "description": "통화 코드 (항상 KRW)"},
|
|
385
|
+
{"name": "product", "type": "string", "description": "상품 구분 (항상 korea_stock)"},
|
|
386
|
+
]
|
|
387
|
+
|
|
388
|
+
OVERSEAS_FUTURES_REAL_POSITION_FIELDS: List[Dict[str, str]] = [
|
|
389
|
+
{"name": "exchange", "type": "string", "description": "거래소 코드"},
|
|
390
|
+
{"name": "symbol", "type": "string", "description": "종목코드 (월물 포함)"},
|
|
391
|
+
{"name": "name", "type": "string", "description": "종목명"},
|
|
392
|
+
{"name": "direction", "type": "string", "description": "포지션 방향 (long / short)"},
|
|
393
|
+
{"name": "close_side", "type": "string", "description": "청산 주문 방향 (sell / buy)"},
|
|
394
|
+
{"name": "qty", "type": "number", "description": "보유 계약 수"},
|
|
395
|
+
{"name": "quantity", "type": "number", "description": "보유 계약 수 (주문 노드 호환 별칭)"},
|
|
396
|
+
{"name": "price", "type": "number", "description": "현재가 (주문 노드 호환 별칭)"},
|
|
397
|
+
{"name": "entry_price", "type": "number", "description": "진입 가격"},
|
|
398
|
+
{"name": "current_price", "type": "number", "description": "현재가"},
|
|
399
|
+
{"name": "pnl_amount", "type": "number", "description": "평가 손익 (금액)"},
|
|
400
|
+
{"name": "pnl_rate", "type": "number", "description": "수익률 (%)"},
|
|
401
|
+
{"name": "currency", "type": "string", "description": "통화 코드"},
|
|
402
|
+
{"name": "product", "type": "string", "description": "상품 구분 (항상 overseas_futures)"},
|
|
227
403
|
]
|
|
228
404
|
|
|
229
405
|
ORDER_RESULT_FIELDS: List[Dict[str, str]] = [
|
|
@@ -236,16 +412,44 @@ ORDER_RESULT_FIELDS: List[Dict[str, str]] = [
|
|
|
236
412
|
{"name": "status", "type": "string", "description": "주문 상태"},
|
|
237
413
|
]
|
|
238
414
|
|
|
415
|
+
# ⚠️ 이 선언은 **런타임이 실제로 내보내는 키와 정확히 같아야 한다.**
|
|
416
|
+
# resolver 가 이 이름들로 필드 존재를 정적 검증하므로, 어긋나면 두 방향으로 다 틀린다:
|
|
417
|
+
# 실제 있는 필드를 바인딩 → INVALID_EXPRESSION_REF 로 **거부**
|
|
418
|
+
# 실제 없는 필드를 바인딩 → **통과**시킨 뒤 런타임에 조용히 None
|
|
419
|
+
# (2026-07-13 실측: 선언이 current_price/change_percent 였으나 런타임은 price/change_pct 였다.)
|
|
420
|
+
# 런타임 정본: executor.py `MarketDataNodeExecutor._fetch_overseas_stock`
|
|
239
421
|
PRICE_DATA_FIELDS: List[Dict[str, str]] = [
|
|
240
|
-
{"name": "exchange", "type": "string", "description": "거래소 코드"},
|
|
241
422
|
{"name": "symbol", "type": "string", "description": "종목코드"},
|
|
242
|
-
{"name": "
|
|
423
|
+
{"name": "exchange", "type": "string", "description": "거래소 코드"},
|
|
424
|
+
{"name": "price", "type": "number", "description": "현재가"},
|
|
425
|
+
{"name": "change", "type": "number", "description": "전일대비 (하락이면 음수)"},
|
|
426
|
+
{"name": "change_pct", "type": "number", "description": "등락률 (%)"},
|
|
243
427
|
{"name": "volume", "type": "number", "description": "거래량"},
|
|
244
|
-
{"name": "
|
|
428
|
+
{"name": "open", "type": "number", "description": "시가"},
|
|
429
|
+
{"name": "high", "type": "number", "description": "고가"},
|
|
430
|
+
{"name": "low", "type": "number", "description": "저가"},
|
|
431
|
+
{"name": "close", "type": "number", "description": "종가 (현재가와 동일)"},
|
|
245
432
|
{"name": "per", "type": "number", "description": "PER (주가수익비율)"},
|
|
246
433
|
{"name": "eps", "type": "number", "description": "EPS (주당순이익)"},
|
|
247
434
|
]
|
|
248
435
|
|
|
436
|
+
# ── 해외선물 시세 전용 (o3105 기반) ──
|
|
437
|
+
# 해외주식과 모양이 다르다(symbol_name 있음 / per·eps 없음). 상수를 공유하면 드리프트가 난다.
|
|
438
|
+
# 런타임 정본: executor.py `MarketDataNodeExecutor._fetch_overseas_futures`
|
|
439
|
+
OVERSEAS_FUTURES_PRICE_DATA_FIELDS: List[Dict[str, str]] = [
|
|
440
|
+
{"name": "symbol", "type": "string", "description": "종목코드 (월물 심볼)"},
|
|
441
|
+
{"name": "exchange", "type": "string", "description": "거래소 코드"},
|
|
442
|
+
{"name": "symbol_name", "type": "string", "description": "종목명"},
|
|
443
|
+
{"name": "price", "type": "number", "description": "현재가"},
|
|
444
|
+
{"name": "change", "type": "number", "description": "전일대비 (하락이면 음수)"},
|
|
445
|
+
{"name": "change_pct", "type": "number", "description": "등락률 (%)"},
|
|
446
|
+
{"name": "volume", "type": "number", "description": "거래량"},
|
|
447
|
+
{"name": "open", "type": "number", "description": "시가"},
|
|
448
|
+
{"name": "high", "type": "number", "description": "고가"},
|
|
449
|
+
{"name": "low", "type": "number", "description": "저가"},
|
|
450
|
+
{"name": "close", "type": "number", "description": "종가 (현재가와 동일)"},
|
|
451
|
+
]
|
|
452
|
+
|
|
249
453
|
FUNDAMENTAL_DATA_FIELDS: List[Dict[str, str]] = [
|
|
250
454
|
{"name": "exchange", "type": "string", "description": "거래소 코드"},
|
|
251
455
|
{"name": "symbol", "type": "string", "description": "종목코드"},
|
|
@@ -324,26 +528,19 @@ ORDER_EVENT_FIELDS: List[Dict[str, str]] = [
|
|
|
324
528
|
{"name": "timestamp", "type": "string", "description": "이벤트 시각"},
|
|
325
529
|
]
|
|
326
530
|
|
|
531
|
+
# ── 실시간 시세 노드의 봉(bar) ──
|
|
532
|
+
# 🔴 실시간 노드의 `ohlcv_data` / `data` 는 **같은 객체**다 (`data` 는 별칭).
|
|
533
|
+
# 값의 모양은 `{"AAPL": [bar, ...]}` — 종목코드로 키를 잡은 dict 이고, bar 가 아래 필드다.
|
|
534
|
+
# 옛 선언은 `exchange` / `timestamp` 를 약속했지만 런타임 bar 에 그런 키는 없고(실제는 `date`),
|
|
535
|
+
# `data` 포트는 아예 `current_price` / `bid_price` / `ask_price` / `change_percent` 를 약속했는데
|
|
536
|
+
# **그 값을 만드는 코드(`_build_full_data`)는 아무도 호출하지 않는 죽은 코드였다.**
|
|
327
537
|
OHLCV_DATA_FIELDS: List[Dict[str, str]] = [
|
|
328
|
-
{"name": "
|
|
329
|
-
{"name": "symbol", "type": "string", "description": "종목코드"},
|
|
538
|
+
{"name": "date", "type": "string", "description": "봉 일자 (YYYYMMDD)"},
|
|
330
539
|
{"name": "open", "type": "number", "description": "시가"},
|
|
331
540
|
{"name": "high", "type": "number", "description": "고가"},
|
|
332
541
|
{"name": "low", "type": "number", "description": "저가"},
|
|
333
|
-
{"name": "close", "type": "number", "description": "종가"},
|
|
334
|
-
{"name": "volume", "type": "number", "description": "거래량"},
|
|
335
|
-
{"name": "timestamp", "type": "string", "description": "캔들 시각"},
|
|
336
|
-
]
|
|
337
|
-
|
|
338
|
-
MARKET_DATA_FULL_FIELDS: List[Dict[str, str]] = [
|
|
339
|
-
{"name": "exchange", "type": "string", "description": "거래소 코드"},
|
|
340
|
-
{"name": "symbol", "type": "string", "description": "종목코드"},
|
|
341
|
-
{"name": "current_price", "type": "number", "description": "현재가"},
|
|
342
|
-
{"name": "bid_price", "type": "number", "description": "매수호가 (해외주식 실시간에서는 미제공)"},
|
|
343
|
-
{"name": "ask_price", "type": "number", "description": "매도호가 (해외주식 실시간에서는 미제공)"},
|
|
542
|
+
{"name": "close", "type": "number", "description": "종가 (= 최근 체결가)"},
|
|
344
543
|
{"name": "volume", "type": "number", "description": "거래량"},
|
|
345
|
-
{"name": "change_percent", "type": "number", "description": "등락률 (%)"},
|
|
346
|
-
{"name": "timestamp", "type": "string", "description": "시세 시각"},
|
|
347
544
|
]
|
|
348
545
|
|
|
349
546
|
CONDITION_RESULT_FIELDS: List[Dict[str, str]] = [
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/data_futures.py
RENAMED
|
@@ -22,7 +22,7 @@ from programgarden_core.nodes.base import (
|
|
|
22
22
|
OutputPort,
|
|
23
23
|
ProductScope,
|
|
24
24
|
BrokerProvider,
|
|
25
|
-
|
|
25
|
+
OVERSEAS_FUTURES_PRICE_DATA_FIELDS,
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
|
|
@@ -74,7 +74,7 @@ class OverseasFuturesMarketDataNode(BaseNode):
|
|
|
74
74
|
],
|
|
75
75
|
}
|
|
76
76
|
_features: ClassVar[List[str]] = [
|
|
77
|
-
"Returns a single futures contract's snapshot:
|
|
77
|
+
"Returns a single futures contract's snapshot: symbol, exchange, symbol_name, price, change, change_pct, volume, open, high, low, close",
|
|
78
78
|
"Item-based execution: pair with SplitNode to query multiple contracts in sequence",
|
|
79
79
|
"is_tool_enabled=True — AI Agent can call this node to look up live futures prices autonomously",
|
|
80
80
|
"Supported exchanges: CME, EUREX, SGX, HKEX — symbol format includes contract month code (e.g., ESH26)",
|
|
@@ -121,7 +121,7 @@ class OverseasFuturesMarketDataNode(BaseNode):
|
|
|
121
121
|
}
|
|
122
122
|
],
|
|
123
123
|
},
|
|
124
|
-
"expected_output": "values port: array of {symbol, exchange,
|
|
124
|
+
"expected_output": "values port: array of {symbol, exchange, symbol_name, price, change, change_pct, volume, open, high, low, close}.",
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
"title": "Multi-contract scan — compare HKEX futures prices",
|
|
@@ -161,9 +161,9 @@ class OverseasFuturesMarketDataNode(BaseNode):
|
|
|
161
161
|
"Broker connection auto-injected from OverseasFuturesBrokerNode."
|
|
162
162
|
),
|
|
163
163
|
"output_consumption": (
|
|
164
|
-
"Consume the `values` port ONLY — an array of {symbol, exchange,
|
|
164
|
+
"Consume the `values` port ONLY — an array of {symbol, exchange, symbol_name, price, change, change_pct, volume, open, high, low, close}. "
|
|
165
165
|
"⚠️ The `value` (singular) port is NOT populated at runtime — the executor emits `values` only; "
|
|
166
|
-
"binding `{{ nodes.market.value }}` (or `.value.
|
|
166
|
+
"binding `{{ nodes.market.value }}` (or `.value.price`) silently resolves to None. "
|
|
167
167
|
"TableDisplayNode.data ← `{{ nodes.market.values }}`; PositionSizingNode.market_data ← `{{ nodes.market.values }}`."
|
|
168
168
|
),
|
|
169
169
|
"common_combinations": [
|
|
@@ -183,11 +183,13 @@ class OverseasFuturesMarketDataNode(BaseNode):
|
|
|
183
183
|
InputPort(name="trigger", type="signal", description="i18n:ports.trigger", required=False),
|
|
184
184
|
]
|
|
185
185
|
_outputs: List[OutputPort] = [
|
|
186
|
-
OutputPort(name="value", type="market_data", description="i18n:ports.market_data_value",
|
|
186
|
+
OutputPort(name="value", type="market_data", description="i18n:ports.market_data_value",
|
|
187
|
+
fields=OVERSEAS_FUTURES_PRICE_DATA_FIELDS),
|
|
187
188
|
OutputPort(
|
|
188
189
|
name="values",
|
|
189
190
|
type="array",
|
|
190
|
-
description="Array of per-contract market quotes — [{symbol, exchange,
|
|
191
|
+
description="Array of per-contract market quotes — [{symbol, exchange, price, change, change_pct, ...}, ...]",
|
|
192
|
+
fields=OVERSEAS_FUTURES_PRICE_DATA_FIELDS,
|
|
191
193
|
),
|
|
192
194
|
]
|
|
193
195
|
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/data_korea_stock.py
RENAMED
|
@@ -74,7 +74,7 @@ class KoreaStockMarketDataNode(BaseNode):
|
|
|
74
74
|
],
|
|
75
75
|
}
|
|
76
76
|
_features: ClassVar[List[str]] = [
|
|
77
|
-
"Returns a single domestic stock's snapshot:
|
|
77
|
+
"Returns a single domestic stock's snapshot: symbol, exchange, name, price, change, change_pct, volume, open, high, low, close, per, pbr",
|
|
78
78
|
"Symbol format is a 6-digit KRX code (e.g., '005930') — no exchange field required (domestic market only)",
|
|
79
79
|
"Item-based execution: pair with SplitNode to query multiple domestic stocks in sequence",
|
|
80
80
|
"is_tool_enabled=True — AI Agent can call this node to look up KRX stock prices autonomously",
|
|
@@ -121,7 +121,7 @@ class KoreaStockMarketDataNode(BaseNode):
|
|
|
121
121
|
}
|
|
122
122
|
],
|
|
123
123
|
},
|
|
124
|
-
"expected_output": "values port: array of {symbol,
|
|
124
|
+
"expected_output": "values port: array of {symbol, exchange, name, price, change, change_pct, volume, open, high, low, close, per, pbr} per domestic stock.",
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
"title": "Price lookup for domestic PositionSizingNode",
|
|
@@ -165,11 +165,11 @@ class KoreaStockMarketDataNode(BaseNode):
|
|
|
165
165
|
),
|
|
166
166
|
"output_consumption": (
|
|
167
167
|
"Consume the `values` port ONLY — an array of per-symbol snapshots "
|
|
168
|
-
"[{symbol,
|
|
168
|
+
"[{symbol, exchange, name, price, change, change_pct, volume, open, high, low, close, per, pbr}, ...]. "
|
|
169
169
|
"⚠️ The `value` (singular) port is NOT populated at runtime — the executor emits `values` only; "
|
|
170
|
-
"binding `{{ nodes.market.value }}` (or `.value.
|
|
170
|
+
"binding `{{ nodes.market.value }}` (or `.value.price`) silently resolves to None. "
|
|
171
171
|
"TableDisplayNode.data ← `{{ nodes.market.values }}`; PositionSizingNode.market_data ← `{{ nodes.market.values }}` "
|
|
172
|
-
"(the array carries each symbol's
|
|
172
|
+
"(the array carries each symbol's price, which PositionSizingNode resolves internally)."
|
|
173
173
|
),
|
|
174
174
|
"common_combinations": [
|
|
175
175
|
"KoreaStockMarketDataNode.values → TableDisplayNode.data (KRX price monitor)",
|
|
@@ -189,7 +189,12 @@ class KoreaStockMarketDataNode(BaseNode):
|
|
|
189
189
|
]
|
|
190
190
|
_outputs: List[OutputPort] = [
|
|
191
191
|
OutputPort(name="value", type="market_data", description="i18n:ports.market_data_value", fields=KOREA_STOCK_PRICE_DATA_FIELDS),
|
|
192
|
-
OutputPort(
|
|
192
|
+
OutputPort(
|
|
193
|
+
name="values",
|
|
194
|
+
type="array",
|
|
195
|
+
description="Array of per-symbol market quotes — [{symbol, exchange, name, price, change, change_pct, ...}, ...]",
|
|
196
|
+
fields=KOREA_STOCK_PRICE_DATA_FIELDS,
|
|
197
|
+
),
|
|
193
198
|
]
|
|
194
199
|
|
|
195
200
|
_version: ClassVar[str] = "1.0.0"
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/data_stock.py
RENAMED
|
@@ -74,7 +74,7 @@ class OverseasStockMarketDataNode(BaseNode):
|
|
|
74
74
|
],
|
|
75
75
|
}
|
|
76
76
|
_features: ClassVar[List[str]] = [
|
|
77
|
-
"Returns a single symbol's snapshot:
|
|
77
|
+
"Returns a single symbol's snapshot: symbol, exchange, price, change, change_pct, volume, open, high, low, close, per, eps",
|
|
78
78
|
"Item-based execution: receives one {exchange, symbol} dict per call and emits one value dict — pair with SplitNode for multi-symbol queries",
|
|
79
79
|
"is_tool_enabled=True — AI Agent can call this node to look up live prices autonomously",
|
|
80
80
|
"Broker connection is auto-injected via DAG traversal — no explicit binding needed",
|
|
@@ -121,7 +121,7 @@ class OverseasStockMarketDataNode(BaseNode):
|
|
|
121
121
|
}
|
|
122
122
|
],
|
|
123
123
|
},
|
|
124
|
-
"expected_output": "values port: array of {symbol, exchange,
|
|
124
|
+
"expected_output": "values port: array of {symbol, exchange, price, change, change_pct, volume, open, high, low, close, per, eps} for each symbol.",
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
"title": "Price lookup feeding PositionSizingNode",
|
|
@@ -164,12 +164,11 @@ class OverseasStockMarketDataNode(BaseNode):
|
|
|
164
164
|
"The node auto-receives the broker connection via DAG traversal from OverseasStockBrokerNode."
|
|
165
165
|
),
|
|
166
166
|
"output_consumption": (
|
|
167
|
-
"Consume the `values` port ONLY — an array of {symbol, exchange,
|
|
168
|
-
"bid, ask, per, eps, 52w_high, 52w_low}. "
|
|
167
|
+
"Consume the `values` port ONLY — an array of {symbol, exchange, price, change, change_pct, volume, open, high, low, close, per, eps}. "
|
|
169
168
|
"⚠️ The `value` (singular) port is NOT populated at runtime — the executor emits `values` only; "
|
|
170
|
-
"binding `{{ nodes.market.value }}` (or `.value.
|
|
169
|
+
"binding `{{ nodes.market.value }}` (or `.value.price`) silently resolves to None. "
|
|
171
170
|
"TableDisplayNode.data ← `{{ nodes.market.values }}`; PositionSizingNode.market_data ← `{{ nodes.market.values }}` "
|
|
172
|
-
"(the array carries each symbol's
|
|
171
|
+
"(the array carries each symbol's price, resolved internally)."
|
|
173
172
|
),
|
|
174
173
|
"common_combinations": [
|
|
175
174
|
"WatchlistNode/SymbolQueryNode → OverseasStockMarketDataNode.symbols (multi-symbol price fetch)",
|
|
@@ -194,11 +193,16 @@ class OverseasStockMarketDataNode(BaseNode):
|
|
|
194
193
|
description="i18n:ports.market_data_value",
|
|
195
194
|
fields=PRICE_DATA_FIELDS,
|
|
196
195
|
example={
|
|
197
|
-
"exchange": "NASDAQ",
|
|
198
196
|
"symbol": "AAPL",
|
|
199
|
-
"
|
|
197
|
+
"exchange": "NASDAQ",
|
|
198
|
+
"price": 187.45,
|
|
199
|
+
"change": -2.34,
|
|
200
|
+
"change_pct": -1.23,
|
|
200
201
|
"volume": 12_345_678,
|
|
201
|
-
"
|
|
202
|
+
"open": 189.10,
|
|
203
|
+
"high": 190.02,
|
|
204
|
+
"low": 186.90,
|
|
205
|
+
"close": 187.45,
|
|
202
206
|
"per": 28.5,
|
|
203
207
|
"eps": 6.57,
|
|
204
208
|
},
|
|
@@ -206,7 +210,8 @@ class OverseasStockMarketDataNode(BaseNode):
|
|
|
206
210
|
OutputPort(
|
|
207
211
|
name="values",
|
|
208
212
|
type="array",
|
|
209
|
-
description="Array of per-symbol market quotes — [{symbol, exchange,
|
|
213
|
+
description="Array of per-symbol market quotes — [{symbol, exchange, price, change, change_pct, ...}, ...]",
|
|
214
|
+
fields=PRICE_DATA_FIELDS,
|
|
210
215
|
),
|
|
211
216
|
]
|
|
212
217
|
|
|
@@ -10,7 +10,7 @@ ProgramGarden Core - Display Nodes
|
|
|
10
10
|
- SummaryDisplayNode: JSON/요약 데이터 표시
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
from typing import Optional, List, Literal, Dict, Any, ClassVar, TYPE_CHECKING
|
|
13
|
+
from typing import Optional, List, Literal, Dict, Any, Union, ClassVar, TYPE_CHECKING
|
|
14
14
|
from pydantic import Field
|
|
15
15
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
@@ -304,9 +304,16 @@ class TableDisplayNode(BaseDisplayNode):
|
|
|
304
304
|
_updated_at: ClassVar[str] = "2026-05-19"
|
|
305
305
|
_change_note: ClassVar[Optional[str]] = None
|
|
306
306
|
|
|
307
|
-
columns
|
|
307
|
+
# columns 는 두 형태를 모두 받는다(스키마=코드=카탈로그 정합; 렌더러 _normalize_columns):
|
|
308
|
+
# 1) 필드명 문자열 리스트 — ["symbol", "close"]
|
|
309
|
+
# 2) {key, label} 객체 리스트 — [{"key": "close", "label": "종가"}] (현지화 헤더용)
|
|
310
|
+
# 옛 선언은 List[str] 뿐이라 객체형을 코드만 몰래 허용하는 드리프트였다.
|
|
311
|
+
columns: Optional[List[Union[str, Dict[str, Any]]]] = Field(
|
|
308
312
|
default=None,
|
|
309
|
-
description=
|
|
313
|
+
description=(
|
|
314
|
+
"표시할 컬럼 (미지정시 전체). 필드명 문자열 리스트 [\"symbol\",\"close\"] 또는 "
|
|
315
|
+
"현지화 헤더용 객체 리스트 [{\"key\":\"close\",\"label\":\"종가\"}] 둘 다 허용."
|
|
316
|
+
),
|
|
310
317
|
)
|
|
311
318
|
|
|
312
319
|
limit: Optional[int] = Field(
|
|
@@ -336,7 +343,10 @@ class TableDisplayNode(BaseDisplayNode):
|
|
|
336
343
|
"columns": FieldSchema(
|
|
337
344
|
name="columns",
|
|
338
345
|
type=FieldType.ARRAY,
|
|
339
|
-
description=
|
|
346
|
+
description=(
|
|
347
|
+
"표시할 컬럼. 필드명 문자열 리스트 [\"symbol\",\"close\"] 또는 "
|
|
348
|
+
"{key,label} 객체 리스트 [{\"key\":\"close\",\"label\":\"종가\"}](현지화 헤더) 허용"
|
|
349
|
+
),
|
|
340
350
|
category=FieldCategory.SETTINGS,
|
|
341
351
|
expression_mode=ExpressionMode.FIXED_ONLY,
|
|
342
352
|
ui_options={"multiple": True},
|
|
@@ -457,6 +457,11 @@ class SplitNode(BaseNode):
|
|
|
457
457
|
"reason": "Auto-iterate is implicit and handled by the executor — adding SplitNode duplicates the iteration and doubles the workload.",
|
|
458
458
|
"alternative": "Bind `{{ item }}` directly on the downstream node and delete SplitNode.",
|
|
459
459
|
},
|
|
460
|
+
{
|
|
461
|
+
"pattern": "Adding SplitNode without wiring its `array` input to an upstream list producer",
|
|
462
|
+
"reason": "SplitNode with no array source (no WatchlistNode/MarketUniverseNode/AccountNode/ConditionNode feeding a list, and no `array` binding) emits zero items — downstream `{{ nodes.split.item }}` resolves empty and the workflow produces silent empty results.",
|
|
463
|
+
"alternative": "Wire an upstream node that outputs an array into SplitNode, or — for a single known symbol — delete SplitNode and bind that symbol directly on the downstream node.",
|
|
464
|
+
},
|
|
460
465
|
{
|
|
461
466
|
"pattern": "parallel=True for order-placement loops",
|
|
462
467
|
"reason": "Placing orders concurrently without delay_ms will trip LS-Sec TR rate limits and cause spurious cancellations.",
|
|
@@ -530,7 +535,7 @@ class SplitNode(BaseNode):
|
|
|
530
535
|
},
|
|
531
536
|
]
|
|
532
537
|
_node_guide: ClassVar[Dict[str, Any]] = {
|
|
533
|
-
"input_handling": "`array` input
|
|
538
|
+
"input_handling": "`array` input is REQUIRED and MUST have a source: either an incoming edge from an upstream node that outputs an ARRAY (WatchlistNode/MarketUniverseNode → symbols, AccountNode → positions/held_symbols, ConditionNode → values, etc.), or an explicit `array` config binding. SplitNode has NOTHING to split if no upstream produces a list — it emits zero items and every downstream `{{ nodes.split.item }}` silently resolves empty. If you already know a single concrete symbol (e.g. 'AAPL'), do NOT add SplitNode — bind that one symbol directly on the downstream node.",
|
|
534
539
|
"output_consumption": "Downstream binds `{{ nodes.split.item }}` for the current element, `{{ nodes.split.index }}` for the 0-based position, and `{{ nodes.split.total }}` for the count.",
|
|
535
540
|
"common_combinations": [
|
|
536
541
|
"WatchlistNode → SplitNode → OverseasStockFundamentalNode",
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/realtime_futures.py
RENAMED
|
@@ -21,11 +21,10 @@ from programgarden_core.nodes.base import (
|
|
|
21
21
|
ProductScope,
|
|
22
22
|
BrokerProvider,
|
|
23
23
|
OVERSEAS_FUTURES_REAL_BALANCE_FIELDS,
|
|
24
|
-
MARKET_DATA_FULL_FIELDS,
|
|
25
24
|
OHLCV_DATA_FIELDS,
|
|
26
25
|
ORDER_EVENT_FIELDS,
|
|
27
26
|
ORDER_LIST_FIELDS,
|
|
28
|
-
|
|
27
|
+
OVERSEAS_FUTURES_REAL_POSITION_FIELDS,
|
|
29
28
|
SYMBOL_LIST_FIELDS,
|
|
30
29
|
PRICE_DATA_FIELDS,
|
|
31
30
|
)
|
|
@@ -65,7 +64,7 @@ class OverseasFuturesRealMarketDataNode(BaseNode):
|
|
|
65
64
|
],
|
|
66
65
|
}
|
|
67
66
|
_features: ClassVar[List[str]] = [
|
|
68
|
-
"Streams real-time trade events from LS Securities WebSocket — ohlcv_data
|
|
67
|
+
"Streams real-time trade events from LS Securities WebSocket — ohlcv_data 포트가 집계된 봉을 내보낸다 (data 포트는 같은 객체의 별칭)",
|
|
69
68
|
"stay_connected=True keeps the WebSocket subscription live between cycles (recommended for realtime strategies)",
|
|
70
69
|
"Supports CME, EUREX, SGX, HKEX contracts — symbol must include contract month code (e.g., ESH26)",
|
|
71
70
|
"Item-based execution: one subscription per node; use multiple nodes to watch multiple contracts",
|
|
@@ -179,8 +178,11 @@ class OverseasFuturesRealMarketDataNode(BaseNode):
|
|
|
179
178
|
"Use OverseasFuturesBrokerNode as the upstream broker."
|
|
180
179
|
),
|
|
181
180
|
"output_consumption": (
|
|
182
|
-
"
|
|
183
|
-
"
|
|
181
|
+
"`ohlcv_data` 와 `data` 는 **같은 객체**다 — `data` 는 별칭이다. "
|
|
182
|
+
"(예전 문서는 `data` 가 bid/ask 를 담은 raw tick dict 이라고 했지만, 그 값을 만드는 코드는 "
|
|
183
|
+
"아무도 호출하지 않는 죽은 코드였다. 런타임에 bid/ask/current_price/change_percent 는 없다.) "
|
|
184
|
+
"값의 모양: {\"AAPL\": [{date, open, high, low, close, volume}, ...]} — 종목코드로 키를 잡은 dict 이고, "
|
|
185
|
+
"봉 한 줄에는 exchange/symbol/timestamp 가 없다. "
|
|
184
186
|
"ALWAYS insert ThrottleNode before any order or AI node downstream."
|
|
185
187
|
),
|
|
186
188
|
"common_combinations": [
|
|
@@ -210,7 +212,8 @@ class OverseasFuturesRealMarketDataNode(BaseNode):
|
|
|
210
212
|
]
|
|
211
213
|
_outputs: List[OutputPort] = [
|
|
212
214
|
OutputPort(name="ohlcv_data", type="ohlcv_data", description="i18n:ports.ohlcv_data", fields=OHLCV_DATA_FIELDS),
|
|
213
|
-
OutputPort(name="data", type="
|
|
215
|
+
OutputPort(name="data", type="ohlcv_data", description="i18n:ports.market_data_full", fields=OHLCV_DATA_FIELDS),
|
|
216
|
+
OutputPort(name="symbols", type="symbol_list", description="i18n:ports.symbols", fields=SYMBOL_LIST_FIELDS),
|
|
214
217
|
]
|
|
215
218
|
|
|
216
219
|
_version: ClassVar[str] = "1.0.0"
|
|
@@ -401,7 +404,7 @@ class OverseasFuturesRealAccountNode(BaseNode):
|
|
|
401
404
|
OutputPort(name="held_symbols", type="symbol_list", description="i18n:ports.held_symbols", fields=SYMBOL_LIST_FIELDS),
|
|
402
405
|
OutputPort(name="balance", type="balance_data", description="i18n:ports.balance", fields=OVERSEAS_FUTURES_REAL_BALANCE_FIELDS),
|
|
403
406
|
OutputPort(name="open_orders", type="order_list", description="i18n:ports.open_orders", fields=ORDER_LIST_FIELDS),
|
|
404
|
-
OutputPort(name="positions", type="position_data", description="i18n:ports.positions", fields=
|
|
407
|
+
OutputPort(name="positions", type="position_data", description="i18n:ports.positions", fields=OVERSEAS_FUTURES_REAL_POSITION_FIELDS),
|
|
405
408
|
]
|
|
406
409
|
|
|
407
410
|
_version: ClassVar[str] = "1.0.0"
|
|
@@ -21,11 +21,10 @@ from programgarden_core.nodes.base import (
|
|
|
21
21
|
ProductScope,
|
|
22
22
|
BrokerProvider,
|
|
23
23
|
KOREA_STOCK_REAL_BALANCE_FIELDS,
|
|
24
|
-
MARKET_DATA_FULL_FIELDS,
|
|
25
24
|
OHLCV_DATA_FIELDS,
|
|
26
25
|
ORDER_EVENT_FIELDS,
|
|
27
26
|
ORDER_LIST_FIELDS,
|
|
28
|
-
|
|
27
|
+
KOREA_STOCK_REAL_POSITION_FIELDS,
|
|
29
28
|
SYMBOL_LIST_FIELDS,
|
|
30
29
|
)
|
|
31
30
|
|
|
@@ -64,7 +63,7 @@ class KoreaStockRealMarketDataNode(BaseNode):
|
|
|
64
63
|
],
|
|
65
64
|
}
|
|
66
65
|
_features: ClassVar[List[str]] = [
|
|
67
|
-
"Streams real-time trade events from LS Securities WebSocket for KRX stocks — ohlcv_data
|
|
66
|
+
"Streams real-time trade events from LS Securities WebSocket for KRX stocks — ohlcv_data 포트가 집계된 봉을 내보낸다 (data 포트는 같은 객체의 별칭)",
|
|
68
67
|
"stay_connected=True keeps the WebSocket subscription live between cycles (recommended for realtime strategies)",
|
|
69
68
|
"Symbol format: 6-digit KRX code (e.g., '005930') without exchange field — domestic market implied",
|
|
70
69
|
"Item-based execution: one subscription per node; use multiple nodes to watch multiple domestic stocks",
|
|
@@ -178,8 +177,11 @@ class KoreaStockRealMarketDataNode(BaseNode):
|
|
|
178
177
|
"Use KoreaStockBrokerNode as the upstream broker."
|
|
179
178
|
),
|
|
180
179
|
"output_consumption": (
|
|
181
|
-
"
|
|
182
|
-
"
|
|
180
|
+
"`ohlcv_data` 와 `data` 는 **같은 객체**다 — `data` 는 별칭이다. "
|
|
181
|
+
"(예전 문서는 `data` 가 bid/ask 를 담은 raw tick dict 이라고 했지만, 그 값을 만드는 코드는 "
|
|
182
|
+
"아무도 호출하지 않는 죽은 코드였다. 런타임에 bid/ask/current_price/change_percent 는 없다.) "
|
|
183
|
+
"값의 모양: {\"AAPL\": [{date, open, high, low, close, volume}, ...]} — 종목코드로 키를 잡은 dict 이고, "
|
|
184
|
+
"봉 한 줄에는 exchange/symbol/timestamp 가 없다. "
|
|
183
185
|
"ALWAYS insert ThrottleNode before any order or AI node downstream."
|
|
184
186
|
),
|
|
185
187
|
"common_combinations": [
|
|
@@ -209,7 +211,8 @@ class KoreaStockRealMarketDataNode(BaseNode):
|
|
|
209
211
|
]
|
|
210
212
|
_outputs: List[OutputPort] = [
|
|
211
213
|
OutputPort(name="ohlcv_data", type="ohlcv_data", description="i18n:ports.ohlcv_data", fields=OHLCV_DATA_FIELDS),
|
|
212
|
-
OutputPort(name="data", type="
|
|
214
|
+
OutputPort(name="data", type="ohlcv_data", description="i18n:ports.market_data_full", fields=OHLCV_DATA_FIELDS),
|
|
215
|
+
OutputPort(name="symbols", type="symbol_list", description="i18n:ports.symbols", fields=SYMBOL_LIST_FIELDS),
|
|
213
216
|
]
|
|
214
217
|
|
|
215
218
|
_version: ClassVar[str] = "1.0.0"
|
|
@@ -404,7 +407,7 @@ class KoreaStockRealAccountNode(BaseNode):
|
|
|
404
407
|
OutputPort(name="held_symbols", type="symbol_list", description="i18n:ports.held_symbols", fields=SYMBOL_LIST_FIELDS),
|
|
405
408
|
OutputPort(name="balance", type="balance_data", description="i18n:ports.balance", fields=KOREA_STOCK_REAL_BALANCE_FIELDS),
|
|
406
409
|
OutputPort(name="open_orders", type="order_list", description="i18n:ports.open_orders", fields=ORDER_LIST_FIELDS),
|
|
407
|
-
OutputPort(name="positions", type="position_data", description="i18n:ports.positions", fields=
|
|
410
|
+
OutputPort(name="positions", type="position_data", description="i18n:ports.positions", fields=KOREA_STOCK_REAL_POSITION_FIELDS),
|
|
408
411
|
]
|
|
409
412
|
|
|
410
413
|
_version: ClassVar[str] = "1.0.0"
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/realtime_stock.py
RENAMED
|
@@ -21,11 +21,10 @@ from programgarden_core.nodes.base import (
|
|
|
21
21
|
ProductScope,
|
|
22
22
|
BrokerProvider,
|
|
23
23
|
OVERSEAS_STOCK_REAL_BALANCE_FIELDS,
|
|
24
|
-
MARKET_DATA_FULL_FIELDS,
|
|
25
24
|
OHLCV_DATA_FIELDS,
|
|
26
25
|
ORDER_EVENT_FIELDS,
|
|
27
26
|
ORDER_LIST_FIELDS,
|
|
28
|
-
|
|
27
|
+
OVERSEAS_STOCK_REAL_POSITION_FIELDS,
|
|
29
28
|
SYMBOL_LIST_FIELDS,
|
|
30
29
|
PRICE_DATA_FIELDS,
|
|
31
30
|
)
|
|
@@ -66,7 +65,7 @@ class OverseasStockRealMarketDataNode(BaseNode):
|
|
|
66
65
|
],
|
|
67
66
|
}
|
|
68
67
|
_features: ClassVar[List[str]] = [
|
|
69
|
-
"Streams GSC (trade/tick) events from LS Securities WebSocket — ohlcv_data
|
|
68
|
+
"Streams GSC (trade/tick) events from LS Securities WebSocket — ohlcv_data 포트가 집계된 봉을 내보낸다 (data 포트는 같은 객체의 별칭)",
|
|
70
69
|
"stay_connected=True keeps the WebSocket subscription live between cycles (recommended for realtime strategies)",
|
|
71
70
|
"Item-based execution: one subscription per node; use multiple nodes or SplitNode to watch several symbols",
|
|
72
71
|
"Automatically re-subscribes after WebSocket reconnection events",
|
|
@@ -180,8 +179,11 @@ class OverseasStockRealMarketDataNode(BaseNode):
|
|
|
180
179
|
"Set stay_connected=True (default) to avoid reconnection overhead on every cycle."
|
|
181
180
|
),
|
|
182
181
|
"output_consumption": (
|
|
183
|
-
"
|
|
184
|
-
"
|
|
182
|
+
"`ohlcv_data` 와 `data` 는 **같은 객체**다 — `data` 는 별칭이다. "
|
|
183
|
+
"(예전 문서는 `data` 가 bid/ask 를 담은 raw tick dict 이라고 했지만, 그 값을 만드는 코드는 "
|
|
184
|
+
"아무도 호출하지 않는 죽은 코드였다. 런타임에 bid/ask/current_price/change_percent 는 없다.) "
|
|
185
|
+
"값의 모양: {\"AAPL\": [{date, open, high, low, close, volume}, ...]} — 종목코드로 키를 잡은 dict 이고, "
|
|
186
|
+
"봉 한 줄에는 exchange/symbol/timestamp 가 없다. "
|
|
185
187
|
"ALWAYS insert ThrottleNode before any order or AI node downstream."
|
|
186
188
|
),
|
|
187
189
|
"common_combinations": [
|
|
@@ -211,7 +213,8 @@ class OverseasStockRealMarketDataNode(BaseNode):
|
|
|
211
213
|
]
|
|
212
214
|
_outputs: List[OutputPort] = [
|
|
213
215
|
OutputPort(name="ohlcv_data", type="ohlcv_data", description="i18n:ports.ohlcv_data", fields=OHLCV_DATA_FIELDS),
|
|
214
|
-
OutputPort(name="data", type="
|
|
216
|
+
OutputPort(name="data", type="ohlcv_data", description="i18n:ports.market_data_full", fields=OHLCV_DATA_FIELDS),
|
|
217
|
+
OutputPort(name="symbols", type="symbol_list", description="i18n:ports.symbols", fields=SYMBOL_LIST_FIELDS),
|
|
215
218
|
]
|
|
216
219
|
|
|
217
220
|
_version: ClassVar[str] = "1.0.0"
|
|
@@ -406,7 +409,7 @@ class OverseasStockRealAccountNode(BaseNode):
|
|
|
406
409
|
OutputPort(name="held_symbols", type="symbol_list", description="i18n:ports.held_symbols", fields=SYMBOL_LIST_FIELDS),
|
|
407
410
|
OutputPort(name="balance", type="balance_data", description="i18n:ports.balance", fields=OVERSEAS_STOCK_REAL_BALANCE_FIELDS),
|
|
408
411
|
OutputPort(name="open_orders", type="order_list", description="i18n:ports.open_orders", fields=ORDER_LIST_FIELDS),
|
|
409
|
-
OutputPort(name="positions", type="position_data", description="i18n:ports.positions", fields=
|
|
412
|
+
OutputPort(name="positions", type="position_data", description="i18n:ports.positions", fields=OVERSEAS_STOCK_REAL_POSITION_FIELDS),
|
|
410
413
|
]
|
|
411
414
|
|
|
412
415
|
_version: ClassVar[str] = "1.0.0"
|
|
@@ -24,6 +24,8 @@ from programgarden_core.nodes.base import (
|
|
|
24
24
|
InputPort,
|
|
25
25
|
OutputPort,
|
|
26
26
|
SYMBOL_LIST_FIELDS,
|
|
27
|
+
SCREENER_SYMBOL_FIELDS,
|
|
28
|
+
MARKET_UNIVERSE_SYMBOL_FIELDS,
|
|
27
29
|
)
|
|
28
30
|
from programgarden_core.models.exchange import SymbolEntry, ProductType
|
|
29
31
|
|
|
@@ -251,7 +253,7 @@ class MarketUniverseNode(BaseNode):
|
|
|
251
253
|
|
|
252
254
|
_inputs: List[InputPort] = []
|
|
253
255
|
_outputs: List[OutputPort] = [
|
|
254
|
-
OutputPort(name="symbols", type="symbol_list", description="i18n:ports.symbols", fields=
|
|
256
|
+
OutputPort(name="symbols", type="symbol_list", description="i18n:ports.symbols", fields=MARKET_UNIVERSE_SYMBOL_FIELDS),
|
|
255
257
|
OutputPort(name="count", type="integer", description="종목 수"),
|
|
256
258
|
]
|
|
257
259
|
|
|
@@ -453,7 +455,7 @@ class ScreenerNode(BaseNode):
|
|
|
453
455
|
),
|
|
454
456
|
]
|
|
455
457
|
_outputs: List[OutputPort] = [
|
|
456
|
-
OutputPort(name="symbols", type="symbol_list", description="i18n:ports.symbols", fields=
|
|
458
|
+
OutputPort(name="symbols", type="symbol_list", description="i18n:ports.symbols", fields=SCREENER_SYMBOL_FIELDS),
|
|
457
459
|
OutputPort(name="count", type="integer", description="결과 종목 수"),
|
|
458
460
|
]
|
|
459
461
|
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/symbol_futures.py
RENAMED
|
@@ -20,6 +20,7 @@ from programgarden_core.nodes.base import (
|
|
|
20
20
|
ProductScope,
|
|
21
21
|
BrokerProvider,
|
|
22
22
|
SYMBOL_LIST_FIELDS,
|
|
23
|
+
OVERSEAS_FUTURES_SYMBOL_QUERY_FIELDS,
|
|
23
24
|
)
|
|
24
25
|
|
|
25
26
|
|
|
@@ -182,7 +183,7 @@ class OverseasFuturesSymbolQueryNode(BaseNode):
|
|
|
182
183
|
|
|
183
184
|
_inputs: List[InputPort] = []
|
|
184
185
|
_outputs: List[OutputPort] = [
|
|
185
|
-
OutputPort(name="symbols", type="symbol_list", description="i18n:ports.symbols", fields=
|
|
186
|
+
OutputPort(name="symbols", type="symbol_list", description="i18n:ports.symbols", fields=OVERSEAS_FUTURES_SYMBOL_QUERY_FIELDS),
|
|
186
187
|
OutputPort(name="count", type="integer", description="Total symbol count"),
|
|
187
188
|
]
|
|
188
189
|
|
|
@@ -18,7 +18,7 @@ from programgarden_core.nodes.base import (
|
|
|
18
18
|
OutputPort,
|
|
19
19
|
ProductScope,
|
|
20
20
|
BrokerProvider,
|
|
21
|
-
|
|
21
|
+
KOREA_STOCK_SYMBOL_QUERY_FIELDS,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
|
|
@@ -169,7 +169,7 @@ class KoreaStockSymbolQueryNode(BaseNode):
|
|
|
169
169
|
|
|
170
170
|
_inputs: List[InputPort] = []
|
|
171
171
|
_outputs: List[OutputPort] = [
|
|
172
|
-
OutputPort(name="symbols", type="symbol_list", description="i18n:ports.symbols", fields=
|
|
172
|
+
OutputPort(name="symbols", type="symbol_list", description="i18n:ports.symbols", fields=KOREA_STOCK_SYMBOL_QUERY_FIELDS),
|
|
173
173
|
OutputPort(name="count", type="integer", description="Total symbol count"),
|
|
174
174
|
]
|
|
175
175
|
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/symbol_stock.py
RENAMED
|
@@ -18,7 +18,7 @@ from programgarden_core.nodes.base import (
|
|
|
18
18
|
OutputPort,
|
|
19
19
|
ProductScope,
|
|
20
20
|
BrokerProvider,
|
|
21
|
-
|
|
21
|
+
OVERSEAS_STOCK_SYMBOL_QUERY_FIELDS,
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
|
|
@@ -177,7 +177,7 @@ class OverseasStockSymbolQueryNode(BaseNode):
|
|
|
177
177
|
|
|
178
178
|
_inputs: List[InputPort] = []
|
|
179
179
|
_outputs: List[OutputPort] = [
|
|
180
|
-
OutputPort(name="symbols", type="symbol_list", description="i18n:ports.symbols", fields=
|
|
180
|
+
OutputPort(name="symbols", type="symbol_list", description="i18n:ports.symbols", fields=OVERSEAS_STOCK_SYMBOL_QUERY_FIELDS),
|
|
181
181
|
OutputPort(name="count", type="integer", description="Total symbol count"),
|
|
182
182
|
]
|
|
183
183
|
|
|
@@ -5,7 +5,7 @@ authors = [
|
|
|
5
5
|
homepage = "https://programgarden.com"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
name = "programgarden-core"
|
|
8
|
-
version = "1.
|
|
8
|
+
version = "1.20.0"
|
|
9
9
|
license = "AGPL-3.0-or-later"
|
|
10
10
|
description = "ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의"
|
|
11
11
|
readme = "README.md"
|
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/bases/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/bases/components.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/bases/listener.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/bases/products.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/expression/__init__.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/expression/evaluator.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/i18n/locales/en.json
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/i18n/locales/ko.json
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/i18n/translator.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/__init__.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/connection_rule.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/credential.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/exchange.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/field_binding.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/plugin_resource.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/resilience.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/resource.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/validation.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/models/workflow.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/__init__.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/backtest.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/backtest_futures.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/backtest_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/calculation.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/condition.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/market_external.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/market_status.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/nodes/portfolio.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/presets/__init__.py
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/presets/news_analyst.json
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/presets/risk_manager.json
RENAMED
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/presets/strategist.json
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/registry/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/registry/node_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-1.18.0 → programgarden_core-1.20.0}/programgarden_core/retry_executor.py
RENAMED
|
File without changes
|