- Introduced `train_daily.sh` for automating daily model retraining, including data download and model training steps. - Added `install_cron.sh` for setting up a cron job to run the daily training script. - Created `setup_schedule.sh` for configuring Systemd timers for daily training tasks. - Implemented a terminal UI using Rich for real-time monitoring of trading performance, including metrics display and log handling. - Updated `pyproject.toml` to include the `rich` dependency for UI functionality. - Enhanced `.gitignore` to exclude model and log files. - Added database support for trade persistence and metrics calculation. - Updated README with installation and usage instructions for the new features.
196 lines
5.8 KiB
Python
196 lines
5.8 KiB
Python
"""Thread-safe shared state for UI and trading loop."""
|
|
import threading
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
from datetime import datetime, timezone
|
|
|
|
|
|
@dataclass
|
|
class PositionState:
|
|
"""Current position information."""
|
|
|
|
trade_id: str = ""
|
|
symbol: str = ""
|
|
side: str = ""
|
|
entry_price: float = 0.0
|
|
current_price: float = 0.0
|
|
size: float = 0.0
|
|
size_usdt: float = 0.0
|
|
unrealized_pnl: float = 0.0
|
|
unrealized_pnl_pct: float = 0.0
|
|
stop_loss_price: float = 0.0
|
|
take_profit_price: float = 0.0
|
|
|
|
|
|
@dataclass
|
|
class StrategyState:
|
|
"""Current strategy signal state."""
|
|
|
|
z_score: float = 0.0
|
|
probability: float = 0.0
|
|
funding_rate: float = 0.0
|
|
last_action: str = "hold"
|
|
last_reason: str = ""
|
|
last_signal_time: str = ""
|
|
|
|
|
|
@dataclass
|
|
class AccountState:
|
|
"""Account balance information."""
|
|
|
|
balance: float = 0.0
|
|
available: float = 0.0
|
|
leverage: int = 1
|
|
|
|
|
|
class SharedState:
|
|
"""
|
|
Thread-safe shared state between trading loop and UI.
|
|
|
|
All access to state fields should go through the getter/setter methods
|
|
which use a lock for thread safety.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._lock = threading.Lock()
|
|
self._position: Optional[PositionState] = None
|
|
self._strategy = StrategyState()
|
|
self._account = AccountState()
|
|
self._is_running = True
|
|
self._last_cycle_time: Optional[str] = None
|
|
self._mode = "DEMO"
|
|
self._eth_symbol = "ETH/USDT:USDT"
|
|
self._btc_symbol = "BTC/USDT:USDT"
|
|
|
|
# Position methods
|
|
def get_position(self) -> Optional[PositionState]:
|
|
"""Get current position state."""
|
|
with self._lock:
|
|
return self._position
|
|
|
|
def set_position(self, position: Optional[PositionState]) -> None:
|
|
"""Set current position state."""
|
|
with self._lock:
|
|
self._position = position
|
|
|
|
def update_position_price(self, current_price: float) -> None:
|
|
"""Update current price and recalculate PnL."""
|
|
with self._lock:
|
|
if self._position is None:
|
|
return
|
|
|
|
self._position.current_price = current_price
|
|
|
|
if self._position.side == "long":
|
|
pnl = (current_price - self._position.entry_price)
|
|
self._position.unrealized_pnl = pnl * self._position.size
|
|
pnl_pct = (current_price / self._position.entry_price - 1) * 100
|
|
else:
|
|
pnl = (self._position.entry_price - current_price)
|
|
self._position.unrealized_pnl = pnl * self._position.size
|
|
pnl_pct = (1 - current_price / self._position.entry_price) * 100
|
|
|
|
self._position.unrealized_pnl_pct = pnl_pct
|
|
|
|
def clear_position(self) -> None:
|
|
"""Clear current position."""
|
|
with self._lock:
|
|
self._position = None
|
|
|
|
# Strategy methods
|
|
def get_strategy(self) -> StrategyState:
|
|
"""Get current strategy state."""
|
|
with self._lock:
|
|
return StrategyState(
|
|
z_score=self._strategy.z_score,
|
|
probability=self._strategy.probability,
|
|
funding_rate=self._strategy.funding_rate,
|
|
last_action=self._strategy.last_action,
|
|
last_reason=self._strategy.last_reason,
|
|
last_signal_time=self._strategy.last_signal_time,
|
|
)
|
|
|
|
def update_strategy(
|
|
self,
|
|
z_score: float,
|
|
probability: float,
|
|
funding_rate: float,
|
|
action: str,
|
|
reason: str,
|
|
) -> None:
|
|
"""Update strategy state."""
|
|
with self._lock:
|
|
self._strategy.z_score = z_score
|
|
self._strategy.probability = probability
|
|
self._strategy.funding_rate = funding_rate
|
|
self._strategy.last_action = action
|
|
self._strategy.last_reason = reason
|
|
self._strategy.last_signal_time = datetime.now(
|
|
timezone.utc
|
|
).isoformat()
|
|
|
|
# Account methods
|
|
def get_account(self) -> AccountState:
|
|
"""Get current account state."""
|
|
with self._lock:
|
|
return AccountState(
|
|
balance=self._account.balance,
|
|
available=self._account.available,
|
|
leverage=self._account.leverage,
|
|
)
|
|
|
|
def update_account(
|
|
self,
|
|
balance: float,
|
|
available: float,
|
|
leverage: int,
|
|
) -> None:
|
|
"""Update account state."""
|
|
with self._lock:
|
|
self._account.balance = balance
|
|
self._account.available = available
|
|
self._account.leverage = leverage
|
|
|
|
# Control methods
|
|
def is_running(self) -> bool:
|
|
"""Check if trading loop is running."""
|
|
with self._lock:
|
|
return self._is_running
|
|
|
|
def stop(self) -> None:
|
|
"""Signal to stop trading loop."""
|
|
with self._lock:
|
|
self._is_running = False
|
|
|
|
def get_last_cycle_time(self) -> Optional[str]:
|
|
"""Get last trading cycle time."""
|
|
with self._lock:
|
|
return self._last_cycle_time
|
|
|
|
def set_last_cycle_time(self, time_str: str) -> None:
|
|
"""Set last trading cycle time."""
|
|
with self._lock:
|
|
self._last_cycle_time = time_str
|
|
|
|
# Config methods
|
|
def get_mode(self) -> str:
|
|
"""Get trading mode (DEMO/LIVE)."""
|
|
with self._lock:
|
|
return self._mode
|
|
|
|
def set_mode(self, mode: str) -> None:
|
|
"""Set trading mode."""
|
|
with self._lock:
|
|
self._mode = mode
|
|
|
|
def get_symbols(self) -> tuple[str, str]:
|
|
"""Get trading symbols (eth, btc)."""
|
|
with self._lock:
|
|
return self._eth_symbol, self._btc_symbol
|
|
|
|
def set_symbols(self, eth_symbol: str, btc_symbol: str) -> None:
|
|
"""Set trading symbols."""
|
|
with self._lock:
|
|
self._eth_symbol = eth_symbol
|
|
self._btc_symbol = btc_symbol
|