BfxAPI-ATR-SL-TP / BfxAPI_ATR_SL_Short_test01e.py
BfxAPI_ATR_SL_Short_test01e.py
Raw
# 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()