programgarden-core 1.30.0__tar.gz → 1.30.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 (85) hide show
  1. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/PKG-INFO +9 -1
  2. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/README.md +8 -0
  3. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/i18n/locales/en.json +2 -2
  4. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/i18n/locales/ko.json +2 -2
  5. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/order.py +137 -149
  6. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/pyproject.toml +1 -1
  7. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/__init__.py +0 -0
  8. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/bases/__init__.py +0 -0
  9. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/bases/client.py +0 -0
  10. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/bases/components.py +0 -0
  11. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/bases/listener.py +0 -0
  12. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/bases/mixins.py +0 -0
  13. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/bases/products.py +0 -0
  14. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/bases/sql.py +0 -0
  15. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/bases/storage.py +0 -0
  16. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/code_node.py +0 -0
  17. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/exceptions.py +0 -0
  18. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/expression/__init__.py +0 -0
  19. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/expression/evaluator.py +0 -0
  20. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/i18n/__init__.py +0 -0
  21. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/i18n/translator.py +0 -0
  22. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/korea_alias.py +0 -0
  23. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/__init__.py +0 -0
  24. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/connection_rule.py +0 -0
  25. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/credential.py +0 -0
  26. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/edge.py +0 -0
  27. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/event.py +0 -0
  28. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/exchange.py +0 -0
  29. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/field_binding.py +0 -0
  30. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/job.py +0 -0
  31. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/order_diagnostics.py +0 -0
  32. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/plugin_resource.py +0 -0
  33. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/resilience.py +0 -0
  34. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/resource.py +0 -0
  35. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/validation.py +0 -0
  36. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/models/workflow.py +0 -0
  37. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/__init__.py +0 -0
  38. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/account_futures.py +0 -0
  39. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/account_korea_stock.py +0 -0
  40. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/account_stock.py +0 -0
  41. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/ai.py +0 -0
  42. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/backtest.py +0 -0
  43. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/backtest_futures.py +0 -0
  44. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/backtest_korea_stock.py +0 -0
  45. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/backtest_stock.py +0 -0
  46. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/base.py +0 -0
  47. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/broker.py +0 -0
  48. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/calculation.py +0 -0
  49. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/code.py +0 -0
  50. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/condition.py +0 -0
  51. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/data.py +0 -0
  52. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/data_futures.py +0 -0
  53. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/data_korea_stock.py +0 -0
  54. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/data_stock.py +0 -0
  55. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/display.py +0 -0
  56. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/event.py +0 -0
  57. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/fundamental_korea_stock.py +0 -0
  58. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/fundamental_stock.py +0 -0
  59. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/infra.py +0 -0
  60. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/market_external.py +0 -0
  61. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/market_status.py +0 -0
  62. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/open_orders_futures.py +0 -0
  63. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/open_orders_korea_stock.py +0 -0
  64. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/open_orders_stock.py +0 -0
  65. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/orderable_quantity_futures.py +0 -0
  66. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/portfolio.py +0 -0
  67. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/realtime_futures.py +0 -0
  68. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/realtime_korea_stock.py +0 -0
  69. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/realtime_stock.py +0 -0
  70. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/risk.py +0 -0
  71. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/symbol.py +0 -0
  72. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/symbol_futures.py +0 -0
  73. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/symbol_korea_stock.py +0 -0
  74. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/symbol_stock.py +0 -0
  75. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/nodes/trigger.py +0 -0
  76. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/presets/__init__.py +0 -0
  77. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/presets/news_analyst.json +0 -0
  78. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/presets/risk_manager.json +0 -0
  79. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/presets/strategist.json +0 -0
  80. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/presets/technical_analyst.json +0 -0
  81. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/registry/__init__.py +0 -0
  82. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/registry/credential_registry.py +0 -0
  83. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/registry/node_registry.py +0 -0
  84. {programgarden_core-1.30.0 → programgarden_core-1.30.1}/programgarden_core/registry/plugin_registry.py +0 -0
  85. {programgarden_core-1.30.0 → programgarden_core-1.30.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: 1.30.0
3
+ Version: 1.30.1
4
4
  Summary: ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Author: 프로그램동산
@@ -131,3 +131,11 @@ unbound or cross-product nodes. Reuse one broker for all consumers. Account-free
131
131
  workflows and unrelated credentials remain supported. See
132
132
  [the shared contract](../../docs/workflow_account_policy.md).
133
133
 
134
+
135
+ ## Cancellation result contract (unreleased P4)
136
+
137
+ CancelOrder metadata describes request acknowledgement (`accepted`,
138
+ `confirmation_pending=true`), not final cancellation. Replacement orders require
139
+ separate matching completion evidence. See
140
+ [the cancellation contract](../../docs/cancellation-acknowledgements.md).
141
+
@@ -115,3 +115,11 @@ connection. Additional connections produce `DUPLICATE_BROKER_NODE`, including
115
115
  unbound or cross-product nodes. Reuse one broker for all consumers. Account-free
116
116
  workflows and unrelated credentials remain supported. See
117
117
  [the shared contract](../../docs/workflow_account_policy.md).
118
+
119
+
120
+ ## Cancellation result contract (unreleased P4)
121
+
122
+ CancelOrder metadata describes request acknowledgement (`accepted`,
123
+ `confirmation_pending=true`), not final cancellation. Replacement orders require
124
+ separate matching completion evidence. See
125
+ [the cancellation contract](../../docs/cancellation-acknowledgements.md).
@@ -931,10 +931,10 @@
931
931
  "ports.blocked_reason": "Block reason",
932
932
  "ports.blocked_symbols": "Blocked symbols",
933
933
  "ports.buy_signal": "Buy signal",
934
- "ports.cancel_result": "Cancel result",
934
+ "ports.cancel_result": "Cancel request result",
935
935
  "ports.cancel_trigger": "Cancel execution trigger",
936
936
  "ports.cancelled": "Order cancelled",
937
- "ports.cancelled_order_id": "Cancelled order ID",
937
+ "ports.cancelled_order_id": "Cancellation target order ID",
938
938
  "ports.cancelled_orders": "Cancelled orders",
939
939
  "ports.closed": "Exchange closed",
940
940
  "ports.combined_curve": "Combined equity curve",
@@ -931,10 +931,10 @@
931
931
  "ports.blocked_reason": "차단 사유",
932
932
  "ports.blocked_symbols": "차단된 종목",
933
933
  "ports.buy_signal": "매수 시그널",
934
- "ports.cancel_result": "취소 결과",
934
+ "ports.cancel_result": "취소 요청 결과",
935
935
  "ports.cancel_trigger": "취소 실행 트리거",
936
936
  "ports.cancelled": "취소됨",
937
- "ports.cancelled_order_id": "취소된 주문번호",
937
+ "ports.cancelled_order_id": "취소 대상 주문번호",
938
938
  "ports.cancelled_orders": "취소된 주문",
939
939
  "ports.closed": "거래소 폐장",
940
940
  "ports.combined_curve": "통합 자산 곡선",
@@ -44,6 +44,18 @@ from programgarden_core.models.connection_rule import (
44
44
  )
45
45
 
46
46
 
47
+ # Cancellation acknowledgements are not terminal order outcomes.
48
+ CANCEL_RESULT_FIELDS: List[Dict[str, str]] = [
49
+ {"name": "success", "type": "boolean", "description": "Whether the cancellation request was acknowledged"},
50
+ {"name": "status", "type": "string", "description": "accepted means request acknowledgement only"},
51
+ {"name": "confirmation_pending", "type": "boolean", "description": "True until separate broker completion evidence is verified"},
52
+ {"name": "order_id", "type": "string", "description": "Original target order number"},
53
+ {"name": "cancel_order_no", "type": "string", "description": "New cancellation request number"},
54
+ {"name": "product", "type": "string", "description": "Broker product"},
55
+ {"name": "error", "type": "string", "description": "Request failure reason when available"},
56
+ ]
57
+
58
+
47
59
  # =============================================================================
48
60
  # 베이스 클래스
49
61
  # =============================================================================
@@ -917,7 +929,7 @@ class OverseasStockCancelOrderNode(BaseModifyOrderNode):
917
929
  "when_to_use": [
918
930
  "Cancel an existing unfilled or partially-filled overseas stock order",
919
931
  "Implement a time-based expiry: cancel open orders that have not filled within N minutes",
920
- "Cancel all open orders on a stop-loss trigger before placing new protective orders",
932
+ "Cancel all open orders on a stop-loss trigger without submitting a replacement before confirmed completion",
921
933
  ],
922
934
  "when_not_to_use": [
923
935
  "When the order is already fully filled — the cancel will be rejected by the broker",
@@ -927,7 +939,7 @@ class OverseasStockCancelOrderNode(BaseModifyOrderNode):
927
939
  "typical_scenarios": [
928
940
  "ScheduleNode (end-of-day) → OverseasStockOpenOrdersNode → OverseasStockCancelOrderNode (cancel-all EOD)",
929
941
  "OverseasStockRealOrderEventNode (timeout/no-fill) → OverseasStockCancelOrderNode (stale order cleanup)",
930
- "IfNode (stop-loss triggered) → OverseasStockCancelOrderNode then OverseasStockNewOrderNode (cancel then replace)",
942
+ "IfNode (stop-loss triggered) → OverseasStockCancelOrderNode (request cancellation; verify completion separately)",
931
943
  ],
932
944
  }
933
945
  _features: ClassVar[List[str]] = [
@@ -990,48 +1002,35 @@ class OverseasStockCancelOrderNode(BaseModifyOrderNode):
990
1002
  }
991
1003
  ],
992
1004
  },
993
- "expected_output": "cancel_result port: {order_id, symbol, exchange, status='cancelled'}; cancelled_order_id port: string.",
994
- },
995
- {
996
- "title": "Cancel a specific order then place a replacement",
997
- "description": "Stop-loss triggers: cancel the original buy order and place a market sell to exit the position.",
998
- "workflow_snippet": {
999
- "id": "overseas_stock_cancel_and_replace",
1000
- "name": "Cancel and Replace Order",
1001
- "nodes": [
1002
- {"id": "start", "type": "StartNode"},
1003
- {"id": "broker", "type": "OverseasStockBrokerNode", "credential_id": "broker_cred", "paper_trading": False},
1004
- {"id": "open_orders", "type": "OverseasStockOpenOrdersNode"},
1005
- {"id": "cancel", "type": "OverseasStockCancelOrderNode",
1006
- "original_order_id": "{{ nodes.open_orders.open_orders[0].order_id }}",
1007
- "symbol": "AAPL",
1008
- "exchange": "NASDAQ"},
1009
- {"id": "sell", "type": "OverseasStockNewOrderNode",
1010
- "side": "sell",
1011
- "order_type": "market",
1012
- "order": {"symbol": "AAPL", "exchange": "NASDAQ", "quantity": 10}},
1013
- ],
1014
- "edges": [
1015
- {"from": "start", "to": "broker"},
1016
- {"from": "broker", "to": "open_orders"},
1017
- {"from": "open_orders", "to": "cancel"},
1018
- {"from": "cancel", "to": "sell"},
1019
- {"from": "broker", "to": "cancel"},
1020
- {"from": "broker", "to": "sell"},
1021
- ],
1022
- "credentials": [
1023
- {
1024
- "credential_id": "broker_cred",
1025
- "type": "broker_ls_overseas_stock",
1026
- "data": [
1027
- {"key": "appkey", "value": "", "type": "password", "label": "App Key"},
1028
- {"key": "appsecret", "value": "", "type": "password", "label": "App Secret"},
1029
- ],
1030
- }
1031
- ],
1032
- },
1033
- "expected_output": "cancel_result with status='cancelled', then sell result with status='accepted' market order.",
1005
+ "expected_output": "cancel_result: {success: true, status: accepted, confirmation_pending: true, order_id, cancel_order_no}; cancelled_order_id is a legacy alias for the target original order, not proof of cancellation.",
1034
1006
  },
1007
+ {'title': 'Request cancellation of an explicitly verified order',
1008
+ 'description': 'Replace the illustrative order number and symbol only after verifying the current account '
1009
+ 'and workflow ownership. Stop after acknowledgement; no replacement order is submitted.',
1010
+ 'workflow_snippet': {'id': 'OverseasStockCancelOrderNode_explicit_cancel',
1011
+ 'name': 'Explicit cancellation request',
1012
+ 'nodes': [{'id': 'start', 'type': 'StartNode'},
1013
+ {'id': 'broker',
1014
+ 'type': 'OverseasStockBrokerNode',
1015
+ 'credential_id': 'broker_cred'},
1016
+ {'id': 'cancel',
1017
+ 'type': 'OverseasStockCancelOrderNode',
1018
+ 'original_order_id': '123456',
1019
+ 'symbol': 'AAPL',
1020
+ 'exchange': 'NASDAQ'}],
1021
+ 'edges': [{'from': 'start', 'to': 'broker'}, {'from': 'broker', 'to': 'cancel'}],
1022
+ 'credentials': [{'credential_id': 'broker_cred',
1023
+ 'type': 'broker_ls_overseas_stock',
1024
+ 'data': [{'key': 'appkey',
1025
+ 'value': '',
1026
+ 'type': 'password',
1027
+ 'label': 'App Key'},
1028
+ {'key': 'appsecret',
1029
+ 'value': '',
1030
+ 'type': 'password',
1031
+ 'label': 'App Secret'}]}]},
1032
+ 'expected_output': 'cancel_result.status=accepted and confirmation_pending=true. Preserve both order '
1033
+ 'numbers and wait for matching broker completion evidence.'},
1035
1034
  ]
1036
1035
  _node_guide: ClassVar[Dict[str, Any]] = {
1037
1036
  "input_handling": (
@@ -1040,19 +1039,24 @@ class OverseasStockCancelOrderNode(BaseModifyOrderNode):
1040
1039
  "The broker connection is auto-injected via DAG traversal."
1041
1040
  ),
1042
1041
  "output_consumption": (
1043
- "Two ports: `cancel_result` (full cancellation confirmation dict) and `cancelled_order_id` (string). "
1044
- "Wire cancel_result to TableDisplayNode for audit logging, or use cancelled_order_id in downstream logic."
1042
+ "cancel_result.success=true and status='accepted' mean the cancel request was acknowledged; "
1043
+ "confirmation_pending=true means completion is not established. cancelled_order_id is a legacy "
1044
+ "alias for the original target, not proof of cancellation. The auxiliary cancelled_order.status "
1045
+ "is 'cancel_requested'. Match broker cancellation/fill evidence before any replacement order."
1045
1046
  ),
1046
1047
  "common_combinations": [
1047
1048
  "OverseasStockOpenOrdersNode → OverseasStockCancelOrderNode (cancel all open orders)",
1048
- "OverseasStockCancelOrderNode → OverseasStockNewOrderNode (cancel then replace pattern)",
1049
1049
  "ScheduleNode → OverseasStockOpenOrdersNode → OverseasStockCancelOrderNode (EOD cleanup)",
1050
1050
  ],
1051
1051
  "pitfalls": [
1052
+ "A partial fill remains a position; cancel only the remaining quantity and never invent a sale or realized profit",
1053
+ "Absence from open orders can mean a fill or a cancellation; require matching account, original order and completion evidence",
1054
+ "Do not connect a replacement order directly to cancellation acknowledgement; wait for confirmed completion and refresh holdings",
1055
+ "An account-wide open-order list can include manual/other-strategy orders; automated cleanup must filter explicit workflow ownership",
1052
1056
  "Cancelling an already-filled order returns a broker error; check order status via OpenOrdersNode before cancelling",
1053
1057
  "fallback.mode='error' (default) will halt the workflow on a failed cancel — set to 'skip' for best-effort cancel-all patterns",
1054
1058
  "The 5-second rate-limit guard means large cancel batches take at minimum 5s per order",
1055
- "LS can return no error while leaving the order uncancelled (e.g. out-of-hours), so do not trust a clean response alone — confirm cancellation by re-querying OpenOrdersNode afterward",
1059
+ "A clean cancel response or a new cancel-order number proves acknowledgement only, never final cancellation",
1056
1060
  ],
1057
1061
  }
1058
1062
 
@@ -1073,7 +1077,7 @@ class OverseasStockCancelOrderNode(BaseModifyOrderNode):
1073
1077
  name="cancel_result",
1074
1078
  type="order_result",
1075
1079
  description="i18n:ports.cancel_result",
1076
- fields=ORDER_RESULT_FIELDS,
1080
+ fields=CANCEL_RESULT_FIELDS,
1077
1081
  ),
1078
1082
  OutputPort(
1079
1083
  name="cancelled_order_id",
@@ -1082,9 +1086,9 @@ class OverseasStockCancelOrderNode(BaseModifyOrderNode):
1082
1086
  ),
1083
1087
  ]
1084
1088
 
1085
- _version: ClassVar[str] = "1.0.1"
1086
- _updated_at: ClassVar[str] = "2026-06-01"
1087
- _change_note: ClassVar[Optional[str]] = "Document out-of-hours cancel silent no-op risk in AI metadata."
1089
+ _version: ClassVar[str] = "1.0.2"
1090
+ _updated_at: ClassVar[str] = "2026-09-16"
1091
+ _change_note: ClassVar[Optional[str]] = "Distinguish cancellation acknowledgement from completion; remove unconfirmed replacement examples."
1088
1092
 
1089
1093
  @classmethod
1090
1094
  def get_field_schema(cls) -> Dict[str, "FieldSchema"]:
@@ -1749,48 +1753,35 @@ class OverseasFuturesCancelOrderNode(BaseModifyOrderNode):
1749
1753
  }
1750
1754
  ],
1751
1755
  },
1752
- "expected_output": "cancel_result port: {order_id, symbol, exchange, status='cancelled'}; cancelled_order_id port: string.",
1753
- },
1754
- {
1755
- "title": "Cancel a specific futures order and replace with new price",
1756
- "description": "Cancel an existing limit order and immediately place a new one at a revised price.",
1757
- "workflow_snippet": {
1758
- "id": "overseas_futures_cancel_and_replace",
1759
- "name": "Futures Cancel and Replace",
1760
- "nodes": [
1761
- {"id": "start", "type": "StartNode"},
1762
- {"id": "broker", "type": "OverseasFuturesBrokerNode", "credential_id": "futures_cred", "paper_trading": False},
1763
- {"id": "open_orders", "type": "OverseasFuturesOpenOrdersNode"},
1764
- {"id": "cancel", "type": "OverseasFuturesCancelOrderNode",
1765
- "original_order_id": "{{ nodes.open_orders.open_orders[0].order_id }}",
1766
- "symbol": "NQM25",
1767
- "exchange": "CME"},
1768
- {"id": "new_order", "type": "OverseasFuturesNewOrderNode",
1769
- "side": "buy",
1770
- "order_type": "limit",
1771
- "order": {"symbol": "NQM25", "exchange": "CME", "quantity": 1, "price": 21100.0}},
1772
- ],
1773
- "edges": [
1774
- {"from": "start", "to": "broker"},
1775
- {"from": "broker", "to": "open_orders"},
1776
- {"from": "open_orders", "to": "cancel"},
1777
- {"from": "cancel", "to": "new_order"},
1778
- {"from": "broker", "to": "cancel"},
1779
- {"from": "broker", "to": "new_order"},
1780
- ],
1781
- "credentials": [
1782
- {
1783
- "credential_id": "futures_cred",
1784
- "type": "broker_ls_overseas_futureoption",
1785
- "data": [
1786
- {"key": "appkey", "value": "", "type": "password", "label": "App Key"},
1787
- {"key": "appsecret", "value": "", "type": "password", "label": "App Secret"},
1788
- ],
1789
- }
1790
- ],
1791
- },
1792
- "expected_output": "cancel_result with status='cancelled', then new order result with status='accepted'.",
1756
+ "expected_output": "cancel_result: {success: true, status: accepted, confirmation_pending: true, order_id, cancel_order_no}; cancelled_order_id is a legacy alias for the target original order, not proof of cancellation.",
1793
1757
  },
1758
+ {'title': 'Request cancellation of an explicitly verified order',
1759
+ 'description': 'Replace the illustrative order number and symbol only after verifying the current account '
1760
+ 'and workflow ownership. Stop after acknowledgement; no replacement order is submitted.',
1761
+ 'workflow_snippet': {'id': 'OverseasFuturesCancelOrderNode_explicit_cancel',
1762
+ 'name': 'Explicit cancellation request',
1763
+ 'nodes': [{'id': 'start', 'type': 'StartNode'},
1764
+ {'id': 'broker',
1765
+ 'type': 'OverseasFuturesBrokerNode',
1766
+ 'credential_id': 'broker_cred'},
1767
+ {'id': 'cancel',
1768
+ 'type': 'OverseasFuturesCancelOrderNode',
1769
+ 'original_order_id': '123456',
1770
+ 'symbol': 'OWNER_CONFIRMED_CONTRACT',
1771
+ 'exchange': 'CME'}],
1772
+ 'edges': [{'from': 'start', 'to': 'broker'}, {'from': 'broker', 'to': 'cancel'}],
1773
+ 'credentials': [{'credential_id': 'broker_cred',
1774
+ 'type': 'broker_ls_overseas_futureoption',
1775
+ 'data': [{'key': 'appkey',
1776
+ 'value': '',
1777
+ 'type': 'password',
1778
+ 'label': 'App Key'},
1779
+ {'key': 'appsecret',
1780
+ 'value': '',
1781
+ 'type': 'password',
1782
+ 'label': 'App Secret'}]}]},
1783
+ 'expected_output': 'cancel_result.status=accepted and confirmation_pending=true. Preserve both order '
1784
+ 'numbers and wait for matching broker completion evidence.'},
1794
1785
  ]
1795
1786
  _node_guide: ClassVar[Dict[str, Any]] = {
1796
1787
  "input_handling": (
@@ -1799,19 +1790,24 @@ class OverseasFuturesCancelOrderNode(BaseModifyOrderNode):
1799
1790
  "The broker connection is auto-injected via DAG traversal from OverseasFuturesBrokerNode."
1800
1791
  ),
1801
1792
  "output_consumption": (
1802
- "Two ports: `cancel_result` (cancellation confirmation dict) and `cancelled_order_id` (string). "
1803
- "Chain cancel_result to TableDisplayNode for audit logging, or sequence cancelled_order_id into a new order node."
1793
+ "cancel_result.success=true and status='accepted' mean the cancel request was acknowledged; "
1794
+ "confirmation_pending=true means completion is not established. cancelled_order_id is a legacy "
1795
+ "alias for the original target, not proof of cancellation. The auxiliary cancelled_order.status "
1796
+ "is 'cancel_requested'. Match broker cancellation/fill evidence before any replacement order."
1804
1797
  ),
1805
1798
  "common_combinations": [
1806
1799
  "OverseasFuturesOpenOrdersNode → OverseasFuturesCancelOrderNode (cancel all open futures)",
1807
- "OverseasFuturesCancelOrderNode → OverseasFuturesNewOrderNode (cancel then replace pattern)",
1808
1800
  "ScheduleNode → OverseasFuturesOpenOrdersNode → OverseasFuturesCancelOrderNode (session-end cleanup)",
1809
1801
  ],
1810
1802
  "pitfalls": [
1803
+ "A partial fill remains a position; cancel only the remaining quantity and never invent a sale or realized profit",
1804
+ "Absence from open orders can mean a fill or a cancellation; require matching account, original order and completion evidence",
1805
+ "Do not connect a replacement order directly to cancellation acknowledgement; wait for confirmed completion and refresh holdings",
1806
+ "An account-wide open-order list can include manual/other-strategy orders; automated cleanup must filter explicit workflow ownership",
1811
1807
  "Cancelling an already-filled futures order returns a broker error — check order status first via OpenOrdersNode",
1812
1808
  "fallback.mode='error' (default) halts workflow on cancel failure — use 'skip' for best-effort cancel-all loops",
1813
1809
  "The 5-second rate-limit guard means a 10-order cancel batch takes at least 50 seconds",
1814
- "LS can return no error while leaving the futures order uncancelled (e.g. out-of-hours; trading hours vary by exchange CME/EUREX/SGX/HKEX), so confirm cancellation by re-querying OpenOrdersNode afterward rather than trusting a clean response",
1810
+ "A clean cancel response or a new cancel-order number proves acknowledgement only, never final cancellation",
1815
1811
  ],
1816
1812
  }
1817
1813
 
@@ -1832,7 +1828,7 @@ class OverseasFuturesCancelOrderNode(BaseModifyOrderNode):
1832
1828
  name="cancel_result",
1833
1829
  type="order_result",
1834
1830
  description="i18n:ports.cancel_result",
1835
- fields=ORDER_RESULT_FIELDS,
1831
+ fields=CANCEL_RESULT_FIELDS,
1836
1832
  ),
1837
1833
  OutputPort(
1838
1834
  name="cancelled_order_id",
@@ -1841,9 +1837,9 @@ class OverseasFuturesCancelOrderNode(BaseModifyOrderNode):
1841
1837
  ),
1842
1838
  ]
1843
1839
 
1844
- _version: ClassVar[str] = "1.0.1"
1845
- _updated_at: ClassVar[str] = "2026-06-01"
1846
- _change_note: ClassVar[Optional[str]] = "Document out-of-hours cancel silent no-op risk in AI metadata."
1840
+ _version: ClassVar[str] = "1.0.2"
1841
+ _updated_at: ClassVar[str] = "2026-09-16"
1842
+ _change_note: ClassVar[Optional[str]] = "Distinguish cancellation acknowledgement from completion; remove unconfirmed replacement examples."
1847
1843
 
1848
1844
  @classmethod
1849
1845
  def get_field_schema(cls) -> Dict[str, "FieldSchema"]:
@@ -2444,7 +2440,7 @@ class KoreaStockCancelOrderNode(BaseModifyOrderNode):
2444
2440
  "typical_scenarios": [
2445
2441
  "ScheduleNode (end-of-day) → KoreaStockOpenOrdersNode → KoreaStockCancelOrderNode (cancel-all)",
2446
2442
  "KoreaStockRealOrderEventNode (timeout) → KoreaStockCancelOrderNode (stale order cleanup)",
2447
- "IfNode (stop-loss triggered) → KoreaStockCancelOrderNode → KoreaStockNewOrderNode (cancel then exit)",
2443
+ "IfNode (stop-loss triggered) → KoreaStockCancelOrderNode (request cancellation; verify completion separately)",
2448
2444
  ],
2449
2445
  }
2450
2446
  _features: ClassVar[List[str]] = [
@@ -2506,47 +2502,34 @@ class KoreaStockCancelOrderNode(BaseModifyOrderNode):
2506
2502
  }
2507
2503
  ],
2508
2504
  },
2509
- "expected_output": "cancel_result port: {order_id, symbol, status='cancelled'}; cancelled_order_id port: string.",
2510
- },
2511
- {
2512
- "title": "Cancel a specific order and place exit market order",
2513
- "description": "Stop-loss condition triggers: cancel the open limit buy and immediately place a market sell to exit.",
2514
- "workflow_snippet": {
2515
- "id": "korea_stock_cancel_and_exit",
2516
- "name": "Korea Stock Cancel and Exit",
2517
- "nodes": [
2518
- {"id": "start", "type": "StartNode"},
2519
- {"id": "broker", "type": "KoreaStockBrokerNode", "credential_id": "kr_broker_cred"},
2520
- {"id": "open_orders", "type": "KoreaStockOpenOrdersNode"},
2521
- {"id": "cancel", "type": "KoreaStockCancelOrderNode",
2522
- "original_order_id": "{{ nodes.open_orders.open_orders[0].order_id }}",
2523
- "symbol": "005930"},
2524
- {"id": "sell", "type": "KoreaStockNewOrderNode",
2525
- "side": "sell",
2526
- "order_type": "market",
2527
- "order": {"symbol": "005930", "quantity": 10}},
2528
- ],
2529
- "edges": [
2530
- {"from": "start", "to": "broker"},
2531
- {"from": "broker", "to": "open_orders"},
2532
- {"from": "open_orders", "to": "cancel"},
2533
- {"from": "cancel", "to": "sell"},
2534
- {"from": "broker", "to": "cancel"},
2535
- {"from": "broker", "to": "sell"},
2536
- ],
2537
- "credentials": [
2538
- {
2539
- "credential_id": "kr_broker_cred",
2540
- "type": "broker_ls_korea_stock",
2541
- "data": [
2542
- {"key": "appkey", "value": "", "type": "password", "label": "App Key"},
2543
- {"key": "appsecret", "value": "", "type": "password", "label": "App Secret"},
2544
- ],
2545
- }
2546
- ],
2547
- },
2548
- "expected_output": "cancel_result with status='cancelled', then sell result with status='accepted' market order.",
2505
+ "expected_output": "cancel_result: {success: true, status: accepted, confirmation_pending: true, order_id, cancel_order_no}; cancelled_order_id is a legacy alias for the target original order, not proof of cancellation.",
2549
2506
  },
2507
+ {'title': 'Request cancellation of an explicitly verified order',
2508
+ 'description': 'Replace the illustrative order number and symbol only after verifying the current account '
2509
+ 'and workflow ownership. Stop after acknowledgement; no replacement order is submitted.',
2510
+ 'workflow_snippet': {'id': 'KoreaStockCancelOrderNode_explicit_cancel',
2511
+ 'name': 'Explicit cancellation request',
2512
+ 'nodes': [{'id': 'start', 'type': 'StartNode'},
2513
+ {'id': 'broker',
2514
+ 'type': 'KoreaStockBrokerNode',
2515
+ 'credential_id': 'broker_cred'},
2516
+ {'id': 'cancel',
2517
+ 'type': 'KoreaStockCancelOrderNode',
2518
+ 'original_order_id': '123456',
2519
+ 'symbol': '005930'}],
2520
+ 'edges': [{'from': 'start', 'to': 'broker'}, {'from': 'broker', 'to': 'cancel'}],
2521
+ 'credentials': [{'credential_id': 'broker_cred',
2522
+ 'type': 'broker_ls_korea_stock',
2523
+ 'data': [{'key': 'appkey',
2524
+ 'value': '',
2525
+ 'type': 'password',
2526
+ 'label': 'App Key'},
2527
+ {'key': 'appsecret',
2528
+ 'value': '',
2529
+ 'type': 'password',
2530
+ 'label': 'App Secret'}]}]},
2531
+ 'expected_output': 'cancel_result.status=accepted and confirmation_pending=true. Preserve both order '
2532
+ 'numbers and wait for matching broker completion evidence.'},
2550
2533
  ]
2551
2534
  _node_guide: ClassVar[Dict[str, Any]] = {
2552
2535
  "input_handling": (
@@ -2556,19 +2539,24 @@ class KoreaStockCancelOrderNode(BaseModifyOrderNode):
2556
2539
  "Requires KoreaStockBrokerNode upstream (auto-injected by executor)."
2557
2540
  ),
2558
2541
  "output_consumption": (
2559
- "Two ports: `cancel_result` (cancellation confirmation dict) and `cancelled_order_id` (string). "
2560
- "Chain cancel_result to TableDisplayNode for audit logging, or sequence into a new order node."
2542
+ "cancel_result.success=true and status='accepted' mean the cancel request was acknowledged; "
2543
+ "confirmation_pending=true means completion is not established. cancelled_order_id is a legacy "
2544
+ "alias for the original target, not proof of cancellation. The auxiliary cancelled_order.status "
2545
+ "is 'cancel_requested'. Match broker cancellation/fill evidence before any replacement order."
2561
2546
  ),
2562
2547
  "common_combinations": [
2563
2548
  "KoreaStockOpenOrdersNode → KoreaStockCancelOrderNode (cancel all open KR orders)",
2564
- "KoreaStockCancelOrderNode → KoreaStockNewOrderNode (cancel then replace pattern)",
2565
2549
  "ScheduleNode → KoreaStockOpenOrdersNode → KoreaStockCancelOrderNode (EOD cleanup)",
2566
2550
  ],
2567
2551
  "pitfalls": [
2552
+ "A partial fill remains a position; cancel only the remaining quantity and never invent a sale or realized profit",
2553
+ "Absence from open orders can mean a fill or a cancellation; require matching account, original order and completion evidence",
2554
+ "Do not connect a replacement order directly to cancellation acknowledgement; wait for confirmed completion and refresh holdings",
2555
+ "An account-wide open-order list can include manual/other-strategy orders; automated cleanup must filter explicit workflow ownership",
2568
2556
  "Cancelling an already-filled Korean stock order returns a KRX error — check status via OpenOrdersNode first",
2569
2557
  "Paper trading is NOT supported for Korean domestic stocks — do not configure KoreaStockBrokerNode in paper mode",
2570
2558
  "fallback.mode='error' (default) halts workflow on cancel failure — use 'skip' for best-effort cancel-all loops",
2571
- "LS can return no error while leaving the order uncancelled (e.g. outside KRX trading hours 09:00-15:30 KST), so confirm cancellation by re-querying OpenOrdersNode afterward rather than trusting a clean response",
2559
+ "A clean cancel response or a new cancel-order number proves acknowledgement only, never final cancellation",
2572
2560
  ],
2573
2561
  }
2574
2562
 
@@ -2589,7 +2577,7 @@ class KoreaStockCancelOrderNode(BaseModifyOrderNode):
2589
2577
  name="cancel_result",
2590
2578
  type="order_result",
2591
2579
  description="i18n:ports.cancel_result",
2592
- fields=ORDER_RESULT_FIELDS,
2580
+ fields=CANCEL_RESULT_FIELDS,
2593
2581
  ),
2594
2582
  OutputPort(
2595
2583
  name="cancelled_order_id",
@@ -2598,9 +2586,9 @@ class KoreaStockCancelOrderNode(BaseModifyOrderNode):
2598
2586
  ),
2599
2587
  ]
2600
2588
 
2601
- _version: ClassVar[str] = "1.0.1"
2602
- _updated_at: ClassVar[str] = "2026-06-01"
2603
- _change_note: ClassVar[Optional[str]] = "Document out-of-hours cancel silent no-op risk in AI metadata."
2589
+ _version: ClassVar[str] = "1.0.2"
2590
+ _updated_at: ClassVar[str] = "2026-09-16"
2591
+ _change_note: ClassVar[Optional[str]] = "Distinguish cancellation acknowledgement from completion; remove unconfirmed replacement examples."
2604
2592
 
2605
2593
  @classmethod
2606
2594
  def get_field_schema(cls) -> Dict[str, "FieldSchema"]:
@@ -5,7 +5,7 @@ authors = [
5
5
  homepage = "https://programgarden.com"
6
6
  requires-python = ">=3.12"
7
7
  name = "programgarden-core"
8
- version = "1.30.0"
8
+ version = "1.30.1"
9
9
  license = "AGPL-3.0-or-later"
10
10
  description = "ProgramGarden Core - 노드 기반 DSL 핵심 타입 정의"
11
11
  readme = "README.md"