programgarden-core 2.5.1__tar.gz → 2.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/PKG-INFO +8 -1
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/README.md +6 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/i18n/locales/en.json +1 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/i18n/locales/ko.json +1 -0
- programgarden_core-2.5.3/programgarden_core/market_calendar.py +33 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/base.py +1 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/session_gate.py +28 -10
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/pyproject.toml +2 -1
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/__init__.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/bases/__init__.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/bases/client.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/bases/components.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/bases/listener.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/bases/mixins.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/bases/products.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/bases/sql.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/bases/storage.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/code_node.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/exceptions.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/expression/__init__.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/expression/evaluator.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/expression/reference.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/i18n/__init__.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/i18n/translator.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/korea_alias.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/__init__.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/connection_rule.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/credential.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/edge.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/event.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/exchange.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/field_binding.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/job.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/order_diagnostics.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/plugin_resource.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/resilience.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/resource.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/validation.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/workflow.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/__init__.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/account_futures.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/account_korea_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/account_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/ai.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/backtest.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/backtest_futures.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/backtest_korea_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/backtest_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/broker.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/calculation.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/code.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/condition.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/data.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/data_futures.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/data_korea_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/data_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/display.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/event.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/fundamental_korea_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/fundamental_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/infra.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/open_orders_futures.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/open_orders_korea_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/open_orders_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/order.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/orderable_quantity_futures.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/portfolio.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/realtime_futures.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/realtime_korea_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/realtime_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/risk.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/symbol.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/symbol_futures.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/symbol_korea_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/symbol_stock.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/trigger.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/presets/__init__.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/presets/news_analyst.json +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/presets/risk_manager.json +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/presets/strategist.json +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/presets/technical_analyst.json +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/registry/__init__.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/registry/credential_registry.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/registry/node_registry.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/registry/plugin_registry.py +0 -0
- {programgarden_core-2.5.1 → programgarden_core-2.5.3}/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
|
+
Version: 2.5.3
|
|
4
4
|
Summary: ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의
|
|
5
5
|
License-Expression: AGPL-3.0-or-later
|
|
6
6
|
Author: 프로그램동산
|
|
@@ -10,12 +10,19 @@ Classifier: Programming Language :: Python :: 3
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.13
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: exchange-calendars (==4.13.2) ; python_version >= "3.12" and python_version < "4.0"
|
|
13
14
|
Requires-Dist: pydantic (>=2.0.1,<3.0.0)
|
|
14
15
|
Requires-Dist: tzdata (>=2025.2,<2026.0)
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
16
17
|
|
|
18
|
+
US regular-session requests can opt into SessionGateNode.exchange_calendar (XNYS/XNAS) for scheduled holidays and early closes. See [session gates](../../docs/session-gates-and-guarded-split.md).
|
|
19
|
+
|
|
17
20
|
# ProgramGarden Core
|
|
18
21
|
|
|
22
|
+
The REST overseas-stock position catalog declares `sellable_qty`, matching the
|
|
23
|
+
broker-reported capacity preserved by the engine. Missing evidence remains null;
|
|
24
|
+
see [the contract](../../docs/overseas-stock-sellable-quantity.md).
|
|
25
|
+
|
|
19
26
|
ProgramGarden은 AI 시대에 맞춰 파이썬을 모르는 투자자도 개인화된 시스템 트레이딩을 자동으로 수행할 수 있게 돕는 오픈소스입니다. 본 저장소는 노드 기반 DSL의 핵심 타입, 베이스 클래스, 레지스트리, i18n을 정의하는 "코어" 모듈입니다.
|
|
20
27
|
|
|
21
28
|
- 문서(비개발자 빠른 시작): https://programgarden.gitbook.io/docs/invest/non_dev_quick_guide
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
US regular-session requests can opt into SessionGateNode.exchange_calendar (XNYS/XNAS) for scheduled holidays and early closes. See [session gates](../../docs/session-gates-and-guarded-split.md).
|
|
2
|
+
|
|
1
3
|
# ProgramGarden Core
|
|
2
4
|
|
|
5
|
+
The REST overseas-stock position catalog declares `sellable_qty`, matching the
|
|
6
|
+
broker-reported capacity preserved by the engine. Missing evidence remains null;
|
|
7
|
+
see [the contract](../../docs/overseas-stock-sellable-quantity.md).
|
|
8
|
+
|
|
3
9
|
ProgramGarden은 AI 시대에 맞춰 파이썬을 모르는 투자자도 개인화된 시스템 트레이딩을 자동으로 수행할 수 있게 돕는 오픈소스입니다. 본 저장소는 노드 기반 DSL의 핵심 타입, 베이스 클래스, 레지스트리, i18n을 정의하는 "코어" 모듈입니다.
|
|
4
10
|
|
|
5
11
|
- 문서(비개발자 빠른 시작): https://programgarden.gitbook.io/docs/invest/non_dev_quick_guide
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/i18n/locales/en.json
RENAMED
|
@@ -1266,6 +1266,7 @@
|
|
|
1266
1266
|
"enums.query_type.new": "New",
|
|
1267
1267
|
"enums.query_type.close": "Liquidation",
|
|
1268
1268
|
"enums.query_type.total": "Total",
|
|
1269
|
+
"nodes.SessionGateNode.exchange_calendar": "Optional scheduled regular-session calendar: XNYS (NYSE) or XNAS (Nasdaq). Intersects configured windows with holidays and early closes. Does not observe live halts.",
|
|
1269
1270
|
"nodes.SessionGateNode.allowed": "True only inside a configured window; bind to IfNode.",
|
|
1270
1271
|
"nodes.SessionGateNode.closed_dates": "Explicit YYYY-MM-DD exclusions. Either local calendar date or opening date blocks the window.",
|
|
1271
1272
|
"nodes.SessionGateNode.days": "Allowed opening weekdays. The post-midnight portion belongs to the previous opening day.",
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/i18n/locales/ko.json
RENAMED
|
@@ -1266,6 +1266,7 @@
|
|
|
1266
1266
|
"enums.query_type.new": "신규",
|
|
1267
1267
|
"enums.query_type.close": "청산",
|
|
1268
1268
|
"enums.query_type.total": "총가능",
|
|
1269
|
+
"nodes.SessionGateNode.exchange_calendar": "미국 정규장 달력을 선택합니다. XNYS(뉴욕증권거래소) 또는 XNAS(나스닥)의 휴장일·조기폐장을 적용합니다. 실시간 거래정지는 확인하지 않습니다.",
|
|
1269
1270
|
"nodes.SessionGateNode.allowed": "허용 구간 안에서만 true입니다. IfNode에 연결하세요.",
|
|
1270
1271
|
"nodes.SessionGateNode.closed_dates": "YYYY-MM-DD 제외일 목록입니다. 현지 날짜 또는 구간 시작 날짜가 해당하면 차단합니다.",
|
|
1271
1272
|
"nodes.SessionGateNode.days": "시작일 기준 허용 요일입니다. 자정 이후 구간은 전날의 시작 요일에 속합니다.",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Scheduled US equity sessions, independent of credentials and live market data.
|
|
2
|
+
|
|
3
|
+
This schedule includes published holidays and early closes. It cannot observe
|
|
4
|
+
new emergency closures, instrument halts, or a broker's ability to accept orders.
|
|
5
|
+
"""
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
from zoneinfo import ZoneInfo
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@lru_cache(maxsize=8)
|
|
12
|
+
def _calendar(name: str, year: int):
|
|
13
|
+
import exchange_calendars
|
|
14
|
+
|
|
15
|
+
return exchange_calendars.get_calendar(
|
|
16
|
+
name, start=f"{year}-01-01", end=f"{year}-12-31", side="left",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def scheduled_session_open(name: str, instant: datetime) -> bool:
|
|
21
|
+
"""Exact seconds: opening is inclusive and closing is exclusive."""
|
|
22
|
+
if name not in {"XNYS", "XNAS"}:
|
|
23
|
+
raise ValueError("Unsupported exchange calendar")
|
|
24
|
+
if instant.tzinfo is None or instant.utcoffset() is None:
|
|
25
|
+
raise ValueError("Calendar clock must include timezone")
|
|
26
|
+
market_date = instant.astimezone(ZoneInfo("America/New_York")).date()
|
|
27
|
+
schedule = _calendar(name, market_date.year).schedule
|
|
28
|
+
day = market_date.isoformat()
|
|
29
|
+
if day not in schedule.index:
|
|
30
|
+
return False
|
|
31
|
+
row = schedule.loc[day]
|
|
32
|
+
clock = instant.astimezone(timezone.utc)
|
|
33
|
+
return bool(row["open"] <= clock < row["close"])
|
|
@@ -316,6 +316,7 @@ OVERSEAS_STOCK_POSITION_FIELDS: List[Dict[str, str]] = [
|
|
|
316
316
|
{"name": "name", "type": "string", "description": "종목명"},
|
|
317
317
|
{"name": "qty", "type": "number", "description": "보유 수량"},
|
|
318
318
|
{"name": "quantity", "type": "number", "description": "보유 수량 (주문 노드 호환 별칭)"},
|
|
319
|
+
{"name": "sellable_qty", "type": "number", "description": "Observed COSOQ00201 sellable quantity; null when the broker field is absent"},
|
|
319
320
|
{"name": "direction", "type": "string", "description": "포지션 방향 (주식은 항상 long)"},
|
|
320
321
|
{"name": "close_side", "type": "string", "description": "청산 주문 방향 (주식은 항상 sell)"},
|
|
321
322
|
{"name": "avg_price", "type": "number", "description": "평균 매입가"},
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/session_gate.py
RENAMED
|
@@ -32,8 +32,9 @@ class SessionWindow(BaseModel):
|
|
|
32
32
|
class SessionGateNode(BaseNode):
|
|
33
33
|
"""Return a time decision immediately; use IfNode to enforce it.
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
An explicit exchange_calendar intersects the window with scheduled US
|
|
36
|
+
equity sessions. It does not observe live halts or grant trading authority.
|
|
37
|
+
Recheck near the order boundary.
|
|
37
38
|
"""
|
|
38
39
|
type: Literal['SessionGateNode'] = 'SessionGateNode'
|
|
39
40
|
category: NodeCategory = NodeCategory.SCHEDULE
|
|
@@ -42,6 +43,7 @@ class SessionGateNode(BaseNode):
|
|
|
42
43
|
windows: list[SessionWindow] = Field(..., min_length=1, description='i18n:nodes.SessionGateNode.windows')
|
|
43
44
|
days: list[Literal['mon','tue','wed','thu','fri','sat','sun']] = Field(..., min_length=1, description='i18n:nodes.SessionGateNode.days')
|
|
44
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')
|
|
45
47
|
|
|
46
48
|
_inputs: list[InputPort] = [InputPort(name='trigger',type='signal',required=False,description='i18n:nodes.SessionGateNode.trigger')]
|
|
47
49
|
_outputs: list[OutputPort] = [OutputPort(name=k,type=t,description=d) for k,t,d in [
|
|
@@ -51,21 +53,21 @@ class SessionGateNode(BaseNode):
|
|
|
51
53
|
('local_time','string','i18n:nodes.SessionGateNode.local_time'),
|
|
52
54
|
('reason','string','i18n:nodes.SessionGateNode.reason'),
|
|
53
55
|
]]
|
|
54
|
-
_change_note: ClassVar[str] = 'Add
|
|
55
|
-
_version: ClassVar[str] = '1.
|
|
56
|
-
_updated_at: ClassVar[str] = '2026-09-
|
|
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'
|
|
57
59
|
_usage: ClassVar[dict[str,Any]] = {
|
|
58
60
|
'when_to_use':['Check a market-local order window immediately before submission.','Handle overnight sessions, breaks and daylight-saving time.'],
|
|
59
|
-
'when_not_to_use':['
|
|
61
|
+
'when_not_to_use':['Live trading halts or new emergency closures must be observed.','Wait until opening: use TradingHoursFilterNode.'],
|
|
60
62
|
'typical_scenarios':['ScheduleNode -> SessionGateNode -> IfNode(allowed) -> strategy','Reservation -> SessionGateNode -> IfNode(allowed) -> OrderNode'],
|
|
61
63
|
}
|
|
62
|
-
_features: ClassVar[list[str]] = ['No credentials or network.','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 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.']
|
|
63
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.'}]
|
|
64
66
|
_node_guide: ClassVar[dict[str,Any]] = {
|
|
65
67
|
'input_handling':'Configure timezone, windows, days and optional closed_dates. No current-time override is accepted from workflow data.',
|
|
66
68
|
'output_consumption':'Bind nodes.session.allowed to an explicit IfNode; local_date can anchor completed-bar calculations.',
|
|
67
69
|
'common_combinations':['ScheduleNode, IfNode, CodeNode, SQLiteNode, OrderNode'],
|
|
68
|
-
'pitfalls':['
|
|
70
|
+
'pitfalls':['For US regular-session requests set exchange_calendar=XNAS or XNYS on BOTH initial and final gates. Weekdays alone do not handle holidays or early closes.', 'Calendar rules do not observe live halts or new emergency closures; use a live market-state source when required.','Recheck near the order: earlier success does not stay valid indefinitely.','Independent workflows still require independent order guards.'],
|
|
69
71
|
}
|
|
70
72
|
_examples: ClassVar[list[dict[str, Any]]] = [
|
|
71
73
|
{
|
|
@@ -113,10 +115,17 @@ class SessionGateNode(BaseNode):
|
|
|
113
115
|
def get_field_schema(cls):
|
|
114
116
|
from programgarden_core.models.field_binding import FieldSchema, FieldType, FieldCategory, ExpressionMode
|
|
115
117
|
descriptions={name:cls.model_fields[name].description for name in ('timezone','windows','days','closed_dates')}
|
|
116
|
-
|
|
118
|
+
fields = {name:FieldSchema(name=name,type=kind,description=descriptions[name],required=name!='closed_dates',
|
|
117
119
|
category=FieldCategory.PARAMETERS,expression_mode=ExpressionMode.FIXED_ONLY,
|
|
118
120
|
**({'array_item_type':FieldType.OBJECT if name=='windows' else FieldType.STRING} if kind==FieldType.ARRAY else {}))
|
|
119
121
|
for name,kind in [('timezone',FieldType.STRING),('windows',FieldType.ARRAY),('days',FieldType.ARRAY),('closed_dates',FieldType.ARRAY)]}
|
|
122
|
+
fields['exchange_calendar'] = FieldSchema(
|
|
123
|
+
name='exchange_calendar', type=FieldType.STRING,
|
|
124
|
+
description=cls.model_fields['exchange_calendar'].description, required=False,
|
|
125
|
+
enum_values=['XNYS', 'XNAS'], category=FieldCategory.PARAMETERS,
|
|
126
|
+
expression_mode=ExpressionMode.FIXED_ONLY,
|
|
127
|
+
)
|
|
128
|
+
return fields
|
|
120
129
|
|
|
121
130
|
def evaluate_at(self, instant: datetime) -> dict[str, Any]:
|
|
122
131
|
"""Pure time decision; the executable node always supplies the real clock."""
|
|
@@ -134,8 +143,17 @@ class SessionGateNode(BaseNode):
|
|
|
134
143
|
if inside and opening.weekday() in allowed_days and not {local.date().isoformat(),opening.isoformat()} & set(self.closed_dates):
|
|
135
144
|
matched=opening.isoformat()
|
|
136
145
|
break
|
|
146
|
+
reason = 'inside_configured_window' if matched else 'outside_configured_window'
|
|
147
|
+
if matched and self.exchange_calendar is not None:
|
|
148
|
+
from programgarden_core.market_calendar import scheduled_session_open
|
|
149
|
+
try:
|
|
150
|
+
if not scheduled_session_open(self.exchange_calendar, instant):
|
|
151
|
+
matched, reason = None, 'outside_exchange_session'
|
|
152
|
+
except Exception:
|
|
153
|
+
# A broken/missing calendar must never fall back to weekdays.
|
|
154
|
+
matched, reason = None, 'calendar_unavailable'
|
|
137
155
|
return {'allowed':matched is not None,'local_date':local.date().isoformat(),'session_date':matched or '',
|
|
138
|
-
'local_time':local.isoformat(),'reason':
|
|
156
|
+
'local_time':local.isoformat(),'reason':reason}
|
|
139
157
|
|
|
140
158
|
async def execute(self, context: Any) -> dict[str, Any]:
|
|
141
159
|
return self.evaluate_at(datetime.now(timezone.utc))
|
|
@@ -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.
|
|
8
|
+
version = "2.5.3"
|
|
9
9
|
license = "AGPL-3.0-or-later"
|
|
10
10
|
description = "ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의"
|
|
11
11
|
readme = "README.md"
|
|
@@ -18,6 +18,7 @@ packages = [
|
|
|
18
18
|
[tool.poetry.dependencies]
|
|
19
19
|
pydantic = ">=2.0.1,<3.0.0"
|
|
20
20
|
tzdata = "^2025.2"
|
|
21
|
+
exchange-calendars = {version = "4.13.2", python = ">=3.12,<4.0"}
|
|
21
22
|
|
|
22
23
|
[tool.poetry.group.dev.dependencies]
|
|
23
24
|
pytest = "^8.0.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/bases/components.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/expression/__init__.py
RENAMED
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/expression/evaluator.py
RENAMED
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/expression/reference.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/connection_rule.py
RENAMED
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/credential.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/field_binding.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/order_diagnostics.py
RENAMED
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/plugin_resource.py
RENAMED
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/resilience.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/models/validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/account_futures.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/account_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/backtest_futures.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/backtest_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/calculation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/data_futures.py
RENAMED
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/data_korea_stock.py
RENAMED
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/data_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/fundamental_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/open_orders_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/realtime_futures.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/realtime_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/symbol_futures.py
RENAMED
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/symbol_korea_stock.py
RENAMED
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/nodes/symbol_stock.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/presets/__init__.py
RENAMED
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/presets/news_analyst.json
RENAMED
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/presets/risk_manager.json
RENAMED
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/presets/strategist.json
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/registry/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/registry/node_registry.py
RENAMED
|
File without changes
|
{programgarden_core-2.5.1 → programgarden_core-2.5.3}/programgarden_core/registry/plugin_registry.py
RENAMED
|
File without changes
|
|
File without changes
|