341 lines
No EOL
11 KiB
Python
341 lines
No EOL
11 KiB
Python
'''
|
|
Market Maker Strategy Overview:
|
|
|
|
Market Making Logic:
|
|
- Places buy orders near recent lows, sell orders near recent highs
|
|
- Uses dynamic range based on recent high-low spread
|
|
- Only trades within configured percentage of recent range extremes
|
|
|
|
Risk Management:
|
|
- ATR-based volatility filtering (no trading if volatility too high)
|
|
- Emergency position size limits with automatic closure
|
|
- Time-based position limits (auto-close after time limit)
|
|
- Stop losses with trailing functionality
|
|
|
|
Entry Conditions:
|
|
- BUY: When bid drops below (low + range_percentage)
|
|
- SELL: When ask rises above (high - range_percentage)
|
|
- Range calculated from recent N-period high/low
|
|
- Filtered out during high volatility periods
|
|
|
|
Exit Conditions:
|
|
- Profit target: 0.4% gain from entry
|
|
- Stop loss: 0.1% loss from entry
|
|
- Time limit: 2 hours maximum hold time
|
|
- Emergency size limit: $1100 maximum risk
|
|
|
|
Technical Filters:
|
|
- Uses 5-minute timeframe with 180-period lookback (15 hours)
|
|
- ATR volatility filter prevents trading in choppy markets
|
|
- Recent high/low trend analysis prevents bad entries
|
|
|
|
WARNING: This strategy is complex and has not been fully backtested.
|
|
Use only for educational purposes without proper testing.
|
|
|
|
WARNING: Do not run without thorough backtesting and understanding!
|
|
'''
|
|
|
|
import ccxt
|
|
import pandas as pd
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
import schedule
|
|
import warnings
|
|
import os
|
|
from dotenv import load_dotenv
|
|
|
|
warnings.filterwarnings("ignore")
|
|
|
|
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',
|
|
}
|
|
})
|
|
|
|
# Configuration
|
|
size = 4200
|
|
symbol = 'DYDXUSDT'
|
|
perc_from_lh = 0.35
|
|
close_seconds = 60 * 47 # 47 minutes
|
|
trade_pause_mins = 15
|
|
max_lh = 1250
|
|
timeframe = '5m'
|
|
num_bars = 180
|
|
max_risk = 1100
|
|
sl_perc = 0.1
|
|
exit_perc = 0.004
|
|
max_tr = 550
|
|
quartile = 0.33
|
|
time_limit = 120 # minutes
|
|
sleep_time = 30
|
|
params = {'timeInForce': 'GTC'}
|
|
|
|
def get_bid_ask(symbol=symbol):
|
|
orderbook = exchange.fetch_order_book(symbol)
|
|
bid = orderbook['bids'][0][0]
|
|
ask = orderbook['asks'][0][0]
|
|
bid_vol = orderbook['bids'][0][1]
|
|
ask_vol = orderbook['asks'][0][1]
|
|
return ask, bid, ask_vol, bid_vol
|
|
|
|
def get_open_positions(symbol=symbol):
|
|
positions = exchange.fetch_positions([symbol])
|
|
|
|
for pos in positions:
|
|
if pos['symbol'] == symbol and float(pos['contracts']) != 0:
|
|
return {
|
|
'side': pos['side'],
|
|
'size': float(pos['contracts']),
|
|
'entry_price': float(pos['entryPrice']),
|
|
'is_long': pos['side'] == 'long',
|
|
'has_position': True
|
|
}
|
|
|
|
return {
|
|
'side': None,
|
|
'size': 0,
|
|
'entry_price': 0,
|
|
'is_long': None,
|
|
'has_position': False
|
|
}
|
|
|
|
def size_kill(symbol=symbol):
|
|
position = get_open_positions(symbol)
|
|
|
|
if position['has_position']:
|
|
position_cost = abs(position['size'] * position['entry_price'])
|
|
|
|
if position_cost > max_risk:
|
|
print(f'EMERGENCY: Position size ${position_cost} exceeds max risk ${max_risk}')
|
|
kill_switch(symbol)
|
|
print('Emergency closure complete. Sleeping 72 hours...')
|
|
time.sleep(260000)
|
|
|
|
def kill_switch(symbol=symbol):
|
|
print('KILL SWITCH ACTIVATED')
|
|
|
|
while True:
|
|
position = get_open_positions(symbol)
|
|
|
|
if not position['has_position']:
|
|
break
|
|
|
|
exchange.cancel_all_orders(symbol)
|
|
ask, bid, _, _ = get_bid_ask(symbol)
|
|
|
|
if position['is_long']:
|
|
exchange.create_limit_sell_order(symbol, position['size'], ask, params)
|
|
print(f'SELL to close: {position["size"]} at {ask}')
|
|
else:
|
|
exchange.create_limit_buy_order(symbol, position['size'], bid, params)
|
|
print(f'BUY to close: {position["size"]} at {bid}')
|
|
|
|
time.sleep(30)
|
|
|
|
def get_order_status(symbol=symbol):
|
|
open_orders = exchange.fetch_open_orders(symbol)
|
|
|
|
has_stop_loss = any(order['type'] == 'stop_market' for order in open_orders)
|
|
has_close_order = any(order['type'] == 'limit' for order in open_orders)
|
|
has_entry_order = len(open_orders) > 0 and not get_open_positions(symbol)['has_position']
|
|
|
|
return {
|
|
'has_both': has_stop_loss and has_close_order,
|
|
'needs_stop_loss': not has_stop_loss,
|
|
'needs_close_order': not has_close_order,
|
|
'has_entry_pending': has_entry_order
|
|
}
|
|
|
|
def calculate_atr(df, period=7):
|
|
df['prev_close'] = df['close'].shift(1)
|
|
df['high_low'] = abs(df['high'] - df['low'])
|
|
df['high_prev_close'] = abs(df['high'] - df['prev_close'])
|
|
df['low_prev_close'] = abs(df['low'] - df['prev_close'])
|
|
|
|
df['tr'] = df[['high_low', 'high_prev_close', 'low_prev_close']].max(axis=1)
|
|
df['atr'] = df['tr'].rolling(period).mean()
|
|
|
|
return df
|
|
|
|
def get_pnl(symbol=symbol):
|
|
position = get_open_positions(symbol)
|
|
|
|
if not position['has_position']:
|
|
return "No position", 0, 0
|
|
|
|
ask, bid, _, _ = get_bid_ask(symbol)
|
|
current_price = bid
|
|
entry_price = position['entry_price']
|
|
|
|
if position['is_long']:
|
|
price_diff = current_price - entry_price
|
|
else:
|
|
price_diff = entry_price - current_price
|
|
|
|
try:
|
|
pnl_percent = (price_diff / entry_price) * 100
|
|
except:
|
|
pnl_percent = 0
|
|
|
|
pnl_text = f'PnL: {pnl_percent:.2f}%'
|
|
print(pnl_text)
|
|
|
|
return pnl_text, ask, bid
|
|
|
|
def create_stop_order(symbol, reference_price, direction):
|
|
position = get_open_positions(symbol)
|
|
position_size = position['size'] if position['has_position'] else size
|
|
|
|
if direction == 'SELL': # Stop for long position
|
|
stop_price = reference_price * 1.001
|
|
exchange.create_order(
|
|
symbol, 'stop_market', 'sell', position_size, None,
|
|
params={'stopPrice': stop_price}
|
|
)
|
|
print(f'Stop loss created: SELL at {stop_price}')
|
|
|
|
elif direction == 'BUY': # Stop for short position
|
|
stop_price = reference_price * 0.999
|
|
exchange.create_order(
|
|
symbol, 'stop_market', 'buy', position_size, None,
|
|
params={'stopPrice': stop_price}
|
|
)
|
|
print(f'Stop loss created: BUY at {stop_price}')
|
|
|
|
def bot():
|
|
print('\n---- MARKET MAKER ACTIVE ----\n')
|
|
|
|
# Check PnL and get current prices
|
|
pnl_info = get_pnl(symbol)
|
|
ask = pnl_info[1]
|
|
bid = pnl_info[2]
|
|
|
|
# Emergency size check
|
|
size_kill()
|
|
|
|
# Get market data
|
|
bars = exchange.fetch_ohlcv(symbol, timeframe=timeframe, limit=num_bars)
|
|
df = pd.DataFrame(bars[:-1], columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
|
|
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
|
|
|
|
# Calculate technical indicators
|
|
df = calculate_atr(df)
|
|
|
|
# Get range data
|
|
low = df['low'].min()
|
|
high = df['high'].max()
|
|
range_size = high - low
|
|
avg_price = (high + low) / 2
|
|
|
|
print(f'Range: {low:.2f} - {high:.2f} | Size: {range_size:.2f} | Avg: {avg_price:.2f}')
|
|
|
|
# Check if range is too large (high volatility)
|
|
if range_size > max_lh:
|
|
print('RANGE TOO LARGE - No trading')
|
|
kill_switch()
|
|
return
|
|
|
|
# Check recent trend (no new highs/lows in last 17 bars)
|
|
recent_lows = df['low'].tail(17).tolist()
|
|
no_trading = any(low >= recent_low for recent_low in recent_lows)
|
|
|
|
if no_trading:
|
|
print('Recent trend unfavorable - No trading')
|
|
return
|
|
|
|
# Get position and order status
|
|
position = get_open_positions(symbol)
|
|
order_status = get_order_status(symbol)
|
|
|
|
# Calculate entry levels
|
|
open_range = range_size * perc_from_lh
|
|
sell_to_open_limit = high - open_range
|
|
buy_to_open_limit = low + open_range
|
|
|
|
print(f'Entry levels - Buy below: {buy_to_open_limit:.2f}, Sell above: {sell_to_open_limit:.2f}')
|
|
|
|
# Calculate exit price for existing position
|
|
if position['has_position']:
|
|
if position['is_long']:
|
|
exit_price = position['entry_price'] * (1 + exit_perc)
|
|
else:
|
|
exit_price = position['entry_price'] * (1 - exit_perc)
|
|
|
|
# Check time limit for existing positions
|
|
current_time = int(time.time())
|
|
|
|
# Main trading logic
|
|
if not position['has_position'] and not order_status['has_entry_pending']:
|
|
# Look for entry opportunities
|
|
|
|
if ask > sell_to_open_limit:
|
|
print('SELLING to open - Price above sell level')
|
|
exchange.cancel_all_orders(symbol)
|
|
|
|
# Create sell order
|
|
sell_price = ask * 1.0009
|
|
exchange.create_limit_sell_order(symbol, size, sell_price, params)
|
|
print(f'SELL order placed at {sell_price}')
|
|
|
|
# Create stop loss
|
|
create_stop_order(symbol, high, 'SELL')
|
|
time.sleep(300) # 5 minute pause
|
|
|
|
elif bid < buy_to_open_limit:
|
|
print('BUYING to open - Price below buy level')
|
|
exchange.cancel_all_orders(symbol)
|
|
|
|
# Create buy order
|
|
buy_price = bid * 0.9991
|
|
exchange.create_limit_buy_order(symbol, size, buy_price, params)
|
|
print(f'BUY order placed at {buy_price}')
|
|
|
|
# Create stop loss
|
|
create_stop_order(symbol, low, 'BUY')
|
|
time.sleep(300) # 5 minute pause
|
|
|
|
else:
|
|
print('Price in middle of range - No entry')
|
|
|
|
elif position['has_position']:
|
|
# Manage existing position
|
|
|
|
if order_status['needs_close_order']:
|
|
# Create close order at profit target
|
|
if position['is_long']:
|
|
exchange.create_limit_sell_order(symbol, position['size'], exit_price, params)
|
|
print(f'Close order: SELL at {exit_price}')
|
|
else:
|
|
exchange.create_limit_buy_order(symbol, position['size'], exit_price, params)
|
|
print(f'Close order: BUY at {exit_price}')
|
|
|
|
if order_status['needs_stop_loss']:
|
|
# Create missing stop loss
|
|
if position['is_long']:
|
|
create_stop_order(symbol, low, 'BUY')
|
|
else:
|
|
create_stop_order(symbol, high, 'SELL')
|
|
|
|
else:
|
|
print('All orders in place - Monitoring...')
|
|
time.sleep(12)
|
|
|
|
print('=' * 40)
|
|
|
|
schedule.every(25).seconds.do(bot)
|
|
|
|
while True:
|
|
try:
|
|
schedule.run_pending()
|
|
time.sleep(15)
|
|
except Exception as e:
|
|
print(f'Bot error: {e}')
|
|
print('Sleeping 75 seconds...')
|
|
time.sleep(75) |