From b0a57c5330cc5c79925429bf991ee584d901292e Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 1 Apr 2025 21:22:02 +0300 Subject: [PATCH] fixed showing all moves --- realtime.py | 105 ++++++++++++++++++++++++++++++++------ train_rl_with_realtime.py | 27 +++++++--- 2 files changed, 108 insertions(+), 24 deletions(-) diff --git a/realtime.py b/realtime.py index f1c1229..8848c9f 100644 --- a/realtime.py +++ b/realtime.py @@ -30,6 +30,9 @@ class BinanceHistoricalData: """ def __init__(self): self.base_url = "https://api.binance.com/api/v3" + self.cache_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cache') + if not os.path.exists(self.cache_dir): + os.makedirs(self.cache_dir) def get_historical_candles(self, symbol, interval_seconds=3600, limit=1000): """ @@ -60,6 +63,14 @@ class BinanceHistoricalData: # Format symbol for Binance API (remove slash) formatted_symbol = symbol.replace("/", "") + # Check if we have cached data first + cache_file = self._get_cache_filename(formatted_symbol, interval) + cached_data = self._load_from_cache(formatted_symbol, interval) + + if cached_data is not None and len(cached_data) >= limit: + logger.info(f"Using cached historical data for {symbol} ({interval})") + return cached_data + try: # Build URL for klines endpoint url = f"{self.base_url}/klines" @@ -93,14 +104,48 @@ class BinanceHistoricalData: # Sort by timestamp df = df.sort_values("timestamp") + # Save to cache for future use + self._save_to_cache(df, formatted_symbol, interval) + logger.info(f"Fetched {len(df)} candles for {symbol} ({interval})") return df except Exception as e: logger.error(f"Error fetching historical data from Binance: {str(e)}") + # Return cached data if we have it, even if it's not enough + if cached_data is not None: + logger.warning(f"Using cached data instead (may be incomplete)") + return cached_data # Return empty dataframe on error return pd.DataFrame() + def _get_cache_filename(self, symbol, interval): + """Get filename for cache file""" + return os.path.join(self.cache_dir, f"{symbol}_{interval}_candles.csv") + + def _load_from_cache(self, symbol, interval): + """Load candles from cache file""" + try: + cache_file = self._get_cache_filename(symbol, interval) + if os.path.exists(cache_file): + df = pd.read_csv(cache_file) + df["timestamp"] = pd.to_datetime(df["timestamp"]) + return df + except Exception as e: + logger.error(f"Error loading cached data: {str(e)}") + return None + + def _save_to_cache(self, df, symbol, interval): + """Save candles to cache file""" + try: + cache_file = self._get_cache_filename(symbol, interval) + df.to_csv(cache_file, index=False) + logger.info(f"Saved {len(df)} candles to cache: {cache_file}") + return True + except Exception as e: + logger.error(f"Error saving to cache: {str(e)}") + return False + def get_recent_trades(self, symbol, limit=1000): """Get recent trades for a symbol""" formatted_symbol = symbol.replace("/", "") @@ -553,6 +598,7 @@ class TickStorage: try: # Load data for different timeframes timeframes = [ + (1, '1s'), # 1 second (60, '1m'), # 1 minute (300, '5m'), # 5 minutes (900, '15m'), # 15 minutes @@ -562,7 +608,22 @@ class TickStorage: ] for interval_seconds, interval_key in timeframes: - df = historical_data.get_historical_candles(symbol, interval_seconds) + # Set appropriate limits based on timeframe + limit = 1000 # Default + if interval_seconds == 1: + limit = 500 # 1s is too much data, limit to 500 + elif interval_seconds < 60: + limit = 750 # For seconds-level data + elif interval_seconds < 300: + limit = 1000 # 1m + elif interval_seconds < 900: + limit = 500 # 5m + elif interval_seconds < 3600: + limit = 300 # 15m + else: + limit = 200 # hourly/daily data + + df = historical_data.get_historical_candles(symbol, interval_seconds, limit) if df is not None and not df.empty: logger.info(f"Loaded {len(df)} historical candles for {symbol} ({interval_key})") @@ -578,12 +639,14 @@ class TickStorage: } self.candles[interval_key].append(candle) - # Also use the close price to simulate ticks - self.add_tick( - price=row['close'], - volume=row['volume'], - timestamp=row['timestamp'] - ) + # For 1m and above, also use the close price to simulate ticks + # but don't do this for seconds-level data as it creates too many ticks + if interval_seconds >= 60 and interval_key == '1m': + self.add_tick( + price=row['close'], + volume=row['volume'], + timestamp=row['timestamp'] + ) # Update latest price from most recent candle if len(df) > 0: @@ -904,14 +967,19 @@ class RealTimeChart: sell_times = [] sell_prices = [] - # Use only last 20 positions for clarity - for position in self.positions[-20:]: - if position.action == "BUY": - buy_times.append(position.entry_timestamp) - buy_prices.append(position.entry_price) - elif position.action == "SELL" and position.exit_timestamp: - sell_times.append(position.exit_timestamp) - sell_prices.append(position.exit_price) + # Use all positions for chart display + # Filter recent ones based on visible time range + now = datetime.now() + time_limit = now - timedelta(hours=24) # Show at most 24h of trades + + for position in self.positions: + if position.entry_timestamp > time_limit: + if position.action == "BUY": + buy_times.append(position.entry_timestamp) + buy_prices.append(position.entry_price) + elif position.action == "SELL" and position.exit_timestamp: + sell_times.append(position.exit_timestamp) + sell_prices.append(position.exit_price) # Add buy markers (green triangles pointing up) if buy_times: @@ -1112,14 +1180,17 @@ class RealTimeChart: winning_trades = sum(1 for p in self.positions if p.pnl and p.pnl > 0) win_rate = winning_trades / total_trades * 100 if total_trades > 0 else 0 + # Format display colors for PnL + pnl_color = "green" if self.accumulative_pnl >= 0 else "red" + summary_row = html.Tr([ html.Td("SUMMARY", colSpan=2, style={"fontWeight": "bold"}), html.Td(f"Trades: {total_trades}"), html.Td(f"Win Rate: {win_rate:.1f}%"), html.Td("Total PnL:", style={"fontWeight": "bold"}), html.Td(f"${self.accumulative_pnl:.2f}", - style={"color": "green" if self.accumulative_pnl >= 0 else "red", "fontWeight": "bold"}), - html.Td("") + style={"color": pnl_color, "fontWeight": "bold"}), + html.Td(f"Balance: ${self.current_balance:.2f}") ], style={"backgroundColor": "rgba(80, 80, 80, 0.3)"}) # Create the table with improved styling diff --git a/train_rl_with_realtime.py b/train_rl_with_realtime.py index 81e90b5..40c6560 100644 --- a/train_rl_with_realtime.py +++ b/train_rl_with_realtime.py @@ -516,17 +516,27 @@ class RLTrainingIntegrator: if len(self.price_history) > self.price_history_max_len: self.price_history = self.price_history[-self.price_history_max_len:] + # Normalize rewards to be realistic for crypto trading (smaller values) + normalized_reward = reward * 0.1 # Scale down rewards + if abs(normalized_reward) > 5.0: # Cap maximum reward value + normalized_reward = 5.0 if normalized_reward > 0 else -5.0 + # Update session PnL and balance self.session_step += 1 - self.session_pnl += reward + self.session_pnl += normalized_reward - # Increase balance based on reward - self.session_balance += reward + # Increase balance based on reward - cap to reasonable values + self.session_balance += normalized_reward + self.session_balance = min(self.session_balance, 1000.0) # Cap maximum balance + self.session_balance = max(self.session_balance, 0.0) # Prevent negative balance # Update chart's accumulativePnL and balance if available if self.chart: if hasattr(self.chart, 'accumulative_pnl'): self.chart.accumulative_pnl = self.session_pnl + # Cap accumulated PnL to reasonable values + self.chart.accumulative_pnl = min(self.chart.accumulative_pnl, 500.0) + self.chart.accumulative_pnl = max(self.chart.accumulative_pnl, -100.0) if hasattr(self.chart, 'current_balance'): self.chart.current_balance = self.session_balance @@ -751,7 +761,7 @@ def _add_trade_compat(chart, price, timestamp, amount, pnl=0.0, action="BUY"): position.close(price, timestamp) # Use realistic PnL values rather than the enormous ones from the model # Cap PnL to reasonable values based on position size and price - max_reasonable_pnl = price * amount * 0.10 # Max 10% profit + max_reasonable_pnl = price * amount * 0.05 # Max 5% profit per trade if abs(pnl) > max_reasonable_pnl: if pnl > 0: pnl = max_reasonable_pnl * 0.8 # Positive but reasonable @@ -762,11 +772,14 @@ def _add_trade_compat(chart, price, timestamp, amount, pnl=0.0, action="BUY"): # Update chart's accumulated PnL if available if hasattr(chart, 'accumulative_pnl'): chart.accumulative_pnl += pnl + # Cap accumulated PnL to reasonable values + chart.accumulative_pnl = min(chart.accumulative_pnl, 500.0) + chart.accumulative_pnl = max(chart.accumulative_pnl, -100.0) - # Add to positions list, keeping only the last 10 if we have more + # Add to positions list, keeping only the last 200 for chart display chart.positions.append(position) - if len(chart.positions) > 10: - chart.positions = chart.positions[-10:] + if len(chart.positions) > 200: + chart.positions = chart.positions[-200:] logger.info(f"Added {action} trade: price={price:.2f}, amount={amount}, pnl={pnl:.2f}") return True