Show realized trade PnL in Android app
This commit is contained in:
+29
-4
@@ -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()
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user