programgarden 2.5.5__tar.gz → 2.5.6__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 (66) hide show
  1. {programgarden-2.5.5 → programgarden-2.5.6}/PKG-INFO +1 -1
  2. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/order_recovery.py +29 -8
  3. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/workflow_position_tracker.py +11 -1
  4. {programgarden-2.5.5 → programgarden-2.5.6}/pyproject.toml +1 -1
  5. {programgarden-2.5.5 → programgarden-2.5.6}/README.md +0 -0
  6. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/__init__.py +0 -0
  7. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/binding_validator.py +0 -0
  8. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/client.py +0 -0
  9. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/code_worker.py +0 -0
  10. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/context.py +0 -0
  11. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/__init__.py +0 -0
  12. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/adjustment_delivery.py +0 -0
  13. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/broker_evidence.py +0 -0
  14. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/broker_order_totals.py +0 -0
  15. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/broker_snapshot.py +0 -0
  16. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/checkpoint_manager.py +0 -0
  17. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/db_naming.py +0 -0
  18. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/execution_storage.py +0 -0
  19. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/fill_reconciler.py +0 -0
  20. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/order_cancellation.py +0 -0
  21. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/owned_order_cancellation.py +0 -0
  22. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/position_reconciliation.py +0 -0
  23. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/query_builder.py +0 -0
  24. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/database/workflow_risk_tracker.py +0 -0
  25. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/deep_fixtures.py +0 -0
  26. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/executor.py +0 -0
  27. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/futures_orderable.py +0 -0
  28. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/futures_pnl.py +0 -0
  29. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/futures_read_evidence.py +0 -0
  30. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/incremental_build.py +0 -0
  31. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/managed_order_control.py +0 -0
  32. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/node_runner.py +0 -0
  33. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/order_lifecycle.py +0 -0
  34. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/plugin/__init__.py +0 -0
  35. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/plugin/sandbox.py +0 -0
  36. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/providers/__init__.py +0 -0
  37. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/providers/llm_errors.py +0 -0
  38. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/providers/llm_provider.py +0 -0
  39. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/reconnect_handler.py +0 -0
  40. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/replay_contracts.py +0 -0
  41. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/replay_events.py +0 -0
  42. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/replay_external.py +0 -0
  43. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/replay_order_adapter.py +0 -0
  44. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/replay_orders.py +0 -0
  45. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/replay_scenarios.py +0 -0
  46. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/replay_semantics.py +0 -0
  47. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/replay_sources.py +0 -0
  48. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/replay_sqlite.py +0 -0
  49. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/replay_triggers.py +0 -0
  50. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/resolver.py +0 -0
  51. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/resource/__init__.py +0 -0
  52. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/resource/context.py +0 -0
  53. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/resource/limiter.py +0 -0
  54. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/resource/monitor.py +0 -0
  55. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/resource/throttle.py +0 -0
  56. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/semantic_rules.py +0 -0
  57. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/tools/__init__.py +0 -0
  58. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/tools/credential_tools.py +0 -0
  59. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/tools/definition_tools.py +0 -0
  60. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/tools/event_tools.py +0 -0
  61. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/tools/job_tools.py +0 -0
  62. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/tools/registry_tools.py +0 -0
  63. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/tools/sqlite_tools.py +0 -0
  64. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/validation_recommender.py +0 -0
  65. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/validation_replay.py +0 -0
  66. {programgarden-2.5.5 → programgarden-2.5.6}/programgarden/valuation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programgarden
3
- Version: 2.5.5
3
+ Version: 2.5.6
4
4
  Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Author: 프로그램동산
@@ -97,6 +97,9 @@ def initialize_recovery(conn):
97
97
  columns = {row[1] for row in conn.execute("PRAGMA table_info(workflow_position_lots)")}
98
98
  if "recovery_id" not in columns:
99
99
  conn.execute("ALTER TABLE workflow_position_lots ADD COLUMN recovery_id INTEGER")
100
+ recovery_columns = {row[1] for row in conn.execute("PRAGMA table_info(workflow_order_recoveries)")}
101
+ if "unpriced_quantity" not in recovery_columns:
102
+ conn.execute("ALTER TABLE workflow_order_recoveries ADD COLUMN unpriced_quantity TEXT NOT NULL DEFAULT '0'")
100
103
 
101
104
 
102
105
  def recovery_for_order(conn, tracker, day, number):
@@ -159,7 +162,8 @@ def recover_totals(tracker, totals):
159
162
  with sqlite3.connect(tracker.db_path) as conn:
160
163
  conn.execute("BEGIN IMMEDIATE")
161
164
  # Aggregate execution times are only a recovery ordering, never invented
162
- # timestamps for individual executions. Missing basis fails closed.
165
+ # timestamps for individual executions. Missing basis invalidates PnL,
166
+ # not a broker-verified execution or the later fresh holdings check.
163
167
  for total, (number, symbol, total_qty, price) in sorted(
164
168
  validated, key=lambda item: (item[0].order_date, item[0].fill_time, int(item[1][0]))):
165
169
  scope = tracker.product, tracker.provider, tracker.trading_mode
@@ -232,13 +236,18 @@ def recover_totals(tracker, totals):
232
236
  SELECT 1 FROM trade_history WHERE product=? AND provider=? AND trading_mode=?
233
237
  AND symbol=? AND classification='workflow' AND side='sell' AND fill_datetime>=? LIMIT 1
234
238
  """, (*scope, symbol, fill_datetime)).fetchone()
239
+ later_recovery = conn.execute("""
240
+ SELECT 1 FROM workflow_order_recoveries WHERE product=? AND provider=? AND trading_mode=?
241
+ AND symbol=? AND side='sell' AND fill_datetime>=? LIMIT 1
242
+ """, (*scope, symbol, fill_datetime)).fetchone()
235
243
  adjusted = conn.execute("SELECT 1 FROM sqlite_master WHERE name='position_adjustments'").fetchone()
236
244
  if adjusted:
237
245
  adjusted = conn.execute("""SELECT 1 FROM position_adjustments WHERE product=?
238
246
  AND provider=? AND trading_mode=? AND symbol=? LIMIT 1""", (*scope, symbol)).fetchone()
239
- if later_sell or adjusted:
247
+ if later_sell or later_recovery or adjusted:
240
248
  raise ReconciliationUnavailable("historical_buy_requires_full_replay")
241
249
  pnl = Decimal(0)
250
+ unpriced = Decimal(0)
242
251
  lots = []
243
252
  if total.side == "sell" and missing:
244
253
  lots = conn.execute("""
@@ -246,9 +255,12 @@ def recover_totals(tracker, totals):
246
255
  AND provider=? AND trading_mode=? AND symbol=? AND classification='workflow'
247
256
  AND remaining_qty>0 AND fill_datetime<=? ORDER BY fill_datetime,id
248
257
  """, (*scope, symbol, fill_datetime)).fetchall()
249
- if (any(quantity(row[1]) <= 0 for row in lots)
250
- or sum((quantity(row[2]) for row in lots), Decimal(0)) < missing):
251
- raise ReconciliationUnavailable("recovery_cost_basis_unavailable")
258
+ # Account-wide strategies can sell holdings acquired before this
259
+ # workflow. Never invent their purchase lots or a zero-cost basis.
260
+ # Validate stored numbers even when there are too few owned lots.
261
+ for _, basis, available in lots:
262
+ quantity(basis)
263
+ quantity(available)
252
264
  cursor = conn.execute("""
253
265
  INSERT INTO workflow_order_recoveries
254
266
  (product,provider,trading_mode,order_date,order_no,symbol,exchange,side,
@@ -273,16 +285,25 @@ def recover_totals(tracker, totals):
273
285
  remainder = missing
274
286
  for lot_id, basis, available in lots:
275
287
  used = min(remainder, quantity(available))
276
- pnl += (recovered_price - quantity(basis)) * used
288
+ if quantity(basis) > 0:
289
+ pnl += (recovered_price - quantity(basis)) * used
290
+ else:
291
+ unpriced += used
277
292
  conn.execute("UPDATE workflow_position_lots SET remaining_qty=? WHERE id=?",
278
293
  (float(quantity(available) - used), lot_id))
279
294
  remainder -= used
280
295
  if not remainder:
281
296
  break
282
- conn.execute("UPDATE workflow_order_recoveries SET estimated_pnl=? WHERE id=?", (str(pnl), recovery_id))
297
+ unpriced += remainder
298
+ # estimated_pnl stores only the matched portion for audit. Public
299
+ # readers must return null whenever any quantity lacks a basis.
300
+ conn.execute("UPDATE workflow_order_recoveries SET estimated_pnl=?, unpriced_quantity=? WHERE id=?",
301
+ (str(pnl), str(unpriced), recovery_id))
283
302
  results.append({"id": recovery_id, "order_date": total.order_date, "order_no": number,
284
303
  "symbol": symbol, "side": total.side, "quantity": str(missing),
285
- "estimated_pnl": str(pnl), "currency": total.currency,
304
+ "estimated_pnl": None if unpriced else str(pnl), "currency": total.currency,
305
+ "unpriced_quantity": str(unpriced),
306
+ "cost_basis_status": "unavailable" if unpriced else "estimated",
286
307
  "basis": "broker_order_total", "is_estimated": True, "is_trade": False})
287
308
  return results
288
309
 
@@ -1818,10 +1818,16 @@ class WorkflowPositionTracker:
1818
1818
  """Read the immutable recovery audit; this is not individual fill history."""
1819
1819
  with sqlite3.connect(self.db_path) as conn:
1820
1820
  conn.row_factory = sqlite3.Row
1821
- return [dict(row) for row in conn.execute(
1821
+ rows = [dict(row) for row in conn.execute(
1822
1822
  "SELECT * FROM workflow_order_recoveries WHERE product=? AND provider=? "
1823
1823
  "AND trading_mode=? ORDER BY id",
1824
1824
  (self.product, self.provider, self.trading_mode))]
1825
+ for row in rows:
1826
+ unknown = Decimal(row["unpriced_quantity"]) > 0
1827
+ row["cost_basis_status"] = "unavailable" if unknown else "estimated"
1828
+ if unknown:
1829
+ row["estimated_pnl"] = None
1830
+ return rows
1825
1831
 
1826
1832
  def get_position_adjustments(self, limit: int = 50) -> List[Dict[str, Any]]:
1827
1833
  """Return the current product/mode audit without creating synthetic trades."""
@@ -2249,6 +2255,10 @@ class WorkflowPositionTracker:
2249
2255
  reason = "futures_fifo_not_monetary"
2250
2256
  elif not symbol:
2251
2257
  reason = "missing_symbol"
2258
+ elif any(fill.get("cost_basis_status") == "unavailable" for fill in fills):
2259
+ # Verified order quantity can permit startup without establishing
2260
+ # historical profit. Preserve the existing unavailable contract.
2261
+ reason = "incomplete_fifo_basis"
2252
2262
  elif len(units - {None}) > 1 or unit_conflict:
2253
2263
  # Contradictory units cannot establish a comparable FIFO basis.
2254
2264
  reason = "incomplete_fifo_basis"
@@ -5,7 +5,7 @@ authors = [
5
5
  homepage = "https://programgarden.com"
6
6
  requires-python = ">=3.12"
7
7
  name = "programgarden"
8
- version = "2.5.5"
8
+ version = "2.5.6"
9
9
  license = "AGPL-3.0-or-later"
10
10
  description = "ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진"
11
11
  readme = "README.md"
File without changes