fix: critical — track capital properly: load invested positions on startup, deduct on act, sync every 5min
This commit is contained in:
@@ -551,13 +551,41 @@ class TradingEngine:
|
||||
except Exception:
|
||||
logger.debug("Could not load circuit breaker state — using inactive")
|
||||
|
||||
# Build portfolio state with defaults
|
||||
# Default initial capital for paper trading — overridden by broker sync
|
||||
# Build portfolio state from broker account (real buying power)
|
||||
initial_capital = 100000.0
|
||||
try:
|
||||
acct_row = await self.pool.fetchrow(
|
||||
"SELECT config FROM broker_accounts WHERE active = TRUE ORDER BY created_at DESC LIMIT 1"
|
||||
)
|
||||
if acct_row and acct_row["config"]:
|
||||
cfg = acct_row["config"] if isinstance(acct_row["config"], dict) else {}
|
||||
initial_capital = float(cfg.get("portfolio_value", cfg.get("cash", 100000.0)))
|
||||
except Exception:
|
||||
logger.debug("Could not load broker account — using default capital")
|
||||
|
||||
# Load actual positions to calculate invested amount
|
||||
invested = 0.0
|
||||
open_count = 0
|
||||
try:
|
||||
pos_rows = await self.pool.fetch(
|
||||
"SELECT quantity, avg_entry_price FROM positions WHERE quantity > 0"
|
||||
)
|
||||
for pr in pos_rows:
|
||||
invested += float(pr["quantity"]) * float(pr["avg_entry_price"])
|
||||
open_count += 1
|
||||
except Exception:
|
||||
logger.debug("Could not load positions — assuming no invested capital")
|
||||
|
||||
available = max(0.0, initial_capital - reserve_balance - invested)
|
||||
self.portfolio_state = PortfolioState(
|
||||
reserve_pool=reserve_balance,
|
||||
active_pool=max(0.0, initial_capital - reserve_balance),
|
||||
active_pool=available,
|
||||
total_value=initial_capital,
|
||||
open_position_count=open_count,
|
||||
)
|
||||
logger.info(
|
||||
"Portfolio state: total=$%.2f invested=$%.2f available=$%.2f reserve=$%.2f positions=%d",
|
||||
initial_capital, invested, available, reserve_balance, open_count,
|
||||
)
|
||||
|
||||
async def _decision_loop(self) -> None:
|
||||
@@ -672,6 +700,12 @@ class TradingEngine:
|
||||
|
||||
# For "act" decisions: push order to broker queue
|
||||
if decision.decision == "act":
|
||||
# Deduct from active pool immediately to prevent over-allocation
|
||||
order_cost = (decision.computed_share_quantity or 0) * rec.get("current_price", 0)
|
||||
if self.portfolio_state and order_cost > 0:
|
||||
self.portfolio_state.active_pool = max(0.0, self.portfolio_state.active_pool - order_cost)
|
||||
self.portfolio_state.open_position_count += 1
|
||||
|
||||
order_job = {
|
||||
"trading_decision_id": decision.id,
|
||||
"ticker": decision.ticker,
|
||||
@@ -684,9 +718,11 @@ class TradingEngine:
|
||||
broker_queue = queue_key(QUEUE_BROKER)
|
||||
await self.redis.rpush(broker_queue, json.dumps(order_job))
|
||||
logger.info(
|
||||
"Pushed order for %s (%d shares) to broker queue",
|
||||
"Pushed order for %s (%d shares, $%.2f) to broker queue — remaining pool: $%.2f",
|
||||
decision.ticker,
|
||||
decision.computed_share_quantity or 0,
|
||||
order_cost,
|
||||
self.portfolio_state.active_pool if self.portfolio_state else 0,
|
||||
)
|
||||
|
||||
# Persist decision
|
||||
@@ -816,6 +852,20 @@ class TradingEngine:
|
||||
if self.portfolio_state is None:
|
||||
continue
|
||||
|
||||
# Sync active_pool with actual positions from DB
|
||||
try:
|
||||
if self.pool is not None:
|
||||
pos_rows = await self.pool.fetch(
|
||||
"SELECT quantity, avg_entry_price FROM positions WHERE quantity > 0"
|
||||
)
|
||||
invested = sum(float(r["quantity"]) * float(r["avg_entry_price"]) for r in pos_rows)
|
||||
open_count = len(pos_rows)
|
||||
available = max(0.0, self.portfolio_state.total_value - self.portfolio_state.reserve_pool - invested)
|
||||
self.portfolio_state.active_pool = available
|
||||
self.portfolio_state.open_position_count = open_count
|
||||
except Exception:
|
||||
logger.debug("Could not sync positions for portfolio state")
|
||||
|
||||
# Update portfolio heat and metrics from current positions
|
||||
try:
|
||||
metrics = self.performance_computer.compute_metrics(
|
||||
|
||||
Reference in New Issue
Block a user