anaucoin commited on
Commit
e4e1709
·
1 Parent(s): 845a27c

V3 staging

Browse files
CC-Trade-Log-50.csv ADDED
@@ -0,0 +1 @@
 
 
1
+ Type,Signal,Date/Time,Price USDT,Contracts,Profit USDT,Profit %,Cum. Profit USDT,Cum. Profit %,Run-up USDT,Run-up %,Drawdown USDT,Drawdown %
CT-Trade-Log-50.csv ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Type,Signal,Date/Time,Price USDT,Contracts,Profit USDT,Profit %,Cum. Profit USDT,Cum. Profit %,Run-up USDT,Run-up %,Drawdown USDT,Drawdown %
2
+ Exit Long Long Exit (all) 2023-06-28 14:31 1822.20 0.2797 -12.24 -2.35 30.79 -1.17 1.51 0.29 13.44 2.58
3
+ Entry Long Long 2023-06-28 00:10 1863.21 0.2797 -12.24 -2.35 30.79 -1.17 1.51 0.29 13.44 2.58
4
+ Exit Long Long Exit (all) 2023-06-28 14:31 1822.20 0.2797 -12.16 -2.33 19.02 -1.18 1.59 0.31 13.36 2.56
5
+ Entry Long Pyramiding Entry 2023-06-28 00:11 1862.92 0.2797 -12.16 -2.33 19.02 -1.18 1.59 0.31 13.36 2.56
SB-Trade-Log-50.csv ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Type,Signal,Date/Time,Price USDT,Contracts,Profit USDT,Profit %,Cum. Profit USDT,Cum. Profit %,Run-up USDT,Run-up %,Drawdown USDT,Drawdown %
2
+
historical_app.py ADDED
@@ -0,0 +1,981 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ---
2
+ # jupyter:
3
+ # jupytext:
4
+ # text_representation:
5
+ # extension: .py
6
+ # format_name: light
7
+ # format_version: '1.5'
8
+ # jupytext_version: 1.14.2
9
+ # kernelspec:
10
+ # display_name: Python [conda env:bbytes] *
11
+ # language: python
12
+ # name: conda-env-bbytes-py
13
+ # ---
14
+
15
+ # +
16
+ import csv
17
+ import pandas as pd
18
+ from datetime import datetime, timedelta
19
+ import numpy as np
20
+ import datetime as dt
21
+ import matplotlib.pyplot as plt
22
+ from pathlib import Path
23
+ import time
24
+ import plotly.graph_objects as go
25
+ import plotly.io as pio
26
+ from PIL import Image
27
+
28
+ import streamlit as st
29
+ import plotly.express as px
30
+ import altair as alt
31
+ import dateutil.parser
32
+ from matplotlib.colors import LinearSegmentedColormap
33
+
34
+
35
+ # +
36
+ class color:
37
+ PURPLE = '\033[95m'
38
+ CYAN = '\033[96m'
39
+ DARKCYAN = '\033[36m'
40
+ BLUE = '\033[94m'
41
+ GREEN = '\033[92m'
42
+ YELLOW = '\033[93m'
43
+ RED = '\033[91m'
44
+ BOLD = '\033[1m'
45
+ UNDERLINE = '\033[4m'
46
+ END = '\033[0m'
47
+
48
+ @st.cache_data
49
+ def print_PL(amnt, thresh, extras = "" ):
50
+ if amnt > 0:
51
+ return color.BOLD + color.GREEN + str(amnt) + extras + color.END
52
+ elif amnt < 0:
53
+ return color.BOLD + color.RED + str(amnt)+ extras + color.END
54
+ elif np.isnan(amnt):
55
+ return str(np.nan)
56
+ else:
57
+ return str(amnt + extras)
58
+
59
+ @st.cache_data
60
+ def get_headers(logtype):
61
+ otimeheader = ""
62
+ cheader = ""
63
+ plheader = ""
64
+ fmat = '%Y-%m-%d %H:%M:%S'
65
+
66
+ if logtype == "ByBit":
67
+ otimeheader = 'Create Time'
68
+ cheader = 'Contracts'
69
+ plheader = 'Closed P&L'
70
+ fmat = '%Y-%m-%d %H:%M:%S'
71
+
72
+ if logtype == "BitGet":
73
+ otimeheader = 'Date'
74
+ cheader = 'Futures'
75
+ plheader = 'Realized P/L'
76
+ fmat = '%Y-%m-%d %H:%M:%S'
77
+
78
+ if logtype == "MEXC":
79
+ otimeheader = 'Trade time'
80
+ cheader = 'Futures'
81
+ plheader = 'closing position'
82
+ fmat = '%Y/%m/%d %H:%M'
83
+
84
+ if logtype == "Binance":
85
+ otimeheader = 'Date'
86
+ cheader = 'Symbol'
87
+ plheader = 'Realized Profit'
88
+ fmat = '%Y-%m-%d %H:%M:%S'
89
+
90
+ #if logtype == "Kucoin":
91
+ # otimeheader = 'Time'
92
+ # cheader = 'Contract'
93
+ # plheader = ''
94
+ # fmat = '%Y/%m/%d %H:%M:%S'
95
+
96
+
97
+ if logtype == "Kraken":
98
+ otimeheader = 'time'
99
+ cheader = 'asset'
100
+ plheader = 'amount'
101
+ fmat = '%Y-%m-%d %H:%M:%S.%f'
102
+
103
+ if logtype == "OkX":
104
+ otimeheader = '\ufeffOrder Time'
105
+ cheader = '\ufeffInstrument'
106
+ plheader = '\ufeffPL'
107
+ fmat = '%Y-%m-%d %H:%M:%S'
108
+
109
+ return otimeheader.lower(), cheader.lower(), plheader.lower(), fmat
110
+
111
+ @st.cache_data
112
+ def get_coin_info(df_coin, principal_balance,plheader):
113
+ numtrades = int(len(df_coin))
114
+ numwin = int(sum(df_coin[plheader] > 0))
115
+ numloss = int(sum(df_coin[plheader] < 0))
116
+ winrate = np.round(100*numwin/numtrades,2)
117
+
118
+ grosswin = sum(df_coin[df_coin[plheader] > 0][plheader])
119
+ grossloss = sum(df_coin[df_coin[plheader] < 0][plheader])
120
+ if grossloss != 0:
121
+ pfactor = -1*np.round(grosswin/grossloss,2)
122
+ else:
123
+ pfactor = np.nan
124
+
125
+ cum_PL = np.round(sum(df_coin[plheader].values),2)
126
+ cum_PL_perc = np.round(100*cum_PL/principal_balance,2)
127
+ mean_PL = np.round(sum(df_coin[plheader].values/len(df_coin)),2)
128
+ mean_PL_perc = np.round(100*mean_PL/principal_balance,2)
129
+
130
+ return numtrades, numwin, numloss, winrate, pfactor, cum_PL, cum_PL_perc, mean_PL, mean_PL_perc
131
+
132
+ @st.cache_data
133
+ def get_hist_info(df_coin, principal_balance,plheader):
134
+ numtrades = int(len(df_coin))
135
+ numwin = int(sum(df_coin[plheader] > 0))
136
+ numloss = int(sum(df_coin[plheader] < 0))
137
+ if numtrades != 0:
138
+ winrate = int(np.round(100*numwin/numtrades,2))
139
+ else:
140
+ winrate = np.nan
141
+
142
+ grosswin = sum(df_coin[df_coin[plheader] > 0][plheader])
143
+ grossloss = sum(df_coin[df_coin[plheader] < 0][plheader])
144
+ if grossloss != 0:
145
+ pfactor = -1*np.round(grosswin/grossloss,2)
146
+ else:
147
+ pfactor = np.nan
148
+ return numtrades, numwin, numloss, winrate, pfactor
149
+
150
+ @st.cache_data
151
+ def get_rolling_stats(df, lev, otimeheader, days):
152
+ max_roll = (df[otimeheader].max() - df[otimeheader].min()).days
153
+
154
+ if max_roll >= days:
155
+ rollend = df[otimeheader].max()-timedelta(days=days)
156
+ rolling_df = df[df[otimeheader] >= rollend]
157
+
158
+ if len(rolling_df) > 0:
159
+ rolling_perc = rolling_df['Return Per Trade'].dropna().cumprod().values[-1]-1
160
+ else:
161
+ rolling_perc = np.nan
162
+ else:
163
+ rolling_perc = np.nan
164
+ return 100*rolling_perc
165
+ @st.cache_data
166
+ def cc_coding(row):
167
+ return ['background-color: lightgrey'] * len(row) if row['Exit Date'] <= datetime.strptime('2022-12-16 00:00:00','%Y-%m-%d %H:%M:%S').date() else [''] * len(row)
168
+ def ctt_coding(row):
169
+ return ['background-color: lightgrey'] * len(row) if row['Exit Date'] <= datetime.strptime('2023-01-02 00:00:00','%Y-%m-%d %H:%M:%S').date() else [''] * len(row)
170
+
171
+ @st.cache_data
172
+ def my_style(v, props=''):
173
+ props = 'color:red' if v < 0 else 'color:green'
174
+ return props
175
+
176
+ def filt_df(df, cheader, symbol_selections):
177
+
178
+ df = df.copy()
179
+ df = df[df[cheader].isin(symbol_selections)]
180
+
181
+ return df
182
+
183
+ def tv_reformat(close50filename):
184
+ try:
185
+ data = pd.read_csv(open(close50filename,'r'), sep='[,|\t]', engine='python')
186
+ except:
187
+ data = pd.DataFrame([])
188
+
189
+ if data.empty:
190
+ return data
191
+ else:
192
+ entry_df = data[data['Type'] == "Entry Long"]
193
+ exit_df = data[data['Type']=="Exit Long"]
194
+
195
+ entry_df.index = range(len(entry_df))
196
+ exit_df.index = range(len(exit_df))
197
+
198
+ df = pd.DataFrame([], columns=['Trade','Entry Date','Buy Price', 'Sell Price','Exit Date', 'P/L per token', 'P/L %', 'Drawdown %'])
199
+
200
+ df['Trade'] = entry_df.index
201
+ df['Entry Date'] = entry_df['Date/Time']
202
+ df['Buy Price'] = entry_df['Price USDT']
203
+
204
+ df['Sell Price'] = exit_df['Price USDT']
205
+ df['Exit Date'] = exit_df['Date/Time']
206
+ df['P/L per token'] = df['Sell Price'] - df['Buy Price']
207
+ df['P/L %'] = exit_df['Profit %']
208
+ df['Drawdown %'] = exit_df['Drawdown %']
209
+ df['Close 50'] = [int(i == "Close 50% of Position") for i in exit_df['Signal']]
210
+ df.loc[df['Close 50'] == 1, 'Exit Date'] = np.copy(df.loc[df[df['Close 50'] == 1].index.values -1]['Exit Date'])
211
+
212
+ grouped_df = df.groupby('Entry Date').agg({'Entry Date': 'min', 'Buy Price':'mean',
213
+ 'Sell Price' : 'mean',
214
+ 'Exit Date': 'max',
215
+ 'P/L per token': 'mean',
216
+ 'P/L %' : 'mean'})
217
+
218
+ grouped_df.insert(0,'Trade', range(len(grouped_df)))
219
+ grouped_df.index = range(len(grouped_df))
220
+ return grouped_df
221
+
222
+ def load_data(filename, otimeheader, fmat):
223
+ df = pd.read_csv(open(filename,'r'), sep='\t') # so as not to mutate cached value
224
+ close50filename = filename.split('.')[0] + '-50.' + filename.split('.')[1]
225
+ df2 = tv_reformat(close50filename)
226
+
227
+ if filename == "CT-Trade-Log.csv":
228
+ df.columns = ['Trade','Entry Date','Buy Price', 'Sell Price','Exit Date', 'P/L per token', 'P/L %', 'Drawdown %']
229
+ df.insert(1, 'Signal', ['Long']*len(df))
230
+ elif filename == "CC-Trade-Log.csv":
231
+ df.columns = ['Trade','Signal','Entry Date','Buy Price', 'Sell Price','Exit Date', 'P/L per token', 'P/L %', 'Drawdown %']
232
+ else:
233
+ df.columns = ['Trade','Signal','Entry Date','Buy Price', 'Sell Price','Exit Date', 'P/L per token', 'P/L %']
234
+
235
+ if filename != "CT-Toasted-Trade-Log.csv":
236
+ df['Signal'] = df['Signal'].str.replace(' ', '', regex=True)
237
+ df['Buy Price'] = df['Buy Price'].str.replace('$', '', regex=True)
238
+ df['Sell Price'] = df['Sell Price'].str.replace('$', '', regex=True)
239
+ df['Buy Price'] = df['Buy Price'].str.replace(',', '', regex=True)
240
+ df['Sell Price'] = df['Sell Price'].str.replace(',', '', regex=True)
241
+ df['P/L per token'] = df['P/L per token'].str.replace('$', '', regex=True)
242
+ df['P/L per token'] = df['P/L per token'].str.replace(',', '', regex=True)
243
+ df['P/L %'] = df['P/L %'].str.replace('%', '', regex=True)
244
+
245
+ df['Buy Price'] = pd.to_numeric(df['Buy Price'])
246
+ df['Sell Price'] = pd.to_numeric(df['Sell Price'])
247
+ df['P/L per token'] = pd.to_numeric(df['P/L per token'])
248
+ df['P/L %'] = pd.to_numeric(df['P/L %'])
249
+
250
+ if df2.empty:
251
+ df = df
252
+ else:
253
+ df = pd.concat([df,df2], axis=0, ignore_index=True)
254
+
255
+ if filename == "CT-Trade-Log.csv":
256
+ df['Signal'] = ['Long']*len(df)
257
+
258
+ dateheader = 'Date'
259
+ theader = 'Time'
260
+
261
+ df[dateheader] = [tradetimes.split(" ")[0] for tradetimes in df[otimeheader].values]
262
+ df[theader] = [tradetimes.split(" ")[1] for tradetimes in df[otimeheader].values]
263
+
264
+ df[otimeheader]= [dateutil.parser.parse(date+' '+time)
265
+ for date,time in zip(df[dateheader],df[theader])]
266
+ df[otimeheader] = pd.to_datetime(df[otimeheader])
267
+ df['Exit Date'] = pd.to_datetime(df['Exit Date'])
268
+ df.sort_values(by=otimeheader, inplace=True)
269
+
270
+ df[dateheader] = [dateutil.parser.parse(date).date() for date in df[dateheader]]
271
+ df[theader] = [dateutil.parser.parse(time).time() for time in df[theader]]
272
+ df['Trade'] = df.index + 1 #reindex
273
+
274
+ if filename == "CT-Trade-Log.csv":
275
+ df['DCA'] = np.nan
276
+
277
+ for exit in pd.unique(df['Exit Date']):
278
+ df_exit = df[df['Exit Date']==exit]
279
+ if dateutil.parser.parse(str(exit)) < dateutil.parser.parse('2023-02-07 13:00:00'):
280
+ for i in range(len(df_exit)):
281
+ ind = df_exit.index[i]
282
+ df.loc[ind,'DCA'] = i+1
283
+
284
+ else:
285
+ for i in range(len(df_exit)):
286
+ ind = df_exit.index[i]
287
+ df.loc[ind,'DCA'] = i+1.1
288
+ return df
289
+
290
+
291
+ def get_sd_df(sd_df, sd, bot_selections, dca1, dca2, dca3, dca4, dca5, dca6, fees, lev, dollar_cap, principal_balance):
292
+ sd = 2*.00026
293
+ # ------ Standard Dev. Calculations.
294
+ if bot_selections == "Cinnamon Toast":
295
+ dca_map = {1: dca1/100, 2: dca2/100, 3: dca3/100, 4: dca4/100, 1.1: dca5/100, 2.1: dca6/100}
296
+ sd_df['DCA %'] = sd_df['DCA'].map(dca_map)
297
+ sd_df['Calculated Return % (+)'] = df['Signal'].map(signal_map)*(df['DCA %'])*(1-fees)*((df['Sell Price']*(1+df['Signal'].map(signal_map)*sd) - df['Buy Price']*(1-df['Signal'].map(signal_map)*sd))/df['Buy Price']*(1-df['Signal'].map(signal_map)*sd) - fees) #accounts for fees on open and close of trade
298
+ sd_df['Calculated Return % (-)'] = df['Signal'].map(signal_map)*(df['DCA %'])*(1-fees)*((df['Sell Price']*(1-df['Signal'].map(signal_map)*sd)-df['Buy Price']*(1+df['Signal'].map(signal_map)*sd))/df['Buy Price']*(1+df['Signal'].map(signal_map)*sd) - fees) #accounts for fees on open and close of trade
299
+ sd_df['DCA'] = np.floor(sd_df['DCA'].values)
300
+
301
+ sd_df['Return Per Trade (+)'] = np.nan
302
+ sd_df['Return Per Trade (-)'] = np.nan
303
+ sd_df['Balance used in Trade (+)'] = np.nan
304
+ sd_df['Balance used in Trade (-)'] = np.nan
305
+ sd_df['New Balance (+)'] = np.nan
306
+ sd_df['New Balance (-)'] = np.nan
307
+
308
+ g1 = sd_df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return % (+)'].reset_index(name='Return Per Trade (+)')
309
+ g2 = sd_df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return % (-)'].reset_index(name='Return Per Trade (-)')
310
+ sd_df.loc[sd_df['DCA']==1.0,'Return Per Trade (+)'] = 1+lev*g1['Return Per Trade (+)'].values
311
+ sd_df.loc[sd_df['DCA']==1.0,'Return Per Trade (-)'] = 1+lev*g2['Return Per Trade (-)'].values
312
+
313
+ sd_df['Compounded Return (+)'] = sd_df['Return Per Trade (+)'].cumprod()
314
+ sd_df['Compounded Return (-)'] = sd_df['Return Per Trade (-)'].cumprod()
315
+ sd_df.loc[sd_df['DCA']==1.0,'New Balance (+)'] = [min(dollar_cap/lev, bal*principal_balance) for bal in sd_df.loc[sd_df['DCA']==1.0,'Compounded Return (+)']]
316
+ sd_df.loc[sd_df['DCA']==1.0,'Balance used in Trade (+)'] = np.concatenate([[principal_balance], sd_df.loc[sd_df['DCA']==1.0,'New Balance (+)'].values[:-1]])
317
+
318
+ sd_df.loc[sd_df['DCA']==1.0,'New Balance (-)'] = [min(dollar_cap/lev, bal*principal_balance) for bal in sd_df.loc[sd_df['DCA']==1.0,'Compounded Return (-)']]
319
+ sd_df.loc[sd_df['DCA']==1.0,'Balance used in Trade (-)'] = np.concatenate([[principal_balance], sd_df.loc[sd_df['DCA']==1.0,'New Balance (-)'].values[:-1]])
320
+ else:
321
+ sd_df['Calculated Return % (+)'] = df['Signal'].map(signal_map)*(1-fees)*((df['Sell Price']*(1+df['Signal'].map(signal_map)*sd) - df['Buy Price']*(1-df['Signal'].map(signal_map)*sd))/df['Buy Price']*(1-df['Signal'].map(signal_map)*sd) - fees) #accounts for fees on open and close of trade
322
+ sd_df['Calculated Return % (-)'] = df['Signal'].map(signal_map)*(1-fees)*((df['Sell Price']*(1-df['Signal'].map(signal_map)*sd)-df['Buy Price']*(1+df['Signal'].map(signal_map)*sd))/df['Buy Price']*(1+df['Signal'].map(signal_map)*sd) - fees) #accounts for fees on open and close of trade
323
+ sd_df['Return Per Trade (+)'] = np.nan
324
+ sd_df['Return Per Trade (-)'] = np.nan
325
+
326
+ g1 = sd_df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return % (+)'].reset_index(name='Return Per Trade (+)')
327
+ g2 = sd_df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return % (-)'].reset_index(name='Return Per Trade (-)')
328
+ sd_df['Return Per Trade (+)'] = 1+lev*g1['Return Per Trade (+)'].values
329
+ sd_df['Return Per Trade (-)'] = 1+lev*g2['Return Per Trade (-)'].values
330
+
331
+ sd_df['Compounded Return (+)'] = sd_df['Return Per Trade (+)'].cumprod()
332
+ sd_df['Compounded Return (-)'] = sd_df['Return Per Trade (-)'].cumprod()
333
+ sd_df['New Balance (+)'] = [min(dollar_cap/lev, bal*principal_balance) for bal in sd_df['Compounded Return (+)']]
334
+ sd_df['Balance used in Trade (+)'] = np.concatenate([[principal_balance], sd_df['New Balance (+)'].values[:-1]])
335
+
336
+ sd_df['New Balance (-)'] = [min(dollar_cap/lev, bal*principal_balance) for bal in sd_df['Compounded Return (-)']]
337
+ sd_df['Balance used in Trade (-)'] = np.concatenate([[principal_balance], sd_df['New Balance (-)'].values[:-1]])
338
+
339
+ sd_df['Net P/L Per Trade (+)'] = (sd_df['Return Per Trade (+)']-1)*sd_df['Balance used in Trade (+)']
340
+ sd_df['Cumulative P/L (+)'] = sd_df['Net P/L Per Trade (+)'].cumsum()
341
+
342
+ sd_df['Net P/L Per Trade (-)'] = (sd_df['Return Per Trade (-)']-1)*sd_df['Balance used in Trade (-)']
343
+ sd_df['Cumulative P/L (-)'] = sd_df['Net P/L Per Trade (-)'].cumsum()
344
+ return sd_df
345
+
346
+ def runapp() -> None:
347
+ #st.header("Trading Bot Dashboard :bread: :moneybag:")
348
+ #st.write("Welcome to the Trading Bot Dashboard by BreadBytes! You can use this dashboard to track " +
349
+ # "the performance of our trading bots, or upload and track your own performance data from a supported exchange.")
350
+ #if 'auth_user' not in st.session_state:
351
+ # with st.form("Login"):
352
+ # user = st.text_input("Username")
353
+ # secret = st.text_input("Password")
354
+
355
+ # submitted = st.form_submit_button("Submit")
356
+ # if submitted:
357
+ # if user == st.secrets.get("db_username") and secret == st.secrets.get("db_password"):
358
+ # st.success("Success!")
359
+ # st.session_state['auth_user'] = True
360
+ # else:
361
+ # st.success("Incorrect username and/or password. Please try again.")
362
+ # st.session_state['auth_user'] = False
363
+
364
+ #try:
365
+ # st.session_state['auth_user'] == True
366
+ #except:
367
+ # st.error("Please log in.")
368
+ # return
369
+
370
+ #if st.session_state['auth_user'] == True:
371
+ if True:
372
+ st.sidebar.header("FAQ")
373
+
374
+ with st.sidebar.subheader("FAQ"):
375
+ st.markdown(Path("FAQ_README.md").read_text(), unsafe_allow_html=True)
376
+
377
+ no_errors = True
378
+
379
+ exchanges = ["ByBit", "BitGet", "Binance","Kraken","MEXC","OkX", "BreadBytes Historical Logs"]
380
+ logtype = st.selectbox("Select your Exchange", options=exchanges)
381
+
382
+ if logtype != "BreadBytes Historical Logs":
383
+ uploaded_data = st.file_uploader(
384
+ "Drag and Drop files here or click Browse files.", type=[".csv", ".xlsx"], accept_multiple_files=False
385
+ )
386
+ if uploaded_data is None:
387
+ st.info("Please upload a file, or select BreadBytes Historical Logs as your exchange.")
388
+ else:
389
+ st.success("Your file was uploaded successfully!")
390
+
391
+ uploadtype = uploaded_data.name.split(".")[1]
392
+ if uploadtype == "csv":
393
+ df = pd.read_csv(uploaded_data)
394
+ if uploadtype == "xlsx":
395
+ df = pd.read_excel(uploaded_data)
396
+
397
+ otimeheader, cheader, plheader, fmat = get_headers(logtype)
398
+
399
+ df.columns = [c.lower() for c in df.columns]
400
+
401
+ if not(uploaded_data is None):
402
+ with st.container():
403
+ bot_selections = "Other"
404
+ if bot_selections == "Other":
405
+ try:
406
+ symbols = list(df[cheader].unique())
407
+ symbol_selections = st.multiselect(
408
+ "Select/Deselect Asset(s)", options=symbols, default=symbols
409
+ )
410
+ except:
411
+ st.error("Please select your exchange or upload a supported trade log file.")
412
+ no_errors = False
413
+ if no_errors and symbol_selections == None:
414
+ st.error("Please select at least one asset.")
415
+ no_errors = False
416
+
417
+
418
+ if no_errors:
419
+ if logtype == 'Binance':
420
+ otimeheader = df.filter(regex=otimeheader).columns.values[0]
421
+ fmat = '%Y-%m-%d %H:%M:%S'
422
+ df = df[df[plheader] != 0]
423
+ #if logtype == "Kucoin":
424
+ # df = df.replace('\r\n','', regex=True)
425
+ with st.container():
426
+ col1, col2 = st.columns(2)
427
+ with col1:
428
+ try:
429
+ startdate = st.date_input("Start Date", value=pd.to_datetime(df[otimeheader]).min())
430
+ except:
431
+ st.error("Please select your exchange or upload a supported trade log file.")
432
+ no_errors = False
433
+ with col2:
434
+ try:
435
+ enddate = st.date_input("End Date", value=pd.to_datetime(df[otimeheader]).max())
436
+ except:
437
+ st.error("Please select your exchange or upload a supported trade log file.")
438
+ no_errors = False
439
+ #st.sidebar.subheader("Customize your Dashboard")
440
+
441
+ if no_errors and (enddate < startdate):
442
+ st.error("End Date must be later than Start date. Please try again.")
443
+ no_errors = False
444
+ with st.container():
445
+ col1,col2 = st.columns(2)
446
+ with col1:
447
+ principal_balance = st.number_input('Starting Balance', min_value=0.00, value=1000.00, max_value= 1000000.00, step=10.00)
448
+
449
+ with st.expander("Raw Trade Log"):
450
+ st.write(df)
451
+
452
+
453
+ if no_errors:
454
+ df = filt_df(df, cheader, symbol_selections)
455
+
456
+ if len(df) == 0:
457
+ st.error("There are no available trades matching your selections. Please try again!")
458
+ no_errors = False
459
+
460
+ if no_errors:
461
+ ## reformating / necessary calculations
462
+ if logtype == 'BitGet':
463
+ try:
464
+ badcol = df.filter(regex='Unnamed').columns.values[0]
465
+ except:
466
+ badcol = []
467
+ df = df[[col for col in df.columns if col != badcol]]
468
+ df = df[df[plheader] != 0]
469
+ if uploadtype == "xlsx":
470
+ fmat = '%Y-%m-%d %H:%M:%S.%f'
471
+ if logtype == 'MEXC':
472
+ df = df[df[plheader] != 0]
473
+ # collapse on transaction ID then calculate oppsition prices!!!
474
+ if logtype == "Kraken":
475
+ df = df.replace('\r\n','', regex=True)
476
+ df[otimeheader] = [str(time.split(".")[0]) for time in df[otimeheader].values]
477
+ df = df[df['type']=='margin']
478
+ df[plheader] = df[plheader]-df['fee']
479
+ fmat = '%Y-%m-%d %H:%M:%S'
480
+ if len(df) == 0:
481
+ st.error("File Type Error. Please upload a Ledger history file from Kraken.")
482
+ no_errors = False
483
+
484
+ if no_errors:
485
+ dateheader = 'Trade Date'
486
+ theader = 'Trade Time'
487
+
488
+ if type(df[otimeheader].values[0]) != str: #clunky fix to catch non-strings since np.datetime64 unstable
489
+ df[otimeheader] = [str(date) for date in df[otimeheader]]
490
+
491
+ df[dateheader] = [tradetimes.split(" ")[0] for tradetimes in df[otimeheader].values]
492
+ df[theader] = [tradetimes.split(" ")[1] for tradetimes in df[otimeheader].values]
493
+
494
+ dfmat = fmat.split(" ")[0]
495
+ tfmat = fmat.split(" ")[1]
496
+
497
+ df[otimeheader]= [datetime.strptime(date+' '+time,fmat)
498
+ for date,time in zip(df[dateheader],df[theader])]
499
+
500
+ df[dateheader] = [datetime.strptime(date,dfmat).date() for date in df[dateheader].values]
501
+ df[theader] = [datetime.strptime(time,tfmat).time() for time in df[theader].values]
502
+
503
+ df[otimeheader] = pd.to_datetime(df[otimeheader])
504
+
505
+ df.sort_values(by=otimeheader, inplace=True)
506
+ df.index = range(0,len(df))
507
+
508
+ start = df.iloc[0][dateheader] if (not startdate) else startdate
509
+ stop = df.iloc[len(df)-1][dateheader] if (not enddate) else enddate
510
+
511
+ df = df[(df[dateheader] >= start) & (df[dateheader] <= stop)]
512
+
513
+ results_df = pd.DataFrame([], columns = ['Coin', '# of Trades', 'Wins', 'Losses', 'Win Rate',
514
+ 'Profit Factor', 'Cum. P/L', 'Cum. P/L (%)', 'Avg. P/L', 'Avg. P/L (%)'])
515
+
516
+ for currency in pd.unique(df[cheader]):
517
+ df_coin = df[(df[cheader] == currency) & (df[dateheader] >= start) & (df[dateheader] <= stop)]
518
+ data = get_coin_info(df_coin, principal_balance, plheader)
519
+ results_df.loc[len(results_df)] = list([currency]) + list(i for i in data)
520
+
521
+ if bot_selections == "Other" and len(pd.unique(df[cheader])) > 1:
522
+ df_dates = df[(df[dateheader] >= start) & (df[dateheader] <= stop)]
523
+ data = get_coin_info(df_dates, principal_balance, plheader)
524
+ results_df.loc[len(results_df)] = list(['Total']) + list(i for i in data)
525
+
526
+ account_plural = "s" if len(bot_selections) > 1 else ""
527
+ st.subheader(f"Results for your Account{account_plural}")
528
+ totals = results_df[~(results_df['Coin'] == 'Total')].groupby('Coin', as_index=False).sum()
529
+ if len(bot_selections) > 1:
530
+ st.metric(
531
+ "Gains for All Accounts",
532
+ f"${totals['Cum. P/L'].sum():.2f}",
533
+ f"{totals['Cum. P/L (%)'].sum():.2f} %",
534
+ )
535
+
536
+ max_col = 4
537
+ tot_rows = int(np.ceil(len(totals)/max_col))
538
+
539
+ for r in np.arange(0,tot_rows):
540
+ #for column, row in zip(st.columns(len(totals)), totals.itertuples()):
541
+ for column, row in zip(st.columns(max_col), totals.iloc[r*max_col:(r+1)*max_col].itertuples()):
542
+ column.metric(
543
+ row.Coin,
544
+ f"${row._7:.2f}",
545
+ f"{row._8:.2f} %",
546
+ )
547
+ st.subheader(f"Historical Performance")
548
+ cmap=LinearSegmentedColormap.from_list('rg',["r", "grey", "g"], N=100)
549
+ df['Cumulative P/L'] = df[plheader].cumsum()
550
+ if logtype == "Binance": #Binance (utc) doesnt show up in st line charts???
551
+ xx = dateheader
552
+ else:
553
+ xx = otimeheader
554
+
555
+
556
+ #st.line_chart(data=df, x=xx, y='Cumulative P/L', use_container_width=True)
557
+ # Create figure
558
+ fig = go.Figure()
559
+
560
+ pyLogo = Image.open("logo.png")
561
+
562
+ # Add trace
563
+ fig.add_trace(
564
+ go.Scatter(x=df[xx], y=np.round(df['Cumulative P/L'].values,2), line_shape='spline', line = {'smoothing': .2, 'color' : 'rgba(31, 119, 200,.8)'}, name='Cumulative P/L')
565
+ )
566
+
567
+ fig.add_layout_image(
568
+ dict(
569
+ source=pyLogo,
570
+ xref="paper",
571
+ yref="paper",
572
+ x = 0.05, #dfdata['Exit Date'].astype('int64').min() // 10**9,
573
+ y = .85, #dfdata['Cumulative P/L'].max(),
574
+ sizex= .9, #(dfdata['Exit Date'].astype('int64').max() - dfdata['Exit Date'].astype('int64').min()) // 10**9,
575
+ sizey= .9, #(dfdata['Cumulative P/L'].max() - dfdata['Cumulative P/L'].min()),
576
+ sizing="contain",
577
+ opacity=0.2,
578
+ layer = "below")
579
+ )
580
+
581
+ #style layout
582
+ fig.update_layout(
583
+ height = 600,
584
+ xaxis=dict(
585
+ title="Exit Date",
586
+ tickmode='array',
587
+ ),
588
+ yaxis=dict(
589
+ title="Cumulative P/L"
590
+ ) )
591
+
592
+ st.plotly_chart(fig, theme=None, use_container_width=True,height=600)
593
+
594
+ st.subheader("Summarized Results")
595
+ if df.empty:
596
+ st.error("Oops! None of the data provided matches your selection(s). Please try again.")
597
+ else:
598
+ st.dataframe(results_df.style.format({'Win Rate': '{:.2f}%','Profit Factor' : '{:.2f}',
599
+ 'Avg. P/L (%)': '{:.2f}%', 'Cum. P/L (%)': '{:.2f}%',
600
+ 'Cum. P/L': '{:.2f}', 'Avg. P/L': '{:.2f}'})\
601
+ .text_gradient(subset=['Win Rate'],cmap=cmap, vmin = 0, vmax = 100)\
602
+ .text_gradient(subset=['Profit Factor'],cmap=cmap, vmin = 0, vmax = 2), use_container_width=True)
603
+
604
+ if logtype == "BreadBytes Historical Logs" and no_errors:
605
+
606
+ bots = ["Cinnamon Toast", "Short Bread", "Cosmic Cupcake"]#, "CT Toasted"]
607
+ bot_selections = st.selectbox("Select your Trading Bot", options=bots)
608
+ otimeheader = 'Exit Date'
609
+ fmat = '%Y-%m-%d %H:%M:%S'
610
+ fees = .075/100
611
+
612
+ if bot_selections == "Cinnamon Toast":
613
+ lev_cap = 5
614
+ dollar_cap = 100000.00
615
+ data = load_data("CT-Trade-Log.csv",otimeheader, fmat)
616
+ if bot_selections == "French Toast":
617
+ lev_cap = 3
618
+ dollar_cap = 10000000000.00
619
+ data = load_data("FT-Trade-Log.csv",otimeheader, fmat)
620
+ if bot_selections == "Short Bread":
621
+ lev_cap = 5
622
+ dollar_cap = 100000.00
623
+ data = load_data("SB-Trade-Log.csv",otimeheader, fmat)
624
+ if bot_selections == "Cosmic Cupcake":
625
+ lev_cap = 3
626
+ dollar_cap = 100000.00
627
+ data = load_data("CC-Trade-Log.csv",otimeheader, fmat)
628
+ if bot_selections == "CT Toasted":
629
+ lev_cap = 5
630
+ dollar_cap = 100000.00
631
+ data = load_data("CT-Toasted-Trade-Log.csv",otimeheader, fmat)
632
+
633
+ df = data.copy(deep=True)
634
+
635
+ dateheader = 'Date'
636
+ theader = 'Time'
637
+
638
+ st.subheader("Choose your settings:")
639
+ with st.form("user input", ):
640
+ if no_errors:
641
+ with st.container():
642
+ col1, col2 = st.columns(2)
643
+ with col1:
644
+ try:
645
+ startdate = st.date_input("Start Date", value=pd.to_datetime(df[otimeheader]).min())
646
+ except:
647
+ st.error("Please select your exchange or upload a supported trade log file.")
648
+ no_errors = False
649
+ with col2:
650
+ try:
651
+ enddate = st.date_input("End Date", value=datetime.today())
652
+ except:
653
+ st.error("Please select your exchange or upload a supported trade log file.")
654
+ no_errors = False
655
+ #st.sidebar.subheader("Customize your Dashboard")
656
+
657
+ if no_errors and (enddate < startdate):
658
+ st.error("End Date must be later than Start date. Please try again.")
659
+ no_errors = False
660
+ with st.container():
661
+ col1,col2 = st.columns(2)
662
+ with col2:
663
+ lev = st.number_input('Leverage', min_value=1, value=1, max_value= lev_cap, step=1)
664
+ with col1:
665
+ principal_balance = st.number_input('Starting Balance', min_value=0.00, value=1000.00, max_value= dollar_cap, step=.01)
666
+
667
+ if bot_selections == "Cinnamon Toast":
668
+ st.write("Choose your DCA setup (for trades before 02/07/2023)")
669
+ with st.container():
670
+ col1, col2, col3, col4 = st.columns(4)
671
+ with col1:
672
+ dca1 = st.number_input('DCA 1 Allocation', min_value=0, value=25, max_value= 100, step=1)
673
+ with col2:
674
+ dca2 = st.number_input('DCA 2 Allocation', min_value=0, value=25, max_value= 100, step=1)
675
+ with col3:
676
+ dca3 = st.number_input('DCA 3 Allocation', min_value=0, value=25, max_value= 100, step=1)
677
+ with col4:
678
+ dca4 = st.number_input('DCA 4 Allocation', min_value=0, value=25, max_value= 100, step=1)
679
+ st.write("Choose your DCA setup (for trades on or after 02/07/2023)")
680
+ with st.container():
681
+ col1, col2 = st.columns(2)
682
+ with col1:
683
+ dca5 = st.number_input('DCA 1 Allocation', min_value=0, value=50, max_value= 100, step=1)
684
+ with col2:
685
+ dca6 = st.number_input('DCA 2 Allocation', min_value=0, value=50, max_value= 100, step=1)
686
+
687
+ #hack way to get button centered
688
+ c = st.columns(9)
689
+ with c[4]:
690
+ submitted = st.form_submit_button("Get Cookin'!")
691
+
692
+ if submitted and principal_balance * lev > dollar_cap:
693
+ lev = np.floor(dollar_cap/principal_balance)
694
+ st.error(f"WARNING: (Starting Balance)*(Leverage) exceeds the ${dollar_cap} limit. Using maximum available leverage of {lev}")
695
+
696
+ if submitted and no_errors:
697
+ df = df[(df[dateheader] >= startdate) & (df[dateheader] <= enddate)]
698
+ signal_map = {'Long': 1, 'Short':-1}
699
+
700
+
701
+ if len(df) == 0:
702
+ st.error("There are no available trades matching your selections. Please try again!")
703
+ no_errors = False
704
+
705
+ if no_errors:
706
+ if bot_selections == "Cinnamon Toast":
707
+ dca_map = {1: dca1/100, 2: dca2/100, 3: dca3/100, 4: dca4/100, 1.1: dca5/100, 2.1: dca6/100}
708
+ df['DCA %'] = df['DCA'].map(dca_map)
709
+ df['Calculated Return %'] = df['Signal'].map(signal_map)*(df['DCA %'])*(1-fees)*((df['Sell Price']-df['Buy Price'])/df['Buy Price'] - fees) #accounts for fees on open and close of trade
710
+ df['DCA'] = np.floor(df['DCA'].values)
711
+
712
+ df['Return Per Trade'] = np.nan
713
+ df['Balance used in Trade'] = np.nan
714
+ df['New Balance'] = np.nan
715
+
716
+ g = df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return %'].reset_index(name='Return Per Trade')
717
+ df.loc[df['DCA']==1.0,'Return Per Trade'] = 1+lev*g['Return Per Trade'].values
718
+
719
+ df['Compounded Return'] = df['Return Per Trade'].cumprod()
720
+ df.loc[df['DCA']==1.0,'New Balance'] = [min(dollar_cap/lev, bal*principal_balance) for bal in df.loc[df['DCA']==1.0,'Compounded Return']]
721
+ df.loc[df['DCA']==1.0,'Balance used in Trade'] = np.concatenate([[principal_balance], df.loc[df['DCA']==1.0,'New Balance'].values[:-1]])
722
+ else:
723
+ df['Calculated Return %'] = df['Signal'].map(signal_map)*(1-fees)*((df['Sell Price']-df['Buy Price'])/df['Buy Price'] - fees) #accounts for fees on open and close of trade
724
+ df['Return Per Trade'] = np.nan
725
+ g = df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return %'].reset_index(name='Return Per Trade')
726
+ df['Return Per Trade'] = 1+lev*g['Return Per Trade'].values
727
+
728
+ df['Compounded Return'] = df['Return Per Trade'].cumprod()
729
+ df['New Balance'] = [min(dollar_cap/lev, bal*principal_balance) for bal in df['Compounded Return']]
730
+ df['Balance used in Trade'] = np.concatenate([[principal_balance], df['New Balance'].values[:-1]])
731
+ df['Net P/L Per Trade'] = (df['Return Per Trade']-1)*df['Balance used in Trade']
732
+ df['Cumulative P/L'] = df['Net P/L Per Trade'].cumsum()
733
+
734
+ if bot_selections == "Cinnamon Toast" or bot_selections == "Cosmic Cupcake":
735
+ cum_pl = df.loc[df.drop('Drawdown %', axis=1).dropna().index[-1],'Cumulative P/L'] + principal_balance
736
+ #cum_sdp = sd_df.loc[sd_df.drop('Drawdown %', axis=1).dropna().index[-1],'Cumulative P/L (+)'] + principal_balance
737
+ #cum_sdm = sd_df.loc[sd_df.drop('Drawdown %', axis=1).dropna().index[-1],'Cumulative P/L (-)'] + principal_balance
738
+ else:
739
+ cum_pl = df.loc[df.dropna().index[-1],'Cumulative P/L'] + principal_balance
740
+ #cum_sdp = sd_df.loc[sd_df.dropna().index[-1],'Cumulative P/L (+)'] + principal_balance
741
+ #cum_sdm = sd_df.loc[sd_df.dropna().index[-1],'Cumulative P/L (-)'] + principal_balance
742
+ #sd = 2*.00026
743
+ #sd_df = get_sd_df(get_sd_df(df.copy(), sd, bot_selections, dca1, dca2, dca3, dca4, dca5, dca6, fees, lev, dollar_cap, principal_balance)
744
+
745
+ effective_return = 100*((cum_pl - principal_balance)/principal_balance)
746
+
747
+ st.header(f"{bot_selections} Results")
748
+ with st.container():
749
+
750
+ if len(bot_selections) > 1:
751
+ col1, col2 = st.columns(2)
752
+ with col1:
753
+ st.metric(
754
+ "Total Account Balance",
755
+ f"${cum_pl:.2f}",
756
+ f"{100*(cum_pl-principal_balance)/(principal_balance):.2f} %",
757
+ )
758
+
759
+ # with col2:
760
+ # st.write("95% of trades should fall within this 2 std. dev. range.")
761
+ # st.metric(
762
+ # "High Range (+ 2 std. dev.)",
763
+ # f"", #${cum_sdp:.2f}
764
+ # f"{100*(cum_sdp-principal_balance)/(principal_balance):.2f} %",
765
+ # )
766
+ # st.metric(
767
+ # "Low Range (- 2 std. dev.)",
768
+ # f"" ,#${cum_sdm:.2f}"
769
+ # f"{100*(cum_sdm-principal_balance)/(principal_balance):.2f} %",
770
+ # )
771
+ if bot_selections == "Cinnamon Toast" or bot_selections == "Cosmic Cupcake":
772
+ #st.line_chart(data=df.drop('Drawdown %', axis=1).dropna(), x='Exit Date', y='Cumulative P/L', use_container_width=True)
773
+ dfdata = df.drop('Drawdown %', axis=1).dropna()
774
+ #sd_df = sd_df.drop('Drawdown %', axis=1).dropna()
775
+ else:
776
+ #st.line_chart(data=df.dropna(), x='Exit Date', y='Cumulative P/L', use_container_width=True)
777
+ dfdata = df.dropna()
778
+ #sd_df = sd_df.dropna()
779
+
780
+ # Create figure
781
+ fig = go.Figure()
782
+
783
+ pyLogo = Image.open("logo.png")
784
+
785
+ # fig.add_traces(go.Scatter(x=sd_df['Exit Date'], y = sd_df['Cumulative P/L (+)'],line_shape='spline',
786
+ # line = dict(smoothing = 1.3, color='rgba(31, 119, 200,0)'), showlegend = False)
787
+ # )
788
+
789
+ # fig.add_traces(go.Scatter(x=sd_df['Exit Date'], y = sd_df['Cumulative P/L (-)'],
790
+ # line = dict(smoothing = 1.3, color='rgba(31, 119, 200,0)'), line_shape='spline',
791
+ # fill='tonexty',
792
+ # fillcolor = 'rgba(31, 119, 200,.2)', name = '+/- Standard Deviation')
793
+ # )
794
+
795
+ # Add trace
796
+ fig.add_trace(
797
+ go.Scatter(x=dfdata['Exit Date'], y=np.round(dfdata['Cumulative P/L'].values,2), line_shape='spline',
798
+ line = {'smoothing': 1.0, 'color' : 'rgba(31, 119, 200,.8)'},
799
+ name='Cumulative P/L')
800
+ )
801
+ buyhold = (principal_balance/dfdata['Buy Price'][dfdata.index[0]])*(dfdata['Buy Price']-dfdata['Buy Price'][dfdata.index[0]])
802
+ fig.add_trace(go.Scatter(x=dfdata['Exit Date'], y=np.round(buyhold.values,2), line_shape='spline',
803
+ line = {'smoothing': 1.0, 'color' :'red'}, name = 'Buy & Hold Return')
804
+ )
805
+
806
+ fig.add_layout_image(
807
+ dict(
808
+ source=pyLogo,
809
+ xref="paper",
810
+ yref="paper",
811
+ x = 0.05, #dfdata['Exit Date'].astype('int64').min() // 10**9,
812
+ y = .85, #dfdata['Cumulative P/L'].max(),
813
+ sizex= .9, #(dfdata['Exit Date'].astype('int64').max() - dfdata['Exit Date'].astype('int64').min()) // 10**9,
814
+ sizey= .9, #(dfdata['Cumulative P/L'].max() - dfdata['Cumulative P/L'].min()),
815
+ sizing="contain",
816
+ opacity=0.2,
817
+ layer = "below")
818
+ )
819
+
820
+ #style layout
821
+ fig.update_layout(
822
+ height = 600,
823
+ xaxis=dict(
824
+ title="Exit Date",
825
+ tickmode='array',
826
+ ),
827
+ yaxis=dict(
828
+ title="Cumulative P/L"
829
+ ) )
830
+
831
+ st.plotly_chart(fig, theme=None, use_container_width=True,height=600)
832
+ st.write()
833
+ df['Per Trade Return Rate'] = df['Return Per Trade']-1
834
+
835
+ totals = pd.DataFrame([], columns = ['# of Trades', 'Wins', 'Losses', 'Win Rate', 'Profit Factor'])
836
+ if bot_selections == "Cinnamon Toast" or bot_selections == "Cosmic Cupcake":
837
+ data = get_hist_info(df.drop('Drawdown %', axis=1).dropna(), principal_balance,'Per Trade Return Rate')
838
+ else:
839
+ data = get_hist_info(df.dropna(), principal_balance,'Per Trade Return Rate')
840
+ totals.loc[len(totals)] = list(i for i in data)
841
+
842
+ totals['Cum. P/L'] = cum_pl-principal_balance
843
+ totals['Cum. P/L (%)'] = 100*(cum_pl-principal_balance)/principal_balance
844
+
845
+ if df.empty:
846
+ st.error("Oops! None of the data provided matches your selection(s). Please try again.")
847
+ else:
848
+ with st.container():
849
+ for row in totals.itertuples():
850
+ col1, col2, col3, col4= st.columns(4)
851
+ c1, c2, c3, c4 = st.columns(4)
852
+ with col1:
853
+ st.metric(
854
+ "Total Trades",
855
+ f"{row._1:.0f}",
856
+ )
857
+ with c1:
858
+ st.metric(
859
+ "Profit Factor",
860
+ f"{row._5:.2f}",
861
+ )
862
+ with col2:
863
+ st.metric(
864
+ "Wins",
865
+ f"{row.Wins:.0f}",
866
+ )
867
+ with c2:
868
+ st.metric(
869
+ "Cumulative P/L",
870
+ f"${row._6:.2f}",
871
+ f"{row._7:.2f} %",
872
+ )
873
+ with col3:
874
+ st.metric(
875
+ "Losses",
876
+ f"{row.Losses:.0f}",
877
+ )
878
+ with c3:
879
+ st.metric(
880
+ "Rolling 7 Days",
881
+ "",#f"{(1+get_rolling_stats(df,otimeheader, 30))*principal_balance:.2f}",
882
+ f"{get_rolling_stats(df,lev, otimeheader, 7):.2f}%",
883
+ )
884
+ st.metric(
885
+ "Rolling 30 Days",
886
+ "",#f"{(1+get_rolling_stats(df,otimeheader, 30))*principal_balance:.2f}",
887
+ f"{get_rolling_stats(df,lev, otimeheader, 30):.2f}%",
888
+ )
889
+
890
+ with col4:
891
+ st.metric(
892
+ "Win Rate",
893
+ f"{row._4:.1f}%",
894
+ )
895
+ with c4:
896
+ st.metric(
897
+ "Rolling 90 Days",
898
+ "",#f"{(1+get_rolling_stats(df,otimeheader, 30))*principal_balance:.2f}",
899
+ f"{get_rolling_stats(df,lev, otimeheader, 90):.2f}%",
900
+ )
901
+ st.metric(
902
+ "Rolling 180 Days",
903
+ "",#f"{(1+get_rolling_stats(df,otimeheader, 30))*principal_balance:.2f}",
904
+ f"{get_rolling_stats(df,lev, otimeheader, 180):.2f}%",
905
+ )
906
+
907
+ if bot_selections == "Cinnamon Toast":
908
+ if submitted:
909
+ grouped_df = df.groupby('Exit Date').agg({'Signal':'min','Entry Date': 'min','Exit Date': 'max','Buy Price': 'mean',
910
+ 'Sell Price' : 'max',
911
+ 'Net P/L Per Trade': 'mean',
912
+ 'Calculated Return %' : lambda x: np.round(100*lev*x.sum(),2),
913
+ 'DCA': lambda x: int(np.floor(x.max()))})
914
+ grouped_df.index = range(1, len(grouped_df)+1)
915
+ grouped_df.rename(columns={'DCA' : '# of DCAs', 'Buy Price':'Avg. Buy Price',
916
+ 'Net P/L Per Trade':'Net P/L',
917
+ 'Calculated Return %':'P/L %'}, inplace=True)
918
+ else:
919
+ dca_map = {1: 25/100, 2: 25/100, 3: 25/100, 4: 25/100, 1.1: 50/100, 2.1: 50/100}
920
+ df['DCA %'] = df['DCA'].map(dca_map)
921
+ df['Calculated Return %'] = (df['DCA %'])*(1-fees)*((df['Sell Price']-df['Buy Price'])/df['Buy Price'] - fees) #accounts for fees on open and close of trade
922
+
923
+ grouped_df = df.groupby('Exit Date').agg({'Signal':'min','Entry Date': 'min','Exit Date': 'max','Buy Price': 'mean',
924
+ 'Sell Price' : 'max',
925
+ 'P/L per token': 'mean',
926
+ 'Calculated Return %' : lambda x: np.round(100*x.sum(),2),
927
+ 'DCA': lambda x: int(np.floor(x.max()))})
928
+ grouped_df.index = range(1, len(grouped_df)+1)
929
+ grouped_df.rename(columns={'DCA' : '# of DCAs', 'Buy Price':'Avg. Buy Price',
930
+ 'Calculated Return %':'P/L %',
931
+ 'P/L per token':'Net P/L'}, inplace=True)
932
+
933
+ else:
934
+ if submitted:
935
+ grouped_df = df.groupby('Exit Date').agg({'Signal':'min','Entry Date': 'min','Exit Date': 'max','Buy Price': 'mean',
936
+ 'Sell Price' : 'max',
937
+ 'Net P/L Per Trade': 'mean',
938
+ 'Calculated Return %' : lambda x: np.round(100*lev*x.sum(),2)})
939
+ grouped_df.index = range(1, len(grouped_df)+1)
940
+ grouped_df.rename(columns={'Buy Price':'Avg. Buy Price',
941
+ 'Net P/L Per Trade':'Net P/L',
942
+ 'Calculated Return %':'P/L %'}, inplace=True)
943
+ else:
944
+ grouped_df = df.groupby('Exit Date').agg({'Signal':'min','Entry Date': 'min','Exit Date': 'max','Buy Price': 'mean',
945
+ 'Sell Price' : 'max',
946
+ 'P/L per token': 'mean',
947
+ 'P/L %':'mean'})
948
+ grouped_df.index = range(1, len(grouped_df)+1)
949
+ grouped_df.rename(columns={'Buy Price':'Avg. Buy Price',
950
+ 'P/L per token':'Net P/L'}, inplace=True)
951
+ st.subheader("Trade Logs")
952
+ grouped_df['Entry Date'] = pd.to_datetime(grouped_df['Entry Date'])
953
+ grouped_df['Exit Date'] = pd.to_datetime(grouped_df['Exit Date'])
954
+ if bot_selections == "Cosmic Cupcake" or bot_selections == "CT Toasted":
955
+ coding = cc_coding if bot_selections == "Cosmic Cupcake" else ctt_coding
956
+ st.dataframe(grouped_df.style.format({'Entry Date':'{:%m-%d-%Y %H:%M:%S}','Exit Date':'{:%m-%d-%Y %H:%M:%S}','Avg. Buy Price': '${:.2f}', 'Sell Price': '${:.2f}', 'Net P/L':'${:.2f}', 'P/L %':'{:.2f}%'})\
957
+ .apply(coding, axis=1)\
958
+ .applymap(my_style,subset=['Net P/L'])\
959
+ .applymap(my_style,subset=['P/L %']), use_container_width=True)
960
+ new_title = '<div style="text-align: right;"><span style="background-color:lightgrey;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> Not Live Traded</div>'
961
+ st.markdown(new_title, unsafe_allow_html=True)
962
+ else:
963
+ st.dataframe(grouped_df.style.format({'Entry Date':'{:%m-%d-%Y %H:%M:%S}','Exit Date':'{:%m-%d-%Y %H:%M:%S}','Avg. Buy Price': '${:.2f}', 'Sell Price': '${:.2f}', 'Net P/L':'${:.2f}', 'P/L %':'{:.2f}%'})\
964
+ .applymap(my_style,subset=['Net P/L'])\
965
+ .applymap(my_style,subset=['P/L %']), use_container_width=True)
966
+
967
+ # st.subheader("Checking Status")
968
+ # if submitted:
969
+ # st.dataframe(sd_df)
970
+
971
+ if __name__ == "__main__":
972
+ st.set_page_config(
973
+ "Trading Bot Dashboard",
974
+ layout="wide",
975
+ )
976
+ runapp()
977
+ # -
978
+
979
+
980
+
981
+
logo.png ADDED