programgarden-core 1.16.0__tar.gz → 1.18.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.16.0 → programgarden_core-1.18.0}/PKG-INFO +1 -1
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/__init__.py +18 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/exceptions.py +39 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/i18n/locales/en.json +27 -4
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/i18n/locales/ko.json +28 -2
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/credential.py +34 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/exchange.py +22 -1
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/__init__.py +2 -1
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/code.py +50 -5
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/data_futures.py +19 -18
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/data_korea_stock.py +15 -17
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/data_stock.py +17 -20
- programgarden_core-1.18.0/programgarden_core/nodes/symbol_futures.py +497 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/registry/node_registry.py +2 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/registry/plugin_registry.py +1 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/pyproject.toml +1 -1
- programgarden_core-1.16.0/programgarden_core/nodes/symbol_futures.py +0 -237
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/README.md +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/bases/__init__.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/bases/client.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/bases/components.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/bases/listener.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/bases/mixins.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/bases/products.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/bases/sql.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/bases/storage.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/code_node.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/expression/__init__.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/expression/evaluator.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/i18n/__init__.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/i18n/translator.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/korea_alias.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/__init__.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/connection_rule.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/edge.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/event.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/field_binding.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/job.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/order_diagnostics.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/plugin_resource.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/resilience.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/resource.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/validation.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/workflow.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/account_futures.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/account_korea_stock.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/account_stock.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/ai.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/backtest.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/backtest_futures.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/backtest_korea_stock.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/backtest_stock.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/base.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/broker.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/calculation.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/condition.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/data.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/display.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/event.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/fundamental_korea_stock.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/fundamental_stock.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/infra.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/market_external.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/market_status.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/open_orders_futures.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/open_orders_korea_stock.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/open_orders_stock.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/order.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/portfolio.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/realtime_futures.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/realtime_korea_stock.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/realtime_stock.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/risk.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/symbol.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/symbol_korea_stock.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/symbol_stock.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/trigger.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/presets/__init__.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/presets/news_analyst.json +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/presets/risk_manager.json +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/presets/strategist.json +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/presets/technical_analyst.json +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/registry/__init__.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/registry/credential_registry.py +0 -0
- {programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/retry_executor.py +0 -0
|
@@ -35,6 +35,7 @@ __all__ = [
|
|
|
35
35
|
# Nodes - Broker (상품별 분리)
|
|
36
36
|
"OverseasStockBrokerNode",
|
|
37
37
|
"OverseasFuturesBrokerNode",
|
|
38
|
+
"KoreaStockBrokerNode",
|
|
38
39
|
# Nodes - Market (해외주식)
|
|
39
40
|
"OverseasStockMarketDataNode",
|
|
40
41
|
"OverseasStockHistoricalDataNode",
|
|
@@ -45,6 +46,13 @@ __all__ = [
|
|
|
45
46
|
"OverseasFuturesHistoricalDataNode",
|
|
46
47
|
"OverseasFuturesRealMarketDataNode",
|
|
47
48
|
"OverseasFuturesSymbolQueryNode",
|
|
49
|
+
"FuturesContractNode",
|
|
50
|
+
# Nodes - Market (국내주식)
|
|
51
|
+
"KoreaStockMarketDataNode",
|
|
52
|
+
"KoreaStockHistoricalDataNode",
|
|
53
|
+
"KoreaStockRealMarketDataNode",
|
|
54
|
+
"KoreaStockSymbolQueryNode",
|
|
55
|
+
"KoreaStockFundamentalNode",
|
|
48
56
|
# Nodes - Account (해외주식)
|
|
49
57
|
"OverseasStockAccountNode",
|
|
50
58
|
"OverseasStockRealAccountNode",
|
|
@@ -53,6 +61,11 @@ __all__ = [
|
|
|
53
61
|
"OverseasFuturesAccountNode",
|
|
54
62
|
"OverseasFuturesRealAccountNode",
|
|
55
63
|
"OverseasFuturesRealOrderEventNode",
|
|
64
|
+
# Nodes - Account (국내주식)
|
|
65
|
+
"KoreaStockAccountNode",
|
|
66
|
+
"KoreaStockOpenOrdersNode",
|
|
67
|
+
"KoreaStockRealAccountNode",
|
|
68
|
+
"KoreaStockRealOrderEventNode",
|
|
56
69
|
# Nodes - Symbol (상품 무관)
|
|
57
70
|
"WatchlistNode",
|
|
58
71
|
"MarketUniverseNode",
|
|
@@ -79,6 +92,10 @@ __all__ = [
|
|
|
79
92
|
"OverseasFuturesNewOrderNode",
|
|
80
93
|
"OverseasFuturesModifyOrderNode",
|
|
81
94
|
"OverseasFuturesCancelOrderNode",
|
|
95
|
+
# Nodes - Order (국내주식)
|
|
96
|
+
"KoreaStockNewOrderNode",
|
|
97
|
+
"KoreaStockModifyOrderNode",
|
|
98
|
+
"KoreaStockCancelOrderNode",
|
|
82
99
|
# Nodes - Display (6개)
|
|
83
100
|
"TableDisplayNode",
|
|
84
101
|
"LineChartNode",
|
|
@@ -126,6 +143,7 @@ __all__ = [
|
|
|
126
143
|
"ExecutionError",
|
|
127
144
|
"BalanceUnavailableError",
|
|
128
145
|
"ConditionEvaluationError",
|
|
146
|
+
"MissingDependencyError",
|
|
129
147
|
# Exceptions (Finance)
|
|
130
148
|
"FinanceError",
|
|
131
149
|
"AppKeyException",
|
|
@@ -152,6 +152,45 @@ class ConditionEvaluationError(ExecutionError):
|
|
|
152
152
|
super().__init__(message, node_id=node_id, details=details)
|
|
153
153
|
|
|
154
154
|
|
|
155
|
+
class MissingDependencyError(ExecutionError):
|
|
156
|
+
"""선택적 heavy 의존성(extra) 미설치/부분설치 — silent no-op 금지, 명시 에러.
|
|
157
|
+
|
|
158
|
+
Raised by community plugins/nodes that lazily import an optional heavy
|
|
159
|
+
dependency shipped behind a pyproject extra (e.g. ``portfolio`` →
|
|
160
|
+
pyportfolioopt, ``perf`` → quantstats). Importing must fail loud with a
|
|
161
|
+
concrete install hint instead of silently no-op'ing.
|
|
162
|
+
|
|
163
|
+
``transitive=True`` distinguishes the case where the extra itself is
|
|
164
|
+
installed but one of its transitive dependencies is missing/broken
|
|
165
|
+
(e.g. quantstats present but ``pkg_resources``/matplotlib absent) from
|
|
166
|
+
the plain "extra not installed at all" case (``transitive=False``).
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def __init__(
|
|
170
|
+
self,
|
|
171
|
+
message: str,
|
|
172
|
+
extra: Optional[str] = None,
|
|
173
|
+
package: Optional[str] = None,
|
|
174
|
+
install_hint: Optional[str] = None,
|
|
175
|
+
transitive: bool = False,
|
|
176
|
+
node_id: Optional[str] = None,
|
|
177
|
+
details: Optional[dict] = None,
|
|
178
|
+
):
|
|
179
|
+
d = {
|
|
180
|
+
"code": "MISSING_OPTIONAL_DEPENDENCY",
|
|
181
|
+
"extra": extra,
|
|
182
|
+
"package": package,
|
|
183
|
+
"install_hint": install_hint,
|
|
184
|
+
"transitive": transitive,
|
|
185
|
+
}
|
|
186
|
+
d.update(details or {})
|
|
187
|
+
super().__init__(message, node_id=node_id, details=d)
|
|
188
|
+
self.extra = extra
|
|
189
|
+
self.package = package
|
|
190
|
+
self.install_hint = install_hint
|
|
191
|
+
self.transitive = transitive
|
|
192
|
+
|
|
193
|
+
|
|
155
194
|
class DuplicateJobIdError(ProgramGardenError):
|
|
156
195
|
"""중복된 Job ID 오류
|
|
157
196
|
|
{programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/i18n/locales/en.json
RENAMED
|
@@ -66,6 +66,8 @@
|
|
|
66
66
|
"nodes.FearGreedIndexNode.name": "Fear & Greed Index",
|
|
67
67
|
"nodes.FundamentalDataNode.description": "Fetches company financial data via Financial Modeling Prep (FMP) API. Supports four data types: company profile, income statement, balance sheet, and key metrics. Configure period (annual/quarter) and limit (number of periods to fetch). Requires FMP API credentials. Outputs financial data array and query summary. Typical connection: WatchlistNode → FundamentalDataNode → ConditionNode (for fundamental screening) or → TableDisplayNode.",
|
|
68
68
|
"nodes.FundamentalDataNode.name": "Fundamental Data (FMP)",
|
|
69
|
+
"nodes.PerformanceReportNode.name": "Performance Report (quantstats)",
|
|
70
|
+
"nodes.PerformanceReportNode.description": "Computes risk/return metrics (Sharpe, Sortino, max drawdown, CAGR, annualized volatility, Calmar) from an equity curve, price series, or return series using quantstats. Optionally computes beta and alpha against a benchmark series. Emits a drawdown time series and a formatted text report, and runs headless via the matplotlib Agg backend. Requires the optional 'perf' extra (pip install 'programgarden-community[perf]'); a missing or partially-installed extra raises a structured MissingDependencyError. Typical connection: BacktestEngineNode → PerformanceReportNode → TableDisplayNode.",
|
|
69
71
|
"nodes.FieldMappingNode.description": "Renames field names in data objects to a standard format. Use to normalize external API responses so downstream nodes can access data with consistent field names (e.g., rename 'adj_close' to 'close', 'vol' to 'volume'). Configure mappings as from/to pairs. Outputs mapped_data with renamed fields. Set preserve_unmapped=true to keep original fields alongside renamed ones. Note: this node only renames fields — it does not generate or transform data values. Typical connection: HTTPRequestNode → FieldMappingNode → ConditionNode.",
|
|
70
72
|
"nodes.FieldMappingNode.name": "Field Mapping",
|
|
71
73
|
"nodes.FileReaderNode.description": "Reads PDF, TXT, CSV, JSON, MD, DOCX, and XLSX files and converts them to text and structured data. Supports batch processing of multiple files via file paths or base64 data input. The node handles parsing only — file sourcing (uploads, S3, HTTP, etc.) is handled externally. PDF: page range selection, table extraction support. CSV: configurable delimiter and header. XLSX: sheet selection. Security: only /app/data/ paths allowed, file size limits enforced. Outputs text array, data array, and metadata array (auto-iterate compatible). Typical connection: HTTPRequestNode → FileReaderNode → AIAgentNode (document analysis) or → ConditionNode. Can be used as an AI Agent tool.",
|
|
@@ -323,6 +325,12 @@
|
|
|
323
325
|
"fields.FundamentalDataNode.limit": "Number of periods to fetch (most recent N). Default 1.",
|
|
324
326
|
"fields.FundamentalDataNode.timeout_seconds": "API request timeout (seconds). Default 30s.",
|
|
325
327
|
"fields.FundamentalDataNode.resilience": "Retry and fallback settings for API call failures",
|
|
328
|
+
"fields.PerformanceReportNode.data": "Input series: an array of numbers or of {date, <value_field>} objects (equity curve, price levels, or periodic returns).",
|
|
329
|
+
"fields.PerformanceReportNode.data_kind": "Kind of the input series: 'equity', 'prices', or 'returns'. Level series ('equity'/'prices') are converted to returns internally.",
|
|
330
|
+
"fields.PerformanceReportNode.value_field": "Key holding the numeric value when rows are objects (default 'close'; falls back to value/equity/return).",
|
|
331
|
+
"fields.PerformanceReportNode.benchmark": "Optional benchmark series (same format as data). When provided, beta and alpha are added to metrics.",
|
|
332
|
+
"fields.PerformanceReportNode.periods_per_year": "Annualization factor (252 for daily, 12 for monthly data). Default 252.",
|
|
333
|
+
"fields.PerformanceReportNode.risk_free_rate": "Annual risk-free rate as a fraction (e.g. 0.02 for 2%) for Sharpe/Sortino. Default 0.",
|
|
326
334
|
"fields.HTTPRequestNode.body": "Request body for POST/PUT/PATCH. Automatically serialized to JSON.",
|
|
327
335
|
"fields.HTTPRequestNode.credential_id": "Credential ID for authentication. Supports: http_bearer, http_header, http_basic, http_query",
|
|
328
336
|
"fields.HTTPRequestNode.headers": "HTTP headers. Click + to add custom headers (e.g., Content-Type, X-API-Key).",
|
|
@@ -652,6 +660,12 @@
|
|
|
652
660
|
"fieldNames.FundamentalDataNode.limit": "Limit",
|
|
653
661
|
"fieldNames.FundamentalDataNode.timeout_seconds": "Timeout (sec)",
|
|
654
662
|
"fieldNames.FundamentalDataNode.resilience": "Resilience",
|
|
663
|
+
"fieldNames.PerformanceReportNode.data": "Data Series",
|
|
664
|
+
"fieldNames.PerformanceReportNode.data_kind": "Data Kind",
|
|
665
|
+
"fieldNames.PerformanceReportNode.value_field": "Value Field",
|
|
666
|
+
"fieldNames.PerformanceReportNode.benchmark": "Benchmark",
|
|
667
|
+
"fieldNames.PerformanceReportNode.periods_per_year": "Periods / Year",
|
|
668
|
+
"fieldNames.PerformanceReportNode.risk_free_rate": "Risk-Free Rate",
|
|
655
669
|
"fieldNames.HTTPRequestNode.body": "Request Body",
|
|
656
670
|
"fieldNames.HTTPRequestNode.credential_id": "Credential",
|
|
657
671
|
"fieldNames.HTTPRequestNode.headers": "Headers",
|
|
@@ -871,6 +885,10 @@
|
|
|
871
885
|
"outputs.FearGreedIndexNode.value": "Fear & Greed Index (0~100)",
|
|
872
886
|
"outputs.FundamentalDataNode.data": "Financial data array [{symbol, exchange, ...}]",
|
|
873
887
|
"outputs.FundamentalDataNode.summary": "Query summary {data_type, symbol_count, record_count}",
|
|
888
|
+
"outputs.PerformanceReportNode.metrics": "Metrics object {sharpe, sortino, max_drawdown, cagr, volatility, calmar[, beta, alpha]} (null on insufficient data).",
|
|
889
|
+
"outputs.PerformanceReportNode.report": "Formatted multi-line performance report text.",
|
|
890
|
+
"outputs.PerformanceReportNode.drawdown_series": "Drawdown time series as an array of {date, drawdown}.",
|
|
891
|
+
"outputs.PerformanceReportNode.summary": "Summary {observations, return_points, data_kind, has_benchmark[, error]}.",
|
|
874
892
|
"outputs.LLMModelNode.connection": "LLM connection info (connect to AI Agent node's ai_model port)",
|
|
875
893
|
"outputs.MarketStatusNode.statuses": "Real-time market status list. Each entry: {market: canonical key (e.g. 'US'), jangubun: raw LS code (e.g. '9'), jstatus: raw LS status code (e.g. '21'), jstatus_label: English status label from the internal technical table (e.g. 'Market open'), is_open: bool respecting include_extended_hours, is_regular_open: regular-hours only, is_extended_open: includes pre/after-market, updated_at: ISO8601 timestamp}. Markets that have not received an event yet stay at jstatus='unknown' with is_open=False. The jstatus_label field is English-only (internal technical code table, not UI-translated).",
|
|
876
894
|
"outputs.MarketStatusNode.event": "Most recent status transition event. Shape: {market, jstatus, jstatus_label, prev_jstatus, prev_jstatus_label, transitioned_at}. None until the first event arrives.",
|
|
@@ -1259,7 +1277,6 @@
|
|
|
1259
1277
|
"ui.no_tables_create_first": "Create a table first",
|
|
1260
1278
|
"ui.no_tables_found": "No tables found",
|
|
1261
1279
|
"ui.select_table_first": "Select a table first",
|
|
1262
|
-
|
|
1263
1280
|
"connection_rules.realtime_to_order.reason": "Direct connection from realtime node to order node is blocked. Each tick would trigger an order, potentially causing unintended mass orders.",
|
|
1264
1281
|
"connection_rules.realtime_to_order.suggestion": "Place a ThrottleNode between the realtime node and order node to control execution frequency.",
|
|
1265
1282
|
"connection_rules.realtime_to_ai_agent.reason": "Direct connection from realtime node to AIAgentNode is blocked. Each tick would trigger an LLM call, potentially causing excessive costs.",
|
|
@@ -1268,12 +1285,10 @@
|
|
|
1268
1285
|
"connection_rules.realtime_to_external_api.suggestion": "Place a ThrottleNode between the realtime node and external API node to control request frequency.",
|
|
1269
1286
|
"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.",
|
|
1270
1287
|
"connection_rules.realtime_to_http.suggestion": "Place a ThrottleNode between the realtime node and HTTP request node to control request frequency.",
|
|
1271
|
-
|
|
1272
1288
|
"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.",
|
|
1273
1289
|
"fields.BaseOrderNode.rate_limit_action": "Action when order interval is not met. skip: silently skip, error: raise error",
|
|
1274
1290
|
"enums.rate_limit_action.skip": "Skip (default)",
|
|
1275
1291
|
"enums.rate_limit_action.error": "Raise error",
|
|
1276
|
-
|
|
1277
1292
|
"plugins.TimeSeriesMomentum.name": "Time Series Momentum",
|
|
1278
1293
|
"plugins.TimeSeriesMomentum.description": "Determines trading direction based on past returns of individual assets (Moskowitz et al. 2012)",
|
|
1279
1294
|
"plugins.ConnorsRSI.name": "Connors RSI",
|
|
@@ -1303,5 +1318,13 @@
|
|
|
1303
1318
|
"events.restart.data_gap_warning": "Data gap warning during restore: data may be missing for {gap_sec} seconds since checkpoint",
|
|
1304
1319
|
"events.restart.skipped_nodes": "Restore: skipping {count} completed nodes",
|
|
1305
1320
|
"events.restart.checkpoint_expired": "Checkpoint expired ({age_sec}s > 600s limit)",
|
|
1306
|
-
"events.restart.workflow_changed": "Workflow definition changed, cannot restore (hash mismatch)"
|
|
1321
|
+
"events.restart.workflow_changed": "Workflow definition changed, cannot restore (hash mismatch)",
|
|
1322
|
+
"nodes.FuturesContractNode.name": "Futures Contract",
|
|
1323
|
+
"nodes.FuturesContractNode.description": "Resolves an underlying futures product (e.g. Mini Hang Seng) to its currently listed contract symbol at execution time, so the workflow never breaks when a contract expires. Pick the front month, the next month (roll target), or the nearest quarterly. Outputs a symbol list identical in shape to WatchlistNode, so downstream futures nodes wire unchanged. Requires an upstream OverseasFuturesBrokerNode. Typical connection: FuturesContractNode → OverseasFuturesHistoricalDataNode / OverseasFuturesMarketDataNode.",
|
|
1324
|
+
"fieldNames.FuturesContractNode.base_products": "Underlying Products",
|
|
1325
|
+
"fieldNames.FuturesContractNode.contract_selection": "Contract Selection",
|
|
1326
|
+
"fieldNames.FuturesContractNode.futures_exchange": "Exchange",
|
|
1327
|
+
"enums.contract_selection.front": "Front month (nearest expiry)",
|
|
1328
|
+
"enums.contract_selection.next": "Next month (roll target)",
|
|
1329
|
+
"enums.contract_selection.quarterly": "Quarterly (Mar/Jun/Sep/Dec)"
|
|
1307
1330
|
}
|
{programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/i18n/locales/ko.json
RENAMED
|
@@ -70,6 +70,8 @@
|
|
|
70
70
|
"nodes.FearGreedIndexNode.name": "공포/탐욕 지수",
|
|
71
71
|
"nodes.FundamentalDataNode.description": "Financial Modeling Prep (FMP) API로 기업 재무 데이터를 조회합니다. 4가지 데이터 유형: 기업 프로필, 손익계산서, 대차대조표, 핵심 지표. 기간(연간/분기)과 조회 수(최근 N기)를 설정합니다. FMP API 인증 정보 필요. 출력: 재무 데이터 배열, 조회 요약. 일반적인 연결: WatchlistNode → FundamentalDataNode → ConditionNode (펀더멘털 스크리닝) 또는 → TableDisplayNode.",
|
|
72
72
|
"nodes.FundamentalDataNode.name": "재무 데이터 (FMP)",
|
|
73
|
+
"nodes.PerformanceReportNode.name": "성과 리포트 (quantstats)",
|
|
74
|
+
"nodes.PerformanceReportNode.description": "자산곡선(equity)·가격·수익률 시계열로부터 quantstats 로 리스크·수익 지표(Sharpe, Sortino, 최대낙폭, CAGR, 연율 변동성, Calmar)를 산출합니다. 벤치마크 시계열을 지정하면 beta/alpha 를 추가로 계산합니다. 낙폭 시계열과 서식 리포트 텍스트를 출력하며, matplotlib Agg 백엔드로 헤드리스 동작합니다. 선택 'perf' extra 필요(pip install 'programgarden-community[perf]') — 미설치/부분설치 시 구조화된 MissingDependencyError 를 발생시킵니다. 대표 연결: BacktestEngineNode → PerformanceReportNode → TableDisplayNode.",
|
|
73
75
|
"nodes.HTTPRequestNode.description": "GET, POST, PUT, PATCH, DELETE를 지원하는 외부 REST API 호출 노드입니다. URL({{ }} 표현식 지원), 헤더, 쿼리 파라미터, JSON 요청 본문을 설정합니다. 인증 방식: Bearer 토큰, 헤더 키, 기본 인증, 쿼리 파라미터. 서버 에러(5xx), 속도 제한(429), 네트워크 장애 시 자동 재시도 기능 내장. 출력: 응답 데이터, HTTP 상태 코드, 응답 헤더, 응답 시간(ms). 실시간 데이터 소스 연결 시 ThrottleNode 필수. 일반적인 연결: ThrottleNode → HTTPRequestNode → FieldMappingNode. AI Agent 도구로 사용 가능.",
|
|
74
76
|
"nodes.HTTPRequestNode.name": "HTTP 요청",
|
|
75
77
|
"nodes.HistoricalDataNode.description": "지정 기간의 과거 OHLCV(시가, 고가, 저가, 종가, 거래량) 차트 데이터를 조회합니다. 봉 종류: 1분, 5분, 15분, 1시간, 일봉, 주봉, 월봉. 수정주가(주식 분할/배당 반영) 활성화 권장(백테스트용). 이전 노드가 여러 종목을 출력하면 자동 반복 실행. 상위 BrokerNode 연결 필수. 일반적인 연결: WatchlistNode → HistoricalDataNode → ConditionNode 또는 → BacktestEngineNode. AI Agent 도구로 사용 가능.",
|
|
@@ -323,6 +325,12 @@
|
|
|
323
325
|
"fields.FundamentalDataNode.limit": "가져올 기간 수 (최근 N개). 기본 1.",
|
|
324
326
|
"fields.FundamentalDataNode.timeout_seconds": "API 요청 타임아웃 (초). 기본 30초.",
|
|
325
327
|
"fields.FundamentalDataNode.resilience": "API 호출 실패 시 재시도 및 폴백 설정",
|
|
328
|
+
"fields.PerformanceReportNode.data": "입력 시계열: 숫자 배열 또는 {date, <value_field>} 객체 배열(자산곡선/가격/수익률).",
|
|
329
|
+
"fields.PerformanceReportNode.data_kind": "입력 종류: 'equity'/'prices'/'returns'. 레벨 시계열('equity'/'prices')은 내부에서 수익률로 변환됩니다.",
|
|
330
|
+
"fields.PerformanceReportNode.value_field": "행이 객체일 때 숫자 값을 담은 키(기본 'close'; value/equity/return 폴백).",
|
|
331
|
+
"fields.PerformanceReportNode.benchmark": "선택 벤치마크 시계열(data 와 동일 형식). 지정 시 beta/alpha 가 지표에 추가됩니다.",
|
|
332
|
+
"fields.PerformanceReportNode.periods_per_year": "연율화 계수(일간 252, 월간 12). 기본 252.",
|
|
333
|
+
"fields.PerformanceReportNode.risk_free_rate": "무위험 수익률(소수, 예 0.02=2%) — Sharpe/Sortino용. 기본 0.",
|
|
326
334
|
"fields.HTTPRequestNode.body": "요청 본문 (POST/PUT/PATCH용). 자동으로 JSON 직렬화됨.",
|
|
327
335
|
"fields.HTTPRequestNode.credential_id": "인증정보 ID. 지원: http_bearer, http_header, http_basic, http_query",
|
|
328
336
|
"fields.HTTPRequestNode.headers": "HTTP 헤더. + 버튼으로 커스텀 헤더 추가 (예: Content-Type, X-API-Key).",
|
|
@@ -652,6 +660,12 @@
|
|
|
652
660
|
"fieldNames.FundamentalDataNode.limit": "조회수",
|
|
653
661
|
"fieldNames.FundamentalDataNode.timeout_seconds": "타임아웃(초)",
|
|
654
662
|
"fieldNames.FundamentalDataNode.resilience": "재시도설정",
|
|
663
|
+
"fieldNames.PerformanceReportNode.data": "데이터 시계열",
|
|
664
|
+
"fieldNames.PerformanceReportNode.data_kind": "데이터 종류",
|
|
665
|
+
"fieldNames.PerformanceReportNode.value_field": "값 필드",
|
|
666
|
+
"fieldNames.PerformanceReportNode.benchmark": "벤치마크",
|
|
667
|
+
"fieldNames.PerformanceReportNode.periods_per_year": "연간 관측수",
|
|
668
|
+
"fieldNames.PerformanceReportNode.risk_free_rate": "무위험 수익률",
|
|
655
669
|
"fieldNames.HTTPRequestNode.body": "요청본문",
|
|
656
670
|
"fieldNames.HTTPRequestNode.credential_id": "인증정보",
|
|
657
671
|
"fieldNames.HTTPRequestNode.headers": "헤더",
|
|
@@ -871,6 +885,10 @@
|
|
|
871
885
|
"outputs.FearGreedIndexNode.value": "공포/탐욕 지수 (0~100)",
|
|
872
886
|
"outputs.FundamentalDataNode.data": "재무 데이터 배열 [{symbol, exchange, ...}]",
|
|
873
887
|
"outputs.FundamentalDataNode.summary": "조회 요약 {data_type, symbol_count, record_count}",
|
|
888
|
+
"outputs.PerformanceReportNode.metrics": "지표 객체 {sharpe, sortino, max_drawdown, cagr, volatility, calmar[, beta, alpha]} (데이터 부족 시 null).",
|
|
889
|
+
"outputs.PerformanceReportNode.report": "서식 성과 리포트 텍스트.",
|
|
890
|
+
"outputs.PerformanceReportNode.drawdown_series": "낙폭 시계열 [{date, drawdown}] 배열.",
|
|
891
|
+
"outputs.PerformanceReportNode.summary": "요약 {observations, return_points, data_kind, has_benchmark[, error]}.",
|
|
874
892
|
"outputs.LLMModelNode.connection": "LLM 연결 정보 (AI 에이전트 노드의 ai_model 포트에 연결)",
|
|
875
893
|
"outputs.MarketStatusNode.statuses": "실시간 시장 상태 리스트. 각 항목: {market: 표준 키(예: 'US'), jangubun: 원시 LS 코드(예: '9'), jstatus: 원시 상태 코드(예: '21'), jstatus_label: 내부 기술 테이블 기반 영문 상태 레이블(예: 'Market open'), is_open: include_extended_hours 반영한 bool, is_regular_open: 정규장만, is_extended_open: 프리/애프터마켓 포함, updated_at: ISO8601 타임스탬프}. 아직 이벤트를 수신하지 않은 시장은 jstatus='unknown' + is_open=False 상태로 유지됩니다. jstatus_label 필드값은 영문 (내부 기술 코드 테이블 기반, UI 번역 대상 아님).",
|
|
876
894
|
"outputs.MarketStatusNode.event": "최신 상태 전이 이벤트. shape: {market, jstatus, jstatus_label, prev_jstatus, prev_jstatus_label, transitioned_at}. 첫 이벤트 수신 전까지 None.",
|
|
@@ -1300,5 +1318,13 @@
|
|
|
1300
1318
|
"events.restart.data_gap_warning": "복구 중 데이터 갭 경고: 체크포인트 이후 {gap_sec}초 동안의 데이터가 누락될 수 있습니다",
|
|
1301
1319
|
"events.restart.skipped_nodes": "복구: {count}개 완료 노드 스킵",
|
|
1302
1320
|
"events.restart.checkpoint_expired": "체크포인트가 만료됨 ({age_sec}초 > 600초 제한)",
|
|
1303
|
-
"events.restart.workflow_changed": "워크플로우 정의가 변경되어 복구 불가 (해시 불일치)"
|
|
1304
|
-
|
|
1321
|
+
"events.restart.workflow_changed": "워크플로우 정의가 변경되어 복구 불가 (해시 불일치)",
|
|
1322
|
+
"nodes.FuturesContractNode.name": "선물 월물 선택",
|
|
1323
|
+
"nodes.FuturesContractNode.description": "기초자산(예: 미니 항셍)을 실행 시점에 **현재 상장된 월물 종목코드**로 바꿔줍니다. 월물을 직접 적어두면 만기가 지나는 순간 시세가 조용히 끊기는데, 이 노드를 쓰면 시간이 지나도 자동으로 다음 월물로 넘어갑니다. 근월물/차월물/분기월물 중에서 고를 수 있습니다. 출력 형식이 관심종목 노드와 같아서 아래쪽 배선을 그대로 두면 됩니다. 위쪽에 해외선물 브로커 노드가 필요합니다.",
|
|
1324
|
+
"fieldNames.FuturesContractNode.base_products": "기초자산",
|
|
1325
|
+
"fieldNames.FuturesContractNode.contract_selection": "월물 선택",
|
|
1326
|
+
"fieldNames.FuturesContractNode.futures_exchange": "거래소",
|
|
1327
|
+
"enums.contract_selection.front": "근월물 (만기 가장 가까움)",
|
|
1328
|
+
"enums.contract_selection.next": "차월물 (롤오버 대상)",
|
|
1329
|
+
"enums.contract_selection.quarterly": "분기월물 (3·6·9·12월)"
|
|
1330
|
+
}
|
{programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/credential.py
RENAMED
|
@@ -160,6 +160,40 @@ BUILTIN_CREDENTIAL_SCHEMAS: Dict[str, CredentialTypeSchema] = {
|
|
|
160
160
|
}
|
|
161
161
|
),
|
|
162
162
|
# ============================================================
|
|
163
|
+
# LS증권 국내주식 (korea_stock) - 모의투자 미지원 (실전 전용)
|
|
164
|
+
# ============================================================
|
|
165
|
+
"broker_ls_korea_stock": CredentialTypeSchema(
|
|
166
|
+
type_id="broker_ls_korea_stock",
|
|
167
|
+
name="LS증권 국내주식",
|
|
168
|
+
description="국내주식 OpenAPI 인증 정보 (실전 전용 — 모의투자 미지원)",
|
|
169
|
+
widget_schema={
|
|
170
|
+
"fields": [
|
|
171
|
+
{
|
|
172
|
+
"key": "name",
|
|
173
|
+
"type": "text",
|
|
174
|
+
"label": "Credential 이름",
|
|
175
|
+
"hint": "my-ls-korea-cred",
|
|
176
|
+
"description": "이 인증 정보를 식별할 이름",
|
|
177
|
+
"required": True
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"key": "appkey",
|
|
181
|
+
"type": "password",
|
|
182
|
+
"label": "App Key",
|
|
183
|
+
"description": "LS증권에서 발급받은 App Key",
|
|
184
|
+
"required": True
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"key": "appsecret",
|
|
188
|
+
"type": "password",
|
|
189
|
+
"label": "App Secret",
|
|
190
|
+
"description": "LS증권에서 발급받은 App Secret",
|
|
191
|
+
"required": True
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
),
|
|
196
|
+
# ============================================================
|
|
163
197
|
# Telegram Bot
|
|
164
198
|
# ============================================================
|
|
165
199
|
"telegram": CredentialTypeSchema(
|
{programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/models/exchange.py
RENAMED
|
@@ -15,6 +15,7 @@ class ProductType(str, Enum):
|
|
|
15
15
|
"""상품 타입"""
|
|
16
16
|
OVERSEAS_STOCK = "overseas_stock"
|
|
17
17
|
OVERSEAS_FUTURES = "overseas_futures"
|
|
18
|
+
KOREA_STOCK = "korea_stock" # 국내주식 (KOSPI/KOSDAQ, KRX 단일)
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class ExchangeInfo(BaseModel):
|
|
@@ -136,8 +137,20 @@ class ExchangeRegistry:
|
|
|
136
137
|
),
|
|
137
138
|
}
|
|
138
139
|
|
|
140
|
+
# 국내주식 거래소 (KRX 단일 — KOSPI/KOSDAQ 통합)
|
|
141
|
+
ls_korea_stock = {
|
|
142
|
+
"KRX": ExchangeInfo(
|
|
143
|
+
code="KRX",
|
|
144
|
+
name="KRX",
|
|
145
|
+
full_name="Korea Exchange",
|
|
146
|
+
country="KR",
|
|
147
|
+
currency="KRW",
|
|
148
|
+
),
|
|
149
|
+
}
|
|
150
|
+
|
|
139
151
|
self.register_exchanges("ls", ProductType.OVERSEAS_STOCK, ls_overseas_stock)
|
|
140
152
|
self.register_exchanges("ls", ProductType.OVERSEAS_FUTURES, ls_overseas_futures)
|
|
153
|
+
self.register_exchanges("ls", ProductType.KOREA_STOCK, ls_korea_stock)
|
|
141
154
|
|
|
142
155
|
def register_exchanges(
|
|
143
156
|
self,
|
|
@@ -209,7 +222,11 @@ class ExchangeRegistry:
|
|
|
209
222
|
# 해외선물: CME 기본
|
|
210
223
|
if product == ProductType.OVERSEAS_FUTURES:
|
|
211
224
|
return "CME" if "CME" in exchanges else exchanges[0]
|
|
212
|
-
|
|
225
|
+
|
|
226
|
+
# 국내주식: KRX 단일
|
|
227
|
+
if product == ProductType.KOREA_STOCK:
|
|
228
|
+
return "KRX" if "KRX" in exchanges else exchanges[0]
|
|
229
|
+
|
|
213
230
|
return exchanges[0]
|
|
214
231
|
|
|
215
232
|
|
|
@@ -229,6 +246,10 @@ class SymbolEntry(BaseModel):
|
|
|
229
246
|
|
|
230
247
|
def to_api_symbol(self, broker: str, product: ProductType) -> str:
|
|
231
248
|
"""API용 종목코드 생성 (예: 82AAPL)"""
|
|
249
|
+
# 국내주식: 6자리 KRX 종목코드를 그대로 사용 (거래소 접두어 없음)
|
|
250
|
+
if product == ProductType.KOREA_STOCK:
|
|
251
|
+
return self.symbol
|
|
252
|
+
|
|
232
253
|
code = exchange_registry.name_to_code(broker, product, self.exchange)
|
|
233
254
|
if code is None:
|
|
234
255
|
raise ValueError(f"Unknown exchange: {self.exchange}")
|
{programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/__init__.py
RENAMED
|
@@ -45,7 +45,7 @@ from programgarden_core.nodes.account_futures import OverseasFuturesAccountNode
|
|
|
45
45
|
from programgarden_core.nodes.open_orders_stock import OverseasStockOpenOrdersNode
|
|
46
46
|
from programgarden_core.nodes.open_orders_futures import OverseasFuturesOpenOrdersNode
|
|
47
47
|
from programgarden_core.nodes.symbol_stock import OverseasStockSymbolQueryNode
|
|
48
|
-
from programgarden_core.nodes.symbol_futures import OverseasFuturesSymbolQueryNode
|
|
48
|
+
from programgarden_core.nodes.symbol_futures import OverseasFuturesSymbolQueryNode, FuturesContractNode
|
|
49
49
|
# Data (상품 무관)
|
|
50
50
|
from programgarden_core.nodes.data import SQLiteNode, HTTPRequestNode, FieldMappingNode
|
|
51
51
|
from programgarden_core.nodes.code import CodeNode
|
|
@@ -132,6 +132,7 @@ __all__ = [
|
|
|
132
132
|
"OverseasFuturesHistoricalDataNode",
|
|
133
133
|
"OverseasFuturesRealMarketDataNode",
|
|
134
134
|
"OverseasFuturesSymbolQueryNode",
|
|
135
|
+
"FuturesContractNode",
|
|
135
136
|
# Account - Stock (해외주식)
|
|
136
137
|
"OverseasStockAccountNode",
|
|
137
138
|
"OverseasStockRealAccountNode",
|
|
@@ -30,6 +30,22 @@ from programgarden_core.nodes.base import (
|
|
|
30
30
|
OutputPort,
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
+
# ── Single source of truth for the sandbox import whitelist ─────────────────
|
|
34
|
+
# The CodeNode sandbox's allowed-import set is owned by
|
|
35
|
+
# programgarden_core.code_node.DEFAULT_ALLOWED_IMPORTS. Every AI-facing rendering
|
|
36
|
+
# below (the `code` field help_text, _node_guide, _anti_patterns) is DERIVED from
|
|
37
|
+
# it here — never hand-copied — so the schema surface can never drift from the
|
|
38
|
+
# enforced whitelist. Guarded by test_codenode_allowed_imports_surface_no_drift.
|
|
39
|
+
# code_node.py is stdlib-only, so this top-level import introduces no cycle.
|
|
40
|
+
# NOTE: this reflects the SHIPPED default. An embedding host MAY widen the
|
|
41
|
+
# whitelist at call time via `allowed_imports=`; this static surface does not
|
|
42
|
+
# attempt to mirror host extensions (the shipped WorkflowExecutor pins the
|
|
43
|
+
# default, so it is authoritative for chatbot-generated workflows).
|
|
44
|
+
from programgarden_core.code_node import DEFAULT_ALLOWED_IMPORTS
|
|
45
|
+
|
|
46
|
+
_ALLOWED_IMPORTS_SORTED: List[str] = sorted(DEFAULT_ALLOWED_IMPORTS)
|
|
47
|
+
_ALLOWED_IMPORTS_CSV: str = ", ".join(_ALLOWED_IMPORTS_SORTED)
|
|
48
|
+
|
|
33
49
|
|
|
34
50
|
class CodeNode(BaseNode):
|
|
35
51
|
"""
|
|
@@ -155,6 +171,11 @@ class CodeNode(BaseNode):
|
|
|
155
171
|
"reason": "The binding layer checks port existence but never coerces shape; a wrong shape does not fail at the CodeNode — it fails downstream where the typed node reads it (e.g. order nodes expect the Symbol Data Format array of {symbol, exchange, ...}).",
|
|
156
172
|
"alternative": "When feeding a typed market/order/condition node, return the standard shape: a list of {symbol, exchange, ...} dicts. When feeding a display/sink or If/Condition scalar, any shape works.",
|
|
157
173
|
},
|
|
174
|
+
{
|
|
175
|
+
"pattern": "Importing a third-party numeric/data library — numpy, pandas, scipy, pandas-ta, TA-Lib, scikit-learn — inside execute()",
|
|
176
|
+
"reason": f"The sandbox enforces an import whitelist of pure-computation stdlib only ({_ALLOWED_IMPORTS_CSV}); any other import is rejected before the code runs with CODE_NODE_FORBIDDEN, and these libraries are not available in the sandbox.",
|
|
177
|
+
"alternative": "Hand-roll the calculation in pure Python using the allowed stdlib (math, statistics, collections, itertools, functools). E.g. a rolling mean via sum()/len over a slice, an RSI via a manual gain/loss loop, a covariance via statistics — no numpy/pandas needed.",
|
|
178
|
+
},
|
|
158
179
|
]
|
|
159
180
|
_examples: ClassVar[List[Dict[str, Any]]] = [
|
|
160
181
|
{
|
|
@@ -235,10 +256,27 @@ class CodeNode(BaseNode):
|
|
|
235
256
|
],
|
|
236
257
|
"pitfalls": [
|
|
237
258
|
"execute() must be defined exactly with that name; a missing/renamed function is rejected before run (CODE_NODE_NO_EXECUTE).",
|
|
238
|
-
"
|
|
259
|
+
f"Imports are whitelisted to pure-computation stdlib only — exactly: {_ALLOWED_IMPORTS_CSV}. "
|
|
260
|
+
"ANY other import is rejected before run with CODE_NODE_FORBIDDEN: third-party numeric/data "
|
|
261
|
+
"libraries (numpy, pandas, scipy, pandas-ta, TA-Lib, scikit-learn) are NOT available in the "
|
|
262
|
+
"sandbox, and I/O/system stdlib (os, sys, socket, subprocess, urllib, http, requests, open) is "
|
|
263
|
+
"blocked. Re-implement such functionality by hand in pure Python using the allowed stdlib.",
|
|
239
264
|
"No credential/broker/network access — CodeNode cannot place orders or fetch data itself; feed it data from typed nodes.",
|
|
240
265
|
"Declared output ports must all appear as keys in the returned dict, or they resolve to None.",
|
|
241
266
|
],
|
|
267
|
+
# Structured, machine-consumable whitelist for the AI chatbot — DERIVED
|
|
268
|
+
# from DEFAULT_ALLOWED_IMPORTS (single source of truth), never hand-listed.
|
|
269
|
+
"allowed_imports": list(_ALLOWED_IMPORTS_SORTED),
|
|
270
|
+
"import_policy": (
|
|
271
|
+
"The sandbox enforces an import WHITELIST. Only the modules in 'allowed_imports' "
|
|
272
|
+
"(pure-computation stdlib, shipped default) may be imported. Every non-stdlib "
|
|
273
|
+
"library — numpy, pandas, scipy, pandas-ta, TA-Lib, scikit-learn, etc. — and "
|
|
274
|
+
"every I/O/system stdlib module (os, sys, socket, subprocess, urllib, http, "
|
|
275
|
+
"requests, open) is rejected before execution with CODE_NODE_FORBIDDEN. If you "
|
|
276
|
+
"need such functionality, HAND-ROLL it in pure Python using only the allowed "
|
|
277
|
+
"modules (e.g. compute an SMA/RSI/z-score with math + statistics + collections "
|
|
278
|
+
"instead of importing pandas/numpy)."
|
|
279
|
+
),
|
|
242
280
|
}
|
|
243
281
|
|
|
244
282
|
_inputs: ClassVar[List[InputPort]] = [
|
|
@@ -250,9 +288,9 @@ class CodeNode(BaseNode):
|
|
|
250
288
|
OutputPort(name="result", type="any", description="i18n:outputs.CodeNode.result"),
|
|
251
289
|
]
|
|
252
290
|
|
|
253
|
-
_version: ClassVar[str] = "1.0.
|
|
254
|
-
_updated_at: ClassVar[str] = "2026-07-
|
|
255
|
-
_change_note: ClassVar[Optional[str]] = "
|
|
291
|
+
_version: ClassVar[str] = "1.0.1"
|
|
292
|
+
_updated_at: ClassVar[str] = "2026-07-11"
|
|
293
|
+
_change_note: ClassVar[Optional[str]] = "Surface sandbox import whitelist (from DEFAULT_ALLOWED_IMPORTS) in help_text/node_guide + numpy/pandas anti-pattern."
|
|
256
294
|
|
|
257
295
|
def get_outputs(self) -> List[OutputPort]:
|
|
258
296
|
"""Build output ports from the per-instance `outputs` declaration.
|
|
@@ -296,7 +334,14 @@ class CodeNode(BaseNode):
|
|
|
296
334
|
ui_component=UIComponent.CUSTOM_CODE_EDITOR,
|
|
297
335
|
example="async def execute(data, params, context):\n return {'result': data}",
|
|
298
336
|
expected_type="str",
|
|
299
|
-
help_text=
|
|
337
|
+
help_text=(
|
|
338
|
+
"Define async def execute(data, params, context). Sandboxed: no "
|
|
339
|
+
"credentials/network/filesystem. Imports are whitelisted to pure-"
|
|
340
|
+
f"computation stdlib only (shipped default): {_ALLOWED_IMPORTS_CSV}. "
|
|
341
|
+
"Any other import — numpy, pandas, scipy, pandas-ta, TA-Lib, os, sys, "
|
|
342
|
+
"socket, subprocess, urllib, requests, open — is rejected with "
|
|
343
|
+
"CODE_NODE_FORBIDDEN; hand-roll such logic in pure Python."
|
|
344
|
+
),
|
|
300
345
|
),
|
|
301
346
|
"outputs": FieldSchema(
|
|
302
347
|
name="outputs",
|
{programgarden_core-1.16.0 → programgarden_core-1.18.0}/programgarden_core/nodes/data_futures.py
RENAMED
|
@@ -102,14 +102,11 @@ class OverseasFuturesMarketDataNode(BaseNode):
|
|
|
102
102
|
"nodes": [
|
|
103
103
|
{"id": "start", "type": "StartNode"},
|
|
104
104
|
{"id": "broker", "type": "OverseasFuturesBrokerNode", "credential_id": "broker_cred", "paper_trading": False},
|
|
105
|
-
{"id": "
|
|
106
|
-
{"id": "
|
|
107
|
-
{"id": "display", "type": "TableDisplayNode", "data": "{{ nodes.market.value }}"},
|
|
105
|
+
{"id": "market", "type": "OverseasFuturesMarketDataNode", "symbols": [{"symbol": "ESH26", "exchange": "CME"}]},
|
|
106
|
+
{"id": "display", "type": "TableDisplayNode", "data": "{{ nodes.market.values }}"},
|
|
108
107
|
],
|
|
109
108
|
"edges": [
|
|
110
109
|
{"from": "start", "to": "broker"},
|
|
111
|
-
{"from": "broker", "to": "split"},
|
|
112
|
-
{"from": "split", "to": "market"},
|
|
113
110
|
{"from": "broker", "to": "market"},
|
|
114
111
|
{"from": "market", "to": "display"},
|
|
115
112
|
],
|
|
@@ -124,7 +121,7 @@ class OverseasFuturesMarketDataNode(BaseNode):
|
|
|
124
121
|
}
|
|
125
122
|
],
|
|
126
123
|
},
|
|
127
|
-
"expected_output": "
|
|
124
|
+
"expected_output": "values port: array of {symbol, exchange, current_price, volume, open_interest, change_percent, bid, ask}.",
|
|
128
125
|
},
|
|
129
126
|
{
|
|
130
127
|
"title": "Multi-contract scan — compare HKEX futures prices",
|
|
@@ -135,14 +132,11 @@ class OverseasFuturesMarketDataNode(BaseNode):
|
|
|
135
132
|
"nodes": [
|
|
136
133
|
{"id": "start", "type": "StartNode"},
|
|
137
134
|
{"id": "broker", "type": "OverseasFuturesBrokerNode", "credential_id": "broker_cred", "paper_trading": False},
|
|
138
|
-
{"id": "
|
|
139
|
-
{"id": "
|
|
140
|
-
{"id": "display", "type": "TableDisplayNode", "data": "{{ nodes.market.value }}"},
|
|
135
|
+
{"id": "market", "type": "OverseasFuturesMarketDataNode", "symbols": [{"symbol": "MHIH26", "exchange": "HKEX"}, {"symbol": "MHIK26", "exchange": "HKEX"}]},
|
|
136
|
+
{"id": "display", "type": "TableDisplayNode", "data": "{{ nodes.market.values }}"},
|
|
141
137
|
],
|
|
142
138
|
"edges": [
|
|
143
139
|
{"from": "start", "to": "broker"},
|
|
144
|
-
{"from": "broker", "to": "split"},
|
|
145
|
-
{"from": "split", "to": "market"},
|
|
146
140
|
{"from": "broker", "to": "market"},
|
|
147
141
|
{"from": "market", "to": "display"},
|
|
148
142
|
],
|
|
@@ -157,23 +151,25 @@ class OverseasFuturesMarketDataNode(BaseNode):
|
|
|
157
151
|
}
|
|
158
152
|
],
|
|
159
153
|
},
|
|
160
|
-
"expected_output": "market.
|
|
154
|
+
"expected_output": "market.values — array with one {price, volume, ...} entry per HKEX mini-futures contract.",
|
|
161
155
|
},
|
|
162
156
|
]
|
|
163
157
|
_node_guide: ClassVar[Dict[str, Any]] = {
|
|
164
158
|
"input_handling": (
|
|
165
|
-
"The `
|
|
159
|
+
"The `symbols` field takes an array of {exchange, symbol} dicts — or bind a list source. "
|
|
166
160
|
"Exchange values: CME, EUREX, SGX, HKEX. Symbol includes contract month code (e.g., ESH26 for March 2026). "
|
|
167
161
|
"Broker connection auto-injected from OverseasFuturesBrokerNode."
|
|
168
162
|
),
|
|
169
163
|
"output_consumption": (
|
|
170
|
-
"
|
|
171
|
-
"
|
|
164
|
+
"Consume the `values` port ONLY — an array of {symbol, exchange, current_price, volume, open_interest, change_percent, bid, ask}. "
|
|
165
|
+
"⚠️ The `value` (singular) port is NOT populated at runtime — the executor emits `values` only; "
|
|
166
|
+
"binding `{{ nodes.market.value }}` (or `.value.current_price`) silently resolves to None. "
|
|
167
|
+
"TableDisplayNode.data ← `{{ nodes.market.values }}`; PositionSizingNode.market_data ← `{{ nodes.market.values }}`."
|
|
172
168
|
),
|
|
173
169
|
"common_combinations": [
|
|
174
|
-
"
|
|
175
|
-
"OverseasFuturesMarketDataNode.
|
|
176
|
-
"OverseasFuturesMarketDataNode.
|
|
170
|
+
"OverseasFuturesSymbolQueryNode → OverseasFuturesMarketDataNode.symbols (multi-contract fetch)",
|
|
171
|
+
"OverseasFuturesMarketDataNode.values → TableDisplayNode.data (futures monitor)",
|
|
172
|
+
"OverseasFuturesMarketDataNode.values → PositionSizingNode.market_data (with symbols + balance)",
|
|
177
173
|
],
|
|
178
174
|
"pitfalls": [
|
|
179
175
|
"Symbol must include contract month code (e.g., ESH26 not ES) — check OverseasFuturesSymbolQueryNode for valid codes",
|
|
@@ -188,6 +184,11 @@ class OverseasFuturesMarketDataNode(BaseNode):
|
|
|
188
184
|
]
|
|
189
185
|
_outputs: List[OutputPort] = [
|
|
190
186
|
OutputPort(name="value", type="market_data", description="i18n:ports.market_data_value", fields=PRICE_DATA_FIELDS),
|
|
187
|
+
OutputPort(
|
|
188
|
+
name="values",
|
|
189
|
+
type="array",
|
|
190
|
+
description="Array of per-contract market quotes — [{symbol, exchange, current_price, ...}, ...]",
|
|
191
|
+
),
|
|
191
192
|
]
|
|
192
193
|
|
|
193
194
|
_version: ClassVar[str] = "1.0.0"
|