Strategy File
from ibapi.client import *
from ibapi.wrapper import *
import pandas as pd
import numpy as np
from datetime import datetime as dt
import time
import matplotlib.pyplot as plt
class strategy:
def __init__(self,startBal,x_training,y_training,x,y,indicator_training):
self.indicator_training = indicator_training
self.startBal = startBal
self.x_training = x_training
self.y_training = y_training
self.x = x
self.y = y
self.final_df = pd.DataFrame()
def bollinger_bands(self):
#### current spread calculated with beta0 and beta1 from training period
x_bar = sum(self.x_training) / len(self.x_training)
y_bar = sum(self.y_training) / len(self.y_training)
top_sum = 0
bottom_sum = 0
for i,j in zip(self.x_training,self.y_training):
top_sum += (i*x_bar)*(j*y_bar)
bottom_sum += (i*x_bar)**2
beta_1 = top_sum/bottom_sum
beta_node = y_bar - (beta_1*x_bar)
self.final_df['spread'] = self.y - (beta_node+(beta_1*self.x))
betas_as_updated = []
simple_moving_average = []
upper_band = []
lower_band = []
# upper_sl = []
# lower_sl = []
live_balance = []
live_equity = []
current_balance = self.startBal
current_equity = self.startBal
long_shares = []
longs = []
short_shares = []
shorts = []
upper_pos = False
lower_pos = False
trade_count = 0
stop_loss_triggered = 0
take_profit = 0.001
stop_loss = -0.01
total_fees = 0
for i in range(len(self.final_df['spread'])):
betas_as_updated.append(beta_1)
sma = sum(self.indicator_training)/len(self.indicator_training)
sd = np.std(self.indicator_training)
upper = sma+(sd*2)
lower = sma-(sd*2)
#stop_loss_upper = sma+(sd*6)
#stop_loss_lower = sma-(sd*6)
x_cash = (current_balance/(beta_1 + 1))*beta_1
y_cash = current_balance - x_cash
##### above pos #####
if self.final_df['spread'][i] >= upper and upper_pos == False:
ls = x_cash/self.x['close'][i]
long_shares.append(ls)
long_pos = ls * self.x['close'][i]
ss = y_cash/self.y['close'][i]
short_shares.append(ss)
short_pos = ss * self.y['close'][i]
xf = max(1,ls*0.005)
x_fee = min(long_pos*0.01,xf)
yf = max(1,ss*0.005)
y_fee = min(short_pos*0.01,yf)
fees = x_fee + y_fee
total_fees += fees
current_equity -= fees
live_equity.append(current_equity)
longs.append(long_pos)
shorts.append(short_pos)
trade_count += 2
upper_pos = True
lower_pos = False
#### above take profit ####
elif (self.final_df['spread'][i] <= sma and upper_pos == True) or (current_equity >= current_balance+(current_balance*take_profit) and upper_pos == True):
long_prev_value = longs[len(longs)-1]
long_current_value = self.x['close'][i] * long_shares[len(long_shares)-1]
long_profit = long_current_value - long_prev_value
short_prev_value = shorts[len(shorts)-1]
short_current_value = self.y['close'][i] * short_shares[len(short_shares)-1]
short_profit = short_prev_value - short_current_value
total_profit = long_profit + short_profit
xf = max(1,long_shares[len(long_shares)-1]*0.005)
x_fee = min(longs[len(longs)-1]*0.01,xf)
yf = max(1,short_shares[len(short_shares)-1]*0.005)
y_fee = min(shorts[len(shorts)-1]*0.01,yf)
fees = x_fee + y_fee
total_fees += fees
current_balance += (total_profit-fees)
current_equity = current_balance
live_equity.append(current_equity)
trade_count += 2
upper_pos = False
lower_pos = False
#### above stop loss ####
elif current_equity <= current_balance+(current_balance*stop_loss) and upper_pos == True:#self.final_df['spread'][i] >= stop_loss_upper and upper_pos == True:
stop_loss_triggered += 1
long_prev_value = longs[len(longs)-1]
long_current_value = self.x['close'][i] * long_shares[len(long_shares)-1]
long_profit = long_current_value - long_prev_value
short_prev_value = shorts[len(shorts)-1]
short_current_value = self.y['close'][i] * short_shares[len(short_shares)-1]
short_profit = short_prev_value - short_current_value
total_profit = long_profit + short_profit
xf = max(1,long_shares[len(long_shares)-1]*0.005)
x_fee = min(longs[len(longs)-1]*0.01,xf)
yf = max(1,short_shares[len(short_shares)-1]*0.005)
y_fee = min(shorts[len(shorts)-1]*0.01,yf)
fees = x_fee + y_fee
total_fees += fees
current_balance += (total_profit-fees)
current_equity = current_balance
live_equity.append(current_equity)
trade_count += 2
upper_pos = False
lower_pos = False
##### below pos #####
elif self.final_df['spread'][i] <= lower and lower_pos == False:
ss = x_cash/self.x['close'][i]
short_shares.append(ss)
short_pos = ss * self.x['close'][i]
ls = y_cash/self.y['close'][i]
long_shares.append(ls)
long_pos = ls * self.y['close'][i]
longs.append(long_pos)
shorts.append(short_pos)
yf = max(1,ls*0.005)
y_fee = min(long_pos*0.01,yf)
xf = max(1,ss*0.005)
x_fee = min(short_pos*0.01,xf)
fees = x_fee + y_fee
total_fees += fees
current_equity -= fees
live_equity.append(current_equity)
trade_count += 2
upper_pos = False
lower_pos = True
#### below take profit ####
elif self.final_df['spread'][i] >= sma and lower_pos == True or (current_equity >= current_balance+(current_balance*take_profit) and lower_pos == True):
long_prev_value = longs[len(longs)-1]
long_current_value = self.y['close'][i] * long_shares[len(long_shares)-1]
long_profit = long_current_value - long_prev_value
short_prev_value = shorts[len(shorts)-1]
short_current_value = self.x['close'][i] * short_shares[len(short_shares)-1]
short_profit = short_prev_value - short_current_value
total_profit = long_profit + short_profit
yf = max(1,long_shares[len(long_shares)-1]*0.005)
y_fee = min(longs[len(longs)-1]*0.01,yf)
xf = max(1,short_shares[len(short_shares)-1]*0.005)
x_fee = min(shorts[len(shorts)-1]*0.01,xf)
fees = x_fee + y_fee
total_fees += fees
current_balance += (total_profit-fees)
current_equity = current_balance
live_equity.append(current_equity)
trade_count += 2
upper_pos = False
lower_pos = False
#### below stop loss ####
elif current_equity <= current_balance+(current_balance*stop_loss) and lower_pos == True:#self.final_df['spread'][i] <= stop_loss_lower and lower_pos == True:
stop_loss_triggered += 1
long_prev_value = longs[len(longs)-1]
long_current_value = self.y['close'][i] * long_shares[len(long_shares)-1]
long_profit = long_current_value - long_prev_value
short_prev_value = shorts[len(shorts)-1]
short_current_value = self.x['close'][i] * short_shares[len(short_shares)-1]
short_profit = short_prev_value - short_current_value
total_profit = long_profit + short_profit
yf = max(1,long_shares[len(long_shares)-1]*0.005)
y_fee = min(longs[len(longs)-1]*0.01,yf)
xf = max(1,short_shares[len(short_shares)-1]*0.005)
x_fee = min(shorts[len(shorts)-1]*0.01,xf)
fees = x_fee + y_fee
total_fees += fees
current_balance += (total_profit-fees)
current_equity = current_balance
live_equity.append(current_equity)
trade_count += 2
upper_pos = False
lower_pos = False
#### portfolio value when above trade is open ####
elif self.final_df['spread'][i] > sma and upper_pos == True:
long_prev_value = longs[len(longs)-1]
long_current_value = self.x['close'][i] * long_shares[len(long_shares)-1]
long_profit = long_current_value - long_prev_value
short_prev_value = shorts[len(shorts)-1]
short_current_value = self.y['close'][i] * short_shares[len(short_shares)-1]
short_profit = short_prev_value - short_current_value
total_profit = long_profit + short_profit
current_equity += total_profit
live_equity.append(current_equity)
#### portfolio value when below trade is open ####
elif self.final_df['spread'][i] < sma and lower_pos == True:
long_prev_value = longs[len(longs)-1]
long_current_value = self.y['close'][i] * long_shares[len(long_shares)-1]
long_profit = long_current_value - long_prev_value
short_prev_value = shorts[len(shorts)-1]
short_current_value = self.x['close'][i] * short_shares[len(short_shares)-1]
short_profit = short_prev_value - short_current_value
total_profit = long_profit + short_profit
current_equity += total_profit
live_equity.append(current_equity)
#### equity when no positions are open ####
elif (self.final_df['spread'][i] <= sma and lower_pos == False) or (self.final_df['spread'][i] >= sma and upper_pos == False):
live_equity.append(current_equity)
live_balance.append(current_balance)
simple_moving_average.append(sma)
upper_band.append(upper)
lower_band.append(lower)
#upper_sl.append(stop_loss_upper)
#lower_sl.append(stop_loss_lower)
self.indicator_training = self.indicator_training[1:]
self.indicator_training.append(self.final_df['spread'][i])
self.x_training = self.x_training[1:]
self.y_training = self.y_training[1:]
self.x_training.append(self.x['close'][i])
self.y_training.append(self.y['close'][i])
x_bar = sum(self.x_training) / len(self.x_training)
y_bar = sum(self.y_training) / len(self.y_training)
top_sum = 0
bottom_sum = 0
for i,j in zip(self.x_training,self.y_training):
top_sum += (i*x_bar)*(j*y_bar)
bottom_sum += (i*x_bar)**2
beta_1 = top_sum/bottom_sum
self.final_df['spread'] = self.y - (beta_node+(beta_1*self.x))
self.final_df['portfolio value'] = live_balance
self.final_df['equity value'] = live_equity
self.final_df['sma'] = simple_moving_average
self.final_df['upper_band'] = upper_band
self.final_df['lower_band'] = lower_band
# plt.plot(self.final_df['spread'])
# plt.plot(self.final_df['sma'])
# plt.plot(self.final_df['upper_band'])
# plt.plot(self.final_df['lower_band'])
# #plt.plot(self.final_df['upper_sl'])
# #plt.plot(self.final_df['lower_sl'])
# plt.show()
avg_beta = sum(betas_as_updated)/len(betas_as_updated)
return self.final_df, trade_count, stop_loss_triggered
Data File
from ibapi.client import *
from ibapi.wrapper import *
import pandas as pd
from datetime import datetime as dt
import time
class ibData(EWrapper, EClient):
def __init__(self):
EClient.__init__(self, self)
self.bars = {'reqid':[],'date':[],'open':[],'high':[],'low':[],'close':[],'volume':[]}
self.historicalDataResult = {}
self.errorReturned = []
def historicalData(self, reqId, bar):
if len(self.bars['reqid']) > 0:
if self.bars['reqid'][0] == reqId:
self.bars['reqid'].append(reqId)
for key in self.bars.keys():
if key != 'reqid':
self.bars[key].append(getattr(bar,key))
else:
self.bars = {'reqid':[],'date':[],'open':[],'high':[],'low':[],'close':[],'volume':[]}
self.bars['reqid'].append(reqId)
for key in self.bars.keys():
if key != 'reqid':
self.bars[key].append(getattr(bar,key))
else:
self.bars['reqid'].append(reqId)
for key in self.bars.keys():
if key != 'reqid':
self.bars[key].append(getattr(bar,key))
def historicalDataEnd(self, reqId: int, start: str, end: str):
super().historicalDataEnd(reqId, start, end)
self.errorReturned.append(0)
self.historicalDataResult[reqId] = self.bars
print("ReqId:", reqId,"Historical Data from", start, "to", end)
def error(self, reqId, errorCode, errorString):
if reqId == -1:
print(errorString)
elif errorCode == 2176:
pass
elif errorCode == 162:
self.errorReturned.append(errorCode)
print("ReqId:",reqId, "Error:",errorCode,"Message:", errorString )
Interface File
from tkinter import *
from tkinter import ttk
from Interface.Tools.data import *
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.backend_bases import key_press_handler
from matplotlib import pyplot as plt, animation, dates
from matplotlib import style
import mplfinance as mpf
import pandas as pd
from datetime import datetime as dt
import time
import statsmodels
import statsmodels.api as sm
from statsmodels.tsa.stattools import coint, adfuller
import math
import numpy as np
import itertools
import threading
import statistics
from Interface.Tools.strategy import *
import ast
import datetime
class window:
def __init__(self):
self.root = Tk()
self.root.title("StatArb Trading")
self.root.state('zoomed')
self.root.configure(background='#2E3138')
#self.root.winfo_screenheight()
#self.root.winfo_screenwidth()
self.nb = ttk.Notebook(self.root, height=self.root.winfo_screenheight(), width=self.root.winfo_screenwidth())
self.nb.grid()
self.page1 = Frame(self.nb, bg = '#2E3138', border=0, background='#2E3138', )
self.page1.bind('<Return>',self.test_pairs)
self.page1.grid(row=0, column=3, sticky='W', padx=(0,0), pady=(0,0))
self.nb.add(self.page1,text='Engle-Granger')
self.page2 = Frame(self.nb, bg = '#2E3138', border=0, background='#2E3138', )
self.page2.bind('<Return>',self.test_pairs)
self.page2.grid(row=0, column=3, sticky='W', padx=(0,0), pady=(0,0))
self.nb.add(self.page2,text='Backtesting')
self.page3 = Frame(self.nb, bg = '#2E3138', border=0, background='#2E3138', )
self.page3.bind('<Return>',self.test_pairs)
self.page3.grid(row=0, column=3, sticky='W', padx=(0,0), pady=(0,0))
self.nb.add(self.page3,text='Trading')
text_color = 'white'
style = ttk.Style(self.page1)
style.theme_use("clam")
style.configure("Treeview", background="#2E3138", fieldbackground="#2E3138", foreground=text_color)
style.configure("green.Horizontal.TProgressbar", background="#2E3138")
############ Pair Testing Page ############
self.syms_lab = Label(self.page1, text='Symbols',bg="#2E3138", fg=text_color, font=("montserrat", 12), bd=0, highlightthickness=0).grid(row=0, column=1,sticky='w', padx=(0,0), pady=(0,0))
self.s_entry = Entry(self.page1, bg="#23252B", fg=text_color, font=("montserrat", 15), bd=0, highlightthickness=0, justify='center')
self.s_entry.insert(END,"['IYR','VNQ']")
self.s_entry.grid(row=1, column=1, sticky='we',columnspan=4, padx=(0,0), pady=(0,0))
self.s_entry.grid_columnconfigure(0, weight=1)
self.dur_lab = Label(self.page1, text='Duration',bg="#2E3138", fg=text_color, font=("montserrat", 12), bd=0, highlightthickness=0).grid(row=2, column=1,sticky='w', padx=(0,0), pady=(0,0))
self.duration_entry = Entry(self.page1, width=10, bg="#23252B", fg=text_color, font=("montserrat", 15), bd=0, highlightthickness=0, justify='center')
self.duration_entry.insert(END,'1 M')
self.duration_entry.grid(row=3, column=1, sticky='w',padx=(2,2))
self.bs_lab = Label(self.page1, text='Bar Size',bg="#2E3138", fg=text_color, font=("montserrat", 12), bd=0, highlightthickness=0).grid(row=2, column=2,sticky='w', padx=(0,0), pady=(0,0))
self.barSize_entry = Entry(self.page1, width=10, bg="#23252B", fg=text_color, font=("montserrat", 15), bd=0, highlightthickness=0, justify='center')
self.barSize_entry.insert(END,'30 mins')
self.barSize_entry.grid(row=3, column=2, sticky='w',padx=(2,2))
self.ed_lab = Label(self.page1, text='Lookback Window',bg="#2E3138", fg=text_color, font=("montserrat", 12), bd=0, highlightthickness=0).grid(row=2, column=3,sticky='w', padx=(0,0), pady=(0,0))
self.ed_entry = Entry(self.page1, width=10, bg="#23252B", fg=text_color, font=("montserrat", 15), bd=0, highlightthickness=0, justify='center')
self.ed_entry.insert(END,'30')
self.ed_entry.grid(row=3, column=3, sticky='w',padx=(2,2))
self.pb1 = ttk.Progressbar(self.page1,orient='horizontal',mode='determinate',length=900, style='green.Horizontal.TProgressbar')
self.pb1.grid(row=1, column=10, sticky='we',columnspan=9, padx=(0,0), pady=(0,0))
Button(self.page1, text="Test Pairs", command=self.add_to_testview, height=1, fg="#2E3138", font=("montserrat", 10), bd=0, highlightthickness=0) .grid(row=3, column=4, sticky='w',padx=(5,0))
Button(self.page1, text="Add", command=self.add_to_addview, height=1, fg="#2E3138", font=("montserrat", 10), bd=0, highlightthickness=0) .grid(row=3, column=5,padx=(0,10))
Button(self.page1, text="Reset All", command=self.reset_everything, height=1, fg="#2E3138", font=("montserrat", 10), bd=0, highlightthickness=0) .grid(row=3, column=6,padx=(0,10))
sp_rows = 1
sp_cols = 3
self.fig, self.ax = plt.subplots(sp_rows, sp_cols,figsize=(20, 5))
for j in range(sp_cols):
self.ax[j].set_facecolor('#2E3138')
self.ax[j].tick_params(axis='x', colors=text_color)
self.ax[j].tick_params(axis='y', colors=text_color)
self.ax[0].set_title('Prices',color=text_color)
self.ax[1].set_title('Returns',color=text_color)
self.ax[2].set_title('Spread',color=text_color)
self.fig.set_facecolor('#2E3138')
self.canvas = FigureCanvasTkAgg(self.fig, master=self.page1)
self.canvas.get_tk_widget().grid(row=10, column=1,columnspan=250)
self.canvas.draw()
self.test_frame = pd.DataFrame(columns=['Symbol 1','Symbol 2','Beta','P-value','Correlation','Stationary','Cointegrated'])
self.test_frame=self.test_frame.astype({"Stationary":bool,"Cointegrated":bool})
self.create_test_view()
self.create_add_view()
self.pair_assets = []
self.asset_prices = pd.DataFrame()
self.reqid_count = 0
self.av_count = 0
self.data = ibData()
self.order_state = True
############ Backtesting Page ############
self.backtest_prices = pd.DataFrame()
self.backtest_portfolio = pd.DataFrame()
self.backtst_realtime_equity = pd.DataFrame()
self.create_backtest_start_view()
self.create_backtest_portfolio_view()
self.btd_lab = Label(self.page2, text='Duration',bg="#2E3138", fg=text_color, font=("montserrat", 10), bd=0, highlightthickness=0).grid(row=0, column=1, padx=(0,0), pady=(0,0))
self.btd_entry = Entry(self.page2, width=10, bg="#23252B", fg=text_color, font=("montserrat", 15), bd=0, highlightthickness=0, justify='center')
self.btd_entry.insert(END,'1 M')
self.btd_entry.grid(row=1, column=1, sticky='w',padx=(2,2))
self.btbs_lab = Label(self.page2, text='Bar Size',bg="#2E3138", fg=text_color, font=("montserrat", 10), bd=0, highlightthickness=0).grid(row=0, column=2, padx=(0,0), pady=(0,0))
self.btbs_entry = Entry(self.page2, width=10, bg="#23252B", fg=text_color, font=("montserrat", 15), bd=0, highlightthickness=0, justify='center')
self.btbs_entry.insert(END,'30 mins')
self.btbs_entry.grid(row=1, column=2, sticky='w',padx=(2,2))
self.btsb_lab = Label(self.page2, text='Starting Balance',bg="#2E3138", fg=text_color, font=("montserrat", 10), bd=0, highlightthickness=0).grid(row=0, column=3, padx=(0,0), pady=(0,0))
self.btsb_entry = Entry(self.page2, width=10, bg="#23252B", fg=text_color, font=("montserrat", 15), bd=0, highlightthickness=0, justify='center')
self.btsb_entry.insert(END,'1000000')
self.btsb_entry.grid(row=1, column=3, sticky='w',padx=(2,2))
Button(self.page2, text="Test", command=self.backtest_entire_port, height=1, fg="#2E3138", font=("montserrat", 8), bd=0, highlightthickness=0) .grid(row=1, column=5, sticky='n', padx=(5,5), pady=(5,5))
Button(self.page2, text="Add", command=self.add_to_btp_view, height=1, fg="#2E3138", font=("montserrat", 8), bd=0, highlightthickness=0) .grid(row=1, column=4, sticky='n', padx=(5,5), pady=(5,5))
Button(self.page2, text="Remove", command=self.remove_from_btp_view, height=1, fg="#2E3138", font=("montserrat", 8), bd=0, highlightthickness=0) .grid(row=1, column=6, sticky='n', padx=(5,5), pady=(5,5))
self.pb2 = ttk.Progressbar(self.page2,orient='horizontal',mode='determinate',length=100, style='green.Horizontal.TProgressbar')
self.pb2.grid(row=1, column=7, sticky='we',columnspan=6, padx=(0,0), pady=(0,0))
self.btfig, self.btax = plt.subplots(1,1,figsize=(15, 8))
self.btax.set_facecolor('#2E3138')
self.btax.tick_params(axis='x', colors=text_color)
self.btax.tick_params(axis='y', colors=text_color)
self.btax.set_title('Backtest',color=text_color)
self.btfig.set_facecolor('#2E3138')
self.btcanvas = FigureCanvasTkAgg(self.btfig, master=self.page2)
self.btcanvas.get_tk_widget().grid(row=5, column=0,rowspan=10,columnspan=100,pady=(0,0))
self.btcanvas.draw()
self.portfolio_to_test = []
self.btpv_count = 0
self.duration1 = '1 Y'
self.barSize1 = '1 day'
self.lookback = 0
self.lookback_num = 0
self.duration2 = '1 Y'
self.barSize2 = '1 day'
###### Pair Testing Page ######
def reset_everything(self):
self.test_view.delete(*self.test_view.get_children())
self.add_view.delete(*self.add_view.get_children())
self.backtest_start_view.delete(*self.backtest_start_view.get_children())
self.backtest_portfolio_view.delete(*self.backtest_portfolio_view.get_children())
self.asset_prices = pd.DataFrame()
self.backtest_prices = pd.DataFrame()
self.backtest_portfolio = pd.DataFrame()
self.btax.clear()
self.btcanvas.draw()
self.ax[0].clear()
self.ax[1].clear()
self.ax[2].clear()
self.canvas.draw()
self.pair_assets = []
self.portfolio_to_test = []
self.s_entry.delete(0, END)
def add_to_addview(self):
if len(self.test_view.selection()) > 0:
selectedItem = self.test_view.selection()[0]
values = self.test_view.item(selectedItem)['values']
self.add_view.insert('','end',text='',iid=self.av_count,values=values,tags=('view'))
self.backtest_start_view.insert('','end',text='',iid=self.av_count,values=values,tags=('view'))
self.av_count += 1
def create_test_view(self):
self.test_view = ttk.Treeview(self.page1,height=20)
self.test_view.grid(row=4, column=1, padx=(0,0), pady=(5,5),rowspan=3, columnspan=6, sticky=NSEW)
self.test_view['columns'] = ('Symbol 1','Symbol 2','Beta','P-value','Correlation','Stationary','Cointegrated')
self.test_view.column("#0", width=0, stretch=NO)
self.test_view.heading("#0",text="",anchor=CENTER)
for i in self.test_view['columns']:
self.test_view.column(i,anchor=CENTER, width=len(i)*15)
self.test_view.heading(i,text=str(i),anchor=CENTER, command=lambda i=i : self.view_sort(i))
self.test_viewscroll = ttk.Scrollbar(self.page1,orient="vertical", command = self.test_view.yview)
self.test_viewscroll.grid(row=4, column=7, sticky=NS+W, rowspan=5, pady=(5,5))
self.test_view.config(yscrollcommand=self.test_viewscroll.set)
self.test_view.bind("<<TreeviewSelect>>", self.test_view_selected)
if len(self.test_frame)>0:
count = 0
list_frame = self.test_frame.to_numpy().tolist()
self.test_view.delete(*self.test_view.get_children())
self.test_view.update()
for x in list_frame:
vals = [i for i in x]
self.test_view.insert('','end',text='',iid=count,values=vals,tags=('view'))
count += 1
def create_add_view(self):
self.add_view = ttk.Treeview(self.page1,height=20)
self.add_view.grid(row=4, column=9, padx=(0,0), pady=(5,5),rowspan=3, columnspan=50, sticky=NSEW)
self.add_view['columns'] = ('Symbol 1','Symbol 2','Beta','P-value','Correlation','Stationary','Cointegrated')
self.add_view.column("#0", width=0, stretch=NO)
self.add_view.heading("#0",text="",anchor=CENTER)
for i in self.add_view['columns']:
self.add_view.column(i,anchor=CENTER, width=len(i)*15)
self.add_view.heading(i,text=str(i),anchor=CENTER, command=lambda i=i : self.view_sort(i))
self.add_viewscroll = ttk.Scrollbar(self.page1,orient="vertical", command = self.add_view.yview)
self.add_viewscroll.grid(row=4, column=59, sticky=NS+W, rowspan=5, pady=(5,5))
self.add_view.config(yscrollcommand=self.add_viewscroll.set)
#self.tesadd_viewt_view.bind("<<TreeviewSelect>>", self.test_view_selected)
def view_sort(self,column):
if self.order_state:
self.order_state = False
else:
self.order_state = True
self.test_frame=self.test_frame.sort_values(by=[column],ascending=self.order_state)
self.create_test_view()
def web_socket(self):
self.data.run()
if self.event.is_set():
self.data.disconnect()
def add_to_testview(self):
if len(self.test_view.selection()) > 0:
self.test_view.selection_remove(self.test_view.selection()[0])
if self.duration_entry.get() != self.duration1:
self.duration1 = self.duration_entry.get()
self.pair_assets = []
self.asset_prices = pd.DataFrame()
self.test_view.delete(*self.test_view.get_children())
self.add_view.delete(*self.add_view.get_children())
self.backtest_start_view.delete(*self.backtest_start_view.get_children())
self.backtest_portfolio_view.delete(*self.backtest_portfolio_view.get_children())
self.backtest_prices = pd.DataFrame()
self.backtest_portfolio = pd.DataFrame()
self.btax.clear()
self.btcanvas.draw()
self.portfolio_to_test = []
if self.barSize_entry.get() != self.barSize1:
self.barSize1 = self.barSize_entry.get()
self.pair_assets = []
self.asset_prices = pd.DataFrame()
self.test_view.delete(*self.test_view.get_children())
self.add_view.delete(*self.add_view.get_children())
self.backtest_start_view.delete(*self.backtest_start_view.get_children())
self.backtest_portfolio_view.delete(*self.backtest_portfolio_view.get_children())
self.backtest_prices = pd.DataFrame()
self.backtest_portfolio = pd.DataFrame()
self.btax.clear()
self.btcanvas.draw()
self.portfolio_to_test = []
if int(self.ed_entry.get()) != self.lookback_num:
self.pair_assets = []
self.asset_prices = pd.DataFrame()
self.lookback_num = int(self.ed_entry.get())
self.lookback = (dt.today() - datetime.timedelta(days=self.lookback_num)).strftime("%Y%m%d-%H:%M:%S")
self.test_view.delete(*self.test_view.get_children())
self.add_view.delete(*self.add_view.get_children())
self.backtest_start_view.delete(*self.backtest_start_view.get_children())
self.backtest_portfolio_view.delete(*self.backtest_portfolio_view.get_children())
self.backtest_prices = pd.DataFrame()
self.backtest_portfolio = pd.DataFrame()
self.btax.clear()
self.btcanvas.draw()
self.portfolio_to_test = []
self.test_view.update()
s = self.s_entry.get()
symbols = ast.literal_eval(s)
if self.data.isConnected() == False:
self.connect_tws()
if self.data.isConnected() == True:
self.event=threading.Event()
thread1 =threading.Thread(target=self.web_socket)
thread1.start()
bar_progress = 0
pb_step = round(100/len(symbols))
for x in symbols:
if self.pair_assets.__contains__(x) != True:
self.pair_assets.append(x)
fetched_data = self.fetch_data(self.reqid_count,x,self.duration1,self.barSize1)
if isinstance(fetched_data, pd.DataFrame):
sData = fetched_data
if self.reqid_count == 0:
self.asset_prices[x] = sData['close']
self.asset_prices.index = sData['date']
else:
sData.index = sData['date']
self.asset_prices = self.asset_prices.loc[self.asset_prices.index.isin(sData['date'])]
self.asset_prices[x] = sData['close']
self.reqid_count += 1
bar_progress += pb_step
self.pb1['value'] = bar_progress
self.pb1.update()
self.pb1['value'] = 100
self.pb1.update()
self.event.set()
self.asset_prices = self.asset_prices.dropna(axis='columns')
all_pairs = list(itertools.combinations(self.asset_prices.columns, 2))
self.test_view.delete(*self.test_view.get_children())
self.test_view.update()
for i in range(len(all_pairs)):
self.test_view.insert(parent='',iid=i+1,index='end',text='',values=[all_pairs[i][0],all_pairs[i][1]],tags=('view'))
self.test_pairs()
self.pb1.stop()
def test_view_selected(self,a):
if len(self.test_view.selection()) > 0:
selectedItem = self.test_view.selection()[0]
values = self.test_view.item(selectedItem)['values']
beta_1,pval,corr,stat,coint,u,df1,df2,x_ar,y_ar = self.pair_test(values[0],values[1])
self.ax[0].clear()
self.ax[1].clear()
self.ax[2].clear()
self.ax[0].plot(df1.index,df1)
self.ax[0].plot(df2)
self.ax[0].legend([df1.name,df2.name])
self.ax[1].plot(x_ar.index,x_ar)
self.ax[1].plot(y_ar)
self.ax[1].legend([x_ar.name,y_ar.name])
self.ax[2].plot(u.index,u)
self.ax[2].axhline(y=u.mean(), xmin=0.0, color='r')
self.ax[2].legend([df2.name+' - Beta * '+df1.name])
# if str(df1.index[0].time()) != '00:00:00':
# self.ax[0].xaxis.set_major_formatter(dates.DateFormatter('%H:%M'))
# self.ax[1].xaxis.set_major_formatter(dates.DateFormatter('%H:%M'))
# self.ax[2].xaxis.set_major_formatter(dates.DateFormatter('%H:%M'))
# else:
self.ax[0].xaxis.set_major_formatter(dates.DateFormatter('%m-%y'))
self.ax[1].xaxis.set_major_formatter(dates.DateFormatter('%m-%y'))
self.ax[2].xaxis.set_major_formatter(dates.DateFormatter('%m-%y'))
text_color = 'white'
self.ax[0].set_title('Prices',color=text_color)
self.ax[1].set_title('Returns',color=text_color)
self.ax[2].set_title('Spread',color=text_color)
self.canvas.draw()
def test_pairs(self):
self.test_frame = pd.DataFrame(columns=['Symbol 1','Symbol 2','Beta','P-value','Correlation','Stationary','Cointegrated'])
for i in self.test_view.get_children():
pair = self.test_view.item(i)['values']
beta_1,pval,corr,stat,coint,u,df1,df2,x_ar,y_ar = self.pair_test(pair[0],pair[1])
count = 0
for x,y in zip(self.test_frame['Symbol 1'],self.test_frame['Symbol 2']):
if x == pair[0] and y == pair[1]:
count +=1
elif x == pair[1] and y == pair[0]:
count +=1
if count == 0:
self.test_frame = pd.concat([pd.DataFrame([[pair[0],pair[1],beta_1,pval,corr,stat,coint]], columns=self.test_frame.columns), self.test_frame],ignore_index=True)
count = 0
list_frame = self.test_frame.to_numpy().tolist()
self.test_view.delete(*self.test_view.get_children())
self.test_view.update()
for x in list_frame:
vals = [i for i in x]
self.test_view.insert('','end',text='',iid=count,values=vals,tags=('view'))
count += 1
def pair_test(self,symbol1,symbol2):
x = self.asset_prices[symbol1]
x.name = symbol1
y = self.asset_prices[symbol2]
y.name = symbol2
x.index = pd.to_datetime(x.index)
y.index = pd.to_datetime(y.index)
x_ar = x.diff()[1:]
x_ar.name = symbol1 + ' Additive Returns'
y_ar = y.diff()[1:]
y_ar.name = symbol2 + ' Additive Returns'
x_bar = x.mean()
y_bar = y.mean()
x_bar = sum(x) / len(x)
y_bar = sum(y) / len(y)
top_sum = 0
bottom_sum = 0
for i,j in zip(x,y):
top_sum += (i*x_bar)*(j*y_bar)
bottom_sum += (i*x_bar)**2
beta_1 = top_sum/bottom_sum
beta_node = y_bar - (beta_1*x_bar)
z = y - (beta_node+(beta_1*x))
correlation = round((x_ar.corr(y_ar)*100),2)
s_test = self.check_for_stationarity(z)
return beta_1,s_test[0],correlation,s_test[1],s_test[1],z,x,y,x_ar,y_ar
###### Backtesting Page ######
def add_to_btp_view(self):
if len(self.backtest_start_view.selection()) > 0:
selectedItem = self.backtest_start_view.selection()[0]
values = self.backtest_start_view.item(selectedItem)['values']
vals = [values[0],values[1],values[2]]
self.portfolio_to_test.append(vals)
self.backtest_portfolio_view.insert('','end',text='',iid=self.btpv_count,values=vals,tags=('view'))
self.btpv_count += 1
def remove_from_btp_view(self):
if len(self.backtest_portfolio_view.selection()) > 0:
selectedItem = self.backtest_portfolio_view.selection()[0]
values = self.backtest_portfolio_view.item(selectedItem)['values']
for x in range(len(self.portfolio_to_test)):
if self.portfolio_to_test[x][0] == values[0]:
if self.portfolio_to_test[x][1] == values[1]:
if self.portfolio_to_test[x][2] == values[2]:
del self.portfolio_to_test[x]
self.backtest_entire_port()
break
def bt_port_sort(self,column):
if self.order_state:
self.order_state = False
else:
self.order_state = True
self.backtest_portfolio=self.backtest_portfolio.sort_values(by=[column],ascending=self.order_state)
self.create_backtest_portfolio_view()
def create_backtest_start_view(self):
self.backtest_start_view = ttk.Treeview(self.page2,height=10)
self.backtest_start_view.grid(row=3, column=0, padx=(0,0), pady=(5,5),rowspan=2, columnspan=5, sticky=NSEW)
self.backtest_start_view['columns'] = ('Symbol 1','Symbol 2','Beta','P-value','Correlation','Stationary','Cointegrated')
self.backtest_start_view.column("#0", width=0, stretch=NO)
self.backtest_start_view.heading("#0",text="",anchor=CENTER)
for i in self.backtest_start_view['columns']:
self.backtest_start_view.column(i,anchor=CENTER, width=len(i)*10)
self.backtest_start_view.heading(i,text=str(i),anchor=CENTER, command=lambda i=i : self.bt_port_sort(i))
self.backtest_start_viewscroll = ttk.Scrollbar(self.page2,orient="vertical", command = self.backtest_start_view.yview)
self.backtest_start_viewscroll.grid(row=3, column=5, sticky=NS+W, rowspan=2, pady=(5,5))
self.backtest_start_view.config(yscrollcommand=self.backtest_start_viewscroll.set)
def create_backtest_portfolio_view(self):
self.backtest_portfolio_view = ttk.Treeview(self.page2,height=10)
self.backtest_portfolio_view.grid(row=3, column=6, padx=(5,0), pady=(5,5),rowspan=2, columnspan=8, sticky=NSEW)
self.backtest_portfolio_view['columns'] = ('Symbol 1','Symbol 2','Beta','Start','End','Equity Final','Equity Peak','Return %','Vol. %','Sharpe','Max Drawdown','Trades','Stop Losses')
self.backtest_portfolio_view.column("#0", width=0, stretch=NO)
self.backtest_portfolio_view.heading("#0",text="",anchor=CENTER)
for i in self.backtest_portfolio_view['columns']:
self.backtest_portfolio_view.column(i,anchor=CENTER, width=len(i)*10)
self.backtest_portfolio_view.heading(i,text=str(i),anchor=CENTER, command=lambda i=i : self.bt_port_sort(i))
self.backtest_portfolio_viewscroll = ttk.Scrollbar(self.page2,orient="vertical", command = self.backtest_portfolio_view.yview)
self.backtest_portfolio_viewscroll.grid(row=3, column=14, sticky=NS+W, rowspan=2, pady=(5,5))
self.backtest_portfolio_view.config(yscrollcommand=self.backtest_portfolio_viewscroll.set)
if len(self.backtest_portfolio)>0:
count = 0
list_frame = self.backtest_portfolio.to_numpy().tolist()
self.backtest_portfolio_view.delete(*self.backtest_portfolio_view.get_children())
self.backtest_portfolio_view.update()
for x in list_frame:
vals = [i for i in x]
self.backtest_portfolio_view.insert('','end',text='',iid=count,values=vals,tags=('view'))
count += 1
def backtest_entire_port(self):
self.btax.clear()
self.lookback = (dt.today() - datetime.timedelta(days=0)).strftime("%Y%m%d-%H:%M:%S")
if self.btd_entry.get() != self.duration2:
self.duration2 = self.btd_entry.get()
self.backtest_prices = pd.DataFrame()
if self.btbs_entry.get() != self.barSize2:
self.barSize2 = self.btbs_entry.get()
self.backtest_prices = pd.DataFrame()
self.backtest_overall_portfolio = pd.DataFrame()
self.backtst_realtime_equity = pd.DataFrame()
self.backtest_portfolio = pd.DataFrame(columns=['Symbol 1','Symbol 2','Beta','Start','End','Equity Final','Equity Peak','Return %','Vol.','Sharpe','Max Drawdown','Trades', 'Stop Losses'])
self.backtest_portfolio_view.delete(*self.backtest_portfolio_view.get_children())
self.backtest_portfolio_view.update()
if self.data.isConnected() == False:
self.connect_tws()
if self.data.isConnected() == True:
self.event=threading.Event()
thread1 =threading.Thread(target=self.web_socket)
thread1.start()
bar_progress = 0
pb_step = round(100/len(self.portfolio_to_test))
startBal = int(self.btsb_entry.get())
portion = startBal/len(self.portfolio_to_test)
for values in self.portfolio_to_test:
results = self.backtest(values[0],values[1],values[2],portion)
self.backtest_portfolio.loc[len(self.backtest_portfolio)] = results
bar_progress += pb_step
self.pb2['value'] = bar_progress
self.pb2.update()
self.pb2['value'] = 100
self.pb2.update()
self.event.set()
self.backtest_overall_portfolio = self.backtest_overall_portfolio.dropna(axis='rows')
self.backtest_overall_portfolio['portfolio value'] = self.backtest_overall_portfolio[self.backtest_overall_portfolio.columns].sum(axis=1)
self.backtest_overall_portfolio['equity value'] = self.backtst_realtime_equity[self.backtst_realtime_equity.columns].sum(axis=1)
stat_result = self.backtest_statistics(self.backtest_overall_portfolio,startBal)
overall_result = ['Overall','','']
overall_result.extend([x for x in stat_result])
overall_result.extend([self.backtest_portfolio['Trades'].sum()])
overall_result.extend([self.backtest_portfolio['Stop Losses'].sum()])
self.backtest_portfolio.loc[len(self.backtest_portfolio)] = overall_result
count = 0
list_frame = self.backtest_portfolio.to_numpy().tolist()
for x in list_frame:
vals = [i for i in x]
self.backtest_portfolio_view.insert('','end',text='',iid=count,values=vals,tags=('view'))
count += 1
self.btax.plot(self.backtest_overall_portfolio['equity value'])
self.btax.plot(self.backtest_overall_portfolio.index,self.backtest_overall_portfolio['portfolio value'])
self.btax.legend(['Equity','Balance'])
self.btax.axhline(y=startBal, xmin=0.0, color='r')
self.btcanvas.draw()
self.pb2.stop()
def backtest(self,symbol1,symbol2,beta_1,startBal):
symbols = [symbol1,symbol2]
for x in symbols:
if self.backtest_prices.__contains__(x) != True:
fetched_data = self.fetch_data(self.reqid_count,x,self.duration2,self.barSize2)
if isinstance(fetched_data, pd.DataFrame):
sData = fetched_data
if self.reqid_count == 0:
self.backtest_prices[x] = sData['close']
self.backtest_prices.index = sData['date']
self.backtest_prices.index = pd.to_datetime(sData.index)
else:
sData.index = sData['date']
self.backtest_prices = self.backtest_prices.loc[self.backtest_prices.index.isin(sData['date'])]
self.backtest_prices[x] = sData['close']
self.reqid_count += 1
####### symbol data #######
# #
### x data ###
x = pd.DataFrame() #
x['close'] = self.backtest_prices[symbol1] #
x.name = symbol1 #
x.index = pd.to_datetime(x.index) #
### y data ###
y = pd.DataFrame() #
y['close'] = self.backtest_prices[symbol2] #
y.name = symbol2 #
y.index = pd.to_datetime(y.index) #
##############################################
################## indicator training ###################
# #
#### getting prices from testing period ####
x_test = self.asset_prices[symbol1] #
y_test = self.asset_prices[symbol2] #
#### means ####
x_bar = float(x_test.mean()) #
y_bar = float(y_test.mean()) #
#### ols regression #
beta_1 = float(beta_1) #
beta_node = y_bar - (beta_1*x_bar) #
spread = y_test - (beta_node+(beta_1*x_test)) #
#### defining training regions #
indicator_training_periods = 20 #
indicator_training = [x for x in spread[-indicator_training_periods:]] #
xy_training_periods = len(x) #
x_training = [x for x in x['close'][-xy_training_periods:]] #
y_training = [y for y in y['close'][-xy_training_periods:]] #
#############################################################################
##### for rsi ####
# spread = y_test - (beta_node+(beta_1*x_test))
# period = 1
# gain = spread.where(spread > 0, 0)
# loss = -spread.where(spread < 0, 0)
# avg_gain = gain.rolling(period).mean()
# avg_loss = loss.rolling(period).mean()
########## Strategy ##########
strat = strategy(startBal,x_training,y_training,x,y,indicator_training)
#final_df, df1_cash, df2_cash, ratio = strat.buy_hold()
#final_df, df1_cash, df2_cash, ratio = strat.rsi(avg_gain,avg_loss)
final_df, trade_count, stop_loss_triggered = strat.bollinger_bands()
########## Strategy ##########
self.backtest_overall_portfolio[f'{symbol1} {symbol2}'] = final_df['portfolio value']
self.backtst_realtime_equity[f'{symbol1} {symbol2}'] = final_df['equity value']
all_stats = [symbol1,symbol2,beta_1]
btstats = self.backtest_statistics(final_df,startBal)
all_stats.extend([x for x in btstats])
all_stats.append(trade_count)
all_stats.append(stop_loss_triggered)
return all_stats
def backtest_statistics(self,df,startBal):
weighted_return = df['portfolio value'].diff()
weighted_return = weighted_return[1:]
weighted_return = [x/startBal for x in weighted_return]
data_length_weight = np.sqrt(len(weighted_return))
rolling_max = df['portfolio value'].rolling(window=len(df),min_periods=1).max()
daily_drawdown = (df['portfolio value']/rolling_max-1)
max_daily_drawdown = (daily_drawdown.rolling(window=len(df),min_periods=1).min())
start = str(df.index[0])[:10]
end = str(df.index[len(df)-1])[:10]
equity_final = round(df['portfolio value'][len(df)-1],2)
equity_peak = round(df['portfolio value'].max(),2)
return_per = round(((df['portfolio value'][len(df)-1]-startBal)/startBal)*100,2)
volatility = round((statistics.stdev(weighted_return) * data_length_weight)*100,2)
sharpe_ratio = (statistics.mean(weighted_return)/statistics.stdev(weighted_return))*data_length_weight
max_drawdown = round(max_daily_drawdown[len(max_daily_drawdown)-1]*100,2)
stats = [start,end,equity_final,equity_peak,return_per,volatility,sharpe_ratio,max_drawdown]
return stats
##### General functions #####
def connect_tws(self):
self.data.connect("127.0.0.1", 4002, 1)
time.sleep(1)
def check_for_stationarity(self,X, cutoff=0.05):
pvalue = adfuller(X)[1]
if pvalue < cutoff:
return pvalue, True
else:
return pvalue, False
def create_contract(self,symbol,secType,exchange,currency):
contract = Contract()
contract.symbol = symbol
contract.secType = secType
contract.exchange = exchange
contract.currency = currency
return contract
def cointegration(self, a, b):
if coint(a, b)[1] < 0.05:
return True, coint(a, b)[1]
else:
return False, coint(a, b)[1]
def fetch_data(self,reqId,symbol,duration,barSize):
contract = self.create_contract(symbol,'STK','SMART','USD')
self.data.reqHistoricalData(reqId, contract, self.lookback,duration, barSize, 'TRADES' , 0, 1, 0, [])
while len(self.data.historicalDataResult) == 0 and len(self.data.errorReturned) == 0:
pass
while len(self.data.historicalDataResult) != (reqId-(len(self.data.errorReturned)-self.data.errorReturned.count(0)))+1:
pass
if self.data.errorReturned[self.reqid_count] == 0:
sData = pd.DataFrame(self.data.historicalDataResult[reqId])
return sData
elif self.data.errorReturned[self.reqid_count] == 200:
self.pair_assets.remove(symbol)
return 0
elif self.data.errorReturned[self.reqid_count] == 162:
self.pair_assets.remove(symbol)
return 0
##### Start #####
def start(self):
self.root.mainloop()
Start File
from Interface.Interface import window
if __name__ == "__main__":
window().start()