Files
TradeBot/crypto_spot_bot/models.py
T
2026-06-21 09:15:38 +03:00

155 lines
4.2 KiB
Python

from __future__ import annotations
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from typing import Any
def utc_now() -> datetime:
return datetime.now(timezone.utc)
@dataclass(slots=True)
class Candle:
timestamp: int
open: float
high: float
low: float
close: float
volume: float
turnover: float = 0.0
ema_20: float | None = None
ema_50: float | None = None
ema_200: float | None = None
rsi_14: float | None = None
atr_14: float | None = None
macd: float | None = None
macd_signal: float | None = None
macd_hist: float | None = None
volume_ma_20: float | None = None
def as_dict(self) -> dict[str, Any]:
return asdict(self)
@dataclass(slots=True)
class Ticker:
symbol: str
last_price: float
bid: float
ask: float
turnover_24h: float
volume_24h: float
change_24h: float
updated_at: datetime = field(default_factory=utc_now)
@property
def spread_percent(self) -> float:
if self.bid <= 0 or self.ask <= 0:
return 0.0
mid = (self.ask + self.bid) / 2
return ((self.ask - self.bid) / mid) * 100 if mid else 0.0
def as_dict(self) -> dict[str, Any]:
data = asdict(self)
data["updated_at"] = self.updated_at.isoformat()
data["spread_percent"] = self.spread_percent
return data
@dataclass(slots=True)
class Signal:
symbol: str
action: str
confidence: float
reason: str
diagnostics: dict[str, Any] = field(default_factory=dict)
created_at: datetime = field(default_factory=utc_now)
def as_dict(self) -> dict[str, Any]:
data = asdict(self)
data["created_at"] = self.created_at.isoformat()
return data
@dataclass(slots=True)
class Position:
id: int | None
symbol: str
qty: float
entry_price: float
notional_usdt: float
entry_fee_usdt: float
stop_loss: float
take_profit: float
highest_price: float
opened_at: datetime = field(default_factory=utc_now)
entry_reason: str = ""
entry_confidence: float = 0.0
entry_pattern: str = ""
def mark_price(self, price: float) -> float:
return self.qty * price
def unrealized_pnl(self, price: float) -> float:
return (price - self.entry_price) * self.qty - self.entry_fee_usdt
def trailing_stop(self, percent: float) -> float | None:
stop = self.highest_price * (1 - percent)
return stop if stop > self.entry_price else None
def as_dict(self, mark_price: float | None = None) -> dict[str, Any]:
data = asdict(self)
data["opened_at"] = self.opened_at.isoformat()
if mark_price is not None:
data["mark_price"] = mark_price
data["market_value"] = self.mark_price(mark_price)
data["unrealized_pnl"] = self.unrealized_pnl(mark_price)
data["unrealized_pnl_percent"] = (
self.unrealized_pnl(mark_price) / self.notional_usdt * 100
if self.notional_usdt
else 0.0
)
return data
@dataclass(slots=True)
class Trade:
id: int | None
symbol: str
side: str
qty: float
entry_price: float | None = None
exit_price: float | None = None
gross_pnl: float = 0.0
fee_usdt: float = 0.0
net_pnl: float = 0.0
reason: str = ""
entry_pattern: str = ""
entry_confidence: float = 0.0
opened_at: datetime | None = None
closed_at: datetime | None = None
def as_dict(self) -> dict[str, Any]:
data = asdict(self)
data["opened_at"] = self.opened_at.isoformat() if self.opened_at else None
data["closed_at"] = self.closed_at.isoformat() if self.closed_at else None
return data
@dataclass(slots=True)
class BotStatus:
running: bool
mode: str
live_trading_ready: bool
symbols: list[str]
started_at: datetime | None
last_loop_at: datetime | None
message: str = ""
def as_dict(self) -> dict[str, Any]:
data = asdict(self)
data["started_at"] = self.started_at.isoformat() if self.started_at else None
data["last_loop_at"] = self.last_loop_at.isoformat() if self.last_loop_at else None
return data