programgarden-core 2.5.3__tar.gz → 2.6.1__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.
Files changed (87) hide show
  1. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/PKG-INFO +9 -1
  2. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/README.md +8 -0
  3. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/__init__.py +1 -0
  4. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/i18n/locales/en.json +8 -7
  5. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/i18n/locales/ko.json +8 -7
  6. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/market_calendar.py +11 -4
  7. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/__init__.py +2 -0
  8. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/backtest_stock.py +10 -46
  9. programgarden_core-2.6.1/programgarden_core/nodes/contract_info_futures.py +122 -0
  10. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/data.py +10 -1
  11. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/realtime_futures.py +9 -1
  12. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/session_gate.py +6 -6
  13. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/registry/node_registry.py +2 -2
  14. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/pyproject.toml +1 -1
  15. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/bases/__init__.py +0 -0
  16. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/bases/client.py +0 -0
  17. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/bases/components.py +0 -0
  18. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/bases/listener.py +0 -0
  19. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/bases/mixins.py +0 -0
  20. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/bases/products.py +0 -0
  21. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/bases/sql.py +0 -0
  22. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/bases/storage.py +0 -0
  23. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/code_node.py +0 -0
  24. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/exceptions.py +0 -0
  25. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/expression/__init__.py +0 -0
  26. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/expression/evaluator.py +0 -0
  27. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/expression/reference.py +0 -0
  28. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/i18n/__init__.py +0 -0
  29. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/i18n/translator.py +0 -0
  30. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/korea_alias.py +0 -0
  31. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/__init__.py +0 -0
  32. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/connection_rule.py +0 -0
  33. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/credential.py +0 -0
  34. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/edge.py +0 -0
  35. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/event.py +0 -0
  36. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/exchange.py +0 -0
  37. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/field_binding.py +0 -0
  38. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/job.py +0 -0
  39. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/order_diagnostics.py +0 -0
  40. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/plugin_resource.py +0 -0
  41. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/resilience.py +0 -0
  42. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/resource.py +0 -0
  43. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/validation.py +0 -0
  44. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/models/workflow.py +0 -0
  45. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/account_futures.py +0 -0
  46. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/account_korea_stock.py +0 -0
  47. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/account_stock.py +0 -0
  48. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/ai.py +0 -0
  49. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/backtest.py +0 -0
  50. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/backtest_futures.py +0 -0
  51. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/backtest_korea_stock.py +0 -0
  52. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/base.py +0 -0
  53. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/broker.py +0 -0
  54. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/calculation.py +0 -0
  55. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/code.py +0 -0
  56. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/condition.py +0 -0
  57. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/data_futures.py +0 -0
  58. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/data_korea_stock.py +0 -0
  59. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/data_stock.py +0 -0
  60. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/display.py +0 -0
  61. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/event.py +0 -0
  62. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/fundamental_korea_stock.py +0 -0
  63. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/fundamental_stock.py +0 -0
  64. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/infra.py +0 -0
  65. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/open_orders_futures.py +0 -0
  66. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/open_orders_korea_stock.py +0 -0
  67. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/open_orders_stock.py +0 -0
  68. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/order.py +0 -0
  69. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/orderable_quantity_futures.py +0 -0
  70. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/portfolio.py +0 -0
  71. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/realtime_korea_stock.py +0 -0
  72. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/realtime_stock.py +0 -0
  73. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/risk.py +0 -0
  74. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/symbol.py +0 -0
  75. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/symbol_futures.py +0 -0
  76. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/symbol_korea_stock.py +0 -0
  77. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/symbol_stock.py +0 -0
  78. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/nodes/trigger.py +0 -0
  79. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/presets/__init__.py +0 -0
  80. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/presets/news_analyst.json +0 -0
  81. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/presets/risk_manager.json +0 -0
  82. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/presets/strategist.json +0 -0
  83. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/presets/technical_analyst.json +0 -0
  84. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/registry/__init__.py +0 -0
  85. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/registry/credential_registry.py +0 -0
  86. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/registry/plugin_registry.py +0 -0
  87. {programgarden_core-2.5.3 → programgarden_core-2.6.1}/programgarden_core/retry_executor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programgarden-core
3
- Version: 2.5.3
3
+ Version: 2.6.1
4
4
  Summary: ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Author: 프로그램동산
@@ -19,6 +19,14 @@ US regular-session requests can opt into SessionGateNode.exchange_calendar (XNYS
19
19
 
20
20
  # ProgramGarden Core
21
21
 
22
+ An unreleased [stock history interval correction](../../docs/stock-history-interval-contract.md)
23
+ limits the overseas-stock catalog to its implemented g3204 periods and rejects
24
+ unsupported intraday requests before execution. Package adoption is pending.
25
+
26
+ Unreleased schema additions: OverseasFuturesContractInfoNode, SQLiteNode's opt-in
27
+ storage_scope=execution, and SessionGateNode's XHKG calendar. See the
28
+ [runtime contract](../../docs/futures-contract-details-and-rollover-state.md).
29
+
22
30
  The REST overseas-stock position catalog declares `sellable_qty`, matching the
23
31
  broker-reported capacity preserved by the engine. Missing evidence remains null;
24
32
  see [the contract](../../docs/overseas-stock-sellable-quantity.md).
@@ -2,6 +2,14 @@ US regular-session requests can opt into SessionGateNode.exchange_calendar (XNYS
2
2
 
3
3
  # ProgramGarden Core
4
4
 
5
+ An unreleased [stock history interval correction](../../docs/stock-history-interval-contract.md)
6
+ limits the overseas-stock catalog to its implemented g3204 periods and rejects
7
+ unsupported intraday requests before execution. Package adoption is pending.
8
+
9
+ Unreleased schema additions: OverseasFuturesContractInfoNode, SQLiteNode's opt-in
10
+ storage_scope=execution, and SessionGateNode's XHKG calendar. See the
11
+ [runtime contract](../../docs/futures-contract-details-and-rollover-state.md).
12
+
5
13
  The REST overseas-stock position catalog declares `sellable_qty`, matching the
6
14
  broker-reported capacity preserved by the engine. Missing evidence remains null;
7
15
  see [the contract](../../docs/overseas-stock-sellable-quantity.md).
@@ -57,6 +57,7 @@ __all__ = [
57
57
  "OverseasFuturesRealMarketDataNode",
58
58
  "OverseasFuturesSymbolQueryNode",
59
59
  "FuturesContractNode",
60
+ "OverseasFuturesContractInfoNode",
60
61
  # Nodes - Market (국내주식)
61
62
  "KoreaStockMarketDataNode",
62
63
  "KoreaStockHistoricalDataNode",
@@ -140,7 +140,7 @@
140
140
  "nodes.OverseasStockBrokerNode.name": "Stock Broker",
141
141
  "nodes.OverseasStockCancelOrderNode.description": "Cancels a pending US stock order by its original order ID. Requires original_order_id from OverseasStockOpenOrdersNode or OverseasStockRealOrderEventNode. Outputs cancel result and cancelled order ID. Rate-limited to 5-second minimum interval. Typical connection: StockOpenOrdersNode → StockCancelOrderNode, or StockRealOrderEventNode → StockCancelOrderNode.",
142
142
  "nodes.OverseasStockCancelOrderNode.name": "Stock Cancel Order",
143
- "nodes.OverseasStockHistoricalDataNode.description": "Fetches historical OHLCV (Open, High, Low, Close, Volume) chart data for overseas stocks over a specified date range. Supports bar intervals: 1-minute, 5-minute, 15-minute, 1-hour, daily, weekly, and monthly. Enable 'adjust' for split/dividend-adjusted prices (recommended for backtesting). Auto-iterates when previous node outputs multiple symbols. Requires an upstream OverseasStockBrokerNode. Typical connection: WatchlistNode → StockHistoricalDataNode → ConditionNode or → BacktestEngineNode. Supported exchanges: NYSE, NASDAQ, AMEX. Can be used as an AI Agent tool.",
143
+ "nodes.OverseasStockHistoricalDataNode.description": "Fetches historical OHLCV (Open, High, Low, Close, Volume) chart data for overseas stocks over a specified date range. Uses LS g3204 for daily, weekly, monthly and yearly bars. Intraday g3203 is unsupported. Enable 'adjust' for split/dividend-adjusted prices (recommended for backtesting). Auto-iterates when previous node outputs multiple symbols. Requires an upstream OverseasStockBrokerNode. Typical connection: WatchlistNode → StockHistoricalDataNode → ConditionNode or → BacktestEngineNode. Supported exchanges: NYSE, NASDAQ, AMEX. Can be used as an AI Agent tool.",
144
144
  "nodes.OverseasStockHistoricalDataNode.name": "Stock Historical Data",
145
145
  "nodes.OverseasStockFundamentalNode.description": "Fetches fundamental financial data for overseas stocks via LS Securities API. Provides 16 metrics including PER, EPS, PBR, market capitalization, dividend yield, 52-week high/low, and more. Auto-iterates when previous node outputs multiple symbols. Requires an upstream OverseasStockBrokerNode. Typical connection: WatchlistNode → StockFundamentalNode → ConditionNode (for value-based screening). Supported exchanges: NYSE, NASDAQ, AMEX. Can be used as an AI Agent tool.",
146
146
  "nodes.OverseasStockFundamentalNode.name": "Stock Fundamentals",
@@ -296,7 +296,6 @@
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
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.",
298
298
  "fields.HTTPRequestNode.body": "Request body for POST/PUT/PATCH. Automatically serialized to JSON.",
299
-
300
299
  "fields.HTTPRequestNode.credential_id": "Credential ID for authentication. Supports: http_bearer, http_header, http_basic, http_query",
301
300
  "fields.HTTPRequestNode.headers": "HTTP headers. Click + to add custom headers (e.g., Content-Type, X-API-Key).",
302
301
  "fields.HTTPRequestNode.method": "HTTP method for the request",
@@ -421,7 +420,7 @@
421
420
  "fields.OverseasStockHistoricalDataNode.adjust.short": "Apply adjusted price",
422
421
  "fields.OverseasStockHistoricalDataNode.end_date": "End date (YYYYMMDD)",
423
422
  "fields.OverseasStockHistoricalDataNode.end_date.help_text": "e.g., 20241231. With binding: {{ date.today(format='yyyymmdd') }}",
424
- "fields.OverseasStockHistoricalDataNode.interval": "Bar interval (1m, 5m, 15m, 1h, 1d, 1w, 1M)",
423
+ "fields.OverseasStockHistoricalDataNode.interval": "LS g3204 interval (1d, 1w, 1M, 1Y); intraday is unsupported",
425
424
  "fields.OverseasStockHistoricalDataNode.start_date": "Start date (YYYYMMDD)",
426
425
  "fields.OverseasStockHistoricalDataNode.start_date.help_text": "e.g., 20240101. With binding: {{ date.ago(30, format='yyyymmdd') }}",
427
426
  "fields.OverseasStockHistoricalDataNode.symbol": "Enter the symbol code to query",
@@ -602,7 +601,6 @@
602
601
  "fieldNames.PerformanceReportNode.risk_free_rate": "Risk-Free Rate",
603
602
  "fieldNames.HTTPRequestNode.auth_required": "Requires Auth",
604
603
  "fieldNames.HTTPRequestNode.body": "Request Body",
605
-
606
604
  "fieldNames.HTTPRequestNode.credential_id": "Credential",
607
605
  "fieldNames.HTTPRequestNode.headers": "Headers",
608
606
  "fieldNames.HTTPRequestNode.method": "HTTP Method",
@@ -877,7 +875,6 @@
877
875
  "ports.http_request_data": "Request data",
878
876
  "ports.http_response": "API response data",
879
877
  "ports.http_results": "Per-item results (one {item, response, status_code, success, error} entry per iterated item)",
880
-
881
878
  "ports.http_status_code": "HTTP status code",
882
879
  "ports.http_success": "Request success flag",
883
880
  "ports.items": "Item list",
@@ -1035,6 +1032,7 @@
1035
1032
  "enums.kr_price_type.market": "Market",
1036
1033
  "enums.interval.15m": "15 Minutes",
1037
1034
  "enums.interval.1M": "1 Month",
1035
+ "enums.interval.1Y": "1 year",
1038
1036
  "enums.interval.1d": "1 Day",
1039
1037
  "enums.interval.1h": "1 Hour",
1040
1038
  "enums.interval.1m": "1 Minute",
@@ -1207,7 +1205,6 @@
1207
1205
  "connection.OverseasStockBrokerNode.label": "LS Securities overseas stock account",
1208
1206
  "connection.TelegramNode.label": "Telegram bot",
1209
1207
  "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
-
1211
1208
  "connection_rules.realtime_to_http.suggestion": "Place a ThrottleNode between the realtime node and HTTP request node to control request frequency.",
1212
1209
  "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.",
1213
1210
  "fields.BaseOrderNode.rate_limit_action": "Action when order interval is not met. skip: silently skip, error: raise error",
@@ -1251,6 +1248,9 @@
1251
1248
  "enums.contract_selection.front": "Front month (nearest expiry)",
1252
1249
  "enums.contract_selection.next": "Next month (roll target)",
1253
1250
  "enums.contract_selection.quarterly": "Quarterly (Mar/Jun/Sep/Dec)",
1251
+ "nodes.OverseasFuturesContractInfoNode.name": "Futures Contract Details",
1252
+ "nodes.OverseasFuturesContractInfoNode.description": "Reads observed contract dates, margins and reported session times from the connected broker. Missing values remain unknown; no order is placed.",
1253
+ "fieldNames.OverseasFuturesContractInfoNode.symbol": "Futures contract",
1254
1254
  "nodes.OverseasFuturesOrderableQuantityNode.name": "Futures Orderable Quantity",
1255
1255
  "nodes.OverseasFuturesOrderableQuantityNode.description": "Reads contract-specific orderable quantity from the connected futures account. No order is submitted. Require verified=true and sufficient quantity before entry.",
1256
1256
  "fieldNames.OverseasFuturesOrderableQuantityNode.symbol": "Contract",
@@ -1278,5 +1278,6 @@
1278
1278
  "nodes.SessionGateNode.session_date": "Opening date of the matched window, or an empty string when blocked.",
1279
1279
  "nodes.SessionGateNode.timezone": "IANA timezone; daylight-saving transitions use the installed timezone database.",
1280
1280
  "nodes.SessionGateNode.trigger": "Evaluate the current local time when triggered.",
1281
- "nodes.SessionGateNode.windows": "Local HH:MM start/end windows; start inclusive, end exclusive. Overnight windows are supported."
1281
+ "nodes.SessionGateNode.windows": "Local HH:MM start/end windows; start inclusive, end exclusive. Overnight windows are supported.",
1282
+ "fields.SQLiteNode.storage_scope": "Storage scope: shared filename or a durable namespace per execution."
1282
1283
  }
@@ -140,7 +140,7 @@
140
140
  "nodes.OverseasStockBrokerNode.name": "해외주식 브로커",
141
141
  "nodes.OverseasStockCancelOrderNode.description": "미국 주식 미체결 주문을 원래 주문 ID로 취소합니다. OverseasStockOpenOrdersNode 또는 OverseasStockRealOrderEventNode에서 original_order_id를 받아야 합니다. 출력: 취소 결과, 취소된 주문 ID. 최소 5초 간격 제한 적용. 일반적인 연결: StockOpenOrdersNode → StockCancelOrderNode 또는 StockRealOrderEventNode → StockCancelOrderNode.",
142
142
  "nodes.OverseasStockCancelOrderNode.name": "해외주식 취소주문",
143
- "nodes.OverseasStockHistoricalDataNode.description": "지정 기간의 해외주식 과거 OHLCV(시가, 고가, 저가, 종가, 거래량) 차트 데이터를 조회합니다. 봉 종류: 1분, 5분, 15분, 1시간, 일봉, 주봉, 월봉. 수정주가(주식 분할/배당 반영) 활성화 권장(백테스트용). 이전 노드가 여러 종목을 출력하면 자동 반복 실행. 상위 OverseasStockBrokerNode 연결 필수. 일반적인 연결: WatchlistNode → StockHistoricalDataNode → ConditionNode 또는 → BacktestEngineNode. 지원 거래소: NYSE, NASDAQ, AMEX. AI Agent 도구로 사용 가능.",
143
+ "nodes.OverseasStockHistoricalDataNode.description": "지정 기간의 해외주식 과거 OHLCV(시가, 고가, 저가, 종가, 거래량) 차트 데이터를 조회합니다. LS g3204로 일봉·주봉·월봉·연봉을 조회합니다. 분봉·시간봉(g3203)은 지원하지 않습니다. 수정주가(주식 분할/배당 반영) 활성화 권장(백테스트용). 이전 노드가 여러 종목을 출력하면 자동 반복 실행. 상위 OverseasStockBrokerNode 연결 필수. 일반적인 연결: WatchlistNode → StockHistoricalDataNode → ConditionNode 또는 → BacktestEngineNode. 지원 거래소: NYSE, NASDAQ, AMEX. AI Agent 도구로 사용 가능.",
144
144
  "nodes.OverseasStockHistoricalDataNode.name": "해외주식 과거데이터",
145
145
  "nodes.OverseasStockFundamentalNode.description": "LS증권 API로 해외주식 펀더멘털 재무 데이터를 조회합니다. PER, EPS, PBR, 시가총액, 배당수익률, 52주 고/저가 등 16개 지표를 제공합니다. 이전 노드가 여러 종목을 출력하면 자동 반복 실행. 상위 OverseasStockBrokerNode 연결 필수. 일반적인 연결: WatchlistNode → StockFundamentalNode → ConditionNode (가치 기반 스크리닝). 지원 거래소: NYSE, NASDAQ, AMEX. AI Agent 도구로 사용 가능.",
146
146
  "nodes.OverseasStockFundamentalNode.name": "해외주식 종목정보",
@@ -296,7 +296,6 @@
296
296
  "fields.PerformanceReportNode.risk_free_rate": "무위험 수익률(소수, 예 0.02=2%) — Sharpe/Sortino용. 기본 0.",
297
297
  "fields.HTTPRequestNode.auth_required": "이 API 가 인증이 필요한가요? 미설정이면 챗봇이 문맥으로 판단합니다. 비공개 API 는 true(자격증명 필요), 공개 API 는 false 로 두세요.",
298
298
  "fields.HTTPRequestNode.body": "요청 본문 (POST/PUT/PATCH용). 자동으로 JSON 직렬화됨.",
299
-
300
299
  "fields.HTTPRequestNode.credential_id": "인증정보 ID. 지원: http_bearer, http_header, http_basic, http_query",
301
300
  "fields.HTTPRequestNode.headers": "HTTP 헤더. + 버튼으로 커스텀 헤더 추가 (예: Content-Type, X-API-Key).",
302
301
  "fields.HTTPRequestNode.method": "HTTP 요청 메서드",
@@ -421,7 +420,7 @@
421
420
  "fields.OverseasStockHistoricalDataNode.adjust.short": "수정주가 적용 여부",
422
421
  "fields.OverseasStockHistoricalDataNode.end_date": "조회 종료일 (YYYYMMDD)",
423
422
  "fields.OverseasStockHistoricalDataNode.end_date.help_text": "예: 20241231. 바인딩 시 {{ date.today(format='yyyymmdd') }} 사용 가능",
424
- "fields.OverseasStockHistoricalDataNode.interval": "봉 종류 (1m, 5m, 15m, 1h, 1d, 1w, 1M)",
423
+ "fields.OverseasStockHistoricalDataNode.interval": "LS g3204 봉 종류 (1d, 1w, 1M, 1Y). 분봉·시간봉 미지원",
425
424
  "fields.OverseasStockHistoricalDataNode.start_date": "조회 시작일 (YYYYMMDD)",
426
425
  "fields.OverseasStockHistoricalDataNode.start_date.help_text": "예: 20240101. 바인딩 시 {{ date.ago(30, format='yyyymmdd') }} 사용 가능",
427
426
  "fields.OverseasStockHistoricalDataNode.symbol": "조회할 종목 코드를 입력하세요",
@@ -602,7 +601,6 @@
602
601
  "fieldNames.PerformanceReportNode.risk_free_rate": "무위험 수익률",
603
602
  "fieldNames.HTTPRequestNode.auth_required": "인증 필요 여부",
604
603
  "fieldNames.HTTPRequestNode.body": "요청본문",
605
-
606
604
  "fieldNames.HTTPRequestNode.credential_id": "인증정보",
607
605
  "fieldNames.HTTPRequestNode.headers": "헤더",
608
606
  "fieldNames.HTTPRequestNode.method": "HTTP메소드",
@@ -877,7 +875,6 @@
877
875
  "ports.http_request_data": "요청 데이터",
878
876
  "ports.http_response": "API 응답 데이터",
879
877
  "ports.http_results": "항목별 결과 (반복된 항목마다 {item, response, status_code, success, error} 한 건)",
880
-
881
878
  "ports.http_status_code": "HTTP 상태 코드",
882
879
  "ports.http_success": "요청 성공 여부",
883
880
  "ports.items": "아이템 목록",
@@ -1035,6 +1032,7 @@
1035
1032
  "enums.kr_price_type.market": "시장가 (Market)",
1036
1033
  "enums.interval.15m": "15분",
1037
1034
  "enums.interval.1M": "1개월",
1035
+ "enums.interval.1Y": "1년",
1038
1036
  "enums.interval.1d": "1일",
1039
1037
  "enums.interval.1h": "1시간",
1040
1038
  "enums.interval.1m": "1분",
@@ -1207,7 +1205,6 @@
1207
1205
  "connection.OverseasStockBrokerNode.label": "LS증권 해외주식 계좌",
1208
1206
  "connection.TelegramNode.label": "텔레그램 봇",
1209
1207
  "connection_rules.realtime_to_http.reason": "실시간 노드에서 HTTPRequestNode로의 직접 연결은 권장하지 않습니다. 틱 데이터마다 HTTP 요청이 발생하여 외부 API 제한에 걸릴 수 있습니다.",
1210
-
1211
1208
  "connection_rules.realtime_to_http.suggestion": "실시간 노드와 HTTP 요청 노드 사이에 ThrottleNode를 배치하여 요청 빈도를 제어하세요.",
1212
1209
  "fields.BaseOrderNode.rate_limit_interval": "최소 주문 간격 (초). 기본값 5초. 직전 주문 후 이 시간이 지나야 다음 주문이 실행됩니다.",
1213
1210
  "fields.BaseOrderNode.rate_limit_action": "주문 간격 미달 시 동작. skip: 조용히 건너뜀, error: 에러 발생",
@@ -1251,6 +1248,9 @@
1251
1248
  "enums.contract_selection.front": "근월물 (만기 가장 가까움)",
1252
1249
  "enums.contract_selection.next": "차월물 (롤오버 대상)",
1253
1250
  "enums.contract_selection.quarterly": "분기월물 (3·6·9·12월)",
1251
+ "nodes.OverseasFuturesContractInfoNode.name": "해외선물 종목상세 조회",
1252
+ "nodes.OverseasFuturesContractInfoNode.description": "연결된 증권사를 통해 선물 종목의 만기일·최종거래일·증거금·거래시간을 조회합니다. 확인되지 않은 값은 비워 두며 주문을 실행하지 않습니다.",
1253
+ "fieldNames.OverseasFuturesContractInfoNode.symbol": "선물 종목",
1254
1254
  "nodes.OverseasFuturesOrderableQuantityNode.name": "해외선물 주문가능수량",
1255
1255
  "nodes.OverseasFuturesOrderableQuantityNode.description": "연결된 해외선물 계좌에서 해당 종목의 주문가능수량을 조회합니다. 주문은 제출하지 않으며, 확인 성공 여부와 가능 수량을 확인한 뒤 진입합니다.",
1256
1256
  "fieldNames.OverseasFuturesOrderableQuantityNode.symbol": "선물 종목",
@@ -1278,5 +1278,6 @@
1278
1278
  "nodes.SessionGateNode.session_date": "허용 구간의 시작일입니다. 차단되면 빈 문자열입니다.",
1279
1279
  "nodes.SessionGateNode.timezone": "IANA 시간대입니다. 설치된 시간대 데이터로 서머타임을 반영합니다.",
1280
1280
  "nodes.SessionGateNode.trigger": "실행 시점의 현지 시간을 확인합니다.",
1281
- "nodes.SessionGateNode.windows": "현지 HH:MM 구간입니다. 시작은 포함하고 종료는 제외하며 자정을 넘는 구간도 지원합니다."
1281
+ "nodes.SessionGateNode.windows": "현지 HH:MM 구간입니다. 시작은 포함하고 종료는 제외하며 자정을 넘는 구간도 지원합니다.",
1282
+ "fields.SQLiteNode.storage_scope": "저장 범위: 공용 파일 또는 실행별로 분리한 영구 저장 파일"
1282
1283
  }
@@ -1,4 +1,4 @@
1
- """Scheduled US equity sessions, independent of credentials and live market data.
1
+ """Scheduled equity sessions, independent of credentials and live market data.
2
2
 
3
3
  This schedule includes published holidays and early closes. It cannot observe
4
4
  new emergency closures, instrument halts, or a broker's ability to accept orders.
@@ -19,15 +19,22 @@ def _calendar(name: str, year: int):
19
19
 
20
20
  def scheduled_session_open(name: str, instant: datetime) -> bool:
21
21
  """Exact seconds: opening is inclusive and closing is exclusive."""
22
- if name not in {"XNYS", "XNAS"}:
22
+ if name not in {"XNYS", "XNAS", "XHKG"}:
23
23
  raise ValueError("Unsupported exchange calendar")
24
24
  if instant.tzinfo is None or instant.utcoffset() is None:
25
25
  raise ValueError("Calendar clock must include timezone")
26
- market_date = instant.astimezone(ZoneInfo("America/New_York")).date()
26
+ zone = "Asia/Hong_Kong" if name == "XHKG" else "America/New_York"
27
+ market_date = instant.astimezone(ZoneInfo(zone)).date()
27
28
  schedule = _calendar(name, market_date.year).schedule
28
29
  day = market_date.isoformat()
29
30
  if day not in schedule.index:
30
31
  return False
31
32
  row = schedule.loc[day]
32
33
  clock = instant.astimezone(timezone.utc)
33
- return bool(row["open"] <= clock < row["close"])
34
+ if not row["open"] <= clock < row["close"]:
35
+ return False
36
+ # XHKG has a scheduled midday break. NaT comparisons are false for markets
37
+ # without breaks; do not admit the lunch interval as an open session.
38
+ if "break_start" in row and row["break_start"] <= clock < row["break_end"]:
39
+ return False
40
+ return True
@@ -28,6 +28,7 @@ from programgarden_core.nodes.broker import OverseasStockBrokerNode, OverseasFut
28
28
  from programgarden_core.nodes.data_stock import OverseasStockMarketDataNode
29
29
  from programgarden_core.nodes.fundamental_stock import OverseasStockFundamentalNode
30
30
  from programgarden_core.nodes.data_futures import OverseasFuturesMarketDataNode
31
+ from programgarden_core.nodes.contract_info_futures import OverseasFuturesContractInfoNode
31
32
  from programgarden_core.nodes.backtest_stock import OverseasStockHistoricalDataNode
32
33
  from programgarden_core.nodes.backtest_futures import OverseasFuturesHistoricalDataNode
33
34
  from programgarden_core.nodes.realtime_stock import (
@@ -131,6 +132,7 @@ __all__ = [
131
132
  "OverseasFuturesRealMarketDataNode",
132
133
  "OverseasFuturesSymbolQueryNode",
133
134
  "FuturesContractNode",
135
+ "OverseasFuturesContractInfoNode",
134
136
  # Account - Stock (해외주식)
135
137
  "OverseasStockAccountNode",
136
138
  "OverseasStockRealAccountNode",
@@ -65,9 +65,9 @@ class OverseasStockHistoricalDataNode(BaseNode):
65
65
  default="{{ date.today(format='yyyymmdd') }}",
66
66
  description="End date (YYYY-MM-DD or {{ date.today(format='yyyymmdd') }})",
67
67
  )
68
- interval: str = Field(
68
+ interval: Literal["1d", "1w", "1M", "1Y"] = Field(
69
69
  default="1d",
70
- description="Data interval (1m, 5m, 15m, 1h, 1d)",
70
+ description="LS g3204 interval: daily, weekly, monthly or yearly. Intraday is unsupported.",
71
71
  )
72
72
  adjust: bool = Field(
73
73
  default=False,
@@ -80,7 +80,7 @@ class OverseasStockHistoricalDataNode(BaseNode):
80
80
 
81
81
  _usage: ClassVar[Dict[str, Any]] = {
82
82
  "when_to_use": [
83
- "Retrieve daily (or intraday) OHLCV history for a US-listed stock to compute technical indicators or run backtests",
83
+ "Retrieve daily, weekly, monthly or yearly OHLCV history for a US-listed stock to compute technical indicators or run backtests",
84
84
  "Feed time-series data into ConditionNode plugins (RSI, MACD, Bollinger, etc.) that require a `data` list of candles",
85
85
  "Use `{{ date.ago(N, format='yyyymmdd') }}` for start_date to get a rolling lookback window without hardcoding dates",
86
86
  ],
@@ -97,7 +97,7 @@ class OverseasStockHistoricalDataNode(BaseNode):
97
97
  }
98
98
  _features: ClassVar[List[str]] = [
99
99
  "Returns a list of OHLCV candles in `value.time_series`: [{date, open, high, low, close, volume}, ...]",
100
- "Supports intervals: 1m, 5m, 15m, 1h, 1d, 1w, 1M — daily (1d) is the default for swing trading strategies",
100
+ "Supports intervals: 1d, 1w, 1M, 1Y through LS g3204; intraday history is unsupported",
101
101
  "start_date / end_date accept YYYYMMDD strings or expression namespace calls: `{{ date.ago(30, format='yyyymmdd') }}`",
102
102
  "adjust=True applies split/dividend-adjusted prices for accurate long-term backtesting",
103
103
  "is_tool_enabled=True — AI Agent can fetch historical data autonomously for technical analysis",
@@ -156,45 +156,12 @@ class OverseasStockHistoricalDataNode(BaseNode):
156
156
  },
157
157
  "expected_output": "value port: {symbol, exchange, time_series: [{date, open, high, low, close, volume}, ...]}. RSI is computed from time_series.",
158
158
  },
159
- {
160
- "title": "Intraday 5-minute data for MACD",
161
- "description": "Fetch 5-minute bars over the past 5 days for MSFT and compute MACD crossover signal.",
162
- "workflow_snippet": {
163
- "id": "overseas_stock_historical_macd_5m",
164
- "name": "Intraday MACD",
165
- "nodes": [
166
- {"id": "start", "type": "StartNode"},
167
- {"id": "broker", "type": "OverseasStockBrokerNode", "credential_id": "broker_cred", "paper_trading": False},
168
- {'id': 'split', 'type': 'SplitNode', 'array': [{'symbol': 'MSFT', 'exchange': 'NASDAQ'}]},
169
- {"id": "historical", "type": "OverseasStockHistoricalDataNode", "symbol": "{{ nodes.split.item }}", "start_date": "{{ date.ago(5, format='yyyymmdd') }}", "end_date": "{{ date.today(format='yyyymmdd') }}", "interval": "5m", "adjust": False},
170
- {"id": "condition", "type": "ConditionNode", "plugin": "MACD", "items": {"from": "{{ item.time_series }}", "extract": {"symbol": "{{ item.symbol }}", "exchange": "{{ item.exchange }}", "date": "{{ row.date }}", "close": "{{ row.close }}"}}, "fields": {"fast": 12, "slow": 26, "signal": 9, "direction": "bullish_cross"}},
171
- {'id': 'split_results', 'type': 'AggregateNode', 'mode': 'collect'}],
172
- "edges": [
173
- {"from": "start", "to": "broker"},
174
- {"from": "broker", "to": "split"},
175
- {"from": "split", "to": "historical"},
176
- {"from": "broker", "to": "historical"},
177
- {"from": "historical", "to": "condition"},
178
- {'from': 'condition', 'to': 'split_results'}],
179
- "credentials": [
180
- {
181
- "credential_id": "broker_cred",
182
- "type": "broker_ls_overseas_stock",
183
- "data": [
184
- {"key": "appkey", "value": "", "type": "password", "label": "App Key"},
185
- {"key": "appsecret", "value": "", "type": "password", "label": "App Secret"},
186
- ],
187
- }
188
- ],
189
- },
190
- "expected_output": "value.time_series contains 5-minute OHLCV bars; MACD ConditionNode emits signal/histogram on the result port.",
191
- },
192
159
  ]
193
160
  _node_guide: ClassVar[Dict[str, Any]] = {
194
161
  "input_handling": (
195
162
  "The `symbol` field takes a single {exchange, symbol} dict — bind to `{{ nodes.split.item }}` in a per-symbol loop. "
196
163
  "start_date / end_date accept 'YYYYMMDD' strings or `{{ date.ago(N, format='yyyymmdd') }}` expressions. "
197
- "interval options: 1m, 5m, 15m, 1h, 1d, 1w, 1M. Use adjust=True for long-term backtesting to remove split/dividend distortions."
164
+ "Supported intervals: 1d, 1w, 1M, 1Y through LS g3204. Intraday g3203 is not connected to this node."
198
165
  ),
199
166
  "output_consumption": (
200
167
  "The `value` port emits a dict: {symbol, exchange, time_series: [{date, open, high, low, close, volume}, ...]}. "
@@ -210,9 +177,9 @@ class OverseasStockHistoricalDataNode(BaseNode):
210
177
  "pitfalls": [
211
178
  "`symbol: {{ item }}` / `{{ nodes.<split>.item }}` resolves only inside a per-symbol loop (auto-iterate over an upstream array, or a SplitNode branch). If the upstream array is empty for this run the node logs `No symbols to fetch` and returns an empty series — normal no-signal, not a defect. A `No symbols provided` WARNING means the symbol source is missing or the binding did not resolve",
212
179
  "Access time_series via `{{ nodes.historical.value.time_series }}` — the value port is a dict, not a plain list",
213
- "Intraday intervals (1m/5m/15m/1h) may have limited history depth depending on LS Securities API availability",
180
+ "Intraday intervals (1m/5m/15m/1h) are unsupported; never substitute daily bars or invent a g3203 node connection",
214
181
  "adjust=False (default) returns unadjusted prices; set True when comparing long-term price levels across splits",
215
- "Large date ranges with 1m interval can return thousands of rows — set reasonable start_date to avoid memory pressure",
182
+ "The current executor requests one g3204 page; it does not expose response-time, continuation-completion or candle-finality evidence",
216
183
  ],
217
184
  }
218
185
 
@@ -333,15 +300,12 @@ class OverseasStockHistoricalDataNode(BaseNode):
333
300
  description="i18n:fields.OverseasStockHistoricalDataNode.interval",
334
301
  default="1d",
335
302
  required=True,
336
- enum_values=["1m", "5m", "15m", "1h", "1d", "1w", "1M"],
303
+ enum_values=["1d", "1w", "1M", "1Y"],
337
304
  enum_labels={
338
- "1m": "i18n:enums.interval.1m",
339
- "5m": "i18n:enums.interval.5m",
340
- "15m": "i18n:enums.interval.15m",
341
- "1h": "i18n:enums.interval.1h",
342
305
  "1d": "i18n:enums.interval.1d",
343
306
  "1w": "i18n:enums.interval.1w",
344
- "1M": "i18n:enums.interval.1M"
307
+ "1M": "i18n:enums.interval.1M",
308
+ "1Y": "i18n:enums.interval.1Y"
345
309
  },
346
310
  category=FieldCategory.PARAMETERS,
347
311
  expression_mode=ExpressionMode.FIXED_ONLY,
@@ -0,0 +1,122 @@
1
+ """Runtime contract metadata for an exact listed overseas futures symbol."""
2
+ from typing import ClassVar, Dict, List, Literal, Optional
3
+
4
+ from pydantic import Field
5
+
6
+ from programgarden_core.nodes.base import BaseNode, BrokerProvider, InputPort, NodeCategory, OutputPort, ProductScope
7
+ from programgarden_core.models.field_binding import ExpressionMode, FieldCategory, FieldSchema, FieldType
8
+
9
+
10
+ CONTRACT_INFO_FIELDS = [
11
+ {"name": key, "type": "string", "description": description}
12
+ for key, description in {
13
+ "symbol": "Exact observed contract symbol.",
14
+ "exchange": "Observed exchange code matching the request.",
15
+ "symbol_name": "Observed contract name; null if absent.",
16
+ "underlying_code": "Observed underlying product code; null if absent.",
17
+ "currency": "Observed contract currency; null if absent.",
18
+ "maturity_date": "LS MtrtDt, YYYY-MM-DD; null if missing or invalid.",
19
+ "last_trading_date": "LS FnlDlDt, YYYY-MM-DD; distinct from maturity; null if unknown.",
20
+ "first_notice_date": "LS FstTrsfrDt, YYYY-MM-DD; null if unknown or not supplied.",
21
+ "master_received_date": "LS ApplDate, YYYY-MM-DD; not proof of future calendar coverage.",
22
+ "korea_start_date": "LS DlDt date, YYYY-MM-DD.",
23
+ "korea_start_time": "LS DlStrtTm time, HH:MM:SS; a reported session, not all trading windows.",
24
+ "korea_end_time": "LS DlEndTm time, HH:MM:SS; no end date is inferred.",
25
+ "local_start_date": "LS OvsStrDay date, YYYY-MM-DD.",
26
+ "local_start_time": "LS OvsStrTm time, HH:MM:SS.",
27
+ "local_end_date": "LS OvsEndDay date, YYYY-MM-DD.",
28
+ "local_end_time": "LS OvsEndTm time, HH:MM:SS.",
29
+ "trade_status_code": "Raw LS DlPsblCd; no undocumented enum interpretation.",
30
+ "source_tr": "o3105.",
31
+ "observed_at": "UTC time of this observation, ISO8601; not a broker data timestamp.",
32
+ }.items()
33
+ ] + [
34
+ {"name": key, "type": "number", "description": description}
35
+ for key, description in {
36
+ "initial_margin": "Observed OpngMgn; null if absent/invalid. Not account order capacity.",
37
+ "maintenance_margin": "Observed MntncMgn; null if absent/invalid.",
38
+ "tick_size": "Observed positive UntPrc; null if unavailable.",
39
+ "tick_value": "Observed positive MnChgAmt; null if unavailable.",
40
+ }.items()
41
+ ]
42
+
43
+
44
+ class OverseasFuturesContractInfoNode(BaseNode):
45
+ type: Literal["OverseasFuturesContractInfoNode"] = "OverseasFuturesContractInfoNode"
46
+ category: NodeCategory = NodeCategory.MARKET
47
+ description: str = "i18n:nodes.OverseasFuturesContractInfoNode.description"
48
+ _product_scope: ClassVar[ProductScope] = ProductScope.FUTURES
49
+ _broker_provider: ClassVar[BrokerProvider] = BrokerProvider.LS
50
+ _version: ClassVar[str] = "1.0.0"
51
+ _updated_at: ClassVar[str] = "2026-10-03"
52
+ _change_note: ClassVar[str] = "Read native futures contract dates, margins and reported sessions without placing an order."
53
+
54
+ symbol: Optional[Dict[str, str]] = Field(default=None, description="Exact listed contract identity: symbol and exchange.")
55
+ _inputs: List[InputPort] = [InputPort(name="trigger", type="signal", required=False, description="Refresh after resolving the contract.")]
56
+ _outputs: List[OutputPort] = [
57
+ OutputPort(name="value", type="object", fields=CONTRACT_INFO_FIELDS, description="Observed contract details; null when the query fails. Optional fields remain null."),
58
+ OutputPort(name="verified", type="boolean", description="Successful response with matching symbol/exchange; not completeness of all optional fields."),
59
+ OutputPort(name="missing_fields", type="array", description="Optional output fields absent or invalid in the broker response."),
60
+ OutputPort(name="error", type="string", description="Safe failure reason; null for a matching successful response."),
61
+ ]
62
+ _usage: ClassVar[dict] = {
63
+ "when_to_use": ["Inspect runtime maturity/last-trading/notice dates, margins, currency and reported session times for an exact futures contract."],
64
+ "when_not_to_use": ["Selecting front/next months (FuturesContractNode), confirming fills, computing a holiday calendar or authorizing an order."],
65
+ "typical_scenarios": ["FuturesContractNode → OverseasFuturesContractInfoNode → date guard; account-held symbol → detail → rollover eligibility."],
66
+ }
67
+ _features: ClassVar[list] = [
68
+ "One read-only o3105 request; no model call, positive quote requirement or order submission.",
69
+ "Always reads one explicit symbol object; upstream arrays do not repeat the request. Use SplitNode and bind nodes.split.item for an explicit batch.",
70
+ "Missing dates/numbers remain null; source dates are never guessed from the contract month.",
71
+ "Deep validation uses synthetic data; replay uses recorded SDK responses through the same parser.",
72
+ ]
73
+ _node_guide: ClassVar[dict] = {
74
+ "input_handling": "Bind one {symbol, exchange} object, with an ordering edge from its producer and an upstream futures broker. Use the actual held contract for rollover, which may differ from today's front month.",
75
+ "output_consumption": "Require verified=true and the specific non-null fields the decision needs. Dates use YYYY-MM-DD. Session times describe the returned session only; use an authoritative calendar for holidays/trading-day offsets. Confirm close fills before reopening.",
76
+ "pitfalls": ["verified does not assert every optional field exists. Initial margin is not available account capacity. Neither dates nor raw trade status prove the exchange is currently open."],
77
+ "common_combinations": ["FuturesContractNode, OverseasFuturesAccountNode, CodeNode, OverseasFuturesOrderableQuantityNode."],
78
+ }
79
+ _anti_patterns: ClassVar[list] = [{"pattern": "Guess missing expiry from weekdays or reopen on order ACK.", "reason": "A reported date and a broker-confirmed execution are separate inputs.", "alternative": "Require the authoritative date and correlated close fills; report unavailable evidence explicitly."}]
80
+ _examples: ClassVar[list] = [{
81
+ "title": "Inspect the current front contract",
82
+ "description": "Read-only component; no order is submitted.",
83
+ "expected_output": "value contains observed details; unsupported or missing fields stay null.",
84
+ "workflow_snippet": {
85
+ "id": "futures-contract-details", "name": "Inspect futures contract",
86
+ "nodes": [{"id": "start", "type": "StartNode"},
87
+ {"id": "broker", "type": "OverseasFuturesBrokerNode", "credential_id": "broker_cred", "paper_trading": True},
88
+ {"id": "contract", "type": "FuturesContractNode", "base_products": ["HMH"], "contract_selection": "front"},
89
+ {"id": "detail", "type": "OverseasFuturesContractInfoNode", "symbol": "{{ nodes.contract.symbols[0] }}"}],
90
+ "edges": [{"from": a, "to": b} for a, b in [("start", "broker"), ("broker", "contract"), ("contract", "detail")]],
91
+ "credentials": [{"credential_id": "broker_cred", "type": "broker_ls_overseas_futures", "data": [{"key": "appkey", "value": ""}, {"key": "appsecret", "value": ""}]}],
92
+ },
93
+ }, {
94
+ "title": "Inspect the proposed next contract",
95
+ "description": "Resolve and inspect the successor before a separate rollover decision; no order is submitted.",
96
+ "expected_output": "The selected next contract's observed dates and margins, with unknown fields remaining null.",
97
+ "workflow_snippet": {
98
+ "id": "futures-next-contract-details", "name": "Inspect next futures contract",
99
+ "nodes": [{"id": "start", "type": "StartNode"},
100
+ {"id": "broker", "type": "OverseasFuturesBrokerNode", "credential_id": "broker_cred", "paper_trading": True},
101
+ {"id": "contract", "type": "FuturesContractNode", "base_products": ["HMH"], "contract_selection": "next"},
102
+ {"id": "detail", "type": "OverseasFuturesContractInfoNode", "symbol": "{{ nodes.contract.symbols[0] }}"}],
103
+ "edges": [{"from": a, "to": b} for a, b in [("start", "broker"), ("broker", "contract"), ("contract", "detail")]],
104
+ "credentials": [{"credential_id": "broker_cred", "type": "broker_ls_overseas_futures", "data": [{"key": "appkey", "value": ""}, {"key": "appsecret", "value": ""}]}],
105
+ },
106
+ }]
107
+
108
+ @classmethod
109
+ def is_tool_enabled(cls) -> bool:
110
+ return True
111
+
112
+ @classmethod
113
+ def get_field_schema(cls) -> Dict[str, FieldSchema]:
114
+ return {"symbol": FieldSchema(
115
+ name="symbol", type=FieldType.OBJECT,
116
+ display_name="i18n:fieldNames.OverseasFuturesContractInfoNode.symbol",
117
+ description="Exact listed contract symbol and exchange; resolve at run time.",
118
+ category=FieldCategory.PARAMETERS, expression_mode=ExpressionMode.BOTH, required=True,
119
+ object_schema=[{"name": "symbol", "type": "STRING", "required": True}, {"name": "exchange", "type": "STRING", "required": True}],
120
+ bindable_sources=["FuturesContractNode.symbols", "SplitNode.item"],
121
+ example_binding="{{ nodes.contract.symbols[0] }}",
122
+ )}
@@ -177,6 +177,9 @@ class SQLiteNode(BaseNode):
177
177
  _img_url: ClassVar[str] = ""
178
178
 
179
179
  # === 기본 설정 ===
180
+ storage_scope: Literal['shared', 'execution'] = Field(
181
+ default='shared', description='i18n:fields.SQLiteNode.storage_scope',
182
+ )
180
183
  db_name: str = Field(
181
184
  default="default.db",
182
185
  description="데이터베이스 파일명 (/app/data/ 폴더 내)",
@@ -248,7 +251,7 @@ class SQLiteNode(BaseNode):
248
251
  "Upsert support via 'on_conflict' column in simple mode — ideal for idempotent state updates",
249
252
  "Outputs rows (list), affected_count (int), and last_insert_id (int) to cover all read/write patterns",
250
253
  "is_tool_enabled=True — AI Agent can use SQLiteNode as a tool to read or write local state",
251
- "Databases are scoped to /app/data/ directory; db_name selects the file",
254
+ "The host injects storage_dir (default /app/data); db_name selects the file. storage_scope=execution prefixes a stable host execution identity (or workflow ID for standalone use), survives process restarts, and separates managed account executions. Default shared preserves existing filenames. Keep the host directory on durable storage and do not reset reservations on startup.",
252
255
  "Has a dedicated replay adapter (execute_sqlite) and takes no external recording; it runs against a file-backed database rooted at the run's storage directory",
253
256
  "State written on one tick or event frame persists to that database and stays visible on later frames within the run, so a durable duplicate guard can be built on it",
254
257
  "Each execution runs exactly ONE SQL statement (execute_query and every simple-mode action call db.execute once); 'CREATE TABLE …; INSERT …' in one query fails with 'You can only execute one statement at a time'",
@@ -426,6 +429,12 @@ class SQLiteNode(BaseNode):
426
429
  def get_field_schema(cls) -> Dict[str, "FieldSchema"]:
427
430
  from programgarden_core.models.field_binding import FieldSchema, FieldType, FieldCategory, ExpressionMode, UIComponent
428
431
  return {
432
+ "storage_scope": FieldSchema(
433
+ name="storage_scope", type=FieldType.ENUM,
434
+ description="i18n:fields.SQLiteNode.storage_scope", default="shared",
435
+ enum_values=["shared", "execution"], category=FieldCategory.PARAMETERS,
436
+ expression_mode=ExpressionMode.FIXED_ONLY,
437
+ ),
429
438
  # === PARAMETERS: 기본 설정 ===
430
439
  "db_name": FieldSchema(
431
440
  name="db_name",
@@ -601,8 +601,16 @@ class OverseasFuturesRealOrderEventNode(BaseNode):
601
601
 
602
602
  _inputs: List[InputPort] = []
603
603
  _outputs: List[OutputPort] = [
604
+ OutputPort(name="status", type="string", description="Subscription status; subscribed only after successful registration."),
605
+ OutputPort(name="error", type="string", description="Subscription failure when unavailable."),
604
606
  OutputPort(name="accepted", type="order_event", description="i18n:ports.accepted", fields=ORDER_EVENT_FIELDS),
605
- OutputPort(name="filled", type="order_event", description="i18n:ports.filled", fields=ORDER_EVENT_FIELDS),
607
+ OutputPort(name="filled", type="order_event", description="i18n:ports.filled", fields=ORDER_EVENT_FIELDS + [
608
+ {"name": "tr_cd", "type": "string", "description": "Native stream identifier (TC3 for executions)."},
609
+ {"name": "svc_id", "type": "string", "description": "Native service classifier; require CH01 for a positive execution."},
610
+ {"name": "order_date", "type": "string", "description": "Observed broker order date, YYYYMMDD; no local date fallback."},
611
+ {"name": "fill_no", "type": "string", "description": "Observed TC3 execution identity; never equate with REST execution numbers."},
612
+ {"name": "fill_time", "type": "string", "description": "Observed TC3 execution time."},
613
+ ]),
606
614
  OutputPort(name="modified", type="order_event", description="i18n:ports.modified", fields=ORDER_EVENT_FIELDS),
607
615
  OutputPort(name="cancelled", type="order_event", description="i18n:ports.cancelled", fields=ORDER_EVENT_FIELDS),
608
616
  OutputPort(name="rejected", type="order_event", description="i18n:ports.rejected", fields=ORDER_EVENT_FIELDS),
@@ -43,7 +43,7 @@ class SessionGateNode(BaseNode):
43
43
  windows: list[SessionWindow] = Field(..., min_length=1, description='i18n:nodes.SessionGateNode.windows')
44
44
  days: list[Literal['mon','tue','wed','thu','fri','sat','sun']] = Field(..., min_length=1, description='i18n:nodes.SessionGateNode.days')
45
45
  closed_dates: list[str] = Field(default_factory=list, description='i18n:nodes.SessionGateNode.closed_dates')
46
- exchange_calendar: Literal['XNYS', 'XNAS'] | None = Field(default=None, description='i18n:nodes.SessionGateNode.exchange_calendar')
46
+ exchange_calendar: Literal['XNYS', 'XNAS', 'XHKG'] | None = Field(default=None, description='i18n:nodes.SessionGateNode.exchange_calendar')
47
47
 
48
48
  _inputs: list[InputPort] = [InputPort(name='trigger',type='signal',required=False,description='i18n:nodes.SessionGateNode.trigger')]
49
49
  _outputs: list[OutputPort] = [OutputPort(name=k,type=t,description=d) for k,t,d in [
@@ -53,15 +53,15 @@ class SessionGateNode(BaseNode):
53
53
  ('local_time','string','i18n:nodes.SessionGateNode.local_time'),
54
54
  ('reason','string','i18n:nodes.SessionGateNode.reason'),
55
55
  ]]
56
- _change_note: ClassVar[str] = 'Add opt-in scheduled US equity holidays and early closes.'
57
- _version: ClassVar[str] = '1.1.0'
58
- _updated_at: ClassVar[str] = '2026-09-29'
56
+ _change_note: ClassVar[str] = 'Add the XHKG cash-session intersection, including lunch breaks; this is not a derivatives calendar.'
57
+ _version: ClassVar[str] = '1.2.0'
58
+ _updated_at: ClassVar[str] = '2026-10-03'
59
59
  _usage: ClassVar[dict[str,Any]] = {
60
60
  'when_to_use':['Check a market-local order window immediately before submission.','Handle overnight sessions, breaks and daylight-saving time.'],
61
61
  'when_not_to_use':['Live trading halts or new emergency closures must be observed.','Wait until opening: use TradingHoursFilterNode.'],
62
62
  'typical_scenarios':['ScheduleNode -> SessionGateNode -> IfNode(allowed) -> strategy','Reservation -> SessionGateNode -> IfNode(allowed) -> OrderNode'],
63
63
  }
64
- _features: ClassVar[list[str]] = ['No credentials or network.', 'Optional exchange_calendar XNYS/XNAS intersects user windows with scheduled holidays and early closes; missing/broken calendar data blocks entry. Default null preserves explicit-window behavior.','No sleeping and no dry-run bypass.','IANA timezone/DST and opening-weekday semantics.','Explicit IfNode controls downstream work.','Output is a pure function of the evaluation instant and the configuration (windows are start-inclusive and end-exclusive, plus days, closed_dates and the IANA timezone); replay evaluates it at the scenario as_of.','Two frames at the same instant produce the same allowed value; an outside-window scenario must sit at an instant outside the window.','allowed skips nothing by itself; bind it to an IfNode and hang the order chain on the IfNode true edge.']
64
+ _features: ClassVar[list[str]] = ['No credentials or network.', 'Optional exchange_calendar XNYS/XNAS/XHKG intersects user windows with scheduled holidays, breaks and early closes; XHKG is the cash calendar, not all futures sessions; missing/broken calendar data blocks entry. Default null preserves explicit-window behavior.','No sleeping and no dry-run bypass.','IANA timezone/DST and opening-weekday semantics.','Explicit IfNode controls downstream work.','Output is a pure function of the evaluation instant and the configuration (windows are start-inclusive and end-exclusive, plus days, closed_dates and the IANA timezone); replay evaluates it at the scenario as_of.','Two frames at the same instant produce the same allowed value; an outside-window scenario must sit at an instant outside the window.','allowed skips nothing by itself; bind it to an IfNode and hang the order chain on the IfNode true edge.']
65
65
  _anti_patterns: ClassVar[list[dict[str,str]]] = [{'pattern':'Connect directly to an order and assume an ordinary edge checks allowed.','reason':'Edges sequence execution; false output does not automatically skip successors.','alternative':'Bind allowed to IfNode and use its true edge.'}]
66
66
  _node_guide: ClassVar[dict[str,Any]] = {
67
67
  'input_handling':'Configure timezone, windows, days and optional closed_dates. No current-time override is accepted from workflow data.',
@@ -122,7 +122,7 @@ class SessionGateNode(BaseNode):
122
122
  fields['exchange_calendar'] = FieldSchema(
123
123
  name='exchange_calendar', type=FieldType.STRING,
124
124
  description=cls.model_fields['exchange_calendar'].description, required=False,
125
- enum_values=['XNYS', 'XNAS'], category=FieldCategory.PARAMETERS,
125
+ enum_values=['XNYS', 'XNAS', 'XHKG'], category=FieldCategory.PARAMETERS,
126
126
  expression_mode=ExpressionMode.FIXED_ONLY,
127
127
  )
128
128
  return fields
@@ -195,7 +195,7 @@ class NodeTypeRegistry:
195
195
  OverseasFuturesMarketDataNode, OverseasFuturesHistoricalDataNode,
196
196
  OverseasFuturesRealMarketDataNode,
197
197
  OverseasFuturesSymbolQueryNode,
198
- FuturesContractNode,
198
+ FuturesContractNode, OverseasFuturesContractInfoNode,
199
199
  # Account - Stock (해외주식)
200
200
  OverseasStockAccountNode, OverseasStockRealAccountNode, OverseasStockRealOrderEventNode,
201
201
  # Account - Futures (해외선물)
@@ -239,7 +239,7 @@ class NodeTypeRegistry:
239
239
  OverseasFuturesMarketDataNode, OverseasFuturesHistoricalDataNode,
240
240
  OverseasFuturesRealMarketDataNode,
241
241
  OverseasFuturesSymbolQueryNode,
242
- FuturesContractNode,
242
+ FuturesContractNode, OverseasFuturesContractInfoNode,
243
243
  # Account - Stock (해외주식)
244
244
  OverseasStockAccountNode, OverseasStockRealAccountNode, OverseasStockRealOrderEventNode,
245
245
  # Account - Futures (해외선물)
@@ -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.5.3"
8
+ version = "2.6.1"
9
9
  license = "AGPL-3.0-or-later"
10
10
  description = "ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의"
11
11
  readme = "README.md"