226 lines
No EOL
7.2 KiB
Python
226 lines
No EOL
7.2 KiB
Python
import ccxt
|
|
import pandas as pd
|
|
import time
|
|
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 = 'APEUSDT'
|
|
pause_time = 60
|
|
vol_repeat = 11
|
|
vol_time = 5
|
|
pos_size = 100
|
|
params = {'timeInForce': 'GTC'}
|
|
target = 35
|
|
max_loss = -55
|
|
vol_decimal = 0.4
|
|
timeframe = '4h'
|
|
limit = 100
|
|
sma = 20
|
|
|
|
def ask_bid(symbol=symbol):
|
|
orderbook = exchange.fetch_order_book(symbol)
|
|
bid = orderbook['bids'][0][0]
|
|
ask = orderbook['asks'][0][0]
|
|
print(f'Ask price for {symbol}: {ask}')
|
|
return ask, bid
|
|
|
|
def df_sma(symbol=symbol, timeframe=timeframe, limit=limit, sma=sma):
|
|
print('Starting technical indicators...')
|
|
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}_{timeframe}'] = df['close'].rolling(sma).mean()
|
|
_, bid = ask_bid(symbol)
|
|
|
|
df.loc[df[f'sma{sma}_{timeframe}'] > bid, 'sig'] = 'SELL'
|
|
df.loc[df[f'sma{sma}_{timeframe}'] < bid, 'sig'] = 'BUY'
|
|
|
|
df['support'] = df[:-2]['close'].min()
|
|
df['resistance'] = df[:-2]['close'].max()
|
|
df['prev_close'] = df['close'].shift(1)
|
|
df['bullish_confirmation'] = df['close'] > df['prev_close']
|
|
|
|
return df
|
|
|
|
def open_positions(symbol=symbol):
|
|
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'
|
|
print(f'Open position: {side} {size} contracts')
|
|
return positions, True, size, is_long, 0, {}
|
|
|
|
return [], False, 0, None, 0, {}
|
|
|
|
def kill_switch(symbol=symbol):
|
|
print(f'Starting kill switch for {symbol}')
|
|
|
|
while True:
|
|
_, has_position, position_size, is_long, _, _ = open_positions(symbol)
|
|
|
|
if not has_position:
|
|
break
|
|
|
|
print(f'Closing position: {position_size} contracts, long: {is_long}')
|
|
|
|
exchange.cancel_all_orders(symbol)
|
|
ask, bid = ask_bid(symbol)
|
|
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}')
|
|
|
|
print('Waiting 30 seconds for fill...')
|
|
time.sleep(30)
|
|
|
|
def sleep_on_close(symbol=symbol, pause_time=pause_time):
|
|
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
|
|
|
|
print(f'Last trade was {time_since_trade:.1f} minutes ago')
|
|
|
|
if time_since_trade < pause_time - 1:
|
|
print(f'Sleeping 60 seconds (recent trade)')
|
|
time.sleep(60)
|
|
else:
|
|
print('No sleep needed')
|
|
break
|
|
|
|
print(f'Sleep check complete for {symbol}')
|
|
|
|
def ob(symbol=symbol, vol_repeat=vol_repeat, vol_time=vol_time):
|
|
print(f'Analyzing order book volume for {symbol}...')
|
|
|
|
volume_data = []
|
|
|
|
for i in range(vol_repeat):
|
|
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 < vol_repeat - 1:
|
|
time.sleep(vol_time)
|
|
|
|
print(f'Sample {i+1}: Bid Vol: {bid_volume}, Ask Vol: {ask_volume}')
|
|
|
|
df = pd.DataFrame(volume_data)
|
|
total_bid_vol = df['bid_vol'].sum()
|
|
total_ask_vol = df['ask_vol'].sum()
|
|
|
|
minutes = (vol_time * vol_repeat) / 60
|
|
print(f'Total volume over {minutes:.1f} minutes:')
|
|
print(f'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(symbol)
|
|
|
|
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(symbol=symbol, target=target, max_loss=max_loss):
|
|
print(f'Checking PnL for {symbol}...')
|
|
|
|
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(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) * 100
|
|
except:
|
|
pnl_percent = 0
|
|
|
|
print(f'PnL: {pnl_percent:.2f}% (Entry: {entry_price}, Current: {current_price})')
|
|
|
|
if pnl_percent > target:
|
|
print(f'Target hit! Checking volume before closing...')
|
|
volume_under_threshold = ob(symbol)
|
|
|
|
if volume_under_threshold:
|
|
print(f'Volume too low, waiting 30s...')
|
|
time.sleep(30)
|
|
else:
|
|
print(f'Closing profitable position!')
|
|
kill_switch(symbol)
|
|
return True, True, size, is_long
|
|
|
|
elif pnl_percent <= max_loss:
|
|
print(f'Max loss hit! Closing position immediately.')
|
|
kill_switch(symbol)
|
|
return True, True, size, is_long
|
|
|
|
elif pnl_percent > 0:
|
|
print(f'In profit but target not reached')
|
|
else:
|
|
print(f'In loss but within acceptable range')
|
|
|
|
# Stop loss check using 15m SMA
|
|
if True: # Position exists
|
|
df_15m = df_sma(symbol, '15m', 100, 20)
|
|
sma_15m = df_15m.iloc[-1][f'sma{sma}_15m']
|
|
stop_loss_level = sma_15m * 1.008
|
|
print(f'SMA stop loss level: {stop_loss_level}')
|
|
|
|
print(f'PnL check complete for {symbol}')
|
|
return False, True, size, is_long |