261 lines
No EOL
8.6 KiB
Python
261 lines
No EOL
8.6 KiB
Python
'''
|
|
Multi-Ticker Mean Reversion Strategy Overview:
|
|
|
|
Strategy Logic:
|
|
- Trades multiple cryptocurrencies using mean reversion around 15m SMA
|
|
- Uses 4h SMA for trend direction (only trade with the trend)
|
|
- BULLISH trend (4h): Only LONG when price drops below 15m SMA
|
|
- BEARISH trend (4h): Only SHORT when price rises above 15m SMA
|
|
- 5m confirmation: Requires bullish/bearish candle confirmation
|
|
|
|
Entry Conditions:
|
|
- LONG: 4h bullish + price < 15m SMA + 5m bullish confirmation
|
|
- SHORT: 4h bearish + price > 15m SMA + 5m bearish confirmation
|
|
- Entry at 0.8% deviation from 15m SMA
|
|
|
|
Exit Conditions:
|
|
- Profit target: 9% gain
|
|
- Stop loss: 8% loss
|
|
- Automatic order cancellation every 30 minutes
|
|
|
|
Risk Management:
|
|
- Position sizing based on account balance
|
|
- Multiple symbol exposure with individual PnL tracking
|
|
- Leverage: 10x (configurable)
|
|
|
|
WARNING: This strategy has not been backtested. Do not run live without proper testing.
|
|
'''
|
|
|
|
import ccxt
|
|
import pandas as pd
|
|
import numpy as np
|
|
from datetime import datetime
|
|
import time, schedule
|
|
import random
|
|
import os
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv()
|
|
api_key = os.getenv('BINANCE_API_KEY')
|
|
api_secret = os.getenv('BINANCE_SECRET_KEY')
|
|
|
|
exchange = ccxt.binance({
|
|
'apiKey': api_key,
|
|
'secret': api_secret,
|
|
'enableRateLimit': True,
|
|
'options': {
|
|
'defaultType': 'future',
|
|
}
|
|
})
|
|
|
|
pos_size = 30
|
|
target = 9
|
|
max_loss = -8
|
|
leverage = 10
|
|
timeframe = '15m'
|
|
limit = 97
|
|
sma = 20
|
|
params = {'timeInForce': 'GTC'}
|
|
|
|
# Popular crypto symbols for Binance futures
|
|
SYMBOLS = [
|
|
'BTCUSDT', 'ETHUSDT', 'ADAUSDT', 'DOTUSDT', 'LINKUSDT',
|
|
'LTCUSDT', 'BCHUSDT', 'XLMUSDT', 'EOSUSDT', 'TRXUSDT',
|
|
'ETCUSDT', 'DASHUSDT', 'XMRUSDT', 'ZECUSDT', 'XRPUSDT',
|
|
'BNBUSDT', 'SOLUSDT', 'AVAXUSDT', 'MATICUSDT', 'UNIUSDT',
|
|
'SUSHIUSDT', 'AAVEUSDT', 'COMPUSDT', 'MKRUSDT', 'YFIUSDT'
|
|
]
|
|
|
|
def ask_bid(symbol):
|
|
orderbook = exchange.fetch_order_book(symbol)
|
|
bid = orderbook['bids'][0][0]
|
|
ask = orderbook['asks'][0][0]
|
|
return ask, bid
|
|
|
|
def get_open_positions():
|
|
positions = exchange.fetch_positions()
|
|
active_positions = []
|
|
|
|
for pos in positions:
|
|
if pos['symbol'] in SYMBOLS and float(pos['contracts']) != 0:
|
|
active_positions.append({
|
|
'symbol': pos['symbol'],
|
|
'side': pos['side'],
|
|
'size': float(pos['contracts']),
|
|
'entry_price': float(pos['entryPrice']),
|
|
'is_long': pos['side'] == 'long'
|
|
})
|
|
|
|
return active_positions
|
|
|
|
def kill_switch(symbol):
|
|
print(f'Closing position for {symbol}')
|
|
|
|
positions = exchange.fetch_positions([symbol])
|
|
for pos in positions:
|
|
if pos['symbol'] == symbol and float(pos['contracts']) != 0:
|
|
size = float(pos['contracts'])
|
|
is_long = pos['side'] == 'long'
|
|
|
|
exchange.cancel_all_orders(symbol)
|
|
ask, bid = ask_bid(symbol)
|
|
|
|
if is_long:
|
|
exchange.create_limit_sell_order(symbol, size, ask, params)
|
|
print(f'SELL to close: {symbol}')
|
|
else:
|
|
exchange.create_limit_buy_order(symbol, size, bid, params)
|
|
print(f'BUY to close: {symbol}')
|
|
|
|
time.sleep(5)
|
|
break
|
|
|
|
def kill_switch_all():
|
|
print('Closing all positions...')
|
|
active_positions = get_open_positions()
|
|
|
|
for pos in active_positions:
|
|
kill_switch(pos['symbol'])
|
|
|
|
def get_sma_data(symbol, timeframe, limit, sma_period):
|
|
try:
|
|
bars = exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=limit)
|
|
df = pd.DataFrame(bars, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
|
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
|
|
df[f'sma{sma_period}_{timeframe}'] = df['close'].rolling(sma_period).mean()
|
|
|
|
# Add signal based on price vs SMA
|
|
_, bid = ask_bid(symbol)
|
|
df.loc[df[f'sma{sma_period}_{timeframe}'] > bid, 'sig'] = 'SELL'
|
|
df.loc[df[f'sma{sma_period}_{timeframe}'] < bid, 'sig'] = 'BUY'
|
|
|
|
# Add bullish confirmation (current close > previous close)
|
|
df['prev_close'] = df['close'].shift(1)
|
|
df['bullish_confirmation'] = df['close'] > df['prev_close']
|
|
|
|
return df
|
|
except Exception as e:
|
|
print(f'Error getting SMA data for {symbol}: {e}')
|
|
return pd.DataFrame()
|
|
|
|
def pnl_close(symbol):
|
|
positions = exchange.fetch_positions([symbol])
|
|
|
|
for pos in positions:
|
|
if pos['symbol'] == symbol and float(pos['contracts']) != 0:
|
|
entry_price = float(pos['entryPrice'])
|
|
leverage_used = float(pos['leverage'])
|
|
is_long = pos['side'] == 'long'
|
|
|
|
_, current_price = ask_bid(symbol)
|
|
|
|
if is_long:
|
|
price_diff = current_price - entry_price
|
|
else:
|
|
price_diff = entry_price - current_price
|
|
|
|
try:
|
|
pnl_percent = ((price_diff / entry_price) * leverage_used) * 100
|
|
except:
|
|
pnl_percent = 0
|
|
|
|
print(f'{symbol} PnL: {pnl_percent:.2f}%')
|
|
|
|
if pnl_percent >= target:
|
|
print(f'{symbol} hit profit target: {target}%')
|
|
kill_switch(symbol)
|
|
elif pnl_percent <= max_loss:
|
|
print(f'{symbol} hit stop loss: {max_loss}%')
|
|
kill_switch(symbol)
|
|
|
|
break
|
|
|
|
def get_current_time_info():
|
|
now = datetime.now()
|
|
comp24time = int(now.strftime('%H%M'))
|
|
return comp24time
|
|
|
|
def cancel_all_orders_scheduled():
|
|
comp24time = get_current_time_info()
|
|
|
|
# Cancel orders every 30 minutes
|
|
cancel_times = [0, 30, 100, 130, 200, 230, 300, 330, 400, 430, 500, 530,
|
|
600, 630, 700, 730, 800, 830, 900, 930, 1000, 1030, 1100,
|
|
1130, 1200, 1230, 1300, 1330, 1400, 1430, 1500, 1530, 1600,
|
|
1630, 1700, 1730, 1800, 1830, 1900, 1930, 2000, 2030, 2100,
|
|
2130, 2200, 2230, 2300, 2330]
|
|
|
|
if comp24time in cancel_times:
|
|
print('Cancelling all pending orders...')
|
|
for symbol in SYMBOLS:
|
|
try:
|
|
exchange.cancel_all_orders(symbol)
|
|
except:
|
|
pass
|
|
|
|
def bot():
|
|
# Check and close positions first
|
|
active_positions = get_open_positions()
|
|
for pos in active_positions:
|
|
pnl_close(pos['symbol'])
|
|
|
|
# Cancel orders if scheduled
|
|
cancel_all_orders_scheduled()
|
|
|
|
# Select random symbol to analyze
|
|
symbol = random.choice(SYMBOLS)
|
|
|
|
try:
|
|
# Set leverage
|
|
exchange.set_leverage(leverage, symbol)
|
|
|
|
# Get SMA data for different timeframes
|
|
df_4h = get_sma_data(symbol, '4h', 31, sma)
|
|
df_15m = get_sma_data(symbol, '15m', 97, sma)
|
|
df_5m = get_sma_data(symbol, '5m', 100, sma)
|
|
|
|
if df_4h.empty or df_15m.empty or df_5m.empty:
|
|
return
|
|
|
|
# Get current market data
|
|
ask, bid = ask_bid(symbol)
|
|
|
|
# Get signals
|
|
sig_4h = df_4h['sig'].iloc[-1]
|
|
sma_15m = df_15m[f'sma{sma}_15m'].iloc[-1]
|
|
bullish_confirmation_5m = df_5m['bullish_confirmation'].iloc[-1]
|
|
|
|
# Calculate entry levels
|
|
sma_minus_008 = sma_15m * 0.992 # 0.8% below SMA
|
|
sma_plus_008 = sma_15m * 1.008 # 0.8% above SMA
|
|
|
|
# Check if already in position
|
|
in_position = any(pos['symbol'] == symbol for pos in active_positions)
|
|
|
|
# Mean reversion strategy
|
|
if not in_position:
|
|
if sig_4h == 'BUY': # Bullish 4h trend
|
|
# Look for longs when price drops below 15m SMA
|
|
if bid <= sma_15m and bullish_confirmation_5m:
|
|
exchange.cancel_all_orders(symbol)
|
|
exchange.create_limit_buy_order(symbol, pos_size, sma_minus_008, params)
|
|
print(f'BUY to open: {symbol} at {sma_minus_008}')
|
|
|
|
elif sig_4h == 'SELL': # Bearish 4h trend
|
|
# Look for shorts when price rises above 15m SMA
|
|
if bid >= sma_15m and not bullish_confirmation_5m:
|
|
exchange.cancel_all_orders(symbol)
|
|
exchange.create_limit_sell_order(symbol, pos_size, sma_plus_008, params)
|
|
print(f'SELL to open: {symbol} at {sma_plus_008}')
|
|
|
|
except Exception as e:
|
|
print(f'Error processing {symbol}: {e}')
|
|
|
|
schedule.every(10).seconds.do(bot)
|
|
|
|
while True:
|
|
try:
|
|
schedule.run_pending()
|
|
except Exception as e:
|
|
print(f'Bot error: {e}. Sleeping 7 seconds...')
|
|
time.sleep(7) |