programgarden-core 2.3.0__tar.gz → 2.5.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-2.3.0 → programgarden_core-2.5.0}/PKG-INFO +1 -1
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/i18n/locales/en.json +14 -1
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/i18n/locales/ko.json +14 -1
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/credential.py +1 -1
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/ai.py +11 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/broker.py +43 -1
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/data.py +61 -4
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/trigger.py +32 -17
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/registry/node_registry.py +83 -1
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/pyproject.toml +1 -1
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/README.md +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/__init__.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/bases/__init__.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/bases/client.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/bases/components.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/bases/listener.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/bases/mixins.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/bases/products.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/bases/sql.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/bases/storage.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/code_node.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/exceptions.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/expression/__init__.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/expression/evaluator.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/expression/reference.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/i18n/__init__.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/i18n/translator.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/korea_alias.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/__init__.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/connection_rule.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/edge.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/event.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/exchange.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/field_binding.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/job.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/order_diagnostics.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/plugin_resource.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/resilience.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/resource.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/validation.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/workflow.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/__init__.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/account_futures.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/account_korea_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/account_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/backtest.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/backtest_futures.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/backtest_korea_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/backtest_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/base.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/calculation.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/code.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/condition.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/data_futures.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/data_korea_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/data_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/display.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/event.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/fundamental_korea_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/fundamental_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/infra.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/open_orders_futures.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/open_orders_korea_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/open_orders_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/order.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/orderable_quantity_futures.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/portfolio.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/realtime_futures.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/realtime_korea_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/realtime_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/risk.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/session_gate.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/symbol.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/symbol_futures.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/symbol_korea_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/symbol_stock.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/presets/__init__.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/presets/news_analyst.json +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/presets/risk_manager.json +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/presets/strategist.json +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/presets/technical_analyst.json +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/registry/__init__.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/registry/credential_registry.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/registry/plugin_registry.py +0 -0
- {programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/retry_executor.py +0 -0
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/i18n/locales/en.json
RENAMED
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"nodes.NewOrderNode.name": "New Order",
|
|
113
113
|
"nodes.OverseasFuturesAccountNode.description": "Retrieves overseas futures holdings, margin balance, and position details as a one-time REST API snapshot. Outputs held_symbols, balance (margin, maintenance margin, available margin), and positions (per-contract quantity, average cost, unrealized P&L). Requires an upstream OverseasFuturesBrokerNode for authentication. Use OverseasFuturesOpenOrdersNode separately for pending orders. For real-time updates, use OverseasFuturesRealAccountNode instead. Can be used as an AI Agent tool.",
|
|
114
114
|
"nodes.OverseasFuturesAccountNode.name": "Futures Account",
|
|
115
|
-
"nodes.OverseasFuturesBrokerNode.description": "Establishes an LS Securities OpenAPI connection for overseas futures trading (CME, EUREX, SGX, HKEX). Acts as the authentication gateway — downstream account, market data, and order nodes automatically inherit this broker connection through DAG traversal. Requires credential_id referencing broker_ls_overseas_futures credentials.
|
|
115
|
+
"nodes.OverseasFuturesBrokerNode.description": "Establishes an LS Securities OpenAPI connection for overseas futures trading (CME, EUREX, SGX, HKEX and others — which of them an account opens differs per account). Acts as the authentication gateway — downstream account, market data, and order nodes automatically inherit this broker connection through DAG traversal. Requires credential_id referencing broker_ls_overseas_futures credentials. Works with BOTH live and paper-trading accounts — it follows the mode of the credential you registered (paper trading is risk-free practice; live trading is equally supported). Which exchanges an account opens differs per account, so confirm with an orderable-symbol lookup. Typical connection: StartNode → OverseasFuturesBrokerNode → FuturesAccountNode / FuturesMarketDataNode / FuturesNewOrderNode.",
|
|
116
116
|
"nodes.OverseasFuturesBrokerNode.name": "Futures Broker",
|
|
117
117
|
"nodes.OverseasFuturesCancelOrderNode.description": "Cancels a pending overseas futures order by its original order ID. Requires original_order_id from FuturesOpenOrdersNode or FuturesRealOrderEventNode. Outputs cancel result and cancelled order ID. Rate-limited to 5-second minimum interval. Typical connection: FuturesOpenOrdersNode → FuturesCancelOrderNode.",
|
|
118
118
|
"nodes.OverseasFuturesCancelOrderNode.name": "Futures Cancel Order",
|
|
@@ -294,7 +294,9 @@
|
|
|
294
294
|
"fields.PerformanceReportNode.benchmark": "Optional benchmark series (same format as data). When provided, beta and alpha are added to metrics.",
|
|
295
295
|
"fields.PerformanceReportNode.periods_per_year": "Annualization factor (252 for daily, 12 for monthly data). Default 252.",
|
|
296
296
|
"fields.PerformanceReportNode.risk_free_rate": "Annual risk-free rate as a fraction (e.g. 0.02 for 2%) for Sharpe/Sortino. Default 0.",
|
|
297
|
+
"fields.HTTPRequestNode.auth_required": "Does the API require authentication? Leave unset to let the chatbot decide from context; set true for private APIs (a credential is then required), false for public APIs.",
|
|
297
298
|
"fields.HTTPRequestNode.body": "Request body for POST/PUT/PATCH. Automatically serialized to JSON.",
|
|
299
|
+
|
|
298
300
|
"fields.HTTPRequestNode.credential_id": "Credential ID for authentication. Supports: http_bearer, http_header, http_basic, http_query",
|
|
299
301
|
"fields.HTTPRequestNode.headers": "HTTP headers. Click + to add custom headers (e.g., Content-Type, X-API-Key).",
|
|
300
302
|
"fields.HTTPRequestNode.method": "HTTP method for the request",
|
|
@@ -598,7 +600,9 @@
|
|
|
598
600
|
"fieldNames.PerformanceReportNode.benchmark": "Benchmark",
|
|
599
601
|
"fieldNames.PerformanceReportNode.periods_per_year": "Periods / Year",
|
|
600
602
|
"fieldNames.PerformanceReportNode.risk_free_rate": "Risk-Free Rate",
|
|
603
|
+
"fieldNames.HTTPRequestNode.auth_required": "Requires Auth",
|
|
601
604
|
"fieldNames.HTTPRequestNode.body": "Request Body",
|
|
605
|
+
|
|
602
606
|
"fieldNames.HTTPRequestNode.credential_id": "Credential",
|
|
603
607
|
"fieldNames.HTTPRequestNode.headers": "Headers",
|
|
604
608
|
"fieldNames.HTTPRequestNode.method": "HTTP Method",
|
|
@@ -872,6 +876,8 @@
|
|
|
872
876
|
"ports.http_error": "Error message",
|
|
873
877
|
"ports.http_request_data": "Request data",
|
|
874
878
|
"ports.http_response": "API response data",
|
|
879
|
+
"ports.http_results": "Per-item results (one {item, response, status_code, success, error} entry per iterated item)",
|
|
880
|
+
|
|
875
881
|
"ports.http_status_code": "HTTP status code",
|
|
876
882
|
"ports.http_success": "Request success flag",
|
|
877
883
|
"ports.items": "Item list",
|
|
@@ -1194,7 +1200,14 @@
|
|
|
1194
1200
|
"connection_rules.realtime_to_ai_agent.suggestion": "Place a ThrottleNode between the realtime node and AI Agent to control call frequency.",
|
|
1195
1201
|
"connection_rules.realtime_to_external_api.reason": "Direct connection from realtime node to external API node is not recommended. Each tick would trigger an external API request, potentially hitting rate limits.",
|
|
1196
1202
|
"connection_rules.realtime_to_external_api.suggestion": "Place a ThrottleNode between the realtime node and external API node to control request frequency.",
|
|
1203
|
+
"connection.HTTPRequestNode.label": "API key",
|
|
1204
|
+
"connection.KoreaStockBrokerNode.label": "LS Securities Korea stock account",
|
|
1205
|
+
"connection.LLMModelNode.label": "AI model key",
|
|
1206
|
+
"connection.OverseasFuturesBrokerNode.label": "LS Securities overseas futures account",
|
|
1207
|
+
"connection.OverseasStockBrokerNode.label": "LS Securities overseas stock account",
|
|
1208
|
+
"connection.TelegramNode.label": "Telegram bot",
|
|
1197
1209
|
"connection_rules.realtime_to_http.reason": "Direct connection from realtime node to HTTPRequestNode is not recommended. Each tick would trigger an HTTP request, potentially hitting external API rate limits.",
|
|
1210
|
+
|
|
1198
1211
|
"connection_rules.realtime_to_http.suggestion": "Place a ThrottleNode between the realtime node and HTTP request node to control request frequency.",
|
|
1199
1212
|
"fields.BaseOrderNode.rate_limit_interval": "Minimum order interval (seconds). Default 5 seconds. The next order will only execute after this time has elapsed since the last order.",
|
|
1200
1213
|
"fields.BaseOrderNode.rate_limit_action": "Action when order interval is not met. skip: silently skip, error: raise error",
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/i18n/locales/ko.json
RENAMED
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"nodes.NewOrderNode.name": "신규 주문",
|
|
113
113
|
"nodes.OverseasFuturesAccountNode.description": "해외선물 보유종목, 증거금 잔고, 포지션 상세를 REST API 1회 조회로 가져옵니다. 출력: held_symbols, balance (증거금, 유지증거금, 가용증거금), positions (계약별 수량, 평균 단가, 미실현 손익). 상위 OverseasFuturesBrokerNode 연결 필수. 미체결 주문은 OverseasFuturesOpenOrdersNode를 별도로 사용하세요. 실시간 업데이트가 필요하면 OverseasFuturesRealAccountNode를 사용하세요. AI Agent 도구로 사용 가능.",
|
|
114
114
|
"nodes.OverseasFuturesAccountNode.name": "해외선물 계좌 조회",
|
|
115
|
-
"nodes.OverseasFuturesBrokerNode.description": "해외선물 거래를 위한 LS증권 OpenAPI 연결을 설정합니다(CME
|
|
115
|
+
"nodes.OverseasFuturesBrokerNode.description": "해외선물 거래를 위한 LS증권 OpenAPI 연결을 설정합니다(CME·EUREX·SGX·HKEX 등 — 어느 거래소가 열려 있는지는 계좌마다 다릅니다). 인증 게이트웨이 역할 — 하위 계좌, 시세, 주문 노드가 DAG 순회를 통해 자동으로 브로커 연결을 상속합니다. credential_id로 broker_ls_overseas_futures 인증 정보를 참조합니다. 실계좌와 모의투자 계좌를 모두 지원합니다 — 등록한 자격증명의 모드를 그대로 따릅니다(모의투자는 위험 없는 연습용이고, 실거래도 그대로 가능합니다). 계좌가 여는 거래소는 계좌마다 다르므로, 주문 가능 종목 조회로 확인하세요. 일반적인 연결: StartNode → OverseasFuturesBrokerNode → FuturesAccountNode / FuturesMarketDataNode / FuturesNewOrderNode.",
|
|
116
116
|
"nodes.OverseasFuturesBrokerNode.name": "해외선물 브로커",
|
|
117
117
|
"nodes.OverseasFuturesCancelOrderNode.description": "해외선물 미체결 주문을 원래 주문 ID로 취소합니다. FuturesOpenOrdersNode 또는 FuturesRealOrderEventNode에서 original_order_id를 받아야 합니다. 출력: 취소 결과, 취소된 주문 ID. 최소 5초 간격 제한 적용. 일반적인 연결: FuturesOpenOrdersNode → FuturesCancelOrderNode.",
|
|
118
118
|
"nodes.OverseasFuturesCancelOrderNode.name": "해외선물 취소주문",
|
|
@@ -294,7 +294,9 @@
|
|
|
294
294
|
"fields.PerformanceReportNode.benchmark": "선택 벤치마크 시계열(data 와 동일 형식). 지정 시 beta/alpha 가 지표에 추가됩니다.",
|
|
295
295
|
"fields.PerformanceReportNode.periods_per_year": "연율화 계수(일간 252, 월간 12). 기본 252.",
|
|
296
296
|
"fields.PerformanceReportNode.risk_free_rate": "무위험 수익률(소수, 예 0.02=2%) — Sharpe/Sortino용. 기본 0.",
|
|
297
|
+
"fields.HTTPRequestNode.auth_required": "이 API 가 인증이 필요한가요? 미설정이면 챗봇이 문맥으로 판단합니다. 비공개 API 는 true(자격증명 필요), 공개 API 는 false 로 두세요.",
|
|
297
298
|
"fields.HTTPRequestNode.body": "요청 본문 (POST/PUT/PATCH용). 자동으로 JSON 직렬화됨.",
|
|
299
|
+
|
|
298
300
|
"fields.HTTPRequestNode.credential_id": "인증정보 ID. 지원: http_bearer, http_header, http_basic, http_query",
|
|
299
301
|
"fields.HTTPRequestNode.headers": "HTTP 헤더. + 버튼으로 커스텀 헤더 추가 (예: Content-Type, X-API-Key).",
|
|
300
302
|
"fields.HTTPRequestNode.method": "HTTP 요청 메서드",
|
|
@@ -598,7 +600,9 @@
|
|
|
598
600
|
"fieldNames.PerformanceReportNode.benchmark": "벤치마크",
|
|
599
601
|
"fieldNames.PerformanceReportNode.periods_per_year": "연간 관측수",
|
|
600
602
|
"fieldNames.PerformanceReportNode.risk_free_rate": "무위험 수익률",
|
|
603
|
+
"fieldNames.HTTPRequestNode.auth_required": "인증 필요 여부",
|
|
601
604
|
"fieldNames.HTTPRequestNode.body": "요청본문",
|
|
605
|
+
|
|
602
606
|
"fieldNames.HTTPRequestNode.credential_id": "인증정보",
|
|
603
607
|
"fieldNames.HTTPRequestNode.headers": "헤더",
|
|
604
608
|
"fieldNames.HTTPRequestNode.method": "HTTP메소드",
|
|
@@ -872,6 +876,8 @@
|
|
|
872
876
|
"ports.http_error": "에러 메시지",
|
|
873
877
|
"ports.http_request_data": "요청 데이터",
|
|
874
878
|
"ports.http_response": "API 응답 데이터",
|
|
879
|
+
"ports.http_results": "항목별 결과 (반복된 항목마다 {item, response, status_code, success, error} 한 건)",
|
|
880
|
+
|
|
875
881
|
"ports.http_status_code": "HTTP 상태 코드",
|
|
876
882
|
"ports.http_success": "요청 성공 여부",
|
|
877
883
|
"ports.items": "아이템 목록",
|
|
@@ -1194,7 +1200,14 @@
|
|
|
1194
1200
|
"connection_rules.realtime_to_ai_agent.suggestion": "실시간 노드와 AI Agent 사이에 ThrottleNode를 배치하여 호출 빈도를 제어하세요.",
|
|
1195
1201
|
"connection_rules.realtime_to_external_api.reason": "실시간 노드에서 외부 API 노드로의 직접 연결은 권장하지 않습니다. 틱 데이터마다 외부 API 요청이 발생하여 rate limit에 걸릴 수 있습니다.",
|
|
1196
1202
|
"connection_rules.realtime_to_external_api.suggestion": "실시간 노드와 외부 API 노드 사이에 ThrottleNode를 배치하여 요청 빈도를 제어하세요.",
|
|
1203
|
+
"connection.HTTPRequestNode.label": "API 키",
|
|
1204
|
+
"connection.KoreaStockBrokerNode.label": "LS증권 국내주식 계좌",
|
|
1205
|
+
"connection.LLMModelNode.label": "AI 모델 키",
|
|
1206
|
+
"connection.OverseasFuturesBrokerNode.label": "LS증권 해외선물 계좌",
|
|
1207
|
+
"connection.OverseasStockBrokerNode.label": "LS증권 해외주식 계좌",
|
|
1208
|
+
"connection.TelegramNode.label": "텔레그램 봇",
|
|
1197
1209
|
"connection_rules.realtime_to_http.reason": "실시간 노드에서 HTTPRequestNode로의 직접 연결은 권장하지 않습니다. 틱 데이터마다 HTTP 요청이 발생하여 외부 API 제한에 걸릴 수 있습니다.",
|
|
1210
|
+
|
|
1198
1211
|
"connection_rules.realtime_to_http.suggestion": "실시간 노드와 HTTP 요청 노드 사이에 ThrottleNode를 배치하여 요청 빈도를 제어하세요.",
|
|
1199
1212
|
"fields.BaseOrderNode.rate_limit_interval": "최소 주문 간격 (초). 기본값 5초. 직전 주문 후 이 시간이 지나야 다음 주문이 실행됩니다.",
|
|
1200
1213
|
"fields.BaseOrderNode.rate_limit_action": "주문 간격 미달 시 동작. skip: 조용히 건너뜀, error: 에러 발생",
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/credential.py
RENAMED
|
@@ -126,7 +126,7 @@ BUILTIN_CREDENTIAL_SCHEMAS: Dict[str, CredentialTypeSchema] = {
|
|
|
126
126
|
}
|
|
127
127
|
),
|
|
128
128
|
# ============================================================
|
|
129
|
-
# LS증권 해외선물 (overseas_futures) -
|
|
129
|
+
# LS증권 해외선물 (overseas_futures) - 실거래·모의투자 둘 다 지원(등록 폼에서 모드 선택)
|
|
130
130
|
# ============================================================
|
|
131
131
|
"broker_ls_overseas_futures": CredentialTypeSchema(
|
|
132
132
|
type_id="broker_ls_overseas_futures",
|
|
@@ -42,6 +42,17 @@ class LLMModelNode(BaseNode):
|
|
|
42
42
|
type: Literal["LLMModelNode"] = "LLMModelNode"
|
|
43
43
|
category: NodeCategory = NodeCategory.AI
|
|
44
44
|
|
|
45
|
+
# 연결(자격증명) 선언 — 챗봇/편집기/검증기 공용. AI 모델 키는 실행에 반드시 필요하다.
|
|
46
|
+
_connection: ClassVar[Dict[str, Any]] = {
|
|
47
|
+
"purpose": "ai",
|
|
48
|
+
"need": "run",
|
|
49
|
+
"when": "always",
|
|
50
|
+
"label_key": "connection.LLMModelNode.label",
|
|
51
|
+
"presets": [],
|
|
52
|
+
"missing": "draft",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
45
56
|
# === LLM 설정 ===
|
|
46
57
|
credential_id: Optional[str] = Field(
|
|
47
58
|
default=None,
|
|
@@ -81,6 +81,19 @@ class OverseasStockBrokerNode(BaseBrokerNode):
|
|
|
81
81
|
_product_scope: ClassVar[ProductScope] = ProductScope.STOCK
|
|
82
82
|
_broker_provider: ClassVar[BrokerProvider] = BrokerProvider.LS
|
|
83
83
|
|
|
84
|
+
# 연결(자격증명) 선언 — 챗봇/편집기/검증기 공용. types 는 credential_id 필드의
|
|
85
|
+
# credential_types 에서 자동 추출된다. 이 계좌를 상속하는 시세/계좌/주문 노드는
|
|
86
|
+
# 자기 선언 없이 이 브로커 연결을 물려받는다.
|
|
87
|
+
_connection: ClassVar[Dict[str, Any]] = {
|
|
88
|
+
"purpose": "trading",
|
|
89
|
+
"need": "run",
|
|
90
|
+
"when": "always",
|
|
91
|
+
"label_key": "connection.OverseasStockBrokerNode.label",
|
|
92
|
+
"presets": [],
|
|
93
|
+
"missing": "draft",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
84
97
|
_usage: ClassVar[Dict[str, Any]] = {
|
|
85
98
|
"when_to_use": [
|
|
86
99
|
"Every workflow that reads overseas stock market data, account state, or places orders",
|
|
@@ -268,7 +281,10 @@ class OverseasFuturesBrokerNode(BaseBrokerNode):
|
|
|
268
281
|
LS증권 OpenAPI를 통해 해외선물 거래를 위한 브로커 연결을 생성합니다.
|
|
269
282
|
|
|
270
283
|
Note:
|
|
271
|
-
- 해외선물은 모의투자
|
|
284
|
+
- 해외선물은 **실거래와 모의투자 둘 다** 지원한다. 등록 폼에서 모드를 고르는 유일한
|
|
285
|
+
상품이며, ``paper_trading`` 은 등록한 자격증명의 모드를 따른다. (모의투자를 자주
|
|
286
|
+
쓰는 것은 증거금 비용 때문이지, 실거래가 안 되기 때문이 아니다.)
|
|
287
|
+
- 계좌가 여는 거래소(CME 등)는 **계좌마다 다르다** — 주문 가능 종목 조회로만 알 수 있다.
|
|
272
288
|
- credential_types: broker_ls_overseas_futures
|
|
273
289
|
"""
|
|
274
290
|
|
|
@@ -280,6 +296,19 @@ class OverseasFuturesBrokerNode(BaseBrokerNode):
|
|
|
280
296
|
_product_scope: ClassVar[ProductScope] = ProductScope.FUTURES
|
|
281
297
|
_broker_provider: ClassVar[BrokerProvider] = BrokerProvider.LS
|
|
282
298
|
|
|
299
|
+
# 연결(자격증명) 선언 — 챗봇/편집기/검증기 공용. types 는 credential_id 필드의
|
|
300
|
+
# credential_types 에서 자동 추출된다. 이 계좌를 상속하는 시세/계좌/주문 노드는
|
|
301
|
+
# 자기 선언 없이 이 브로커 연결을 물려받는다.
|
|
302
|
+
_connection: ClassVar[Dict[str, Any]] = {
|
|
303
|
+
"purpose": "trading",
|
|
304
|
+
"need": "run",
|
|
305
|
+
"when": "always",
|
|
306
|
+
"label_key": "connection.OverseasFuturesBrokerNode.label",
|
|
307
|
+
"presets": [],
|
|
308
|
+
"missing": "draft",
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
283
312
|
_usage: ClassVar[Dict[str, Any]] = {
|
|
284
313
|
"when_to_use": [
|
|
285
314
|
"Every overseas futures workflow — CME ES / NQ, SGX Nikkei, HKEX HSI mini, etc.",
|
|
@@ -442,6 +471,19 @@ class KoreaStockBrokerNode(BaseBrokerNode):
|
|
|
442
471
|
_product_scope: ClassVar[ProductScope] = ProductScope.KOREA_STOCK
|
|
443
472
|
_broker_provider: ClassVar[BrokerProvider] = BrokerProvider.LS
|
|
444
473
|
|
|
474
|
+
# 연결(자격증명) 선언 — 챗봇/편집기/검증기 공용. types 는 credential_id 필드의
|
|
475
|
+
# credential_types 에서 자동 추출된다. 이 계좌를 상속하는 시세/계좌/주문 노드는
|
|
476
|
+
# 자기 선언 없이 이 브로커 연결을 물려받는다.
|
|
477
|
+
_connection: ClassVar[Dict[str, Any]] = {
|
|
478
|
+
"purpose": "trading",
|
|
479
|
+
"need": "run",
|
|
480
|
+
"when": "always",
|
|
481
|
+
"label_key": "connection.KoreaStockBrokerNode.label",
|
|
482
|
+
"presets": [],
|
|
483
|
+
"missing": "draft",
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
|
|
445
487
|
_usage: ClassVar[Dict[str, Any]] = {
|
|
446
488
|
"when_to_use": [
|
|
447
489
|
"Every KRX-listed (KOSPI / KOSDAQ / NXT) stock workflow",
|
|
@@ -552,6 +552,24 @@ class HTTPRequestNode(BaseNode):
|
|
|
552
552
|
on_throttle="skip",
|
|
553
553
|
)
|
|
554
554
|
|
|
555
|
+
# 연결(자격증명) 선언 — 챗봇/편집기/검증기 공용. HTTP 는 인증형 API 일 때만 키가
|
|
556
|
+
# 필요(when=auth_required, auth_required 설정 참조)하고, 실제 응답 검증에도 필요해
|
|
557
|
+
# need=validate 다. FMP/Finnhub 는 http_query 프리셋으로 param_name 을 잡아준다.
|
|
558
|
+
_connection: ClassVar[Dict[str, Any]] = {
|
|
559
|
+
"purpose": "data",
|
|
560
|
+
"need": "validate",
|
|
561
|
+
"when": "auth_required",
|
|
562
|
+
"label_key": "connection.HTTPRequestNode.label",
|
|
563
|
+
"presets": [
|
|
564
|
+
{"id": "fmp", "label": "FMP", "type": "http_query",
|
|
565
|
+
"fields": {"param_name": "apikey"}},
|
|
566
|
+
{"id": "finnhub", "label": "Finnhub", "type": "http_query",
|
|
567
|
+
"fields": {"param_name": "token"}},
|
|
568
|
+
],
|
|
569
|
+
"missing": "draft",
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
|
|
555
573
|
# === PARAMETERS: 핵심 HTTP 요청 설정 ===
|
|
556
574
|
method: Literal["GET", "POST", "PUT", "PATCH", "DELETE"] = Field(
|
|
557
575
|
default="GET",
|
|
@@ -573,6 +591,15 @@ class HTTPRequestNode(BaseNode):
|
|
|
573
591
|
# === SETTINGS: 부가 설정 ===
|
|
574
592
|
timeout_seconds: int = Field(default=30, description="Request timeout (seconds)")
|
|
575
593
|
|
|
594
|
+
# 이 API 가 인증이 필요한가(비공개 키). None = 챗봇이 판단(기본) — 문맥으로 결정한다.
|
|
595
|
+
# True = 인증 필요(credential 없으면 검증/실행 불가), False = 공개 API(credential 불필요).
|
|
596
|
+
# 기본값을 False 로 두면 비공개 API 에 위험하므로 None(미지정)으로 둔다.
|
|
597
|
+
# `connection.when="auth_required"` 가 이 값을 참조한다.
|
|
598
|
+
auth_required: Optional[bool] = Field(
|
|
599
|
+
default=None,
|
|
600
|
+
description="Does this API require authentication? None = chatbot decides (default), True = auth required, False = public.",
|
|
601
|
+
)
|
|
602
|
+
|
|
576
603
|
# === Resilience: 재시도/실패 처리 (H-21: HTTP 요청은 기본 재시도 활성화) ===
|
|
577
604
|
resilience: ResilienceConfig = Field(
|
|
578
605
|
default_factory=lambda: ResilienceConfig(
|
|
@@ -606,6 +633,7 @@ class HTTPRequestNode(BaseNode):
|
|
|
606
633
|
"Rate-limited: minimum 1-second interval and max 3 concurrent calls; real-time node connections blocked",
|
|
607
634
|
"is_tool_enabled=True — AI Agent can invoke HTTPRequestNode as a tool to fetch live external data",
|
|
608
635
|
"Outputs response (parsed JSON or raw text), status_code, success flag, and error string for downstream branching",
|
|
636
|
+
"Runs once per upstream list element when the config references {{ item… }}/{{ index }}/{{ total }} (e.g. one request per watchlist symbol); the per-item outcomes are exposed on the `results` array port while response/status_code/success/error hold the last item's values. A config without an item reference runs once (unchanged).",
|
|
609
637
|
]
|
|
610
638
|
_anti_patterns: ClassVar[List[Dict[str, str]]] = [
|
|
611
639
|
{
|
|
@@ -693,7 +721,7 @@ class HTTPRequestNode(BaseNode):
|
|
|
693
721
|
]
|
|
694
722
|
_node_guide: ClassVar[Dict[str, Any]] = {
|
|
695
723
|
"input_handling": "The 'url' field is the only required config. Use 'body' for POST/PUT/PATCH payloads (auto-serialized to JSON). Use 'query_params' for GET parameters. The 'data' input port can supply dynamic values from upstream nodes via expression.",
|
|
696
|
-
"output_consumption": "Check 'success' (boolean) before consuming 'response'. On failure, 'error' contains the HTTP status string. 'response' is auto-parsed JSON when the Content-Type is application/json, otherwise raw text.",
|
|
724
|
+
"output_consumption": "Check 'success' (boolean) before consuming 'response'. On failure, 'error' contains the HTTP status string. 'response' is auto-parsed JSON when the Content-Type is application/json, otherwise raw text. When the config references {{ item… }} the node runs once per upstream item: read the 'results' array (one {item, response, status_code, success, error} entry per item, in order) for per-item outcomes; 'response'/'status_code'/'success'/'error' then reflect the LAST item, and top-level 'success' is the AND of all items while top-level 'error' is set only when every item failed.",
|
|
697
725
|
"common_combinations": [
|
|
698
726
|
"StartNode → HTTPRequestNode (GET) → FieldMappingNode → ConditionNode",
|
|
699
727
|
"ConditionNode → HTTPRequestNode (POST webhook) → SummaryDisplayNode",
|
|
@@ -719,13 +747,32 @@ class HTTPRequestNode(BaseNode):
|
|
|
719
747
|
OutputPort(name="status_code", type="number", description="i18n:ports.http_status_code"),
|
|
720
748
|
OutputPort(name="success", type="boolean", description="i18n:ports.http_success"),
|
|
721
749
|
OutputPort(name="error", type="string", description="i18n:ports.http_error"),
|
|
750
|
+
# Per-item results when the node auto-iterates over an upstream list
|
|
751
|
+
# (config references {{ item… }}/{{ index }}/{{ total }}). One entry per
|
|
752
|
+
# item, in order: {item, response, status_code, success, error}. A node
|
|
753
|
+
# that does not reference an iteration item runs once and this array holds
|
|
754
|
+
# that single execution. `response`/`status_code`/`success`/`error` above
|
|
755
|
+
# stay as the LAST item's scalar values (backward compatible with
|
|
756
|
+
# {{ nodes.x.response }}).
|
|
757
|
+
OutputPort(
|
|
758
|
+
name="results", type="array",
|
|
759
|
+
description="i18n:ports.http_results",
|
|
760
|
+
example=[
|
|
761
|
+
{"item": {"symbol": "AAPL", "exchange": "NASDAQ"},
|
|
762
|
+
"response": {"price": 189.2}, "status_code": 200,
|
|
763
|
+
"success": True, "error": None},
|
|
764
|
+
],
|
|
765
|
+
),
|
|
722
766
|
]
|
|
723
767
|
|
|
724
768
|
_field_schema: ClassVar[Dict[str, "FieldSchema"]] = {}
|
|
725
769
|
|
|
726
|
-
_version: ClassVar[str] = "1.
|
|
727
|
-
_updated_at: ClassVar[str] = "2026-
|
|
728
|
-
_change_note: ClassVar[Optional[str]] =
|
|
770
|
+
_version: ClassVar[str] = "1.1.0"
|
|
771
|
+
_updated_at: ClassVar[str] = "2026-09-28"
|
|
772
|
+
_change_note: ClassVar[Optional[str]] = (
|
|
773
|
+
"Added `results` port: one entry per iterated item ({{ item }} refs); "
|
|
774
|
+
"scalar ports keep the last item's value."
|
|
775
|
+
)
|
|
729
776
|
|
|
730
777
|
@classmethod
|
|
731
778
|
def get_field_schema(cls) -> Dict[str, "FieldSchema"]:
|
|
@@ -812,6 +859,16 @@ class HTTPRequestNode(BaseNode):
|
|
|
812
859
|
max_value=300,
|
|
813
860
|
group="advanced",
|
|
814
861
|
),
|
|
862
|
+
"auth_required": FieldSchema(
|
|
863
|
+
name="auth_required", type=FieldType.BOOLEAN, required=False,
|
|
864
|
+
description="i18n:fields.HTTPRequestNode.auth_required",
|
|
865
|
+
category=FieldCategory.SETTINGS,
|
|
866
|
+
expression_mode=ExpressionMode.FIXED_ONLY,
|
|
867
|
+
example=True,
|
|
868
|
+
expected_type="bool",
|
|
869
|
+
group="advanced",
|
|
870
|
+
),
|
|
871
|
+
|
|
815
872
|
# === RESILIENCE: 재시도/실패 처리 (단순화된 UI) ===
|
|
816
873
|
"resilience": FieldSchema(
|
|
817
874
|
name="resilience", type=FieldType.OBJECT, required=False,
|
|
@@ -50,17 +50,29 @@ class ScheduleNode(BaseNode):
|
|
|
50
50
|
default="America/New_York", description="Timezone (e.g., America/New_York, Asia/Seoul)"
|
|
51
51
|
)
|
|
52
52
|
enabled: bool = Field(default=True, description="Schedule enabled")
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
# None (omitted) = unbounded: run until the workflow is stopped. Owner
|
|
54
|
+
# decision 2026-09-26: when the investor names no duration, the bot keeps
|
|
55
|
+
# running on its cron until the user stops the workflow. A provided value
|
|
56
|
+
# is still enforced (> 0) as a cap.
|
|
57
|
+
max_duration_hours: Optional[float] = Field(
|
|
58
|
+
default=None,
|
|
59
|
+
description=(
|
|
60
|
+
"최대 실행 시간 (시간). 생략(None)하면 사용자가 워크플로우를 멈출 때까지 "
|
|
61
|
+
"계속 실행한다. 값을 지정하면 초과 시 스케줄 자동 종료 (지정 시 반드시 > 0)."
|
|
62
|
+
),
|
|
56
63
|
)
|
|
57
64
|
# ⑭ count was read by the executor (config.get("count", 1000)) but not
|
|
58
65
|
# declared on the model — that schema/executor duality meant a canonical
|
|
59
|
-
# field was invisible to validation.
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
# field was invisible to validation. Now declared here. None (omitted) =
|
|
67
|
+
# unbounded: run until the workflow is stopped (owner 2026-09-26). A
|
|
68
|
+
# provided value is enforced (>= 1) as a cycle cap.
|
|
69
|
+
count: Optional[int] = Field(
|
|
70
|
+
default=None,
|
|
71
|
+
description=(
|
|
72
|
+
"Max number of schedule cycles before the scheduler exits. Omit "
|
|
73
|
+
"(None) to run until the workflow is stopped; set only when the "
|
|
74
|
+
"investor named a fixed number of cycles (>= 1)."
|
|
75
|
+
),
|
|
64
76
|
)
|
|
65
77
|
|
|
66
78
|
_inputs: List[InputPort] = []
|
|
@@ -76,7 +88,7 @@ class ScheduleNode(BaseNode):
|
|
|
76
88
|
_usage: ClassVar[Dict[str, Any]] = {
|
|
77
89
|
"when_to_use": [
|
|
78
90
|
"Run the main flow on a cron schedule (every N minutes, daily at 09:30 ET, weekly market close, …)",
|
|
79
|
-
"
|
|
91
|
+
"Run indefinitely until the user stops the workflow — omit max_duration_hours and count; set them only when the investor named a duration or cycle count",
|
|
80
92
|
"Combine with TradingHoursFilterNode to fire only on weekdays within market hours",
|
|
81
93
|
],
|
|
82
94
|
"when_not_to_use": [
|
|
@@ -93,12 +105,12 @@ class ScheduleNode(BaseNode):
|
|
|
93
105
|
_features: ClassVar[List[str]] = [
|
|
94
106
|
"Standard 5-field cron expression — minute / hour / day / month / weekday",
|
|
95
107
|
"Timezone-aware (IANA names) — 'America/New_York', 'Asia/Seoul', 'UTC'",
|
|
96
|
-
"max_duration_hours
|
|
108
|
+
"max_duration_hours and count are optional bounds: omit both and the schedule runs until the workflow is stopped; set one and the scheduler exits cleanly at that limit",
|
|
97
109
|
"enabled=False freezes the trigger without removing the node from the DAG",
|
|
98
110
|
"A tick re-executes the whole main flow: the ScheduleNode returns {trigger: true} without re-registering, so every node downstream of it runs again on each tick",
|
|
99
111
|
"Startup account and open-order snapshots are not retained across schedule ticks; they are retained only across realtime events",
|
|
100
112
|
"In replay a schedule_tick must fall on the cron's next firing instant after the previous frame, evaluated in this node's timezone (default America/New_York)",
|
|
101
|
-
"enabled=false emits no tick;
|
|
113
|
+
"enabled=false emits no tick; when count / max_duration_hours are set, ticks past them are refused; when both are omitted the schedule is unbounded (runs until stopped); an invalid timezone is rejected at startup",
|
|
102
114
|
"Emits exactly one subsequent event type: schedule_tick",
|
|
103
115
|
"Requires exactly one StartNode upstream; the ScheduleNode is never the workflow root",
|
|
104
116
|
]
|
|
@@ -177,7 +189,7 @@ class ScheduleNode(BaseNode):
|
|
|
177
189
|
},
|
|
178
190
|
]
|
|
179
191
|
_node_guide: ClassVar[Dict[str, Any]] = {
|
|
180
|
-
"input_handling": "No data inputs. All behavior is configured via `cron`, `timezone`, `enabled`, `max_duration_hours
|
|
192
|
+
"input_handling": "No data inputs. All behavior is configured via `cron`, `timezone`, `enabled`, and the optional bounds `max_duration_hours` / `count` (omit both to run until the workflow is stopped).",
|
|
181
193
|
"output_consumption": "`trigger` output carries `{fired_at, cycle_index}`. Downstream nodes usually just need an incoming edge; explicit binding is optional.",
|
|
182
194
|
"common_combinations": [
|
|
183
195
|
"StartNode → ScheduleNode → trading body (plain cron workflow)",
|
|
@@ -187,7 +199,7 @@ class ScheduleNode(BaseNode):
|
|
|
187
199
|
"pitfalls": [
|
|
188
200
|
"Always set `timezone` explicitly — server defaults are not portable",
|
|
189
201
|
"Pair with TradingHoursFilterNode when the cron expression does not already encode market hours",
|
|
190
|
-
"
|
|
202
|
+
"For a bot that should run until the user stops it, omit max_duration_hours and count; set them only when the investor named a duration (max_duration_hours > 0) or a cycle count (count >= 1)",
|
|
191
203
|
],
|
|
192
204
|
}
|
|
193
205
|
|
|
@@ -237,9 +249,11 @@ class ScheduleNode(BaseNode):
|
|
|
237
249
|
name="max_duration_hours",
|
|
238
250
|
type=FieldType.NUMBER,
|
|
239
251
|
description="i18n:fields.ScheduleNode.max_duration_hours",
|
|
240
|
-
default
|
|
252
|
+
# No default and NO max_value: omit for an unbounded schedule
|
|
253
|
+
# that runs until the workflow is stopped (owner 2026-09-26).
|
|
254
|
+
# Only a provided value is bounded (> 0).
|
|
255
|
+
default=None,
|
|
241
256
|
min_value=0.1,
|
|
242
|
-
max_value=720.0,
|
|
243
257
|
expression_mode=ExpressionMode.FIXED_ONLY,
|
|
244
258
|
category=FieldCategory.SETTINGS,
|
|
245
259
|
expected_type="float",
|
|
@@ -248,8 +262,9 @@ class ScheduleNode(BaseNode):
|
|
|
248
262
|
"count": FieldSchema(
|
|
249
263
|
name="count",
|
|
250
264
|
type=FieldType.INTEGER,
|
|
251
|
-
description="Max number of schedule cycles before the scheduler exits
|
|
252
|
-
default=
|
|
265
|
+
description="Max number of schedule cycles before the scheduler exits. Omit for an unbounded schedule that runs until the workflow is stopped; set only when a fixed number of cycles is intended.",
|
|
266
|
+
# No default: omitted = unbounded (owner 2026-09-26).
|
|
267
|
+
default=None,
|
|
253
268
|
min_value=1,
|
|
254
269
|
expression_mode=ExpressionMode.FIXED_ONLY,
|
|
255
270
|
category=FieldCategory.SETTINGS,
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/registry/node_registry.py
RENAMED
|
@@ -9,7 +9,7 @@ from typing import Optional, List, Dict, Any, Type
|
|
|
9
9
|
from pydantic import BaseModel, Field
|
|
10
10
|
|
|
11
11
|
from programgarden_core.nodes.base import BaseNode, NodeCategory, InputPort, OutputPort, ProductScope, BrokerProvider
|
|
12
|
-
from programgarden_core.i18n import translate_schema, translate_category
|
|
12
|
+
from programgarden_core.i18n import translate_schema, translate_category, t
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class NodeTypeSchema(BaseModel):
|
|
@@ -112,6 +112,26 @@ class NodeTypeSchema(BaseModel):
|
|
|
112
112
|
"time_rules, venue."
|
|
113
113
|
),
|
|
114
114
|
)
|
|
115
|
+
connection: Optional[Dict[str, Any]] = Field(
|
|
116
|
+
default=None,
|
|
117
|
+
description=(
|
|
118
|
+
"Credential/connection declaration — one place the chatbot, editor "
|
|
119
|
+
"and validator read a node's credential need. Built by the core "
|
|
120
|
+
"registry from the node's `_connection` ClassVar + its credential "
|
|
121
|
+
"field's `credential_types` (locale-independent: both label_ko and "
|
|
122
|
+
"label_en are always present). None when the node needs no credential "
|
|
123
|
+
"of its own — broker-connected nodes (market data / account / order) "
|
|
124
|
+
"inherit their upstream broker's connection and declare nothing. "
|
|
125
|
+
"Keys (all present when set): types (== credential_types), purpose "
|
|
126
|
+
"(trading|data|ai|notify), need (run|validate|optional; run = needed "
|
|
127
|
+
"only to execute, validate = also needed for real-response "
|
|
128
|
+
"validation), when (always|auth_required|never; auth_required = only "
|
|
129
|
+
"when the node config marks the API authenticated), label_ko, "
|
|
130
|
+
"label_en (plain user-facing names), presets (HTTPRequestNode only; "
|
|
131
|
+
"[] elsewhere), missing (draft = may be saved without it and the run "
|
|
132
|
+
"gate blocks; block = must exist before build)."
|
|
133
|
+
),
|
|
134
|
+
)
|
|
115
135
|
|
|
116
136
|
# === Version metadata (UI change detection) ===
|
|
117
137
|
version: str = Field(
|
|
@@ -388,6 +408,14 @@ class NodeTypeRegistry:
|
|
|
388
408
|
|
|
389
409
|
config_schema = self._build_config_schema(node_class, type_name)
|
|
390
410
|
|
|
411
|
+
# 연결(자격증명) 선언 직렬화 (_connection ClassVar → 챗봇/편집기/검증기 공용 dict).
|
|
412
|
+
# types 는 노드의 credential 필드가 export 한 credential_types 에서 그대로 뽑아
|
|
413
|
+
# 항상 동기화한다(별도 선언 안 함). label_ko/label_en 은 i18n 키를 양 언어로
|
|
414
|
+
# 즉시 해석해 locale-독립적으로 둘 다 싣는다(포트/설명과 달리 get_schema(locale)
|
|
415
|
+
# 재번역에 의존하지 않음). credential 필드가 없거나 _connection 미선언이면 None
|
|
416
|
+
# (브로커 연결을 상속하는 시세/계좌/주문 노드는 자기 선언이 없다).
|
|
417
|
+
connection = self._build_connection(node_class, config_schema)
|
|
418
|
+
|
|
391
419
|
# Display 노드의 런타임 데이터 스키마
|
|
392
420
|
display_data_schema = getattr(node_class, '_display_data_schema', None)
|
|
393
421
|
|
|
@@ -423,6 +451,7 @@ class NodeTypeRegistry:
|
|
|
423
451
|
outputs=outputs,
|
|
424
452
|
config_schema=config_schema,
|
|
425
453
|
display_data_schema=display_data_schema,
|
|
454
|
+
connection=connection,
|
|
426
455
|
connection_rules=serialized_connection_rules,
|
|
427
456
|
rate_limit=serialized_rate_limit,
|
|
428
457
|
usage=getattr(node_class, "_usage", None),
|
|
@@ -463,6 +492,45 @@ class NodeTypeRegistry:
|
|
|
463
492
|
|
|
464
493
|
return result
|
|
465
494
|
|
|
495
|
+
# 닫힌 값 집합 — 잘못된 선언을 조기에 잡는다(테스트에서도 이 집합으로 검증).
|
|
496
|
+
_CONNECTION_PURPOSES = ("trading", "data", "ai", "notify")
|
|
497
|
+
_CONNECTION_NEEDS = ("run", "validate", "optional")
|
|
498
|
+
_CONNECTION_WHENS = ("always", "auth_required", "never")
|
|
499
|
+
_CONNECTION_MISSINGS = ("draft", "block")
|
|
500
|
+
|
|
501
|
+
def _build_connection(
|
|
502
|
+
self, node_class: Type[BaseNode], config_schema: Dict[str, Any],
|
|
503
|
+
) -> Optional[Dict[str, Any]]:
|
|
504
|
+
"""`_connection` ClassVar + credential_types → 공용 connection 선언.
|
|
505
|
+
|
|
506
|
+
모든 키가 항상 존재한다. `types` 는 config_schema 안 credential 필드가 export 한
|
|
507
|
+
`credential_types` 의 합집합이라 선언과 어긋나지 않는다. `label_ko`/`label_en` 은
|
|
508
|
+
`label_key` i18n 키를 양 언어로 해석해 둘 다 싣는다(locale-독립).
|
|
509
|
+
"""
|
|
510
|
+
decl = getattr(node_class, "_connection", None)
|
|
511
|
+
if not decl:
|
|
512
|
+
return None
|
|
513
|
+
|
|
514
|
+
cred_types: List[str] = []
|
|
515
|
+
for field_cfg in config_schema.values():
|
|
516
|
+
if not isinstance(field_cfg, dict):
|
|
517
|
+
continue
|
|
518
|
+
for ct in field_cfg.get("credential_types") or []:
|
|
519
|
+
if ct not in cred_types:
|
|
520
|
+
cred_types.append(ct)
|
|
521
|
+
|
|
522
|
+
label_key = decl.get("label_key", "")
|
|
523
|
+
return {
|
|
524
|
+
"types": cred_types,
|
|
525
|
+
"purpose": decl["purpose"],
|
|
526
|
+
"need": decl["need"],
|
|
527
|
+
"when": decl["when"],
|
|
528
|
+
"label_ko": t(label_key, "ko") if label_key else "",
|
|
529
|
+
"label_en": t(label_key, "en") if label_key else "",
|
|
530
|
+
"presets": [dict(p) for p in decl.get("presets", [])],
|
|
531
|
+
"missing": decl["missing"],
|
|
532
|
+
}
|
|
533
|
+
|
|
466
534
|
def _build_config_schema(self, node_class: Type[BaseNode], node_type: str) -> Dict[str, Any]:
|
|
467
535
|
"""
|
|
468
536
|
노드의 필드 스키마를 단순한 config_schema 형식으로 변환합니다.
|
|
@@ -516,6 +584,20 @@ class NodeTypeRegistry:
|
|
|
516
584
|
return NodeTypeSchema(**translated)
|
|
517
585
|
return schema
|
|
518
586
|
|
|
587
|
+
def connection_declaration(self, node_type: str) -> Optional[Dict[str, Any]]:
|
|
588
|
+
"""이 노드가 필요로 하는 자격증명/연결 선언(챗봇·편집기·검증기 공용).
|
|
589
|
+
|
|
590
|
+
credential 이 필요 없는 노드(브로커 연결을 상속하는 시세/계좌/주문 등)는 None.
|
|
591
|
+
반환 dict 는 복사본이라 호출부가 수정해도 캐시에 영향 없다.
|
|
592
|
+
"""
|
|
593
|
+
schema = self._schemas.get(node_type)
|
|
594
|
+
if schema is None or schema.connection is None:
|
|
595
|
+
return None
|
|
596
|
+
conn = dict(schema.connection)
|
|
597
|
+
conn["types"] = list(conn.get("types", []))
|
|
598
|
+
conn["presets"] = [dict(p) for p in conn.get("presets", [])]
|
|
599
|
+
return conn
|
|
600
|
+
|
|
519
601
|
def list_types(self, category: Optional[str] = None) -> List[str]:
|
|
520
602
|
"""List registered node types"""
|
|
521
603
|
if category:
|
|
@@ -5,7 +5,7 @@ authors = [
|
|
|
5
5
|
homepage = "https://programgarden.com"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
name = "programgarden-core"
|
|
8
|
-
version = "2.
|
|
8
|
+
version = "2.5.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
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/bases/components.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
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/expression/__init__.py
RENAMED
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/expression/evaluator.py
RENAMED
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/expression/reference.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/connection_rule.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/field_binding.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/order_diagnostics.py
RENAMED
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/plugin_resource.py
RENAMED
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/resilience.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/models/validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/account_futures.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/account_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/backtest_futures.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/backtest_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/calculation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/data_futures.py
RENAMED
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/data_korea_stock.py
RENAMED
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/data_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/fundamental_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/open_orders_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/realtime_futures.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/realtime_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/session_gate.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/symbol_futures.py
RENAMED
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/symbol_korea_stock.py
RENAMED
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/nodes/symbol_stock.py
RENAMED
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/presets/__init__.py
RENAMED
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/presets/news_analyst.json
RENAMED
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/presets/risk_manager.json
RENAMED
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/presets/strategist.json
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/registry/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.3.0 → programgarden_core-2.5.0}/programgarden_core/registry/plugin_registry.py
RENAMED
|
File without changes
|
|
File without changes
|