300 lines
No EOL
9 KiB
Python
300 lines
No EOL
9 KiB
Python
'''
|
|
SMA + Order Book Strategy Overview:
|
|
|
|
Direction Signal:
|
|
- Uses 20-day SMA on daily timeframe to determine market bias
|
|
- BUY when price > daily SMA, SELL when price < daily SMA
|
|
|
|
Entry Logic:
|
|
- Uses 15-minute SMA with offset levels for precise entries
|
|
- Places dual limit orders around 15m SMA (±0.1% to ±0.3%)
|
|
- Only enters when price aligns with daily SMA direction
|
|
|
|
Risk Management:
|
|
- Profit target: 8% gain
|
|
- Stop loss: 9% loss
|
|
- Order book volume analysis confirms exits
|
|
- Position size management with incremental entries
|
|
|
|
Volume Confirmation:
|
|
- Analyzes bid/ask volume ratio over 55 seconds
|
|
- Delays exits if volume ratio < 0.4 (insufficient confirmation)
|
|
|
|
WARNING: Do not run without thorough backtesting and understanding!
|
|
'''
|
|
|
|
import ccxt
|
|
import pandas as pd
|
|
import time, schedule
|
|
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',
|
|
}
|
|
})
|
|
|
|
symbol = 'BTCUSDT'
|
|
pos_size = 30
|
|
params = {'timeInForce': 'GTC'}
|
|
target = 8
|
|
max_loss = -9
|
|
vol_decimal = 0.4
|
|
|
|
def ask_bid():
|
|
orderbook = exchange.fetch_order_book(symbol)
|
|
bid = orderbook['bids'][0][0]
|
|
ask = orderbook['asks'][0][0]
|
|
return ask, bid
|
|
|
|
def daily_sma():
|
|
print('Calculating daily SMA...')
|
|
bars = exchange.fetch_ohlcv(symbol, timeframe='1d', limit=100)
|
|
df = pd.DataFrame(bars, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
|
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
|
|
df['sma20_d'] = df['close'].rolling(20).mean()
|
|
|
|
_, bid = ask_bid()
|
|
df.loc[df['sma20_d'] > bid, 'sig'] = 'SELL'
|
|
df.loc[df['sma20_d'] < bid, 'sig'] = 'BUY'
|
|
|
|
return df
|
|
|
|
def f15_sma():
|
|
print('Calculating 15m SMA...')
|
|
bars = exchange.fetch_ohlcv(symbol, timeframe='15m', limit=100)
|
|
df = pd.DataFrame(bars, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
|
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
|
|
df['sma20_15'] = df['close'].rolling(20).mean()
|
|
|
|
df['bp_1'] = df['sma20_15'] * 1.001
|
|
df['bp_2'] = df['sma20_15'] * 0.997
|
|
df['sp_1'] = df['sma20_15'] * 0.999
|
|
df['sp_2'] = df['sma20_15'] * 1.003
|
|
|
|
return df
|
|
|
|
def open_positions():
|
|
positions = exchange.fetch_positions([symbol])
|
|
|
|
for pos in positions:
|
|
if pos['symbol'] == symbol and float(pos['contracts']) != 0:
|
|
side = pos['side']
|
|
size = pos['contracts']
|
|
is_long = side == 'long'
|
|
return positions, True, size, is_long
|
|
|
|
return [], False, 0, None
|
|
|
|
def kill_switch():
|
|
print('Starting kill switch...')
|
|
|
|
while True:
|
|
_, has_position, position_size, is_long = open_positions()
|
|
|
|
if not has_position:
|
|
break
|
|
|
|
exchange.cancel_all_orders(symbol)
|
|
ask, bid = ask_bid()
|
|
position_size = float(position_size)
|
|
|
|
if is_long:
|
|
exchange.create_limit_sell_order(symbol, position_size, ask, params)
|
|
print(f'SELL to close: {position_size} {symbol} at {ask}')
|
|
else:
|
|
exchange.create_limit_buy_order(symbol, position_size, bid, params)
|
|
print(f'BUY to close: {position_size} {symbol} at {bid}')
|
|
|
|
time.sleep(30)
|
|
|
|
def sleep_on_close():
|
|
closed_orders = exchange.fetch_closed_orders(symbol)
|
|
|
|
for order in reversed(closed_orders):
|
|
if order['status'] == 'closed':
|
|
order_time = int(order['timestamp'] / 1000)
|
|
current_time = int(exchange.fetch_order_book(symbol)['timestamp'] / 1000)
|
|
time_since_trade = (current_time - order_time) / 60
|
|
|
|
if time_since_trade < 59:
|
|
print(f'Recent trade {time_since_trade:.1f}m ago, sleeping 60s...')
|
|
time.sleep(60)
|
|
else:
|
|
print(f'Last trade {time_since_trade:.1f}m ago, no sleep needed')
|
|
break
|
|
|
|
print('Sleep check complete')
|
|
|
|
def ob():
|
|
print('Analyzing order book volume...')
|
|
|
|
volume_data = []
|
|
|
|
for i in range(11):
|
|
orderbook = exchange.fetch_order_book(symbol)
|
|
|
|
bid_volume = sum([bid[1] for bid in orderbook['bids']])
|
|
ask_volume = sum([ask[1] for ask in orderbook['asks']])
|
|
|
|
volume_data.append({'bid_vol': bid_volume, 'ask_vol': ask_volume})
|
|
|
|
if i < 10:
|
|
time.sleep(5)
|
|
|
|
print(f'Sample {i+1}: Bid: {bid_volume}, Ask: {ask_volume}')
|
|
|
|
df = pd.DataFrame(volume_data)
|
|
total_bid_vol = df['bid_vol'].sum()
|
|
total_ask_vol = df['ask_vol'].sum()
|
|
|
|
print(f'Total volumes - Bid: {total_bid_vol}, Ask: {total_ask_vol}')
|
|
|
|
if total_bid_vol > total_ask_vol:
|
|
control_ratio = total_ask_vol / total_bid_vol
|
|
print(f'Bulls in control: {control_ratio:.3f}')
|
|
else:
|
|
control_ratio = total_bid_vol / total_ask_vol
|
|
print(f'Bears in control: {control_ratio:.3f}')
|
|
|
|
_, has_position, _, is_long = open_positions()
|
|
|
|
if has_position:
|
|
volume_under_threshold = control_ratio < vol_decimal
|
|
position_type = 'long' if is_long else 'short'
|
|
print(f'In {position_type} position. Volume under threshold: {volume_under_threshold}')
|
|
return volume_under_threshold
|
|
else:
|
|
print('Not in position')
|
|
return None
|
|
|
|
def pnl_close():
|
|
print('Checking PnL...')
|
|
|
|
positions = exchange.fetch_positions([symbol])
|
|
|
|
position = None
|
|
for pos in positions:
|
|
if pos['symbol'] == symbol and float(pos['contracts']) != 0:
|
|
position = pos
|
|
break
|
|
|
|
if position is None:
|
|
print('No position open')
|
|
return False, False, 0, None
|
|
|
|
side = position['side']
|
|
size = position['contracts']
|
|
entry_price = float(position['entryPrice'])
|
|
leverage = float(position['leverage'])
|
|
is_long = side == 'long'
|
|
|
|
_, current_price = ask_bid()
|
|
|
|
if is_long:
|
|
price_diff = current_price - entry_price
|
|
else:
|
|
price_diff = entry_price - current_price
|
|
|
|
try:
|
|
pnl_percent = ((price_diff / entry_price) * leverage) * 100
|
|
except:
|
|
pnl_percent = 0
|
|
|
|
print(f'PnL: {pnl_percent:.2f}% (Entry: {entry_price}, Current: {current_price})')
|
|
|
|
if pnl_percent > target:
|
|
print('Target hit! Checking volume...')
|
|
volume_under_threshold = ob()
|
|
|
|
if volume_under_threshold:
|
|
print(f'Volume too low, waiting 30s...')
|
|
time.sleep(30)
|
|
else:
|
|
print('Closing profitable position!')
|
|
kill_switch()
|
|
return True, True, size, is_long
|
|
|
|
elif pnl_percent <= max_loss:
|
|
print('Max loss hit! Closing position.')
|
|
kill_switch()
|
|
return True, True, size, is_long
|
|
|
|
elif pnl_percent > 0:
|
|
print('In profit but target not reached')
|
|
else:
|
|
print('In loss but within acceptable range')
|
|
|
|
# SMA stop loss check
|
|
if True:
|
|
df_15m = f15_sma()
|
|
sma_15m = df_15m.iloc[-1]['sma20_15']
|
|
stop_loss_level = sma_15m * 1.008
|
|
print(f'SMA stop level: {stop_loss_level}')
|
|
|
|
return False, True, size, is_long
|
|
|
|
def bot():
|
|
pnl_close()
|
|
sleep_on_close()
|
|
|
|
df_daily = daily_sma()
|
|
df_15m = f15_sma()
|
|
ask, bid = ask_bid()
|
|
|
|
signal = df_daily.iloc[-1]['sig']
|
|
open_size = pos_size / 2
|
|
|
|
_, in_position, current_size, _ = open_positions()
|
|
current_size = float(current_size) if current_size else 0
|
|
current_price = bid
|
|
sma_15m = df_15m.iloc[-1]['sma20_15']
|
|
|
|
if not in_position and current_size < pos_size:
|
|
exchange.cancel_all_orders(symbol)
|
|
|
|
if signal == 'BUY' and current_price > sma_15m:
|
|
print('Making BUY orders')
|
|
bp_1 = df_15m.iloc[-1]['bp_1']
|
|
bp_2 = df_15m.iloc[-1]['bp_2']
|
|
print(f'Buy prices: {bp_1}, {bp_2}')
|
|
|
|
exchange.create_limit_buy_order(symbol, open_size, bp_1, params)
|
|
exchange.create_limit_buy_order(symbol, open_size, bp_2, params)
|
|
|
|
print('Orders placed, sleeping 2 minutes...')
|
|
time.sleep(120)
|
|
|
|
elif signal == 'SELL' and current_price < sma_15m:
|
|
print('Making SELL orders')
|
|
sp_1 = df_15m.iloc[-1]['sp_1']
|
|
sp_2 = df_15m.iloc[-1]['sp_2']
|
|
print(f'Sell prices: {sp_1}, {sp_2}')
|
|
|
|
exchange.create_limit_sell_order(symbol, open_size, sp_1, params)
|
|
exchange.create_limit_sell_order(symbol, open_size, sp_2, params)
|
|
|
|
print('Orders placed, sleeping 2 minutes...')
|
|
time.sleep(120)
|
|
else:
|
|
print('Price not aligned with SMA, sleeping 10 minutes...')
|
|
time.sleep(600)
|
|
else:
|
|
print('Already in position or at size limit')
|
|
|
|
schedule.every(28).seconds.do(bot)
|
|
|
|
while True:
|
|
try:
|
|
schedule.run_pending()
|
|
except Exception as e:
|
|
print(f'ERROR: {e}. Sleeping 30 seconds...')
|
|
time.sleep(30) |