anaucoin commited on
Commit
340ed9d
·
1 Parent(s): 8ffd726

initial commit

Browse files
Files changed (5) hide show
  1. README.md +7 -5
  2. app.py +599 -0
  3. history.csv +104 -0
  4. logo.png +0 -0
  5. requirements.txt +9 -0
README.md CHANGED
@@ -1,12 +1,14 @@
1
  ---
2
- title: PN Dashboard
3
- emoji: 🏃
4
- colorFrom: red
5
- colorTo: pink
6
  sdk: streamlit
7
- sdk_version: 1.31.1
8
  app_file: app.py
9
  pinned: false
 
 
10
  ---
11
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: CT Dashboard
3
+ emoji: 📚
4
+ colorFrom: indigo
5
+ colorTo: green
6
  sdk: streamlit
7
+ sdk_version: 1.17.0
8
  app_file: app.py
9
  pinned: false
10
+ license: gpl
11
+ fullWidth: true
12
  ---
13
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,599 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.experimental_memo
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.experimental_memo
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.experimental_memo
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.experimental_memo
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.experimental_memo
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.experimental_memo
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.experimental_memo
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
+ def load_data(filename, account, exchange, otimeheader, fmat):
183
+ cols = ['id','datetime', 'exchange', 'subaccount', 'pair', 'side', 'action', 'amount', 'price']
184
+ df = pd.read_csv(filename, header = 0, names= cols)
185
+
186
+ filtdf = df[(df.exchange == exchange) & (df.subaccount == account)].dropna()
187
+ filtdf = filtdf.sort_values('datetime')
188
+ filtdf = filtdf.iloc[np.where(filtdf.action == 'open')[0][0]:, :] #get first open signal in dataframe
189
+
190
+ tnum = 0
191
+ dca = 0
192
+ newdf = pd.DataFrame([], columns=['Trade','Signal','Entry Date','Buy Price', 'Sell Price','Exit Date', 'P/L per token', 'P/L %'])
193
+ for index, row in filtdf.iterrows():
194
+ if row.action == 'open':
195
+ dca += 1
196
+ tnum += 1
197
+ sig = 'Long' if row.side == 'buy' else 'Short'
198
+ temp = pd.DataFrame({'Trade' :[tnum], 'Signal': [sig], 'Entry Date':[row.datetime],'Buy Price': [row.price], 'Sell Price': [np.nan],'Exit Date': [np.nan], 'P/L per token': [np.nan], 'P/L %': [np.nan], 'DCA': [dca]})
199
+ newdf = pd.concat([newdf,temp], ignore_index = True)
200
+ if row.action == 'close':
201
+ for j in np.arange(tnum-1, tnum-dca-1,-1):
202
+ newdf.loc[j,'Sell Price'] = row.price
203
+ newdf.loc[j,'Exit Date'] = row.datetime
204
+ dca = 0
205
+
206
+ newdf['Buy Price'] = pd.to_numeric(newdf['Buy Price'])
207
+ newdf['Sell Price'] = pd.to_numeric(newdf['Sell Price'])
208
+
209
+ newdf['P/L per token'] = newdf['Sell Price'] - newdf['Buy Price']
210
+ newdf['P/L %'] = 100*newdf['P/L per token']/newdf['Buy Price']
211
+ newdf = newdf.dropna()
212
+
213
+
214
+ dateheader = 'Date'
215
+ theader = 'Time'
216
+
217
+ newdf[dateheader] = [tradetimes.split(" ")[0] for tradetimes in newdf[otimeheader].values]
218
+ newdf[theader] = [tradetimes.split(" ")[1] for tradetimes in newdf[otimeheader].values]
219
+
220
+ newdf[otimeheader] = pd.to_datetime(newdf[otimeheader])
221
+ newdf['Exit Date'] = pd.to_datetime(newdf['Exit Date'])
222
+
223
+ newdf[dateheader] = [dateutil.parser.parse(date).date() for date in newdf[dateheader]]
224
+ newdf[theader] = [dateutil.parser.parse(time).time() for time in newdf[theader]]
225
+
226
+ return newdf
227
+
228
+ def get_sd_df(sd_df, sd, bot_selections, dca1, dca2, dca3, dca4, dca5, dca6, fees, lev, dollar_cap, principal_balance):
229
+ sd = 2*.00026
230
+ # ------ Standard Dev. Calculations.
231
+ if bot_selections == "Cinnamon Toast":
232
+ dca_map = {1: dca1/100, 2: dca2/100, 3: dca3/100, 4: dca4/100, 1.1: dca5/100, 2.1: dca6/100}
233
+ sd_df['DCA %'] = sd_df['DCA'].map(dca_map)
234
+ 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
235
+ 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
236
+ sd_df['DCA'] = np.floor(sd_df['DCA'].values)
237
+
238
+ sd_df['Return Per Trade (+)'] = np.nan
239
+ sd_df['Return Per Trade (-)'] = np.nan
240
+ sd_df['Balance used in Trade (+)'] = np.nan
241
+ sd_df['Balance used in Trade (-)'] = np.nan
242
+ sd_df['New Balance (+)'] = np.nan
243
+ sd_df['New Balance (-)'] = np.nan
244
+
245
+ g1 = sd_df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return % (+)'].reset_index(name='Return Per Trade (+)')
246
+ g2 = sd_df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return % (-)'].reset_index(name='Return Per Trade (-)')
247
+ sd_df.loc[sd_df['DCA']==1.0,'Return Per Trade (+)'] = 1+lev*g1['Return Per Trade (+)'].values
248
+ sd_df.loc[sd_df['DCA']==1.0,'Return Per Trade (-)'] = 1+lev*g2['Return Per Trade (-)'].values
249
+
250
+ sd_df['Compounded Return (+)'] = sd_df['Return Per Trade (+)'].cumprod()
251
+ sd_df['Compounded Return (-)'] = sd_df['Return Per Trade (-)'].cumprod()
252
+ 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 (+)']]
253
+ 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]])
254
+
255
+ 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 (-)']]
256
+ 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]])
257
+ else:
258
+ 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
259
+ 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
260
+ sd_df['Return Per Trade (+)'] = np.nan
261
+ sd_df['Return Per Trade (-)'] = np.nan
262
+
263
+ g1 = sd_df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return % (+)'].reset_index(name='Return Per Trade (+)')
264
+ g2 = sd_df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return % (-)'].reset_index(name='Return Per Trade (-)')
265
+ sd_df['Return Per Trade (+)'] = 1+lev*g1['Return Per Trade (+)'].values
266
+ sd_df['Return Per Trade (-)'] = 1+lev*g2['Return Per Trade (-)'].values
267
+
268
+ sd_df['Compounded Return (+)'] = sd_df['Return Per Trade (+)'].cumprod()
269
+ sd_df['Compounded Return (-)'] = sd_df['Return Per Trade (-)'].cumprod()
270
+ sd_df['New Balance (+)'] = [min(dollar_cap/lev, bal*principal_balance) for bal in sd_df['Compounded Return (+)']]
271
+ sd_df['Balance used in Trade (+)'] = np.concatenate([[principal_balance], sd_df['New Balance (+)'].values[:-1]])
272
+
273
+ sd_df['New Balance (-)'] = [min(dollar_cap/lev, bal*principal_balance) for bal in sd_df['Compounded Return (-)']]
274
+ sd_df['Balance used in Trade (-)'] = np.concatenate([[principal_balance], sd_df['New Balance (-)'].values[:-1]])
275
+
276
+ sd_df['Net P/L Per Trade (+)'] = (sd_df['Return Per Trade (+)']-1)*sd_df['Balance used in Trade (+)']
277
+ sd_df['Cumulative P/L (+)'] = sd_df['Net P/L Per Trade (+)'].cumsum()
278
+
279
+ sd_df['Net P/L Per Trade (-)'] = (sd_df['Return Per Trade (-)']-1)*sd_df['Balance used in Trade (-)']
280
+ sd_df['Cumulative P/L (-)'] = sd_df['Net P/L Per Trade (-)'].cumsum()
281
+ return sd_df
282
+
283
+ def runapp() -> None:
284
+ bot_selections = "Pumpernickel"
285
+ otimeheader = 'Exit Date'
286
+ fmat = '%Y-%m-%d %H:%M:%S'
287
+ fees = .075/100
288
+
289
+ #st.header(f"{bot_selections} Performance Dashboard :bread: :moneybag:")
290
+ no_errors = True
291
+ #st.write("Welcome to the Trading Bot Dashboard by BreadBytes! You can use this dashboard to track " +
292
+ # "the performance of our trading bots.")
293
+
294
+ if bot_selections == "Pumpernickel":
295
+ lev_cap = 5
296
+ dollar_cap = 1000000000.00
297
+ data = load_data('history.csv', 'Pumpernickel Test', 'Bybit Futures', otimeheader, fmat)
298
+
299
+ df = data.copy(deep=True)
300
+
301
+ dateheader = 'Date'
302
+ theader = 'Time'
303
+
304
+ #st.subheader("Choose your settings:")
305
+ with st.form("user input", ):
306
+ if no_errors:
307
+ with st.container():
308
+ col1, col2 = st.columns(2)
309
+ with col1:
310
+ try:
311
+ startdate = st.date_input("Start Date", value=pd.to_datetime(df[otimeheader]).min())
312
+ except:
313
+ st.error("Please select your exchange or upload a supported trade log file.")
314
+ no_errors = False
315
+ with col2:
316
+ try:
317
+ enddate = st.date_input("End Date", value=datetime.today())
318
+ except:
319
+ st.error("Please select your exchange or upload a supported trade log file.")
320
+ no_errors = False
321
+ #st.sidebar.subheader("Customize your Dashboard")
322
+
323
+ if no_errors and (enddate < startdate):
324
+ st.error("End Date must be later than Start date. Please try again.")
325
+ no_errors = False
326
+ with st.container():
327
+ col1,col2 = st.columns(2)
328
+ with col2:
329
+ lev = st.number_input('Leverage', min_value=1, value=1, max_value= lev_cap, step=1)
330
+ with col1:
331
+ principal_balance = st.number_input('Starting Balance', min_value=0.00, value=1000.00, max_value= dollar_cap, step=.01)
332
+
333
+ #hack way to get button centered
334
+ c = st.columns(9)
335
+ with c[4]:
336
+ submitted = st.form_submit_button("Get Cookin'!")
337
+ signal_map = {'Long': 1, 'Short':-1}
338
+ if submitted and principal_balance * lev > dollar_cap:
339
+ lev = np.floor(dollar_cap/principal_balance)
340
+ st.error(f"WARNING: (Starting Balance)*(Leverage) exceeds the ${dollar_cap} limit. Using maximum available leverage of {lev}")
341
+
342
+ df = df[(df[dateheader] >= startdate) & (df[dateheader] <= enddate)]
343
+
344
+ if submitted and len(df) == 0:
345
+ st.error("There are no available trades matching your selections. Please try again!")
346
+ no_errors = False
347
+
348
+ if no_errors:
349
+ if bot_selections == "Pumpernickel":
350
+ dca_map = {1: 1/3, 2: 1/3, 3: 1/3}
351
+ df['DCA %'] = df['DCA'].map(dca_map)
352
+ 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
353
+ df['DCA'] = np.floor(df['DCA'].values)
354
+
355
+ df['Return Per Trade'] = np.nan
356
+ df['Balance used in Trade'] = np.nan
357
+ df['New Balance'] = np.nan
358
+
359
+ g = df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return %'].reset_index(name='Return Per Trade')
360
+ df.loc[df['DCA']==1.0,'Return Per Trade'] = 1+lev*g['Return Per Trade'].values
361
+
362
+ df['Compounded Return'] = df['Return Per Trade'].cumprod()
363
+ 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']]
364
+ df.loc[df['DCA']==1.0,'Balance used in Trade'] = np.concatenate([[principal_balance], df.loc[df['DCA']==1.0,'New Balance'].values[:-1]])
365
+ else:
366
+ 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
367
+ df['Return Per Trade'] = np.nan
368
+ g = df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return %'].reset_index(name='Return Per Trade')
369
+ df['Return Per Trade'] = 1+lev*g['Return Per Trade'].values
370
+
371
+ df['Compounded Return'] = df['Return Per Trade'].cumprod()
372
+ df['New Balance'] = [min(dollar_cap/lev, bal*principal_balance) for bal in df['Compounded Return']]
373
+ df['Balance used in Trade'] = np.concatenate([[principal_balance], df['New Balance'].values[:-1]])
374
+ df['Net P/L Per Trade'] = (df['Return Per Trade']-1)*df['Balance used in Trade']
375
+ df['Cumulative P/L'] = df['Net P/L Per Trade'].cumsum()
376
+
377
+
378
+ cum_pl = df.loc[df.dropna().index[-1],'Cumulative P/L'] + principal_balance
379
+
380
+ effective_return = 100*((cum_pl - principal_balance)/principal_balance)
381
+
382
+ #st.header(f"{bot_selections} Results")
383
+ with st.container():
384
+
385
+ if len(bot_selections) > 1:
386
+ col1, col2 = st.columns(2)
387
+ with col1:
388
+ st.metric(
389
+ "Total Account Balance",
390
+ f"${cum_pl:.2f}",
391
+ f"{100*(cum_pl-principal_balance)/(principal_balance):.2f} %",
392
+ )
393
+
394
+ dfdata = df.dropna()
395
+
396
+ # Create figure
397
+ fig = go.Figure()
398
+
399
+ pyLogo = Image.open("logo.png")
400
+
401
+ # fig.add_traces(go.Scatter(x=sd_df['Exit Date'], y = sd_df['Cumulative P/L (+)'],line_shape='spline',
402
+ # line = dict(smoothing = 1.3, color='rgba(31, 119, 200,0)'), showlegend = False)
403
+ # )
404
+
405
+ # fig.add_traces(go.Scatter(x=sd_df['Exit Date'], y = sd_df['Cumulative P/L (-)'],
406
+ # line = dict(smoothing = 1.3, color='rgba(31, 119, 200,0)'), line_shape='spline',
407
+ # fill='tonexty',
408
+ # fillcolor = 'rgba(31, 119, 200,.2)', name = '+/- Standard Deviation')
409
+ # )
410
+
411
+ # Add trace
412
+ fig.add_trace(
413
+ go.Scatter(x=dfdata['Exit Date'], y=np.round(dfdata['Cumulative P/L'].values,2), line_shape='spline',
414
+ line = {'smoothing': .7, 'color' : 'rgba(90, 223, 137, 1)'},
415
+ name='P/L')
416
+ )
417
+ buyhold = (principal_balance/dfdata['Buy Price'][dfdata.index[0]])*(dfdata['Buy Price']-dfdata['Buy Price'][dfdata.index[0]])
418
+ fig.add_trace(go.Scatter(x=dfdata['Exit Date'], y=np.round(buyhold.values,2), line_shape='spline',
419
+ line = {'smoothing': .7, 'color' :'rgba(33, 212, 225, 1)'}, name = 'Buy & Hold')
420
+ )
421
+
422
+ fig.add_layout_image(
423
+ dict(
424
+ source=pyLogo,
425
+ xref="paper",
426
+ yref="paper",
427
+ x = 0.05, #dfdata['Exit Date'].astype('int64').min() // 10**9,
428
+ y = .95, #dfdata['Cumulative P/L'].max(),
429
+ sizex= .9, #(dfdata['Exit Date'].astype('int64').max() - dfdata['Exit Date'].astype('int64').min()) // 10**9,
430
+ sizey= .9, #(dfdata['Cumulative P/L'].max() - dfdata['Cumulative P/L'].min()),
431
+ sizing="contain",
432
+ opacity=0.5,
433
+ layer = "below")
434
+ )
435
+
436
+ #style layout
437
+ fig.update_layout(
438
+ height = 550,
439
+ xaxis=dict(
440
+ title="Exit Date",
441
+ tickmode='array',
442
+ showgrid=False
443
+ ),
444
+ yaxis=dict(
445
+ title="Cumulative P/L",
446
+ showgrid=False
447
+ ),
448
+ legend=dict(
449
+ x=.85,
450
+ y=0.15,
451
+ traceorder="normal"
452
+ ),
453
+ plot_bgcolor = 'rgba(10, 10, 10, 1)'
454
+ )
455
+
456
+ st.plotly_chart(fig, theme=None, use_container_width=True, height=550)
457
+ st.write()
458
+ df['Per Trade Return Rate'] = df['Return Per Trade']-1
459
+
460
+ totals = pd.DataFrame([], columns = ['# of Trades', 'Wins', 'Losses', 'Win Rate', 'Profit Factor'])
461
+ data = get_hist_info(df.dropna(), principal_balance,'Per Trade Return Rate')
462
+ totals.loc[len(totals)] = list(i for i in data)
463
+
464
+ totals['Cum. P/L'] = cum_pl-principal_balance
465
+ totals['Cum. P/L (%)'] = 100*(cum_pl-principal_balance)/principal_balance
466
+
467
+ if df.empty:
468
+ st.error("Oops! None of the data provided matches your selection(s). Please try again.")
469
+ else:
470
+ with st.container():
471
+ for row in totals.itertuples():
472
+ col1, col2, col3, col4= st.columns(4)
473
+ c1, c2, c3, c4 = st.columns(4)
474
+ with col1:
475
+ st.metric(
476
+ "Total Trades",
477
+ f"{row._1:.0f}",
478
+ )
479
+ with c1:
480
+ st.metric(
481
+ "Profit Factor",
482
+ f"{row._5:.2f}",
483
+ )
484
+ with col2:
485
+ st.metric(
486
+ "Wins",
487
+ f"{row.Wins:.0f}",
488
+ )
489
+ with c2:
490
+ st.metric(
491
+ "Cumulative P/L",
492
+ f"${row._6:.2f}",
493
+ f"{row._7:.2f} %",
494
+ )
495
+ with col3:
496
+ st.metric(
497
+ "Losses",
498
+ f"{row.Losses:.0f}",
499
+ )
500
+ with c3:
501
+ st.metric(
502
+ "Rolling 7 Days",
503
+ "",#f"{(1+get_rolling_stats(df,otimeheader, 30))*principal_balance:.2f}",
504
+ f"{get_rolling_stats(df,lev, otimeheader, 7):.2f}%",
505
+ )
506
+ st.metric(
507
+ "Rolling 30 Days",
508
+ "",#f"{(1+get_rolling_stats(df,otimeheader, 30))*principal_balance:.2f}",
509
+ f"{get_rolling_stats(df,lev, otimeheader, 30):.2f}%",
510
+ )
511
+
512
+ with col4:
513
+ st.metric(
514
+ "Win Rate",
515
+ f"{row._4:.1f}%",
516
+ )
517
+ with c4:
518
+ st.metric(
519
+ "Rolling 90 Days",
520
+ "",#f"{(1+get_rolling_stats(df,otimeheader, 30))*principal_balance:.2f}",
521
+ f"{get_rolling_stats(df,lev, otimeheader, 90):.2f}%",
522
+ )
523
+ st.metric(
524
+ "Rolling 180 Days",
525
+ "",#f"{(1+get_rolling_stats(df,otimeheader, 30))*principal_balance:.2f}",
526
+ f"{get_rolling_stats(df,lev, otimeheader, 180):.2f}%",
527
+ )
528
+
529
+ if bot_selections == "Pumpernickel":
530
+ if submitted:
531
+ grouped_df = df.groupby('Exit Date').agg({'Signal':'min','Entry Date': 'min','Exit Date': 'max','Buy Price': 'mean',
532
+ 'Sell Price' : 'max',
533
+ 'Net P/L Per Trade': 'mean',
534
+ 'Calculated Return %' : lambda x: np.round(100*lev*x.sum(),2),
535
+ 'DCA': lambda x: int(np.floor(x.max()))})
536
+ grouped_df.index = range(1, len(grouped_df)+1)
537
+ grouped_df.rename(columns={'DCA' : '# of DCAs', 'Buy Price':'Avg. Buy Price',
538
+ 'Net P/L Per Trade':'Net P/L',
539
+ 'Calculated Return %':'P/L %'}, inplace=True)
540
+ else:
541
+
542
+ grouped_df = df.groupby('Exit Date').agg({'Signal':'min','Entry Date': 'min','Exit Date': 'max','Buy Price': 'mean',
543
+ 'Sell Price' : 'max',
544
+ 'P/L per token': 'mean',
545
+ 'Calculated Return %' : lambda x: np.round(100*x.sum(),2),
546
+ 'DCA': lambda x: int(np.floor(x.max()))})
547
+ grouped_df.index = range(1, len(grouped_df)+1)
548
+ grouped_df.rename(columns={'DCA' : '# of DCAs', 'Buy Price':'Avg. Buy Price',
549
+ 'Calculated Return %':'P/L %',
550
+ 'P/L per token':'Net P/L'}, inplace=True)
551
+
552
+ else:
553
+ if submitted:
554
+ grouped_df = df.groupby('Exit Date').agg({'Signal':'min','Entry Date': 'min','Exit Date': 'max','Buy Price': 'mean',
555
+ 'Sell Price' : 'max',
556
+ 'Net P/L Per Trade': 'mean',
557
+ 'Calculated Return %' : lambda x: np.round(100*lev*x.sum(),2)})
558
+ grouped_df.index = range(1, len(grouped_df)+1)
559
+ grouped_df.rename(columns={'Buy Price':'Avg. Buy Price',
560
+ 'Net P/L Per Trade':'Net P/L',
561
+ 'Calculated Return %':'P/L %'}, inplace=True)
562
+ else:
563
+ grouped_df = df.groupby('Exit Date').agg({'Signal':'min','Entry Date': 'min','Exit Date': 'max','Buy Price': 'mean',
564
+ 'Sell Price' : 'max',
565
+ 'P/L per token': 'mean',
566
+ 'P/L %':'mean'})
567
+ grouped_df.index = range(1, len(grouped_df)+1)
568
+ grouped_df.rename(columns={'Buy Price':'Avg. Buy Price',
569
+ 'P/L per token':'Net P/L'}, inplace=True)
570
+ st.subheader("Trade Logs")
571
+ grouped_df['Entry Date'] = pd.to_datetime(grouped_df['Entry Date'])
572
+ grouped_df['Exit Date'] = pd.to_datetime(grouped_df['Exit Date'])
573
+ if bot_selections == "Cosmic Cupcake" or bot_selections == "CT Toasted":
574
+ coding = cc_coding if bot_selections == "Cosmic Cupcake" else ctt_coding
575
+ 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}%'})\
576
+ .apply(coding, axis=1)\
577
+ .applymap(my_style,subset=['Net P/L'])\
578
+ .applymap(my_style,subset=['P/L %']), use_container_width=True)
579
+ # 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>'
580
+ # st.markdown(new_title, unsafe_allow_html=True)
581
+ else:
582
+ 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}%'})\
583
+ .applymap(my_style,subset=['Net P/L'])\
584
+ .applymap(my_style,subset=['P/L %']), use_container_width=True)
585
+
586
+ # st.subheader("Checking Status")
587
+ # if submitted:
588
+ # st.dataframe(sd_df)
589
+
590
+ if __name__ == "__main__":
591
+ st.set_page_config(
592
+ "Trading Bot Dashboard", layout = 'wide'
593
+ )
594
+ runapp()
595
+ # -
596
+
597
+
598
+
599
+
history.csv ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ,id,datetime,exchange,subaccount,pair,side,action,amount,price
2
+ 0,1,2024-02-12 19:05:48,,,,,,,"Webhook error:
3
+ Traceback (most recent call last):
4
+ File ""/home/doukaslewis/mysite/flask_app.py"", line 315, in webhook
5
+ dec = f_key.decrypt(enc).decode()
6
+ File ""/usr/local/lib/python3.10/site-packages/cryptography/fernet.py"", line 83, in decrypt
7
+ t"
8
+ 1,2,2024-02-12 19:10:57,,,,,,,"Webhook error:
9
+ Traceback (most recent call last):
10
+ File ""/home/doukaslewis/mysite/flask_app.py"", line 315, in webhook
11
+ f_key = bytes(f_key, ""utf-8"")
12
+ UnboundLocalError: local variable 'f_key' referenced before assignment
13
+
14
+ local variable 'f_key' referen"
15
+ 2,3,2024-02-12 19:12:50,,,,,,,"Webhook error:
16
+ Traceback (most recent call last):
17
+ File ""/home/doukaslewis/mysite/flask_app.py"", line 316, in webhook
18
+ f_key = bytes(f_key, ""utf-8"")
19
+ UnboundLocalError: local variable 'f_key' referenced before assignment
20
+
21
+ local variable 'f_key' referen"
22
+ 3,4,2024-02-12 19:17:01,,,,,,,"Webhook error:
23
+ Traceback (most recent call last):
24
+ File ""/home/doukaslewis/mysite/flask_app.py"", line 316, in webhook
25
+ dec = f_key.decrypt(enc).decode()
26
+ File ""/usr/local/lib/python3.10/site-packages/cryptography/fernet.py"", line 83, in decrypt
27
+ t"
28
+ 4,5,2024-02-12 19:18:27,,,,,,,"Webhook error:
29
+ Traceback (most recent call last):
30
+ File ""/home/doukaslewis/mysite/flask_app.py"", line 315, in webhook
31
+ dec = bytes(f_key, ""utf-8"").decrypt(enc).decode()
32
+ TypeError: encoding without a string argument
33
+
34
+ encoding without a string argument"
35
+ 5,6,2024-02-12 19:28:00,,,,,,,"Webhook error:
36
+ Traceback (most recent call last):
37
+ File ""/home/doukaslewis/mysite/flask_app.py"", line 315, in webhook
38
+ dec = f_key.decrypt(enc) #.decode()
39
+ File ""/usr/local/lib/python3.10/site-packages/cryptography/fernet.py"", line 83, in decrypt
40
+ "
41
+ 6,7,2024-02-12 19:32:09,Bybit Futures,test1,BTCUSDT,buy,open,0.001,49881.5
42
+ 7,8,2024-02-12 19:40:29,Bybit Futures,test1,,,,,No need to place order for BTCUSDT.
43
+ 8,9,2024-02-12 19:40:38,Bybit Futures,test1,BTCUSDT,sell,close,0.001,49736.9
44
+ 9,10,2024-02-12 19:40:39,Bybit Futures,test1,BTCUSDT,sell,open,0.001,49736.9
45
+ 10,11,2024-02-12 19:40:43,Bybit Futures,test1,,,,,No need to place order for BTCUSDT.
46
+ 11,12,2024-02-12 19:40:50,Bybit Futures,test1,,,,,No need to place order for BTCUSDT.
47
+ 12,13,2024-02-12 19:41:26,Bybit Futures,test1,BTCUSDT,buy,close,0.001,49733.3
48
+ 13,14,2024-02-12 19:41:31,Bybit Futures,test1,,,,,No need to place order for BTCUSDT.
49
+ 14,15,2024-02-12 19:42:16,Bybit Futures,test1,BTCUSDT,buy,open,0.003,49749.5
50
+ 15,16,2024-02-12 19:42:28,Bybit Futures,test1,BTCUSDT,buy,open,0.003,49737.1
51
+ 16,17,2024-02-12 19:42:36,Bybit Futures,test1,BTCUSDT,sell,close,0.006,49726.4
52
+ 17,18,2024-02-12 19:42:37,Bybit Futures,test1,BTCUSDT,sell,open,0.003,49738
53
+ 18,19,2024-02-12 19:42:43,Bybit Futures,test1,BTCUSDT,sell,open,0.003,49731
54
+ 19,20,2024-02-12 19:43:01,Bybit Futures,test1,BTCUSDT,buy,close,0.006,49752.4
55
+ 20,21,2024-02-12 19:51:14,Bybit Futures,test1,,,,,No need to place order for BTCUSDT.
56
+ 21,22,2024-02-12 19:51:38,Bybit Futures,test1,,,,,"Error: Get active positions:
57
+ Traceback (most recent call last):
58
+ File ""/home/doukaslewis/mysite/bybitUniFuturesClient.py"", line 236, in get_position
59
+ position = self.client.get_positions(category= ""linear"", symbol= pair)['result']['list'][0]
60
+ File ""/"
61
+ 22,23,2024-02-13 00:16:03,Bybit Futures,Pure Bread Test,ETHUSDT,sell,open,0.33,2679.29
62
+ 23,24,2024-02-13 03:11:33,Bybit Futures,Pure Bread Test,ETHUSDT,buy,close,0.33,2653.47
63
+ 24,25,2024-02-13 03:38:01,Bybit Futures,Pure Bread Test,ETHUSDT,buy,open,0.34,2642.86
64
+ 25,26,2024-02-13 09:01:02,Bybit Futures,Pure Bread Test,ETHUSDT,sell,close,0.34,2662.12
65
+ 26,27,2024-02-13 09:01:03,Bybit Futures,Pure Bread Test,ETHUSDT,sell,open,0.34,2662.12
66
+ 27,28,2024-02-13 10:26:43,Bybit Futures,Pumpernickel Test,,,,,Order size (0.0) is less than minimum size (0.1) for ATOMUSDT.
67
+ 28,29,2024-02-13 11:56:56,Bybit Futures,test1,,,,,No need to place order for BTCUSDT.
68
+ 29,30,2024-02-13 11:57:12,Bybit Futures,test1,BTCUSDT,buy,open,0.001,50013.9
69
+ 30,31,2024-02-13 11:57:16,Bybit Futures,test1,,,,,No need to place order for BTCUSDT.
70
+ 31,32,2024-02-13 11:57:39,Bybit Futures,test1,BTCUSDT,sell,close,0.001,50016.1
71
+ 32,33,2024-02-13 11:57:40,Bybit Futures,test1,BTCUSDT,sell,open,0.001,50016.1
72
+ 33,34,2024-02-13 11:58:13,Bybit Futures,test1,BTCUSDT,buy,close,0.001,49970.9
73
+ 34,35,2024-02-13 11:58:22,Bybit Futures,test1,,,,,No need to place order for BTCUSDT.
74
+ 35,36,2024-02-13 11:58:40,Bybit Futures,test1,BTCUSDT,buy,open,0.009,49985.9
75
+ 36,37,2024-02-13 11:58:50,Bybit Futures,test1,BTCUSDT,buy,open,0.009,49982.1
76
+ 37,38,2024-02-13 11:59:10,Bybit Futures,test1,BTCUSDT,sell,close,0.018,49974.5
77
+ 38,39,2024-02-13 11:59:58,Bybit Futures,test1,,,,,"Error: Get active positions:
78
+ Traceback (most recent call last):
79
+ File ""/home/doukaslewis/mysite/bybitUniFuturesClient.py"", line 236, in get_position
80
+ position = self.client.get_positions(category= ""linear"", symbol= pair)['result']['list'][0]
81
+ File ""/"
82
+ 39,40,2024-02-13 12:45:32,Bybit Futures,Pumpernickel Test,,,,,Order size (0.0) is less than minimum size (0.1) for ATOMUSDT.
83
+ 40,41,2024-02-13 13:37:01,Bybit Futures,Pure Bread Test,,,,,No need to place order for ETHUSDT.
84
+ 41,42,2024-02-13 15:15:05,Bybit Futures,Pumpernickel Test,ATOMUSDT,buy,open,19.9,9.983
85
+ 42,43,2024-02-13 15:28:34,Bybit Futures,Pumpernickel Test,ATOMUSDT,sell,close,19.9,10.095
86
+ 43,44,2024-02-13 15:28:35,Bybit Futures,Pumpernickel Test,ATOMUSDT,sell,open,19.8,10.097
87
+ 44,45,2024-02-13 16:41:32,Bybit Futures,Pumpernickel Test,ATOMUSDT,buy,close,19.8,9.974
88
+ 45,46,2024-02-13 16:41:33,Bybit Futures,Pumpernickel Test,ATOMUSDT,buy,open,20.2,9.97471782
89
+ 46,47,2024-02-13 16:58:37,Bybit Futures,Pumpernickel Test,ATOMUSDT,buy,open,20.2,9.93472772
90
+ 47,48,2024-02-13 19:08:03,Bybit Futures,Pumpernickel Test,ATOMUSDT,sell,close,40.4,10.104
91
+ 48,49,2024-02-13 19:08:04,Bybit Futures,Pumpernickel Test,ATOMUSDT,sell,open,20.3,10.104
92
+ 49,50,2024-02-13 19:29:04,Bybit Futures,Pumpernickel Test,ATOMUSDT,sell,open,20.1,10.186
93
+ 50,51,2024-02-13 19:58:20,Bybit Futures,test1,,,,,"Error: Get active positions:
94
+ Traceback (most recent call last):
95
+ File ""/home/doukaslewis/mysite/bybitUniFuturesClient.py"", line 236, in get_position
96
+ position = self.client.get_positions(category= ""linear"", symbol= pair)['result']['list'][0]
97
+ File ""/"
98
+ 51,52,2024-02-13 20:01:33,Bybit Futures,test1,BTCUSDT,buy,open,0.001,49052.6
99
+ 52,53,2024-02-13 14:02:46,Bybit Futures,test1,BTCUSDT,buy,open,0.001,49089.5
100
+ 53,54,2024-02-13 20:10:03,Bybit Futures,Pumpernickel Test,,,,,"Error: Get active positions:
101
+ Traceback (most recent call last):
102
+ File ""/home/doukaslewis/mysite/bybitUniFuturesClient.py"", line 157, in get_position
103
+ position = self.client.get_positions(category= ""linear"", symbol= pair)['result']['list'][0]
104
+ File ""/"
logo.png ADDED
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ pandas==1.5.2
2
+ datetime
3
+ numpy
4
+ matplotlib
5
+ pathlib
6
+ plotly
7
+ altair
8
+ streamlit
9
+ altair<5