Show realized trade PnL in Android app

This commit is contained in:
Codex
2026-06-29 21:06:51 +03:00
parent fe276d95ff
commit 9f47c04235
8 changed files with 227 additions and 10 deletions
+29 -4
View File
@@ -358,10 +358,35 @@ class CryptoSpotBot:
def positions_snapshot(self) -> list[dict]:
prices = self.market.prices()
return [
position.as_dict(mark_price=prices.get(position.symbol, position.entry_price))
for position in self.broker.open_positions()
]
items: list[dict] = []
for position in self.broker.open_positions():
mark_price = prices.get(position.symbol, position.entry_price)
item = position.as_dict(mark_price=mark_price)
exit_signal = self.strategy.exit_signal(
position=position,
candles=self.market.candles.get(position.symbol, []),
ticker=self.market.tickers.get(position.symbol),
learning=self.learner.state.as_dict(),
forecast=self.market.forecasts.get(position.symbol, {}),
)
diagnostics = exit_signal.diagnostics or {}
fallback_stop_loss = position.stop_loss if self.settings.stop_loss_exit_enabled else None
item["exit_plan"] = {
"action": exit_signal.action,
"reason": exit_signal.reason,
"confidence": exit_signal.confidence,
"stop_loss": diagnostics.get("stop_loss", fallback_stop_loss),
"take_profit": diagnostics.get("take_profit", position.take_profit),
"trailing_stop": diagnostics.get("trailing_stop"),
"atr_trailing_stop": diagnostics.get("atr_trailing_stop"),
"highest_price": diagnostics.get("highest_price", position.highest_price),
"stop_loss_exit_enabled": diagnostics.get(
"stop_loss_exit_enabled",
self.settings.stop_loss_exit_enabled,
),
}
items.append(item)
return items
def learning_snapshot(self) -> dict:
snapshot = self.learner.state.as_dict()
+6 -1
View File
@@ -84,7 +84,12 @@ def create_app(settings: Settings | None = None) -> FastAPI:
@app.get("/api/trades")
async def trades(limit: int = 80) -> dict[str, Any]:
return {"items": storage.recent_trades(_limit(limit))}
row_limit = _limit(limit)
return {
"items": storage.recent_trades(row_limit),
"closed_items": storage.closed_trades(row_limit),
"closed_summary": storage.closed_trade_summary(),
}
@app.get("/api/signals")
async def signals(limit: int = 120) -> dict[str, Any]:
+28
View File
@@ -242,6 +242,34 @@ class Storage:
).fetchall()
return [dict(row) for row in rows]
def closed_trade_summary(self) -> dict[str, Any]:
with self.connect() as conn:
row = conn.execute(
"""
SELECT
COUNT(*) AS trades,
COALESCE(SUM(net_pnl), 0) AS net_pnl,
COALESCE(SUM(gross_pnl), 0) AS gross_pnl,
COALESCE(SUM(fee_usdt), 0) AS fee_usdt,
COALESCE(SUM(CASE WHEN net_pnl > 0 THEN 1 ELSE 0 END), 0) AS wins,
COALESCE(SUM(CASE WHEN net_pnl < 0 THEN 1 ELSE 0 END), 0) AS losses
FROM trades
WHERE side='SELL' AND closed_at IS NOT NULL
"""
).fetchone()
trades = int(row["trades"] if row else 0)
wins = int(row["wins"] if row else 0)
losses = int(row["losses"] if row else 0)
return {
"trades": trades,
"net_pnl": round(float(row["net_pnl"] if row else 0.0), 6),
"gross_pnl": round(float(row["gross_pnl"] if row else 0.0), 6),
"fee_usdt": round(float(row["fee_usdt"] if row else 0.0), 6),
"wins": wins,
"losses": losses,
"win_rate": round(wins / trades, 4) if trades else 0.0,
}
def insert_signal(self, signal: Signal) -> None:
with self.connect() as conn:
conn.execute(