''' 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)