# Python Bitfinex API code for calculating ATR (WMA) and set Chandelier Trailing Stop/TP for a CCY pair (e.g. BTCUSDT) # Test ver 0.10e (Date: 20211125) # import os import sys import asyncio import numpy as np import pandas_datareader as pdr import datetime as dt import pandas as pd import smtplib, ssl import schedule import time sys.path.append('../../../') from time import sleep from datetime import datetime from requests import exceptions from twilio.rest import Client as Twilio_client from dotenv import load_dotenv from bfxapi import Client now = int(round(time.time() * 1000)) then = now - (1000 * 60 * 60 * 24 * 1) # 10 days ago ##### set input parameters ##### period = 22 # period for ATR averaging mult = 4.5 # multiplier for ATR (BTC: 4.5; ETH: 4) cal_type = "close" # use bar "high" or "close" value for calculation #cal_type = "high" # use bar "high" or "close" value for calculation lotsize = 0.001 # set order quantity CCY = 'tBTCUSD' # set the trading coin pair (e.g. tBTCUSD, etc.) server = 'live' # set connection to "test" or "live" server filename = 'OCO_orderID_'+CCY+'.txt' # define filename for orderID record retry_time = 10 # time (sec) for retry after exception recWin = 5000 # set command recvWindow time ### take environment variables from .env load_dotenv() ### set bitfinex API keys if server == 'live': api_key = os.environ.get('bitfinex_api') api_secret = os.environ.get('bitfinex_secret') #connect_url = 'https://api.binance.com/api' elif server == 'test': api_key = os.environ.get('bitfinex_api_test') api_secret = os.environ.get('bitfinex_secret_test') #connect_url = 'https://testnet.binance.vision/api' ### set Twilio SMS settings account_sid = os.environ.get('twi_acct_sid') auth_token = os.environ.get('twi_auth_token') twi_number = os.environ.get('twi_phone') cell_number = os.environ.get('twi_cell') twi_client = Twilio_client(account_sid, auth_token) print('Twilio phone setting: ', twi_number, cell_number) ### Setting for email alert port = 465 # For SSL smtp_server = os.environ.get('em_smtp_server') sender_email = os.environ.get('em_sender_email') receiver_email = os.environ.get('em_receiver_email') #password = input("Type your password and press enter: ") password = os.environ.get('em_password') message = """\ Subject: Bitfinex Exception This message is sent from Python: """ context = ssl.create_default_context() print('Alert Email setting: ', receiver_email) start_time = datetime.now() ### Flag for initial running first_run = True new_alert_flag = True ### Function: Send sms/email alert async def send_alert(msg, new_alert): print(msg) #if (new_alert): #print('Send SMS/Email') #twi_client.messages.create(from_=twi_number, to=cell_number, body=msg) #with smtplib.SMTP_SSL(smtp_server, port, context=context) as server: # server.login(sender_email, password) # server.sendmail(sender_email, receiver_email, message+msg) bfx = Client( API_KEY=api_key, API_SECRET=api_secret, logLevel='INFO' ) ### Function: WMA calculation def wma_calc(w): def g(x): return sum(w * x) / sum(w) return g async def log_historical_candles(): hist_candles = await bfx.rest.get_public_candles(CCY, 0, then, limit=period*3-1, tf='1D', section='hist') last_candle = await bfx.rest.get_public_candles(CCY, 0, then, limit='1', tf='1D', section='last') candles = hist_candles[::-1]+list([last_candle]) #print ("Candles:") #[ print (c) for c in candles ] return candles async def create_oco_order(limit_price, stop_price, amt): response = await bfx.rest.submit_order(CCY, price=limit_price, amount=amt, oco_stop_price=stop_price, oco=True) return response #response = await bfx.rest.submit_order('tTESTBTCTESTUSD', price=50000, amount=lotsize, oco_stop_price=80000, oco=True) # response is in the form of a Notification object #for o in response.notify_info: # # each item is in the form of an Order object # print ("Order: ", o) async def cancel_order(orderID): response = await bfx.rest.submit_cancel_order(orderID) # response is in the form of a Notification object # notify_info is in the form of an order object print ("Cancelled Order: ", response.notify_info) async def retrieve_order_status(orderID): now = int(round(time.time() * 1000)) st = now - (1000 * 60 * 60 * 24 * 1) # 1 day ago #response = await bfx.rest.get_order_trades(CCY, orderID) response = await bfx.rest.get_active_orders(CCY) for o in response: #print('Active orders: ', o) if (o.id == orderID): return o.status response2 = await bfx.rest.get_order_history(CCY, st, now) for q in response2: #print('Order History: ', q) if (q.id == orderID): return q.status # if ("EXECUTED" in o.status): # print('Order Hist: ', o) async def run(): ### Flag for initial running first_run = True cur_orderID1 = 0 cur_orderID2 = 0 order_statusID1 = "" order_statusID2 = "" while True: if (not first_run): ### check status of opened order order_statusID1 = await retrieve_order_status(cur_orderID1) order_statusID2 = await retrieve_order_status(cur_orderID2) #print('OCO orders status: ', order_statusID1, order_statusID2) if ('EXECUTED' in order_statusID1 or 'EXECUTED' in order_statusID2): await send_alert('The BitfinexAPI ' + CCY + ' SL/TP order is executed', True) exit() elif ('ACTIVE' in order_statusID1 or 'ACTIVE' in order_statusID2): if ('ACTIVE' in order_statusID1): await cancel_order(cur_orderID1) if ('ACTIVE' in order_statusID2): await cancel_order(cur_orderID2) # prev_order2 = client.get_order(symbol=CCY, orderId=cur_orderID2, recvWindow=recWin) ### if order filled -> quit the program, else if order still exist -> cancel the order # if prev_order1['status'] == 'FILLED' or prev_order2['status'] == 'FILLED': ### Send SMS alert # twi_client.messages.create(from_=twi_number, to=cell_number, body='The BinanceAPI '+CCY+' SL/TP order is executed') # exit() # elif prev_order1['status'] == 'NEW' and prev_order2['status'] == 'NEW': cancel = client.cancel_order(symbol=CCY, orderId=cur_orderID1) ### Cancel all existing open orders if program is first run ### Check existing open orders if (first_run): if os.path.isfile('./'+filename): # check if the orderID record file exist f = open(filename,"r") cur_orderID1 = int(f.read()) cur_orderID2 = cur_orderID1+1 print('first run Order ID: ',cur_orderID1, cur_orderID2) order_statusID1 = await retrieve_order_status(cur_orderID1) order_statusID2 = await retrieve_order_status(cur_orderID2) if ('ACTIVE' in order_statusID1): await cancel_order(cur_orderID1) if ('ACTIVE' in order_statusID2): await cancel_order(cur_orderID2) try: bars = await log_historical_candles() except: await send_alert('BitfinexAPIException alert (log_historical_candles)!', new_alert_flag) await asyncio.sleep(retry_time) new_alert_flag = False continue new_alert_flag = True for line in bars: del line[5:] #print ("Bars:") #[ print (b) for b in bars ] btc_df = pd.DataFrame(bars, columns=['date', 'open', 'close', 'high', 'low']) btc_df = btc_df.astype({"open":float,"close":float,"high":float,"low":float,}) btc_df.set_index('date', inplace=True) btc_df.index = pd.to_datetime(btc_df.index, unit='ms') print ("Dataframe:") #print (btc_df) ### ATR calculation high_low = btc_df['high'] - btc_df['low'] high_close = np.abs(btc_df['high'] - btc_df['close'].shift()) low_close = np.abs(btc_df['low'] - btc_df['close'].shift()) ranges = pd.concat([high_low, high_close, low_close], axis=1) true_range = np.max(ranges, axis=1) ### calculate atr(period) in WMA for testing # atr = true_range.rolling(22).mean() weights = list(reversed([(period - n) * period for n in range(period)])) atr = true_range.rolling(window = period).apply(wma_calc(weights), raw=True) atr_tp = true_range.rolling(window = period).apply(wma_calc(weights), raw=True) #print(atr_tp) ### calculate TrailingSL value (e.g. 3x ATR of previous 22 day high (or close)) if cal_type=="close": btc_df['PeriodMax'] = btc_df.close.rolling(period).max() btc_df['PeriodMin'] = btc_df.close.rolling(period).min() btc_df['day_sl'] = btc_df.PeriodMin + mult*atr btc_df['day_tp'] = ((btc_df.PeriodMax - mult*atr) + (btc_df.PeriodMin - mult*atr))/2 btc_df['final_sl'] = btc_df.day_sl.rolling(period).min() btc_df['final_tp'] = btc_df.day_tp.rolling(period).min() elif cal_type=="high": btc_df['PeriodMax'] = btc_df.high.rolling(period).max() btc_df['PeriodMin'] = btc_df.low.rolling(period).min() btc_df['day_sl'] = btc_df.PeriodMin + mult*atr btc_df['day_tp'] = ((btc_df.PeriodMax - mult*atr) + (btc_df.PeriodMin - mult*atr))/2 btc_df['final_sl'] = btc_df.day_sl.rolling(period).min() btc_df['final_tp'] = btc_df.day_tp.rolling(period).min() print(btc_df) ### Get the last day TP and SL values short_sl = btc_df.final_sl[-1] short_tp = btc_df.final_tp[-1] print('Current',CCY,' $:',btc_df.close[-1]) print('Short Position SL & TP $:', short_sl, short_tp) # Place the OCO order tp = 40000 sl = 70000 try: resp = await create_oco_order(tp, sl, lotsize) except: await send_alert('BitfinexAPIException alert (create_oco_order)!', new_alert_flag) await asyncio.sleep(retry_time) new_alert_flag = False continue new_alert_flag = True cur_orderID1 = resp.notify_info[0].id cur_orderID2 = resp.notify_info[1].id print('Opened OCO OrderID: ',cur_orderID1, cur_orderID2) f = open(filename,"w") f.write(str(cur_orderID1)) f.close() first_run = False print("Current Time:", datetime.now(), "\n(Start Time:",start_time,")") # Cancel the OCO order # for o in resp.notify_info: # await cancel_order(o.id) await asyncio.sleep(10) t = asyncio.ensure_future(run()) loop = asyncio.get_event_loop() #asyncio.get_event_loop().run_until_complete(t) loop.run_until_complete(t) #loop.run_forever() loop.close()