1
Fork 0
crypto_bot_training/trend_following_long_short_with_cost.py
2025-06-21 07:16:58 +02:00

640 lines
No EOL
33 KiB
Python

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!")