programgarden 2.6.3__tar.gz → 2.6.4__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 (70) hide show
  1. {programgarden-2.6.3 → programgarden-2.6.4}/PKG-INFO +5 -1
  2. {programgarden-2.6.3 → programgarden-2.6.4}/README.md +4 -0
  3. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/broker_evidence.py +42 -5
  4. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/executor.py +6 -1
  5. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/futures_read_evidence.py +27 -8
  6. {programgarden-2.6.3 → programgarden-2.6.4}/pyproject.toml +1 -1
  7. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/__init__.py +0 -0
  8. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/binding_validator.py +0 -0
  9. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/client.py +0 -0
  10. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/code_worker.py +0 -0
  11. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/context.py +0 -0
  12. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/__init__.py +0 -0
  13. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/adjustment_delivery.py +0 -0
  14. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/broker_order_totals.py +0 -0
  15. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/broker_snapshot.py +0 -0
  16. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/checkpoint_manager.py +0 -0
  17. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/db_naming.py +0 -0
  18. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/execution_storage.py +0 -0
  19. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/fill_reconciler.py +0 -0
  20. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/order_cancellation.py +0 -0
  21. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/order_recovery.py +0 -0
  22. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/owned_order_cancellation.py +0 -0
  23. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/position_reconciliation.py +0 -0
  24. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/query_builder.py +0 -0
  25. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/sqlite_scope.py +0 -0
  26. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/workflow_position_tracker.py +0 -0
  27. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/database/workflow_risk_tracker.py +0 -0
  28. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/deep_fixtures.py +0 -0
  29. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/futures_contract_info.py +0 -0
  30. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/futures_history.py +0 -0
  31. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/futures_orderable.py +0 -0
  32. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/futures_pnl.py +0 -0
  33. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/incremental_build.py +0 -0
  34. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/managed_order_control.py +0 -0
  35. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/node_runner.py +0 -0
  36. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/order_lifecycle.py +0 -0
  37. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/paper_futures_business_date.py +0 -0
  38. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/plugin/__init__.py +0 -0
  39. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/plugin/sandbox.py +0 -0
  40. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/providers/__init__.py +0 -0
  41. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/providers/llm_errors.py +0 -0
  42. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/providers/llm_provider.py +0 -0
  43. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/reconnect_handler.py +0 -0
  44. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/replay_contracts.py +0 -0
  45. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/replay_events.py +0 -0
  46. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/replay_external.py +0 -0
  47. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/replay_order_adapter.py +0 -0
  48. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/replay_orders.py +0 -0
  49. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/replay_scenarios.py +0 -0
  50. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/replay_semantics.py +0 -0
  51. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/replay_sources.py +0 -0
  52. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/replay_sqlite.py +0 -0
  53. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/replay_triggers.py +0 -0
  54. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/resolver.py +0 -0
  55. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/resource/__init__.py +0 -0
  56. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/resource/context.py +0 -0
  57. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/resource/limiter.py +0 -0
  58. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/resource/monitor.py +0 -0
  59. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/resource/throttle.py +0 -0
  60. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/semantic_rules.py +0 -0
  61. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/tools/__init__.py +0 -0
  62. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/tools/credential_tools.py +0 -0
  63. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/tools/definition_tools.py +0 -0
  64. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/tools/event_tools.py +0 -0
  65. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/tools/job_tools.py +0 -0
  66. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/tools/registry_tools.py +0 -0
  67. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/tools/sqlite_tools.py +0 -0
  68. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/validation_recommender.py +0 -0
  69. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/validation_replay.py +0 -0
  70. {programgarden-2.6.3 → programgarden-2.6.4}/programgarden/valuation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programgarden
3
- Version: 2.6.3
3
+ Version: 2.6.4
4
4
  Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Author: 프로그램동산
@@ -31,6 +31,10 @@ The 2.6.2 release candidate [futures minute alias correction](../../docs/futures
31
31
 
32
32
  # ProgramGarden
33
33
 
34
+ The 2.6.4 release candidate [paper futures account correction](../../docs/paper-futures-business-date.md#account-node-and-rejection-messages-264-candidate)
35
+ returns an empty futures account for the paper empty-account answer and names
36
+ the broker message in unaccepted response errors. Live behavior is unchanged.
37
+
34
38
  The 2.6.3 release candidate [paper futures business-date correction](../../docs/paper-futures-business-date.md)
35
39
  uses a fresh account-scoped broker date for startup pending orders, OpenOrdersNode
36
40
  and legacy fill observation. Missing or mismatched evidence remains unavailable.
@@ -2,6 +2,10 @@ The 2.6.2 release candidate [futures minute alias correction](../../docs/futures
2
2
 
3
3
  # ProgramGarden
4
4
 
5
+ The 2.6.4 release candidate [paper futures account correction](../../docs/paper-futures-business-date.md#account-node-and-rejection-messages-264-candidate)
6
+ returns an empty futures account for the paper empty-account answer and names
7
+ the broker message in unaccepted response errors. Live behavior is unchanged.
8
+
5
9
  The 2.6.3 release candidate [paper futures business-date correction](../../docs/paper-futures-business-date.md)
6
10
  uses a fresh account-scoped broker date for startup pending orders, OpenOrdersNode
7
11
  and legacy fill observation. Missing or mismatched evidence remains unavailable.
@@ -5,6 +5,7 @@ not turn a missing detail block, partial page or rejection into an empty account
5
5
  """
6
6
 
7
7
  import inspect
8
+ import unicodedata
8
9
  from decimal import Decimal, InvalidOperation
9
10
  from datetime import datetime, timedelta, timezone
10
11
 
@@ -30,10 +31,43 @@ _OBSERVED_RESPONSE_CODES = {
30
31
  }
31
32
 
32
33
 
34
+ _BROKER_MESSAGE_LIMIT = 80
35
+
36
+ # Exact empty-result wording observed with rsp_cd 02679 on the stock reads below
37
+ # and on paper CIDBQ01500/CIDBQ02400 reads (2026-10-14). Not a generic success.
38
+ NO_RESULT_MESSAGE = "조회내역이 없습니다."
39
+ # The paper server's own empty-result wording, observed with rsp_cd 00707 on
40
+ # the two futures TRs below.
41
+ PAPER_NO_RESULT_MESSAGE = "모의투자 조회할 내역(자료)이 없습니다."
42
+ # The paper CIDBQ01500/CIDBQ02400 02679 branch accepts either exact wording:
43
+ # the same set the dsl-api startup gate uses for these two TRs
44
+ # (cloudrun/dsl-api/app/services/startup_broker.py, _NO_DATA_MESSAGES), so a
45
+ # start the server admits is not rejected by the engine. Stock branches keep
46
+ # the single NO_RESULT_MESSAGE, as the server gate does.
47
+ PAPER_FUTURES_NO_RESULT_MESSAGES = frozenset({NO_RESULT_MESSAGE, PAPER_NO_RESULT_MESSAGE})
48
+
49
+
33
50
  class StartupEvidenceUnavailable(RuntimeError):
34
51
  """An incomplete response must never authorize execution."""
35
52
 
36
53
 
54
+ def broker_message(value):
55
+ """Return the broker's diagnostic text as one bounded log-safe line.
56
+
57
+ Matches the server startup gate's ``str(rsp_msg or '')[:80]`` for ordinary
58
+ messages; control/format characters become spaces and outer whitespace is
59
+ stripped so a broker message cannot inject extra log lines.
60
+ """
61
+ text = "".join(" " if unicodedata.category(char).startswith("C") else char
62
+ for char in str(value or ""))
63
+ return text.strip()[:_BROKER_MESSAGE_LIMIT]
64
+
65
+
66
+ def unverified_response_code(tr, code, message):
67
+ """Keep the stable ``<TR>: unverified_response_code=<code>`` prefix."""
68
+ return f"{tr}: unverified_response_code={code} rsp_msg={broker_message(message)}"
69
+
70
+
37
71
  class StartupQueryDateMismatch(StartupEvidenceUnavailable):
38
72
  def __init__(self, business_day):
39
73
  super().__init__("COSAQ00102: query_echo_mismatch:OrdDt")
@@ -95,22 +129,25 @@ async def checked_body(response, tr, request=None, *, paper_trading=False):
95
129
  if tr == "COSOQ00201" and code == "02679":
96
130
  # Observed authenticated empty stock balance, 2026-09-21. Explicit
97
131
  # blocks, terminal pagination and matching query scope remain required.
98
- accepted = not rows and response.rsp_msg == "조회내역이 없습니다."
132
+ accepted = not rows and response.rsp_msg == NO_RESULT_MESSAGE
99
133
  if tr == "COSAQ00102" and code == "02679":
100
134
  echo = body["COSAQ00102OutBlock1"]
101
135
  accepted = (not rows and isinstance(echo, dict)
102
- and response.rsp_msg == "조회내역이 없습니다."
136
+ and response.rsp_msg == NO_RESULT_MESSAGE
103
137
  and echo.get("OrdMktCode") == "%"
104
138
  and echo.get("ThdayBnsAppYn") == "1"
105
139
  and echo.get("BnsTpCode") == "0" and echo.get("CrcyCode") == "000")
106
140
  elif tr in {"CIDBQ01500", "CIDBQ02400"} and code == "00707":
107
- accepted = (not rows and response.rsp_msg == "모의투자 조회할 내역(자료)이 없습니다.")
141
+ accepted = (not rows and response.rsp_msg == PAPER_NO_RESULT_MESSAGE)
108
142
  elif paper_trading is True and tr in {"CIDBQ01500", "CIDBQ02400"} and code == "02679":
109
143
  # Observed paper response with complete raw blocks, explicit empty rows
110
144
  # and terminal continuation. The caller still binds date/account scope.
111
- accepted = not rows and response.rsp_msg == "조회내역이 없습니다."
145
+ # The wording set matches the server startup gate for these two TRs.
146
+ accepted = not rows and response.rsp_msg in PAPER_FUTURES_NO_RESULT_MESSAGES
112
147
  if not accepted:
113
- raise StartupEvidenceUnavailable(f"{tr}: unverified_response_code={code}")
148
+ # The broker message distinguishes, for example, an unrecognized empty
149
+ # wording from a rejected query; the stable reason prefix is unchanged.
150
+ raise StartupEvidenceUnavailable(unverified_response_code(tr, code, response.rsp_msg))
114
151
  if tr in {"t0424", "t0425"}:
115
152
  cursor = "cts_expcode" if tr == "t0424" else "cts_ordno"
116
153
  summary = body[_BLOCKS[tr][0]]
@@ -6142,6 +6142,7 @@ class AccountNodeExecutor(NodeExecutorBase):
6142
6142
  from datetime import datetime
6143
6143
  from zoneinfo import ZoneInfo
6144
6144
  from programgarden.futures_read_evidence import require_complete_rows, require_position_identity
6145
+ from programgarden.paper_futures_business_date import is_paper_client
6145
6146
 
6146
6147
  try:
6147
6148
  from programgarden_finance.ls.overseas_futureoption.accno.CIDBQ01500.blocks import CIDBQ01500InBlock1
@@ -6157,7 +6158,11 @@ class AccountNodeExecutor(NodeExecutorBase):
6157
6158
  response = await ls.overseas_futureoption().accno().CIDBQ01500(
6158
6159
  body=position_request,
6159
6160
  ).req_async()
6160
- position_rows = require_complete_rows(response, position_request, "CIDBQ01500")
6161
+ # A paper server answers an empty account with 02679; only that
6162
+ # exact empty envelope is accepted, and only for a paper client.
6163
+ position_rows = require_complete_rows(
6164
+ response, position_request, "CIDBQ01500", paper_trading=is_paper_client(ls),
6165
+ )
6161
6166
 
6162
6167
  # block2 = 종목별 잔고 (list 형태로 변환, NewOrderNode 호환)
6163
6168
  positions = []
@@ -2,19 +2,40 @@
2
2
 
3
3
  import math
4
4
 
5
+ from programgarden.database.broker_evidence import (
6
+ PAPER_FUTURES_NO_RESULT_MESSAGES,
7
+ unverified_response_code,
8
+ )
5
9
 
6
- def require_complete_rows(response, request, tr):
10
+ _NO_DATA_TRS = {"CIDBQ01500", "CIDBQ02400"}
11
+
12
+
13
+ def require_complete_rows(response, request, tr, *, paper_trading=False):
7
14
  """Validate the observed terminal envelope and return its detail rows.
8
15
 
9
16
  00707 is accepted only for an explicitly empty, matching terminal response
10
17
  for the two position/pending TRs observed on the paper server. It is not a
11
18
  generic success code. Continuation pages remain unavailable in this reader.
19
+
20
+ For a paper client only, 02679 with one of the exact paper empty-result
21
+ messages (the startup gate's set for these two TRs) and explicitly empty
22
+ rows is the same complete empty result on those two TRs. Live clients and
23
+ other TRs still reject it.
12
24
  """
25
+ if response.status_code != 200 or response.error_msg:
26
+ raise ValueError(f"{tr} did not return a complete terminal response")
27
+ code = response.rsp_cd
28
+ accepted = code in {"00000", "00136", "00707"} or (
29
+ paper_trading is True
30
+ and tr in _NO_DATA_TRS
31
+ and code == "02679"
32
+ and not response.block2
33
+ and response.rsp_msg in PAPER_FUTURES_NO_RESULT_MESSAGES
34
+ )
35
+ if not accepted:
36
+ raise ValueError(unverified_response_code(tr, code, response.rsp_msg))
13
37
  if (
14
- response.status_code != 200
15
- or response.error_msg
16
- or response.rsp_cd not in {"00000", "00136", "00707"}
17
- or response.header is None
38
+ response.header is None
18
39
  or response.header.tr_cd not in {tr, "CIDBQ"}
19
40
  or response.header.tr_cont != "N"
20
41
  or response.header.tr_cont_key
@@ -29,9 +50,7 @@ def require_complete_rows(response, request, tr):
29
50
  or any(getattr(echo, field) != value for field, value in expected.items())
30
51
  ):
31
52
  raise ValueError(f"{tr} query echo is missing or mismatched")
32
- if response.rsp_cd == "00707" and (
33
- tr not in {"CIDBQ01500", "CIDBQ02400"} or response.block2
34
- ):
53
+ if code == "00707" and (tr not in _NO_DATA_TRS or response.block2):
35
54
  raise ValueError(f"{tr} no-data response is inconsistent")
36
55
  return response.block2
37
56
 
@@ -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.3"
8
+ version = "2.6.4"
9
9
  license = "AGPL-3.0-or-later"
10
10
  description = "ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진"
11
11
  readme = "README.md"