fix(accounting): use size_usdc (not net_cost) for positions in initialize()
CI/CD / build-and-push (push) Successful in 1m48s
CI/CD / build-and-push (push) Successful in 1m48s
Eliminates the phantom 0.4% exposure overage after pod restarts.
During live trading execute() stores size_usdc in portfolio.positions and
deducts net_cost from cash — so total_value = bankroll − fees and
exposure_pct = sum(size_usdc) / (bankroll − fees).
Old initialize() stored net_cost in positions, making total_value = bankroll
and inflating exposure_pct (observed: 30.085% vs runtime 29.670%).
Fix: new get_open_position_data() returns both {market_id: size_usdc} and
total_net_cost in one query; initialize() uses size_usdc for positions and
total_net_cost for cash — identical model to execute().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -91,6 +91,27 @@ class Database:
|
||||
)
|
||||
return {r["market_id"]: float(r["total"]) for r in rows}
|
||||
|
||||
async def get_open_position_data(self) -> tuple[dict[str, float], float]:
|
||||
"""Return (positions_by_size_usdc, total_net_cost) for all open trades.
|
||||
|
||||
positions_by_size_usdc — {market_id: size_usdc} mirrors what live trading
|
||||
stores in portfolio.positions (no fee included).
|
||||
total_net_cost — SUM(net_cost) across all open trades, used to
|
||||
reconstruct cash = bankroll − total_net_cost.
|
||||
|
||||
Together these let initialize() replicate the exact same accounting model
|
||||
that execute() uses at runtime, eliminating the phantom exposure overage
|
||||
caused by the old net_cost-in-positions approach.
|
||||
"""
|
||||
async with self._pool.acquire() as conn:
|
||||
rows = await conn.fetch(
|
||||
"SELECT market_id, SUM(size_usdc) AS sz, SUM(net_cost) AS nc "
|
||||
"FROM trades WHERE closed_at IS NULL GROUP BY market_id"
|
||||
)
|
||||
positions = {r["market_id"]: float(r["sz"]) for r in rows}
|
||||
total_net_cost = sum(float(r["nc"]) for r in rows)
|
||||
return positions, total_net_cost
|
||||
|
||||
async def get_open_families(self) -> set[str]:
|
||||
"""Return the set of family_key values from all open positions.
|
||||
|
||||
|
||||
+22
-8
@@ -82,20 +82,34 @@ class PaperExecutor:
|
||||
Called once after __init__ so the executor reflects any trades that
|
||||
survived a pod restart. After a TRUNCATE the DB is empty and the
|
||||
portfolio resets to a full bankroll automatically.
|
||||
|
||||
Accounting model (must match execute() exactly):
|
||||
positions[market_id] = size_usdc (fee excluded — same as runtime)
|
||||
cash = bankroll - sum(net_cost) (fees already spent)
|
||||
total_value = cash + sum(size_usdc) = bankroll - sum(fees)
|
||||
exposure_pct = sum(size_usdc) / total_value
|
||||
"""
|
||||
positions = await self._db.get_open_positions()
|
||||
if not positions:
|
||||
positions_size, total_net_cost = await self._db.get_open_position_data()
|
||||
if not positions_size:
|
||||
log.info("No open positions in DB — starting with full bankroll")
|
||||
return
|
||||
|
||||
total_deployed = sum(positions.values())
|
||||
self._portfolio.positions = positions
|
||||
self._portfolio.cash = max(0.0, self._portfolio.cash - total_deployed)
|
||||
positions_value = sum(positions_size.values())
|
||||
self._portfolio.positions = positions_size
|
||||
self._portfolio.cash = max(0.0, self._portfolio.cash - total_net_cost)
|
||||
|
||||
total_value = self._portfolio.cash + positions_value
|
||||
exposure_pct = positions_value / total_value if total_value > 0 else 0.0
|
||||
log.info(
|
||||
"Restored %d open position(s) from DB — deployed $%.2f, cash $%.2f",
|
||||
len(positions),
|
||||
total_deployed,
|
||||
"Restored %d open position(s) from DB — "
|
||||
"positions_value=$%.2f net_cost_spent=$%.2f cash=$%.2f "
|
||||
"total_value=$%.2f exposure=%.2f%%",
|
||||
len(positions_size),
|
||||
positions_value,
|
||||
total_net_cost,
|
||||
self._portfolio.cash,
|
||||
total_value,
|
||||
exposure_pct * 100,
|
||||
)
|
||||
|
||||
def get_portfolio(self) -> Portfolio:
|
||||
|
||||
Reference in New Issue
Block a user