import websocket import json import pandas as pd import numpy as np from ta.trend import SMAIndicator from ta.momentum import RSIIndicator from ta.volatility import AverageTrueRange import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation import matplotlib.dates as mdates import logging import time from datetime import timedelta, datetime # Setup logging logging.basicConfig(filename='trade_log.txt', level=logging.INFO, format='%(asctime)s - %(message)s') # Strategy parameters initial_capital = 100000 base_size = 0.01 # 1% of portfolio risk_per_trade = 0.01 # 1% risk simulation_duration = 600 # 10 minutes in seconds buffer_threshold = 0.1 # 10% buffer for position changes ticker = "BTC/USDT" # TRADING COSTS - Realistic exchange fees maker_fee = 0.001 # 0.1% maker fee (limit orders) taker_fee = 0.001 # 0.1% taker fee (market orders) spread_cost = 0.0005 # 0.05% bid-ask spread cost slippage_cost = 0.0002 # 0.02% slippage on market orders # Data storage data = [] position_dollars = 0 # Position size in dollars (positive = long, negative = short) btc_units = 0 # Actual BTC units (positive = owned, negative = borrowed/short) entry_price = 0 stop_loss = 0 trailing_stop = 0 highest_price = 0 # For long positions lowest_price = 0 # For short positions portfolio_value = initial_capital cumulative_costs = 0 # Track all trading costs trade_log = [] cost_log = [] # Track costs over time entries = [] exits = [] df_global = pd.DataFrame() start_time = None # Will be set when first data arrives current_price_base = None # For price scaling # Plot setup - WHITE BACKGROUND with 3 panels plt.style.use('default') # Use default (white) background fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(16, 12), height_ratios=[2, 1, 1]) fig.suptitle(f'Real-Time {ticker} Long/Short Strategy with Trading Costs', fontsize=16, fontweight='bold') # Create right axis for real price ax1_right = ax1.twinx() # Main plot (price and SMAs) price_line, = ax1.plot([], [], label='Price', color='blue', linewidth=2) sma5_line, = ax1.plot([], [], label='SMA5', color='orange', linewidth=1.5) sma15_line, = ax1.plot([], [], label='SMA15', color='green', linewidth=1.5) stop_loss_line, = ax1.plot([], [], label='Stop Loss', color='red', linestyle='--', alpha=0.8) trailing_stop_line, = ax1.plot([], [], label='Trailing Stop', color='purple', linestyle='--', alpha=0.8) # Buy/Sell markers - larger and more visible buy_scatter = ax1.scatter([], [], color='lime', marker='^', label='BUY (Long)', s=200, edgecolor='darkgreen', linewidth=2, zorder=10) short_scatter = ax1.scatter([], [], color='orange', marker='v', label='SHORT', s=200, edgecolor='darkorange', linewidth=2, zorder=10) close_long_scatter = ax1.scatter([], [], color='red', marker='x', label='Close Long', s=200, edgecolor='darkred', linewidth=2, zorder=10) close_short_scatter = ax1.scatter([], [], color='cyan', marker='+', label='Close Short', s=200, edgecolor='darkcyan', linewidth=2, zorder=10) ax1.set_ylabel('Price Change (%)', fontsize=12, fontweight='bold') ax1_right.set_ylabel('Real Price (USDT)', fontsize=12, fontweight='bold', color='gray') ax1.legend(loc='upper left', fontsize=10) ax1.grid(True, alpha=0.3) ax1.set_title('Price Chart with Long/Short Indicators', fontsize=12, fontweight='bold') # Portfolio subplot portfolio_line, = ax2.plot([], [], label='Portfolio Value (After Costs)', color='darkblue', linewidth=2) portfolio_gross_line, = ax2.plot([], [], label='Portfolio Value (Before Costs)', color='lightblue', linewidth=1, linestyle='--', alpha=0.7) ax2.axhline(y=initial_capital, color='gray', linestyle='-', alpha=0.5, label='Initial Capital') ax2.axhline(y=initial_capital * 1.01, color='lightgreen', linestyle='--', alpha=0.7, label='+1%') ax2.axhline(y=initial_capital * 0.99, color='lightcoral', linestyle='--', alpha=0.7, label='-1%') ax2.set_ylabel('Portfolio Value ($)', fontsize=12, fontweight='bold') ax2.legend(loc='upper left', fontsize=10) ax2.grid(True, alpha=0.3) ax2.set_title('Portfolio Performance (Net vs Gross)', fontsize=12, fontweight='bold') # Costs subplot (NEW - 3rd panel) costs_line, = ax3.plot([], [], label='Cumulative Costs', color='red', linewidth=2) fees_line, = ax3.plot([], [], label='Trading Fees', color='orange', linewidth=1.5, alpha=0.8) spread_line, = ax3.plot([], [], label='Spread Costs', color='purple', linewidth=1.5, alpha=0.8) slippage_line, = ax3.plot([], [], label='Slippage Costs', color='brown', linewidth=1.5, alpha=0.8) ax3.set_ylabel('Costs ($)', fontsize=12, fontweight='bold') ax3.set_xlabel('Time (MM:SS)', fontsize=12, fontweight='bold') ax3.legend(loc='upper left', fontsize=10) ax3.grid(True, alpha=0.3) ax3.set_title('Trading Costs Breakdown', fontsize=12, fontweight='bold') def calculate_indicators(df): """Calculate technical indicators with proper error handling""" try: if len(df) >= 5: df['sma5'] = SMAIndicator(df['close'], window=5).sma_indicator() else: df['sma5'] = df['close'] if len(df) >= 15: df['sma15'] = SMAIndicator(df['close'], window=15).sma_indicator() else: df['sma15'] = df['close'] if len(df) >= 7: df['rsi'] = RSIIndicator(df['close'], window=7).rsi() df['atr'] = AverageTrueRange(df['high'], df['low'], df['close'], window=7).average_true_range() else: df['rsi'] = pd.Series([50] * len(df), index=df.index) df['atr'] = pd.Series([1.0] * len(df), index=df.index) if len(df) >= 10: df['roc'] = df['close'].pct_change(10) * 100 else: df['roc'] = pd.Series([0.0] * len(df), index=df.index) except Exception as e: print(f"Error calculating indicators: {e}") df['sma5'] = df['close'] df['sma15'] = df['close'] df['rsi'] = pd.Series([50] * len(df), index=df.index) df['atr'] = pd.Series([1.0] * len(df), index=df.index) df['roc'] = pd.Series([0.0] * len(df), index=df.index) return df def calculate_trading_costs(trade_value, trade_type="market"): """Calculate all trading costs for a given trade""" # Base trading fee (maker or taker) fee_rate = maker_fee if trade_type == "limit" else taker_fee trading_fee = trade_value * fee_rate # Spread cost (always applies) spread_cost_amount = trade_value * spread_cost # Slippage (only for market orders) slippage_cost_amount = trade_value * slippage_cost if trade_type == "market" else 0 total_cost = trading_fee + spread_cost_amount + slippage_cost_amount return { 'total_cost': total_cost, 'trading_fee': trading_fee, 'spread_cost': spread_cost_amount, 'slippage_cost': slippage_cost_amount } def get_time_in_seconds(timestamp): """Convert timestamp to seconds from start""" if start_time is None: return 0 return (timestamp - start_time).total_seconds() def format_time_axis(seconds): """Format seconds as MM:SS""" minutes = int(seconds // 60) secs = int(seconds % 60) return f"{minutes:02d}:{secs:02d}" def get_position_type(): """Return current position type""" if position_dollars > 0: return "LONG" elif position_dollars < 0: return "SHORT" else: return "FLAT" # WebSocket callback functions def on_message(ws, message): global data, position_dollars, btc_units, entry_price, stop_loss, trailing_stop global highest_price, lowest_price, portfolio_value, cumulative_costs, df_global, start_time, current_price_base try: msg = json.loads(message) if 'k' in msg: kline = msg['k'] if kline['x']: # Closed candle timestamp = pd.to_datetime(kline['t'], unit='ms') close = float(kline['c']) high = float(kline['h']) low = float(kline['l']) # Set start time on first data point if start_time is None: start_time = timestamp current_price_base = close print(f"πŸ• Long/Short Strategy with Costs started at {start_time}") print(f"πŸ’° Base price: ${current_price_base:.2f}") print(f"πŸ’Έ Trading Costs: Maker {maker_fee*100:.1f}%, Taker {taker_fee*100:.1f}%, Spread {spread_cost*100:.2f}%, Slippage {slippage_cost*100:.2f}%") # Calculate time in seconds from start time_seconds = get_time_in_seconds(timestamp) data.append({ 'timestamp': timestamp, 'time_seconds': time_seconds, 'close': close, 'high': high, 'low': low }) # Keep only last 100 data points for efficiency data[:] = data[-100:] if len(data) > 100 else data if len(data) > 5: # Minimum data needed df = pd.DataFrame(data) df = calculate_indicators(df) df_global = df.copy() # Store for plotting current = df.iloc[-1] prev = df.iloc[-2] if len(df) > 1 else None # Risk management factors if len(df) >= 5: atr_avg = df['atr'].rolling(5).mean().iloc[-1] vol_factor = 0.8 if current['atr'] > 2 * atr_avg else 1.0 else: vol_factor = 1.0 signal_factor = 1.2 if abs(current['rsi'] - 50) > 20 else 1.0 # Strong RSI signal market_factor = 1.2 if abs(current['roc']) > 3 else (0.8 if abs(current['roc']) < 1 else 1.0) # ENTRY LOGIC - Only when no position if position_dollars == 0 and prev is not None and len(df) >= 15: # LONG ENTRY: SMA5 crosses above SMA15 AND RSI > 50 if (current['sma5'] > current['sma15'] and prev['sma5'] <= prev['sma15'] and current['rsi'] > 50): # Calculate position size in dollars (positive = long) gross_position = (base_size * signal_factor * vol_factor * market_factor) * initial_capital # Calculate trading costs costs = calculate_trading_costs(gross_position, "market") # Market order for entries cumulative_costs += costs['total_cost'] # Net position after costs position_dollars = gross_position entry_price = current['close'] # Calculate actual BTC units (positive = owned) btc_units = position_dollars / entry_price stop_loss = entry_price - 1.5 * current['atr'] highest_price = entry_price trailing_stop = stop_loss entries.append((time_seconds, entry_price, 'BUY')) # Update portfolio with costs portfolio_value -= costs['total_cost'] print(f"πŸš€ LONG ENTRY: Time={format_time_axis(time_seconds)}, Price=${entry_price:.2f}") print(f" Investment: ${position_dollars:.2f}, BTC Units: {btc_units:.6f}, Stop: ${stop_loss:.2f}") print(f" πŸ’Έ Entry Costs: ${costs['total_cost']:.2f} (Fee: ${costs['trading_fee']:.2f}, Spread: ${costs['spread_cost']:.2f}, Slippage: ${costs['slippage_cost']:.2f})") logging.info(f"LONG: Time={format_time_axis(time_seconds)}, Price={entry_price}, Investment=${position_dollars:.2f}, BTC={btc_units:.6f}, Costs=${costs['total_cost']:.2f}") # Log costs cost_log.append({ 'timestamp': timestamp, 'time_seconds': time_seconds, 'cumulative_costs': cumulative_costs, 'trading_fees': costs['trading_fee'], 'spread_costs': costs['spread_cost'], 'slippage_costs': costs['slippage_cost'] }) # SHORT ENTRY: SMA5 crosses below SMA15 AND RSI < 50 elif (current['sma5'] < current['sma15'] and prev['sma5'] >= prev['sma15'] and current['rsi'] < 50): # Calculate position size in dollars (negative = short) gross_position = (base_size * signal_factor * vol_factor * market_factor) * initial_capital # Calculate trading costs costs = calculate_trading_costs(gross_position, "market") # Market order for entries cumulative_costs += costs['total_cost'] # Net position after costs position_dollars = -gross_position # Negative for short entry_price = current['close'] # Calculate actual BTC units (negative = borrowed/short) btc_units = position_dollars / entry_price # Will be negative stop_loss = entry_price + 1.5 * current['atr'] # Stop above entry for shorts lowest_price = entry_price trailing_stop = stop_loss entries.append((time_seconds, entry_price, 'SHORT')) # Update portfolio with costs portfolio_value -= costs['total_cost'] print(f"πŸ”» SHORT ENTRY: Time={format_time_axis(time_seconds)}, Price=${entry_price:.2f}") print(f" Short Size: ${abs(position_dollars):.2f}, BTC Units: {btc_units:.6f}, Stop: ${stop_loss:.2f}") print(f" πŸ’Έ Entry Costs: ${costs['total_cost']:.2f} (Fee: ${costs['trading_fee']:.2f}, Spread: ${costs['spread_cost']:.2f}, Slippage: ${costs['slippage_cost']:.2f})") logging.info(f"SHORT: Time={format_time_axis(time_seconds)}, Price={entry_price}, Size=${abs(position_dollars):.2f}, BTC={btc_units:.6f}, Costs=${costs['total_cost']:.2f}") # Log costs cost_log.append({ 'timestamp': timestamp, 'time_seconds': time_seconds, 'cumulative_costs': cumulative_costs, 'trading_fees': costs['trading_fee'], 'spread_costs': costs['spread_cost'], 'slippage_costs': costs['slippage_cost'] }) # EXIT LOGIC if position_dollars != 0 and len(df) >= 15: exit_reason = "" should_exit = False if position_dollars > 0: # LONG EXIT if current['sma5'] < current['sma15']: exit_reason = "SMA Crossover (Long β†’ Exit)" should_exit = True elif current['close'] < stop_loss: exit_reason = "Stop Loss (Long)" should_exit = True else: # SHORT EXIT if current['sma5'] > current['sma15']: exit_reason = "SMA Crossover (Short β†’ Exit)" should_exit = True elif current['close'] > stop_loss: exit_reason = "Stop Loss (Short)" should_exit = True if should_exit: # Calculate exit costs trade_value = abs(position_dollars) costs = calculate_trading_costs(trade_value, "market") # Market order for exits cumulative_costs += costs['total_cost'] # Profit calculation (before costs): if position_dollars > 0: # Closing long gross_profit = (current['close'] - entry_price) * btc_units exits.append((time_seconds, current['close'], 'CLOSE_LONG')) print(f"πŸšͺ CLOSE LONG: Time={format_time_axis(time_seconds)}, {exit_reason}, Price=${current['close']:.2f}") else: # Closing short gross_profit = (entry_price - current['close']) * abs(btc_units) # Profit when price drops exits.append((time_seconds, current['close'], 'CLOSE_SHORT')) print(f"πŸšͺ CLOSE SHORT: Time={format_time_axis(time_seconds)}, {exit_reason}, Price=${current['close']:.2f}") # Net profit after exit costs net_profit = gross_profit - costs['total_cost'] portfolio_value += gross_profit - costs['total_cost'] # Add net profit print(f" πŸ’° Gross P&L: ${gross_profit:+.2f}") print(f" πŸ’Έ Exit Costs: ${costs['total_cost']:.2f} (Fee: ${costs['trading_fee']:.2f}, Spread: ${costs['spread_cost']:.2f}, Slippage: ${costs['slippage_cost']:.2f})") print(f" πŸ“Š Net P&L: ${net_profit:+.2f}, Portfolio: ${portfolio_value:,.2f}") logging.info(f"EXIT: Time={format_time_axis(time_seconds)}, Price={current['close']}, Reason={exit_reason}, GrossProfit=${gross_profit:.2f}, NetProfit=${net_profit:.2f}, Costs=${costs['total_cost']:.2f}") # Log costs cost_log.append({ 'timestamp': timestamp, 'time_seconds': time_seconds, 'cumulative_costs': cumulative_costs, 'trading_fees': costs['trading_fee'], 'spread_costs': costs['spread_cost'], 'slippage_costs': costs['slippage_cost'] }) # Reset position variables position_dollars = 0 btc_units = 0 entry_price = 0 stop_loss = 0 trailing_stop = 0 highest_price = 0 lowest_price = 0 # Update portfolio tracking if position_dollars != 0: if position_dollars > 0: # Long position current_btc_value = btc_units * current['close'] unrealized_pnl = current_btc_value - position_dollars else: # Short position unrealized_pnl = (entry_price - current['close']) * abs(btc_units) current_portfolio_value = portfolio_value + unrealized_pnl trade_log.append({'timestamp': timestamp, 'time_seconds': time_seconds, 'portfolio_value': current_portfolio_value}) else: trade_log.append({'timestamp': timestamp, 'time_seconds': time_seconds, 'portfolio_value': portfolio_value}) # Always log current costs (even if no new trades) if not cost_log or cost_log[-1]['time_seconds'] != time_seconds: cost_log.append({ 'timestamp': timestamp, 'time_seconds': time_seconds, 'cumulative_costs': cumulative_costs, 'trading_fees': 0, # No new fees 'spread_costs': 0, 'slippage_costs': 0 }) # Print status every 10 seconds if len(data) % 10 == 0: pos_type = get_position_type() status_emoji = {"LONG": "πŸ“ˆ", "SHORT": "πŸ“‰", "FLAT": "⏸️"}[pos_type] price_change = ((current['close'] - current_price_base) / current_price_base) * 100 portfolio_change = ((portfolio_value - initial_capital) / initial_capital) * 100 cost_percentage = (cumulative_costs / initial_capital) * 100 if position_dollars != 0: if position_dollars > 0: # Long current_btc_value = btc_units * current['close'] unrealized_pnl = current_btc_value - position_dollars print(f"{status_emoji} {pos_type} | Time: {format_time_axis(time_seconds)} | Price: ${current['close']:.2f} ({price_change:+.2f}%)") print(f" Investment: ${position_dollars:.2f} | Current Value: ${current_btc_value:.2f} | Unrealized P&L: ${unrealized_pnl:+.2f}") print(f" πŸ’Έ Total Costs: ${cumulative_costs:.2f} ({cost_percentage:.3f}% of capital)") else: # Short unrealized_pnl = (entry_price - current['close']) * abs(btc_units) print(f"{status_emoji} {pos_type} | Time: {format_time_axis(time_seconds)} | Price: ${current['close']:.2f} ({price_change:+.2f}%)") print(f" Short Size: ${abs(position_dollars):.2f} | Entry: ${entry_price:.2f} | Unrealized P&L: ${unrealized_pnl:+.2f}") print(f" πŸ’Έ Total Costs: ${cumulative_costs:.2f} ({cost_percentage:.3f}% of capital)") else: print(f"{status_emoji} {pos_type} | Time: {format_time_axis(time_seconds)} | Price: ${current['close']:.2f} ({price_change:+.2f}%) | Portfolio: ${portfolio_value:,.2f} ({portfolio_change:+.2f}%)") print(f" πŸ’Έ Total Costs: ${cumulative_costs:.2f} ({cost_percentage:.3f}% of capital)") except Exception as e: print(f"❌ Error in on_message: {e}") def on_error(ws, error): print(f"❌ WebSocket Error: {error}") def on_close(ws, close_status_code, close_msg): print("πŸ”Œ WebSocket closed") if trade_log: pd.DataFrame(trade_log).to_csv('trade_log.csv', index=False) print("πŸ’Ύ Trade log saved") if cost_log: pd.DataFrame(cost_log).to_csv('cost_log.csv', index=False) print("πŸ’Ύ Cost log saved") def on_open(ws): print("πŸ”— WebSocket opened - Starting long/short trend following strategy with costs") # Animation update function def update_plot(frame): global df_global, current_price_base try: if df_global.empty or len(df_global) == 0 or current_price_base is None: return (price_line, sma5_line, sma15_line, stop_loss_line, trailing_stop_line, portfolio_line, portfolio_gross_line, costs_line, fees_line, spread_line, slippage_line, buy_scatter, short_scatter, close_long_scatter, close_short_scatter) df = df_global.copy() # Calculate price change percentage for left axis df['price_change_pct'] = ((df['close'] - current_price_base) / current_price_base) * 100 df['sma5_change_pct'] = ((df['sma5'] - current_price_base) / current_price_base) * 100 df['sma15_change_pct'] = ((df['sma15'] - current_price_base) / current_price_base) * 100 # Update main plot - price change percentage price_line.set_data(df['time_seconds'], df['price_change_pct']) if 'sma5' in df.columns and not df['sma5'].isna().all(): sma5_line.set_data(df['time_seconds'], df['sma5_change_pct']) else: sma5_line.set_data([], []) if 'sma15' in df.columns and not df['sma15'].isna().all(): sma15_line.set_data(df['time_seconds'], df['sma15_change_pct']) else: sma15_line.set_data([], []) # Update markers - separate long and short entries/exits long_entries = [(t, p) for t, p, action in entries if action == 'BUY'] short_entries = [(t, p) for t, p, action in entries if action == 'SHORT'] long_exits = [(t, p) for t, p, action in exits if action == 'CLOSE_LONG'] short_exits = [(t, p) for t, p, action in exits if action == 'CLOSE_SHORT'] if long_entries: buy_times, buy_prices = zip(*long_entries) buy_prices_pct = [((price - current_price_base) / current_price_base) * 100 for price in buy_prices] buy_scatter.set_offsets(np.c_[buy_times, buy_prices_pct]) else: buy_scatter.set_offsets(np.array([]).reshape(0, 2)) if short_entries: short_times, short_prices = zip(*short_entries) short_prices_pct = [((price - current_price_base) / current_price_base) * 100 for price in short_prices] short_scatter.set_offsets(np.c_[short_times, short_prices_pct]) else: short_scatter.set_offsets(np.array([]).reshape(0, 2)) if long_exits: close_long_times, close_long_prices = zip(*long_exits) close_long_prices_pct = [((price - current_price_base) / current_price_base) * 100 for price in close_long_prices] close_long_scatter.set_offsets(np.c_[close_long_times, close_long_prices_pct]) else: close_long_scatter.set_offsets(np.array([]).reshape(0, 2)) if short_exits: close_short_times, close_short_prices = zip(*short_exits) close_short_prices_pct = [((price - current_price_base) / current_price_base) * 100 for price in close_short_prices] close_short_scatter.set_offsets(np.c_[close_short_times, close_short_prices_pct]) else: close_short_scatter.set_offsets(np.array([]).reshape(0, 2)) # Handle trade_log for portfolio (net and gross) if trade_log: trade_df = pd.DataFrame(trade_log) portfolio_line.set_data(trade_df['time_seconds'], trade_df['portfolio_value']) # Calculate gross portfolio (before costs) gross_portfolio = trade_df['portfolio_value'] + cumulative_costs portfolio_gross_line.set_data(trade_df['time_seconds'], gross_portfolio) # Handle cost_log for costs breakdown if cost_log: cost_df = pd.DataFrame(cost_log) costs_line.set_data(cost_df['time_seconds'], cost_df['cumulative_costs']) # Calculate cumulative costs by type cost_df['cum_fees'] = cost_df['trading_fees'].cumsum() cost_df['cum_spread'] = cost_df['spread_costs'].cumsum() cost_df['cum_slippage'] = cost_df['slippage_costs'].cumsum() fees_line.set_data(cost_df['time_seconds'], cost_df['cum_fees']) spread_line.set_data(cost_df['time_seconds'], cost_df['cum_spread']) slippage_line.set_data(cost_df['time_seconds'], cost_df['cum_slippage']) # Fixed time scale: 0 to 600 seconds (10 minutes) ax1.set_xlim(0, 600) ax2.set_xlim(0, 600) ax3.set_xlim(0, 600) # Price scale: Β±2% ax1.set_ylim(-2, 2) # Right axis for real prices current_price = df['close'].iloc[-1] price_range = current_price * 0.02 # 2% ax1_right.set_ylim(current_price - price_range, current_price + price_range) # Portfolio scale: initial capital Β±1% capital_range = initial_capital * 0.01 # 1% ax2.set_ylim(initial_capital - capital_range, initial_capital + capital_range) # Costs scale: 0 to max costs with some headroom if cumulative_costs > 0: ax3.set_ylim(0, cumulative_costs * 1.2) else: ax3.set_ylim(0, 100) # Default range # Set tick marks for time (every minute = 60 seconds) time_ticks = range(0, 601, 60) # 0, 60, 120, ..., 600 time_labels = [format_time_axis(t) for t in time_ticks] for ax in [ax1, ax2, ax3]: ax.set_xticks(time_ticks) ax.set_xticklabels(time_labels) # Add grid lines for better readability ax1.grid(True, alpha=0.3) ax2.grid(True, alpha=0.3) ax3.grid(True, alpha=0.3) except Exception as e: print(f"Plot update error: {e}") return (price_line, sma5_line, sma15_line, stop_loss_line, trailing_stop_line, portfolio_line, portfolio_gross_line, costs_line, fees_line, spread_line, slippage_line, buy_scatter, short_scatter, close_long_scatter, close_short_scatter) # Run WebSocket and animation if __name__ == "__main__": print("πŸš€ Starting Enhanced Long/Short Strategy with Trading Costs") print("πŸ“ˆ LONG: SMA5 crosses above SMA15 + RSI > 50") print("πŸ“‰ SHORT: SMA5 crosses below SMA15 + RSI < 50") print("πŸ’Έ Trading Costs Included:") print(f" β€’ Maker Fee: {maker_fee*100:.1f}%") print(f" β€’ Taker Fee: {taker_fee*100:.1f}%") print(f" β€’ Spread Cost: {spread_cost*100:.2f}%") print(f" β€’ Slippage Cost: {slippage_cost*100:.2f}%") print("πŸ“Š 3-Panel View: Price/SMAs, Portfolio (Net vs Gross), Trading Costs") print("⚑ Realistic trading with all costs included!") ws_url = "wss://stream.binance.com:9443/ws/btcusdt@kline_1s" ws = websocket.WebSocketApp(ws_url, on_message=on_message, on_error=on_error, on_close=on_close, on_open=on_open) import threading ws_thread = threading.Thread(target=ws.run_forever) ws_thread.daemon = True ws_thread.start() try: ani = FuncAnimation(fig, update_plot, interval=1000, blit=True, cache_frame_data=False) plt.tight_layout() plt.show() time.sleep(simulation_duration) except KeyboardInterrupt: print("\n⚠️ Interrupted by user") except Exception as e: print(f"❌ Error: {e}") finally: print("πŸ›‘ Shutting down...") ws.close() # Print final summary with costs analysis if entries or exits: print(f"\nπŸ“Š LONG/SHORT TRADING SUMMARY WITH COSTS:") long_entries_count = len([e for e in entries if e[2] == 'BUY']) short_entries_count = len([e for e in entries if e[2] == 'SHORT']) long_exits_count = len([e for e in exits if e[2] == 'CLOSE_LONG']) short_exits_count = len([e for e in exits if e[2] == 'CLOSE_SHORT']) print(f" Long Entries: {long_entries_count}") print(f" Short Entries: {short_entries_count}") print(f" Long Exits: {long_exits_count}") print(f" Short Exits: {short_exits_count}") print(f" Total Trades: {long_entries_count + short_entries_count}") # Cost analysis cost_percentage = (cumulative_costs / initial_capital) * 100 print(f"\nπŸ’Έ COST ANALYSIS:") print(f" Total Trading Costs: ${cumulative_costs:.2f}") print(f" Cost as % of Capital: {cost_percentage:.3f}%") print(f" Average Cost per Trade: ${cumulative_costs/(long_entries_count + short_entries_count):.2f}" if (long_entries_count + short_entries_count) > 0 else " No completed trades") # Performance analysis gross_return = ((portfolio_value + cumulative_costs - initial_capital) / initial_capital) * 100 net_return = ((portfolio_value - initial_capital) / initial_capital) * 100 print(f"\nπŸ“ˆ PERFORMANCE ANALYSIS:") print(f" Gross Return (before costs): {gross_return:+.2f}%") print(f" Net Return (after costs): {net_return:+.2f}%") print(f" Cost Impact: {gross_return - net_return:.2f}%") print(f" Final Portfolio: ${portfolio_value:,.2f}") print("βœ… Long/Short Strategy with Costs completed!")