programgarden 2.6.1__tar.gz → 2.6.2__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 (69) hide show
  1. {programgarden-2.6.1 → programgarden-2.6.2}/PKG-INFO +4 -2
  2. {programgarden-2.6.1 → programgarden-2.6.2}/README.md +2 -0
  3. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/executor.py +25 -7
  4. programgarden-2.6.2/programgarden/futures_history.py +23 -0
  5. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/resolver.py +27 -0
  6. {programgarden-2.6.1 → programgarden-2.6.2}/pyproject.toml +2 -2
  7. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/__init__.py +0 -0
  8. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/binding_validator.py +0 -0
  9. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/client.py +0 -0
  10. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/code_worker.py +0 -0
  11. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/context.py +0 -0
  12. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/__init__.py +0 -0
  13. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/adjustment_delivery.py +0 -0
  14. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/broker_evidence.py +0 -0
  15. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/broker_order_totals.py +0 -0
  16. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/broker_snapshot.py +0 -0
  17. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/checkpoint_manager.py +0 -0
  18. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/db_naming.py +0 -0
  19. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/execution_storage.py +0 -0
  20. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/fill_reconciler.py +0 -0
  21. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/order_cancellation.py +0 -0
  22. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/order_recovery.py +0 -0
  23. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/owned_order_cancellation.py +0 -0
  24. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/position_reconciliation.py +0 -0
  25. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/query_builder.py +0 -0
  26. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/sqlite_scope.py +0 -0
  27. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/workflow_position_tracker.py +0 -0
  28. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/database/workflow_risk_tracker.py +0 -0
  29. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/deep_fixtures.py +0 -0
  30. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/futures_contract_info.py +0 -0
  31. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/futures_orderable.py +0 -0
  32. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/futures_pnl.py +0 -0
  33. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/futures_read_evidence.py +0 -0
  34. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/incremental_build.py +0 -0
  35. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/managed_order_control.py +0 -0
  36. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/node_runner.py +0 -0
  37. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/order_lifecycle.py +0 -0
  38. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/plugin/__init__.py +0 -0
  39. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/plugin/sandbox.py +0 -0
  40. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/providers/__init__.py +0 -0
  41. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/providers/llm_errors.py +0 -0
  42. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/providers/llm_provider.py +0 -0
  43. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/reconnect_handler.py +0 -0
  44. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/replay_contracts.py +0 -0
  45. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/replay_events.py +0 -0
  46. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/replay_external.py +0 -0
  47. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/replay_order_adapter.py +0 -0
  48. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/replay_orders.py +0 -0
  49. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/replay_scenarios.py +0 -0
  50. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/replay_semantics.py +0 -0
  51. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/replay_sources.py +0 -0
  52. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/replay_sqlite.py +0 -0
  53. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/replay_triggers.py +0 -0
  54. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/resource/__init__.py +0 -0
  55. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/resource/context.py +0 -0
  56. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/resource/limiter.py +0 -0
  57. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/resource/monitor.py +0 -0
  58. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/resource/throttle.py +0 -0
  59. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/semantic_rules.py +0 -0
  60. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/tools/__init__.py +0 -0
  61. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/tools/credential_tools.py +0 -0
  62. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/tools/definition_tools.py +0 -0
  63. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/tools/event_tools.py +0 -0
  64. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/tools/job_tools.py +0 -0
  65. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/tools/registry_tools.py +0 -0
  66. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/tools/sqlite_tools.py +0 -0
  67. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/validation_recommender.py +0 -0
  68. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/validation_replay.py +0 -0
  69. {programgarden-2.6.1 → programgarden-2.6.2}/programgarden/valuation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programgarden
3
- Version: 2.6.1
3
+ Version: 2.6.2
4
4
  Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Author: 프로그램동산
@@ -16,7 +16,7 @@ Requires-Dist: croniter (>=6.0.0,<7.0.0)
16
16
  Requires-Dist: litellm (>=1.40.0)
17
17
  Requires-Dist: lxml (>=6.0.2,<7.0.0)
18
18
  Requires-Dist: programgarden-community (>=2.3.1,<3.0.0)
19
- Requires-Dist: programgarden-core (>=2.6.1,<3.0.0)
19
+ Requires-Dist: programgarden-core (>=2.6.2,<3.0.0)
20
20
  Requires-Dist: programgarden-finance (>=2.0.1,<3.0.0)
21
21
  Requires-Dist: psutil (>=6.0.0,<7.0.0)
22
22
  Requires-Dist: psycopg2-binary (>=2.9.11,<3.0.0)
@@ -27,6 +27,8 @@ Requires-Dist: tzdata (>=2025.2,<2026.0)
27
27
  Requires-Dist: yfinance (>=0.2.0,<0.3.0)
28
28
  Description-Content-Type: text/markdown
29
29
 
30
+ The 2.6.2 release candidate [futures minute alias correction](../../docs/futures-minute-alias-contract.md) aligns canonical minute periods with the existing one-page o3103 path and rejects unsupported hourly/adjustment claims. Complete-range coverage and consumer adoption are not implied.
31
+
30
32
  # ProgramGarden
31
33
 
32
34
  An unreleased [stock history interval correction](../../docs/stock-history-interval-contract.md)
@@ -1,3 +1,5 @@
1
+ The 2.6.2 release candidate [futures minute alias correction](../../docs/futures-minute-alias-contract.md) aligns canonical minute periods with the existing one-page o3103 path and rejects unsupported hourly/adjustment claims. Complete-range coverage and consumer adoption are not implied.
2
+
1
3
  # ProgramGarden
2
4
 
3
5
  An unreleased [stock history interval correction](../../docs/stock-history-interval-contract.md)
@@ -35,6 +35,12 @@ from programgarden_core import (
35
35
  OVERSEAS_STOCK_ORDER_MARKET_CODES,
36
36
  )
37
37
  from programgarden.context import ExecutionContext, WorkflowEvent
38
+ from programgarden.futures_history import (
39
+ DAILY_INTERVALS as FUTURES_DAILY_INTERVALS,
40
+ MINUTE_INTERVALS as FUTURES_MINUTE_INTERVALS,
41
+ normalize_interval as normalize_futures_history_interval,
42
+ validate_adjust as validate_futures_history_adjust,
43
+ )
38
44
  from programgarden.order_lifecycle import (
39
45
  FUTURES_PRODUCTS, OrderLifecycleMetadata, exact_futures_credential,
40
46
  inject_futures_connection, operation_key,
@@ -12216,6 +12222,11 @@ class HistoricalDataNodeExecutor(NodeExecutorBase):
12216
12222
  node_type == "HistoricalDataNode" and product in (None, "overseas_stock")
12217
12223
  ):
12218
12224
  self._stock_history_gubun(config.get("interval", "1d"))
12225
+ if node_type == "OverseasFuturesHistoricalDataNode" or (
12226
+ node_type == "HistoricalDataNode" and product in FUTURES_PRODUCTS
12227
+ ):
12228
+ normalize_futures_history_interval(config.get("interval", "1d"))
12229
+ validate_futures_history_adjust(config.get("adjust", False))
12219
12230
 
12220
12231
  # deep_validate: HistoricalDataNode hits the real LS API. Inject a fixture
12221
12232
  # time series so the flow completes without a network call.
@@ -12638,6 +12649,7 @@ class HistoricalDataNodeExecutor(NodeExecutorBase):
12638
12649
 
12639
12650
  반환: [{symbol, exchange, time_series: [{date, open, high, low, close, volume}, ...]}, ...]
12640
12651
  """
12652
+ interval = normalize_futures_history_interval(interval)
12641
12653
  credential = context.get_credential()
12642
12654
  if not credential:
12643
12655
  context.log("error", "Credential not set for HistoricalDataNode(futures)", node_id)
@@ -12669,7 +12681,7 @@ class HistoricalDataNodeExecutor(NodeExecutorBase):
12669
12681
  symbol_to_exchange[sym] = exch
12670
12682
 
12671
12683
  # interval별 분기
12672
- is_minute_data = interval in ["1min", "5min", "15min", "30min", "30s"]
12684
+ is_minute_data = interval in FUTURES_MINUTE_INTERVALS
12673
12685
 
12674
12686
  for symbol in symbols:
12675
12687
  try:
@@ -12720,8 +12732,10 @@ class HistoricalDataNodeExecutor(NodeExecutorBase):
12720
12732
  from programgarden_finance.ls.overseas_futureoption.chart.o3108.blocks import O3108InBlock
12721
12733
 
12722
12734
  # interval → gubun 변환 (0=일, 1=주, 2=월)
12723
- gubun_map = {"1d": "0", "1w": "1", "1M": "2"}
12724
- gubun = gubun_map.get(interval, "0")
12735
+ interval = normalize_futures_history_interval(interval)
12736
+ if interval not in FUTURES_DAILY_INTERVALS:
12737
+ raise ValueError("FUTURES_HISTORY_DAILY_INTERVAL_REQUIRED")
12738
+ gubun = FUTURES_DAILY_INTERVALS[interval]
12725
12739
 
12726
12740
  body = O3108InBlock(
12727
12741
  shcode=symbol,
@@ -12763,17 +12777,21 @@ class HistoricalDataNodeExecutor(NodeExecutorBase):
12763
12777
  context: ExecutionContext,
12764
12778
  node_id: str,
12765
12779
  ) -> List[Dict[str, Any]]:
12766
- """해외선물 분봉 조회 (o3103)"""
12780
+ """Read the initial o3103 page; dates and complete-range coverage are not supplied."""
12767
12781
  from programgarden_finance.ls.overseas_futureoption.chart.o3103.blocks import O3103InBlock
12768
12782
 
12769
12783
  # interval → ncnt 변환 (0=30초, 1=1분, 5=5분, 30=30분)
12770
- ncnt_map = {"30s": 0, "1min": 1, "5min": 5, "15min": 15, "30min": 30}
12771
- ncnt = ncnt_map.get(interval, 1)
12784
+ interval = normalize_futures_history_interval(interval)
12785
+ if interval not in FUTURES_MINUTE_INTERVALS:
12786
+ raise ValueError("FUTURES_HISTORY_MINUTE_INTERVAL_REQUIRED")
12787
+ ncnt = FUTURES_MINUTE_INTERVALS[interval]
12772
12788
 
12773
12789
  body = O3103InBlock(
12774
12790
  shcode=symbol,
12775
12791
  ncnt=ncnt,
12776
- readcnt=200, # 최대 조회 건수
12792
+ readcnt=200, # Existing application request size, not a documented LS maximum.
12793
+ cts_date="",
12794
+ cts_time="",
12777
12795
  )
12778
12796
 
12779
12797
  result = await api.chart().o3103(body=body).req_async()
@@ -0,0 +1,23 @@
1
+ """Explicit mappings for the existing single-page LS futures chart requests."""
2
+
3
+ MINUTE_INTERVALS = {"30s": 0, "1m": 1, "5m": 5, "15m": 15, "30m": 30}
4
+ DAILY_INTERVALS = {"1d": "0", "1w": "1", "1M": "2"}
5
+ INTERVAL_ALIASES = {"1min": "1m", "5min": "5m", "15min": "15m", "30min": "30m"}
6
+
7
+
8
+ def normalize_interval(interval):
9
+ """Preserve known legacy minute names without falling back to daily data."""
10
+ if isinstance(interval, str):
11
+ interval = INTERVAL_ALIASES.get(interval, interval)
12
+ if interval in MINUTE_INTERVALS or interval in DAILY_INTERVALS:
13
+ return interval
14
+ raise ValueError(
15
+ "UNSUPPORTED_FUTURES_HISTORY_INTERVAL: choose 30s, 1m, 5m, 15m, 30m, "
16
+ "1d, 1w or 1M. Hourly and unknown periods are not implemented; no daily fallback."
17
+ )
18
+
19
+
20
+ def validate_adjust(adjust):
21
+ """Never describe exact-contract prices as rollover-adjusted history."""
22
+ if adjust is not False:
23
+ raise ValueError("FUTURES_HISTORY_ADJUST_UNSUPPORTED: adjust must be false; rollover adjustment is not implemented")
@@ -346,6 +346,7 @@ class WorkflowResolver:
346
346
  # Order routing choices are literals, never prices or item bindings.
347
347
  self._validate_order_enums(workflow, registry, result)
348
348
  self._validate_stock_history_interval(workflow, registry, result)
349
+ self._validate_futures_history_contract(workflow, registry, result)
349
350
 
350
351
  # 11. Static recommendations (topology analysis)
351
352
  for rec in run_static_recommendation_rules(
@@ -386,6 +387,32 @@ class WorkflowResolver:
386
387
  suggestion="Choose 1d, 1w, 1M or 1Y explicitly; do not substitute daily bars for an intraday strategy.",
387
388
  ))
388
389
 
390
+ def _validate_futures_history_contract(self, workflow, registry, result) -> None:
391
+ """Keep native validation aligned with period and adjustment routing."""
392
+ from programgarden.futures_history import normalize_interval, validate_adjust
393
+
394
+ node_type = "OverseasFuturesHistoricalDataNode"
395
+ node_class = registry.get(node_type)
396
+ if node_class is None:
397
+ return
398
+ field = node_class.get_field_schema()["interval"]
399
+ for node in workflow.nodes:
400
+ if node.get("type") != node_type:
401
+ continue
402
+ for name, check, default, available in (
403
+ ("interval", normalize_interval, field.default, field.enum_values),
404
+ ("adjust", validate_adjust, False, ["false"]),
405
+ ):
406
+ try:
407
+ check(node.get(name, default))
408
+ except ValueError as exc:
409
+ result.add(build_error(
410
+ ErrorCode.INVALID_FIELD_ENUM, str(exc),
411
+ location=ErrorLocation(node_id=node.get("id"), node_type=node_type, field_path=name),
412
+ available_values=available,
413
+ suggestion="Choose a supported period and keep adjust=false; only exact-contract prices are implemented.",
414
+ ))
415
+
389
416
  def _validate_order_enums(self, workflow, registry, result) -> None:
390
417
  """Enforce fixed price/order types without changing dynamic close sides."""
391
418
  from programgarden_core.models.field_binding import ExpressionMode
@@ -5,7 +5,7 @@ authors = [
5
5
  homepage = "https://programgarden.com"
6
6
  requires-python = ">=3.12"
7
7
  name = "programgarden"
8
- version = "2.6.1"
8
+ version = "2.6.2"
9
9
  license = "AGPL-3.0-or-later"
10
10
  description = "ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진"
11
11
  readme = "README.md"
@@ -36,7 +36,7 @@ litellm = ">=1.40.0"
36
36
  # 로 통째 실패한다(1.35.1 의 조용한 TypeError 보다 강한 모듈 로드 실패).
37
37
  # (1.26.0 이후로도 context.py 는 WorkflowPnLEvent(**event_data) 에 personal_metrics
38
38
  # 를 무조건 실으므로 그 하한 근거도 이 상한에 포함된다.)
39
- programgarden-core = "^2.6.1"
39
+ programgarden-core = "^2.6.2"
40
40
  programgarden-finance = "^2.0.1"
41
41
  programgarden-community = "^2.3.1"
42
42