programgarden 2.7.0__tar.gz → 2.7.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 (75) hide show
  1. {programgarden-2.7.0 → programgarden-2.7.2}/PKG-INFO +1 -1
  2. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/broker_snapshot.py +6 -3
  3. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/paper_futures_business_date.py +66 -11
  4. {programgarden-2.7.0 → programgarden-2.7.2}/pyproject.toml +1 -1
  5. {programgarden-2.7.0 → programgarden-2.7.2}/README.md +0 -0
  6. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/__init__.py +0 -0
  7. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/binding_provenance.py +0 -0
  8. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/binding_validator.py +0 -0
  9. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/client.py +0 -0
  10. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/code_worker.py +0 -0
  11. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/context.py +0 -0
  12. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/__init__.py +0 -0
  13. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/adjustment_delivery.py +0 -0
  14. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/broker_evidence.py +0 -0
  15. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/broker_order_totals.py +0 -0
  16. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/checkpoint_manager.py +0 -0
  17. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/db_naming.py +0 -0
  18. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/execution_storage.py +0 -0
  19. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/fill_reconciler.py +0 -0
  20. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/order_cancellation.py +0 -0
  21. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/order_recovery.py +0 -0
  22. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/owned_order_cancellation.py +0 -0
  23. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/position_reconciliation.py +0 -0
  24. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/query_builder.py +0 -0
  25. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/sqlite_scope.py +0 -0
  26. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/workflow_position_tracker.py +0 -0
  27. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/database/workflow_risk_tracker.py +0 -0
  28. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/deep_fixtures.py +0 -0
  29. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/executor.py +0 -0
  30. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/futures_contract_info.py +0 -0
  31. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/futures_exchange.py +0 -0
  32. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/futures_history.py +0 -0
  33. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/futures_orderable.py +0 -0
  34. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/futures_pnl.py +0 -0
  35. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/futures_read_evidence.py +0 -0
  36. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/incremental_build.py +0 -0
  37. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/managed_order_control.py +0 -0
  38. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/node_runner.py +0 -0
  39. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/order_lifecycle.py +0 -0
  40. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/plugin/__init__.py +0 -0
  41. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/plugin/sandbox.py +0 -0
  42. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/providers/__init__.py +0 -0
  43. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/providers/llm_errors.py +0 -0
  44. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/providers/llm_provider.py +0 -0
  45. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/ranking_nodes.py +0 -0
  46. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/reconnect_handler.py +0 -0
  47. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/replay_contracts.py +0 -0
  48. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/replay_events.py +0 -0
  49. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/replay_external.py +0 -0
  50. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/replay_order_adapter.py +0 -0
  51. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/replay_orders.py +0 -0
  52. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/replay_responders.py +0 -0
  53. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/replay_scenarios.py +0 -0
  54. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/replay_semantics.py +0 -0
  55. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/replay_sources.py +0 -0
  56. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/replay_sqlite.py +0 -0
  57. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/replay_tape.py +0 -0
  58. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/replay_triggers.py +0 -0
  59. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/resolver.py +0 -0
  60. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/resource/__init__.py +0 -0
  61. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/resource/context.py +0 -0
  62. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/resource/limiter.py +0 -0
  63. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/resource/monitor.py +0 -0
  64. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/resource/throttle.py +0 -0
  65. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/semantic_rules.py +0 -0
  66. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/tools/__init__.py +0 -0
  67. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/tools/credential_tools.py +0 -0
  68. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/tools/definition_tools.py +0 -0
  69. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/tools/event_tools.py +0 -0
  70. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/tools/job_tools.py +0 -0
  71. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/tools/registry_tools.py +0 -0
  72. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/tools/sqlite_tools.py +0 -0
  73. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/validation_recommender.py +0 -0
  74. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/validation_replay.py +0 -0
  75. {programgarden-2.7.0 → programgarden-2.7.2}/programgarden/valuation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programgarden
3
- Version: 2.7.0
3
+ Version: 2.7.2
4
4
  Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Author: 프로그램동산
@@ -61,19 +61,22 @@ async def read_broker_snapshot(ls, *, execution_key, product, provider, trading_
61
61
  from programgarden_finance.ls.korea_stock.extension.valuation import collect_domestic_trading_positions
62
62
  from programgarden_finance.ls.korea_stock.extension.account_queries import collect_domestic_open_orders
63
63
 
64
+ # The collector returns t0424 rows (trade-basis balance, chegb=2), not
65
+ # CSPAQ12300 rows: the symbol is expcode and the held quantity janqty,
66
+ # the same fields the server startup gate reads.
64
67
  _, rows = await collect_domestic_trading_positions(ls.korea_stock().accno())
65
68
  for row in rows:
66
- symbol = normalized_symbol(str(required(row, "IsuNo")), product)
69
+ symbol = normalized_symbol(str(required(row, "expcode")), product)
67
70
  if symbol in positions:
68
71
  raise ReconciliationUnavailable("ambiguous_position_rows")
69
- positions[symbol] = quantity(required(row, "BnsBaseBalQty"))
72
+ positions[symbol] = quantity(required(row, "janqty"))
70
73
  await asyncio.sleep(2)
71
74
  pending = await collect_domestic_open_orders(ls.korea_stock().accno())
72
75
  # t0425 is the documented current-day inquiry. Retain that observation
73
76
  # date for exact ledger matching; never widen it to yesterday's order.
74
77
  for row in pending:
75
78
  row["order_date"] = day
76
- source = "CSPAQ12300+t0425"
79
+ source = "t0424+t0425"
77
80
  elif product == "overseas_stock":
78
81
  from programgarden_finance import COSOQ00201, COSAQ00102
79
82
 
@@ -3,14 +3,46 @@
3
3
  The published same-day blank-date contract conflicts with an observed paper
4
4
  response. This adapter uses CIDBQ01500's explicit current-date echo, never a
5
5
  machine date, and does not cache it across calls or relax unavailable evidence.
6
+
7
+ When the paper server returns no business date at all (observed on weekends,
8
+ when its overseas futures day is closed), the order inquiry uses the published
9
+ blank-date same-day form instead, with the same response checks. This is the
10
+ form the server startup gate already uses for that answer, so a start the
11
+ server admits is not refused here.
6
12
  """
7
13
  from dataclasses import dataclass, field
8
14
  from datetime import datetime
9
15
  import hashlib
16
+ import logging
10
17
  import re
11
18
  import time
12
19
 
13
- from programgarden.database.broker_evidence import StartupEvidenceUnavailable, checked_body
20
+ from programgarden.database.broker_evidence import StartupEvidenceUnavailable, broker_message, checked_body
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ _RESPONSE_CODE_REASON = "CIDBQ02400: unverified_response_code="
25
+
26
+
27
+ def broker_business_day(value):
28
+ """The broker's explicit YYYYMMDD business date, or "" when none was given.
29
+
30
+ A blank, missing, padded or impossible value is "no date". It is never
31
+ replaced by a local, previous or cached date.
32
+ """
33
+ if not isinstance(value, str) or not re.fullmatch(r"[0-9]{8}", value):
34
+ return ""
35
+ try:
36
+ datetime.strptime(value, "%Y%m%d")
37
+ except ValueError:
38
+ return ""
39
+ return value
40
+
41
+
42
+ def blank_date_unverified(response):
43
+ """No business date, and the blank-date answer's code was not accepted."""
44
+ return (f"CIDBQ02400: blank_date_unverified rsp_cd={getattr(response, 'rsp_cd', None)} "
45
+ f"rsp_msg={broker_message(getattr(response, 'rsp_msg', None))}")
14
46
 
15
47
 
16
48
  def is_paper_client(ls):
@@ -42,19 +74,41 @@ class PaperFuturesScope:
42
74
 
43
75
  @dataclass(frozen=True)
44
76
  class PaperFuturesBusinessDate:
77
+ """One fresh CIDBQ01500 observation for the following CIDBQ02400 read.
78
+
79
+ ``day`` is the broker's explicit business date, or "" when the paper server
80
+ returned none; "" selects the published blank-date same-day query (both
81
+ dates blank). ``account`` is the positions echo; it is always present with
82
+ a date and may be "" only on a blank-date answer.
83
+ """
45
84
  day: str
46
85
  account: str = field(repr=False)
47
86
  scope: PaperFuturesScope = field(repr=False)
48
87
 
88
+ @property
89
+ def blank_date(self):
90
+ return not self.day
91
+
49
92
  async def check_orders(self, ls, response, query):
50
93
  self.scope.check(ls)
51
- body = await checked_body(response, "CIDBQ02400", query, paper_trading=True)
94
+ try:
95
+ body = await checked_body(response, "CIDBQ02400", query, paper_trading=True)
96
+ except StartupEvidenceUnavailable as exc:
97
+ # Without a business date the published blank-date form is the only
98
+ # query left; name that case, with the broker's answer, when its
99
+ # response code is not accepted. Other failures keep their reasons.
100
+ if self.blank_date and str(exc).startswith(_RESPONSE_CODE_REASON):
101
+ raise StartupEvidenceUnavailable(blank_date_unverified(response)) from None
102
+ raise
52
103
  echo = body["CIDBQ02400OutBlock1"]
53
104
  request = next(iter(query.request_data.body.values()))
54
105
  expected = request.model_dump(exclude={"RecCnt"})
106
+ # A dated observation always carries its account; a blank-date one binds
107
+ # the account only when LS echoed it (the credential scope check below
108
+ # still proves both reads used the same token, key and paper mode).
55
109
  if (request.ThdayTpCode != "1" or request.QrySrtDt != self.day
56
110
  or request.QryEndDt != self.day or not isinstance(echo, dict)
57
- or echo.get("AcntNo") != self.account
111
+ or (self.account and echo.get("AcntNo") != self.account)
58
112
  or any(name not in echo or echo[name] != value for name, value in expected.items())):
59
113
  raise StartupEvidenceUnavailable("CIDBQ02400: business_date_scope_mismatch")
60
114
  self.scope.check(ls)
@@ -72,15 +126,16 @@ async def from_current_positions(ls, response, query, scope):
72
126
  raise StartupEvidenceUnavailable("CIDBQ01500: current_date_query_required")
73
127
  if not isinstance(echo, dict) or any(echo.get(name) != value for name, value in expected.items() if name != "QryDt"):
74
128
  raise StartupEvidenceUnavailable("CIDBQ01500: business_date_scope_mismatch")
75
- day, account = echo.get("QryDt"), echo.get("AcntNo")
76
- if not isinstance(day, str) or not re.fullmatch(r"[0-9]{8}", day):
77
- raise StartupEvidenceUnavailable("CIDBQ01500: unavailable_business_date")
78
- try:
79
- datetime.strptime(day, "%Y%m%d")
80
- except ValueError:
81
- raise StartupEvidenceUnavailable("CIDBQ01500: invalid_business_date") from None
129
+ day, account = broker_business_day(echo.get("QryDt")), echo.get("AcntNo")
82
130
  if not isinstance(account, str) or not account.strip():
83
- raise StartupEvidenceUnavailable("CIDBQ01500: unavailable_account_echo")
131
+ if day:
132
+ raise StartupEvidenceUnavailable("CIDBQ01500: unavailable_account_echo")
133
+ account = ""
134
+ if not day:
135
+ # Weekend paper answers carry no business date. The server startup gate
136
+ # uses the blank-date order form for this answer; so does the engine.
137
+ logger.info("CIDBQ01500: paper server returned no business date; "
138
+ "using the published blank-date CIDBQ02400 same-day form")
84
139
  scope.check(ls)
85
140
  return PaperFuturesBusinessDate(day, account, scope)
86
141
 
@@ -5,7 +5,7 @@ authors = [
5
5
  homepage = "https://programgarden.com"
6
6
  requires-python = ">=3.12"
7
7
  name = "programgarden"
8
- version = "2.7.0"
8
+ version = "2.7.2"
9
9
  license = "AGPL-3.0-or-later"
10
10
  description = "ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진"
11
11
  readme = "README.md"
File without changes