Spaces:
Sleeping
Sleeping
# --- | |
# jupyter: | |
# jupytext: | |
# text_representation: | |
# extension: .py | |
# format_name: light | |
# format_version: '1.5' | |
# jupytext_version: 1.14.2 | |
# kernelspec: | |
# display_name: Python [conda env:bbytes] * | |
# language: python | |
# name: conda-env-bbytes-py | |
# --- | |
# + | |
import csv | |
import pandas as pd | |
from datetime import datetime, timedelta | |
import numpy as np | |
import datetime as dt | |
import matplotlib.pyplot as plt | |
from pathlib import Path | |
import time | |
import plotly.graph_objects as go | |
import plotly.io as pio | |
from PIL import Image | |
import streamlit as st | |
import plotly.express as px | |
import altair as alt | |
import dateutil.parser | |
from matplotlib.colors import LinearSegmentedColormap | |
# + | |
class color: | |
PURPLE = '\033[95m' | |
CYAN = '\033[96m' | |
DARKCYAN = '\033[36m' | |
BLUE = '\033[94m' | |
GREEN = '\033[92m' | |
YELLOW = '\033[93m' | |
RED = '\033[91m' | |
BOLD = '\033[1m' | |
UNDERLINE = '\033[4m' | |
END = '\033[0m' | |
def print_PL(amnt, thresh, extras = "" ): | |
if amnt > 0: | |
return color.BOLD + color.GREEN + str(amnt) + extras + color.END | |
elif amnt < 0: | |
return color.BOLD + color.RED + str(amnt)+ extras + color.END | |
elif np.isnan(amnt): | |
return str(np.nan) | |
else: | |
return str(amnt + extras) | |
def get_headers(logtype): | |
otimeheader = "" | |
cheader = "" | |
plheader = "" | |
fmat = '%Y-%m-%d %H:%M:%S' | |
if logtype == "ByBit": | |
otimeheader = 'Create Time' | |
cheader = 'Contracts' | |
plheader = 'Closed P&L' | |
fmat = '%Y-%m-%d %H:%M:%S' | |
if logtype == "BitGet": | |
otimeheader = 'Date' | |
cheader = 'Futures' | |
plheader = 'Realized P/L' | |
fmat = '%Y-%m-%d %H:%M:%S' | |
if logtype == "MEXC": | |
otimeheader = 'Trade time' | |
cheader = 'Futures' | |
plheader = 'closing position' | |
fmat = '%Y/%m/%d %H:%M' | |
if logtype == "Binance": | |
otimeheader = 'Date' | |
cheader = 'Symbol' | |
plheader = 'Realized Profit' | |
fmat = '%Y-%m-%d %H:%M:%S' | |
#if logtype == "Kucoin": | |
# otimeheader = 'Time' | |
# cheader = 'Contract' | |
# plheader = '' | |
# fmat = '%Y/%m/%d %H:%M:%S' | |
if logtype == "Kraken": | |
otimeheader = 'time' | |
cheader = 'asset' | |
plheader = 'amount' | |
fmat = '%Y-%m-%d %H:%M:%S.%f' | |
if logtype == "OkX": | |
otimeheader = '\ufeffOrder Time' | |
cheader = '\ufeffInstrument' | |
plheader = '\ufeffPL' | |
fmat = '%Y-%m-%d %H:%M:%S' | |
return otimeheader.lower(), cheader.lower(), plheader.lower(), fmat | |
def get_coin_info(df_coin, principal_balance,plheader): | |
numtrades = int(len(df_coin)) | |
numwin = int(sum(df_coin[plheader] > 0)) | |
numloss = int(sum(df_coin[plheader] < 0)) | |
winrate = np.round(100*numwin/numtrades,2) | |
grosswin = sum(df_coin[df_coin[plheader] > 0][plheader]) | |
grossloss = sum(df_coin[df_coin[plheader] < 0][plheader]) | |
if grossloss != 0: | |
pfactor = -1*np.round(grosswin/grossloss,2) | |
else: | |
pfactor = np.nan | |
cum_PL = np.round(sum(df_coin[plheader].values),2) | |
cum_PL_perc = np.round(100*cum_PL/principal_balance,2) | |
mean_PL = np.round(sum(df_coin[plheader].values/len(df_coin)),2) | |
mean_PL_perc = np.round(100*mean_PL/principal_balance,2) | |
return numtrades, numwin, numloss, winrate, pfactor, cum_PL, cum_PL_perc, mean_PL, mean_PL_perc | |
def get_hist_info(df_coin, principal_balance,plheader): | |
numtrades = int(len(df_coin)) | |
numwin = int(sum(df_coin[plheader] > 0)) | |
numloss = int(sum(df_coin[plheader] < 0)) | |
if numtrades != 0: | |
winrate = int(np.round(100*numwin/numtrades,2)) | |
else: | |
winrate = np.nan | |
grosswin = sum(df_coin[df_coin[plheader] > 0][plheader]) | |
grossloss = sum(df_coin[df_coin[plheader] < 0][plheader]) | |
if grossloss != 0: | |
pfactor = -1*np.round(grosswin/grossloss,2) | |
else: | |
pfactor = np.nan | |
return numtrades, numwin, numloss, winrate, pfactor | |
def get_rolling_stats(df, lev, otimeheader, days): | |
max_roll = (df[otimeheader].max() - df[otimeheader].min()).days | |
if max_roll >= days: | |
rollend = df[otimeheader].max()-timedelta(days=days) | |
rolling_df = df[df[otimeheader] >= rollend] | |
if len(rolling_df) > 0: | |
rolling_perc = rolling_df['Return Per Trade'].dropna().cumprod().values[-1]-1 | |
else: | |
rolling_perc = np.nan | |
else: | |
rolling_perc = np.nan | |
return 100*rolling_perc | |
def cc_coding(row): | |
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() and row['Bot'] == "CC") else [''] * len(row) | |
def ctt_coding(row): | |
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) | |
def conditional_formatter(value): | |
return "${:.2f}".format(value) if not (abs(value) < 1.00) else "${:.4f}".format(value) | |
def my_style(v, props=''): | |
props = 'color:red' if v < 0 else 'color:green' | |
return props | |
def filt_df(df, cheader, symbol_selections): | |
df = df.copy() | |
df = df[df[cheader].isin(symbol_selections)] | |
return df | |
def tv_reformat(close50filename): | |
try: | |
data = pd.read_csv(open(close50filename,'r'), sep='[,|\t]', engine='python') | |
except: | |
data = pd.DataFrame([]) | |
if data.empty: | |
return data | |
else: | |
entry_df = data[data['Type'].str.contains("Entry")] | |
exit_df = data[data['Type'].str.contains("Exit")] | |
entry_df.index = range(len(entry_df)) | |
exit_df.index = range(len(exit_df)) | |
df = pd.DataFrame([], columns=['Trade','Entry Date','Buy Price', 'Sell Price','Exit Date', 'P/L per token', 'P/L %', 'Drawdown %']) | |
df['Signal'] = [string.split(' ')[1] for string in entry_df['Type']] | |
df['Trade'] = entry_df.index | |
df['Entry Date'] = entry_df['Date/Time'] | |
df['Buy Price'] = entry_df['Price USDT'] | |
df['Sell Price'] = exit_df['Price USDT'] | |
df['Exit Date'] = exit_df['Date/Time'] | |
df['P/L per token'] = df['Sell Price'] - df['Buy Price'] | |
df['P/L %'] = exit_df['Profit %'] | |
df['Drawdown %'] = exit_df['Drawdown %'] | |
df['Close 50'] = [int(i == "Close 50% of Position") for i in exit_df['Signal']] | |
df = df.sort_values(['Entry Date','Close 50'], ascending = [False, True]) | |
df.index = range(len(df)) | |
df.loc[df['Close 50'] == 1, 'Exit Date'] = np.copy(df.loc[df[df['Close 50'] == 1].index.values-1]['Exit Date']) | |
grouped_df = df.groupby('Entry Date').agg({'Signal' : 'first', 'Entry Date': 'min', 'Buy Price':'mean', | |
'Sell Price' : 'mean', | |
'Exit Date': 'max', | |
'P/L per token': 'mean', | |
'P/L %' : 'mean'}) | |
grouped_df.insert(0,'Trade', range(len(grouped_df))) | |
grouped_df.index = range(len(grouped_df)) | |
return grouped_df | |
def load_data(filename, otimeheader, fmat): | |
df = pd.read_csv(open(filename,'r'), sep='\t') # so as not to mutate cached value | |
close50filename = filename.split('.')[0] + '-50.' + filename.split('.')[1] | |
df2 = tv_reformat(close50filename) | |
if filename == "CT-Trade-Log.csv": | |
df.columns = ['Trade','Entry Date','Buy Price', 'Sell Price','Exit Date', 'P/L per token', 'P/L %', 'Drawdown %'] | |
df.insert(1, 'Signal', ['Long']*len(df)) | |
elif filename == "CC-Trade-Log.csv" or filename == "PB-Trade-Log.csv": | |
df.columns = ['Trade','Signal','Entry Date','Buy Price', 'Sell Price','Exit Date', 'P/L per token', 'P/L %', 'Drawdown %'] | |
else: | |
df.columns = ['Trade','Signal','Entry Date','Buy Price', 'Sell Price','Exit Date', 'P/L per token', 'P/L %'] | |
if filename != "CT-Toasted-Trade-Log.csv": | |
df['Signal'] = df['Signal'].str.replace(' ', '', regex=True) | |
df['Buy Price'] = df['Buy Price'].str.replace('$', '', regex=True) | |
df['Sell Price'] = df['Sell Price'].str.replace('$', '', regex=True) | |
df['Buy Price'] = df['Buy Price'].str.replace(',', '', regex=True) | |
df['Sell Price'] = df['Sell Price'].str.replace(',', '', regex=True) | |
df['P/L per token'] = df['P/L per token'].str.replace('$', '', regex=True) | |
df['P/L per token'] = df['P/L per token'].str.replace(',', '', regex=True) | |
df['P/L %'] = df['P/L %'].str.replace('%', '', regex=True) | |
df['Buy Price'] = pd.to_numeric(df['Buy Price']) | |
df['Sell Price'] = pd.to_numeric(df['Sell Price']) | |
df['P/L per token'] = pd.to_numeric(df['P/L per token']) | |
df['P/L %'] = pd.to_numeric(df['P/L %']) | |
if df2.empty: | |
df = df | |
else: | |
df = pd.concat([df,df2], axis=0, ignore_index=True) | |
if filename == "CT-Trade-Log.csv": | |
df['Signal'] = ['Long']*len(df) | |
dateheader = 'Date' | |
theader = 'Time' | |
df[dateheader] = [tradetimes.split(" ")[0] for tradetimes in df[otimeheader].values] | |
df[theader] = [tradetimes.split(" ")[1] for tradetimes in df[otimeheader].values] | |
df[otimeheader]= [dateutil.parser.parse(date+' '+time) | |
for date,time in zip(df[dateheader],df[theader])] | |
df[otimeheader] = pd.to_datetime(df[otimeheader]) | |
df['Exit Date'] = pd.to_datetime(df['Exit Date']) | |
df.sort_values(by=otimeheader, inplace=True) | |
df[dateheader] = [dateutil.parser.parse(date).date() for date in df[dateheader]] | |
df[theader] = [dateutil.parser.parse(time).time() for time in df[theader]] | |
df['Trade'] = df.index + 1 #reindex | |
if filename == "CT-Trade-Log.csv": | |
df['DCA'] = np.nan | |
for exit in pd.unique(df['Exit Date']): | |
df_exit = df[df['Exit Date']==exit] | |
if dateutil.parser.parse(str(exit)) < dateutil.parser.parse('2023-02-07 13:00:00'): | |
for i in range(len(df_exit)): | |
ind = df_exit.index[i] | |
df.loc[ind,'DCA'] = i+1 | |
else: | |
for i in range(len(df_exit)): | |
ind = df_exit.index[i] | |
df.loc[ind,'DCA'] = i+1.1 | |
return df | |
def get_pl(bot_selections, df, dca1, dca2, dca3, dca4, dca5, dca6, dollar_cap, lev, principal_balance): | |
signal_map = {'Long': 1, 'Short':-1} | |
fees = .075/100 | |
if df.empty: | |
cum_pl = principal_balance | |
effective_return = 0.0 | |
else: | |
if bot_selections == 'ct': | |
dca_map = {1: dca1/100, 2: dca2/100, 3: dca3/100, 4: dca4/100, 1.1: dca5/100, 2.1: dca6/100} | |
df['DCA %'] = df['DCA'].map(dca_map) | |
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 | |
df['DCA'] = np.floor(df['DCA'].values) | |
df['Return Per Trade'] = np.nan | |
df['Balance used in Trade'] = np.nan | |
df['New Balance'] = np.nan | |
g = df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return %'].reset_index(name='Return Per Trade') | |
df.loc[df['DCA']==1.0,'Return Per Trade'] = 1+lev*g['Return Per Trade'].values | |
df['Compounded Return'] = df['Return Per Trade'].cumprod() | |
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']] | |
df.loc[df['DCA']==1.0,'Balance used in Trade'] = np.concatenate([[principal_balance], df.loc[df['DCA']==1.0,'New Balance'].values[:-1]]) | |
else: | |
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 | |
df['Return Per Trade'] = np.nan | |
g = df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return %'].reset_index(name='Return Per Trade') | |
df['Return Per Trade'] = 1+lev*g['Return Per Trade'].values | |
df['Compounded Return'] = df['Return Per Trade'].cumprod() | |
df['New Balance'] = [min(dollar_cap/lev, bal*principal_balance) for bal in df['Compounded Return']] | |
df['Balance used in Trade'] = np.concatenate([[principal_balance], df['New Balance'].values[:-1]]) | |
df['Net P/L Per Trade'] = (df['Return Per Trade']-1)*df['Balance used in Trade'] | |
df['Cumulative P/L'] = df['Net P/L Per Trade'].cumsum() | |
if bot_selections == 'ct' or bot_selections == 'cc' or bot_selections == 'pb': | |
cum_pl = df.loc[df.drop('Drawdown %', axis=1).dropna().index[-1],'Cumulative P/L'] + principal_balance | |
else: | |
cum_pl = df.loc[df.dropna().index[-1],'Cumulative P/L'] + principal_balance | |
effective_return = 100*((cum_pl - principal_balance)/principal_balance) | |
return df, cum_pl, effective_return | |
def get_sd_df(sd_df, sd, bot_selections, dca1, dca2, dca3, dca4, dca5, dca6, fees, lev, dollar_cap, principal_balance): | |
sd = 2*.00026 | |
# ------ Standard Dev. Calculations. | |
if bot_selections == "Cinnamon Toast": | |
dca_map = {1: dca1/100, 2: dca2/100, 3: dca3/100, 4: dca4/100, 1.1: dca5/100, 2.1: dca6/100} | |
sd_df['DCA %'] = sd_df['DCA'].map(dca_map) | |
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 | |
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 | |
sd_df['DCA'] = np.floor(sd_df['DCA'].values) | |
sd_df['Return Per Trade (+)'] = np.nan | |
sd_df['Return Per Trade (-)'] = np.nan | |
sd_df['Balance used in Trade (+)'] = np.nan | |
sd_df['Balance used in Trade (-)'] = np.nan | |
sd_df['New Balance (+)'] = np.nan | |
sd_df['New Balance (-)'] = np.nan | |
g1 = sd_df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return % (+)'].reset_index(name='Return Per Trade (+)') | |
g2 = sd_df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return % (-)'].reset_index(name='Return Per Trade (-)') | |
sd_df.loc[sd_df['DCA']==1.0,'Return Per Trade (+)'] = 1+lev*g1['Return Per Trade (+)'].values | |
sd_df.loc[sd_df['DCA']==1.0,'Return Per Trade (-)'] = 1+lev*g2['Return Per Trade (-)'].values | |
sd_df['Compounded Return (+)'] = sd_df['Return Per Trade (+)'].cumprod() | |
sd_df['Compounded Return (-)'] = sd_df['Return Per Trade (-)'].cumprod() | |
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 (+)']] | |
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]]) | |
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 (-)']] | |
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]]) | |
else: | |
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 | |
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 | |
sd_df['Return Per Trade (+)'] = np.nan | |
sd_df['Return Per Trade (-)'] = np.nan | |
g1 = sd_df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return % (+)'].reset_index(name='Return Per Trade (+)') | |
g2 = sd_df.groupby('Exit Date').sum(numeric_only=True)['Calculated Return % (-)'].reset_index(name='Return Per Trade (-)') | |
sd_df['Return Per Trade (+)'] = 1+lev*g1['Return Per Trade (+)'].values | |
sd_df['Return Per Trade (-)'] = 1+lev*g2['Return Per Trade (-)'].values | |
sd_df['Compounded Return (+)'] = sd_df['Return Per Trade (+)'].cumprod() | |
sd_df['Compounded Return (-)'] = sd_df['Return Per Trade (-)'].cumprod() | |
sd_df['New Balance (+)'] = [min(dollar_cap/lev, bal*principal_balance) for bal in sd_df['Compounded Return (+)']] | |
sd_df['Balance used in Trade (+)'] = np.concatenate([[principal_balance], sd_df['New Balance (+)'].values[:-1]]) | |
sd_df['New Balance (-)'] = [min(dollar_cap/lev, bal*principal_balance) for bal in sd_df['Compounded Return (-)']] | |
sd_df['Balance used in Trade (-)'] = np.concatenate([[principal_balance], sd_df['New Balance (-)'].values[:-1]]) | |
sd_df['Net P/L Per Trade (+)'] = (sd_df['Return Per Trade (+)']-1)*sd_df['Balance used in Trade (+)'] | |
sd_df['Cumulative P/L (+)'] = sd_df['Net P/L Per Trade (+)'].cumsum() | |
sd_df['Net P/L Per Trade (-)'] = (sd_df['Return Per Trade (-)']-1)*sd_df['Balance used in Trade (-)'] | |
sd_df['Cumulative P/L (-)'] = sd_df['Net P/L Per Trade (-)'].cumsum() | |
return sd_df | |
def runapp() -> None: | |
no_errors = True | |
otimeheader = 'Exit Date' | |
fmat = '%Y-%m-%d %H:%M:%S' | |
lev_cap = 5 | |
dollar_cap = 1000000000.00 | |
ct_data = load_data("CT-Trade-Log.csv",otimeheader, fmat) | |
sb_data = load_data("SB-Trade-Log.csv",otimeheader, fmat) | |
cc_lev_cap = 3 | |
cc_data = load_data("CC-Trade-Log.csv",otimeheader, fmat) | |
pb_data = load_data("PB-Trade-Log.csv",otimeheader, fmat) | |
df = pd.concat([ct_data, sb_data, cc_data, pb_data]) | |
ct_df = ct_data.copy(deep=True) | |
sb_df = sb_data.copy(deep=True) | |
cc_df = cc_data.copy(deep=True) | |
pb_df = pb_data.copy(deep=True) | |
dateheader = 'Date' | |
theader = 'Time' | |
with st.form("user input", ): | |
st.header("Choose your settings:") | |
if no_errors: | |
with st.container(): | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
try: | |
startdate = st.date_input("Start Date", value=pd.to_datetime(df[otimeheader]).min()) | |
except: | |
st.error("Please select a valid start date.") | |
no_errors = False | |
with col2: | |
try: | |
enddate = st.date_input("End Date", value=datetime.today()) | |
except: | |
st.error("Please select a valid end date.") | |
no_errors = False | |
with col3: | |
principal_balance = st.number_input('Starting Balance', min_value=0.00, value=1000.00, max_value= dollar_cap, step=.01) | |
#st.sidebar.subheader("Customize your Dashboard") | |
if no_errors and (enddate < startdate): | |
st.error("End Date must be later than Start date. Please try again.") | |
no_errors = False | |
if no_errors: | |
dca1 = 25; dca2 = 25; dca3 = 25; dca4= 25; dca5=50; dca6=50; | |
# st.write("Choose your DCA setup (for Cinnamon Toast trades before 02/07/2023)") | |
# with st.container(): | |
# col1, col2, col3, col4 = st.columns(4) | |
# with col1: | |
# dca1 = st.number_input('DCA 1 Allocation', min_value=0, value=25, max_value= 100, step=1) | |
# with col2: | |
# dca2 = st.number_input('DCA 2 Allocation', min_value=0, value=25, max_value= 100, step=1) | |
# with col3: | |
# dca3 = st.number_input('DCA 3 Allocation', min_value=0, value=25, max_value= 100, step=1) | |
# with col4: | |
# dca4 = st.number_input('DCA 4 Allocation', min_value=0, value=25, max_value= 100, step=1) | |
# st.write("Choose your DCA setup (for Cinnamon Toast trades on or after 02/07/2023)") | |
# with st.container(): | |
# col1, col2 = st.columns(2) | |
# with col1: | |
# dca5 = st.number_input('DCA 1 Allocation', min_value=0, value=50, max_value= 100, step=1) | |
# with col2: | |
# dca6 = st.number_input('DCA 2 Allocation', min_value=0, value=50, max_value= 100, step=1) | |
with st.container(): | |
col1,col2,col3,col4 = st.columns(4) | |
with col1: | |
st.write("**Cinnamon Toast (CT)**") | |
ct_lev = st.number_input('CT Leverage', min_value=1, value=1, max_value= lev_cap, step=1) | |
ct_alloc = st.number_input("CT Allocation (%)", min_value=0, value=25, max_value=100, step=1) | |
with col2: | |
st.write("**Short Bread (SB)**") | |
sb_lev = st.number_input('SB Leverage', min_value=1, value=1, max_value= lev_cap, step=1) | |
sb_alloc = st.number_input("SB Allocation (%)", min_value=0, value=25, max_value=100, step=1) | |
with col3: | |
st.write("**Cosmic Cupcake (CC)**") | |
cc_lev = st.number_input('CC Leverage', min_value=1, value=1, max_value= cc_lev_cap, step=1) | |
cc_alloc = st.number_input("CC Allocation (%)", min_value=0, value=25, max_value=100, step=1) | |
with col4: | |
st.write("**Pure Bread (PB)**") | |
pb_lev = st.number_input('PB Leverage', min_value=1, value=1, max_value= cc_lev_cap, step=1) | |
pb_alloc = st.number_input("PB Allocation (%)", min_value=0, value=25, max_value=100, step=1) | |
#hack way to get button centered | |
c = st.columns(5) | |
with c[2]: | |
submitted = st.form_submit_button("Get Cookin'!") | |
if submitted and principal_balance *ct_alloc/100 * ct_lev > dollar_cap: | |
ct_lev = np.floor(dollar_cap/(principal_balance*ct_alloc/100)) | |
st.error(f"WARNING:Allocated balance for Cinnamon Toast exceeds the ${dollar_cap} limit. Using maximum available leverage of {ct_lev}") | |
if submitted and principal_balance *sb_alloc/100 * sb_lev > dollar_cap: | |
sb_lev = np.floor(dollar_cap/(principal_balance*sb_alloc/100)) | |
st.error(f"WARNING:Allocated balance for Short Bread exceeds the ${dollar_cap} limit. Using maximum available leverage of {sb_lev}") | |
if submitted and principal_balance *cc_alloc/100 * cc_lev > dollar_cap: | |
cc_lev = np.floor(cc_dollar_cap/(principal_balance*cc_alloc/100)) | |
st.error(f"WARNING:Allocated balance for Cosmic Cupcake exceeds the ${dollar_cap} limit. Using maximum available leverage of {cc_lev}") | |
if submitted and principal_balance *pb_alloc/100 * pb_lev > dollar_cap: | |
pb_lev = np.floor(pb_dollar_cap/(principal_balance*pb_alloc/100)) | |
st.error(f"WARNING:Allocated balance for Pure Bread exceeds the ${dollar_cap} limit. Using maximum available leverage of {pb_lev}") | |
if submitted and (ct_alloc + sb_alloc + cc_alloc + pb_alloc) > 100: | |
st.error("Invalid allocation amounts. The total allocations must not exceed 100% of available funds. Please check your allocations and try again.") | |
no_errors = False | |
if submitted and (ct_alloc + sb_alloc + cc_alloc + pb_alloc) < 100: | |
st.error(f'WARNING: The allocation amounts you have selected do not sum to 100%. Only {ct_alloc + sb_alloc + cc_alloc + pb_alloc}% of the starting balance will be used for trading.') | |
if submitted: | |
while no_errors == True: | |
df = df[(df[dateheader] >= startdate) & (df[dateheader] <= enddate)] | |
ct_df = ct_df[(ct_df[dateheader] >= startdate) & (ct_df[dateheader] <= enddate)] | |
sb_df = sb_df[(sb_df[dateheader] >= startdate) & (sb_df[dateheader] <= enddate)] | |
cc_df = cc_df[(cc_df[dateheader] >= startdate) & (cc_df[dateheader] <= enddate)] | |
pb_df = pb_df[(pb_df[dateheader] >= startdate) & (pb_df[dateheader] <= enddate)] | |
if len(df) == 0: | |
st.error("There are no available trades matching your selections. Please try again!") | |
no_errors = False | |
ct_df, ct_cum_pl, ct_effective_return = get_pl('ct', ct_df, dca1, dca2, dca3, dca4, dca5, dca6, dollar_cap, ct_lev, principal_balance*ct_alloc/100) | |
sb_df, sb_cum_pl, sb_effective_return = get_pl('sb', sb_df, dca1, dca2, dca3, dca4, dca5, dca6, dollar_cap, sb_lev, principal_balance*sb_alloc/100) | |
cc_df, cc_cum_pl, cc_effective_return = get_pl('cc', cc_df, dca1, dca2, dca3, dca4, dca5, dca6, dollar_cap, cc_lev, principal_balance*cc_alloc/100) | |
pb_df, pb_cum_pl, pb_effective_return = get_pl('pb', pb_df, dca1, dca2, dca3, dca4, dca5, dca6, dollar_cap, pb_lev, principal_balance*pb_alloc/100) | |
cum_pl = ct_cum_pl + sb_cum_pl + cc_cum_pl + pb_cum_pl | |
effective_return = ct_alloc/100*ct_effective_return + sb_alloc/100*sb_effective_return + cc_alloc/100*cc_effective_return + pb_alloc/100*pb_effective_return | |
st.header(f"Bread Basket Results") | |
with st.container(): | |
st.metric( | |
"Total Account Balance", | |
f"${cum_pl:.2f}", | |
f"{100*(cum_pl-principal_balance)/(principal_balance):.2f} %", | |
) | |
col1, col2, col3, col4 = st.columns(4) | |
with col1: | |
st.metric( | |
"Cinnamon Toast Balance", | |
f"${ct_cum_pl:.2f}", | |
f"{ct_alloc*(ct_cum_pl-principal_balance*ct_alloc/100)/(principal_balance*ct_alloc/100):.2f} %", | |
) | |
with col2: | |
st.metric( | |
"Short Bread Balance", | |
f"${sb_cum_pl:.2f}", | |
f"{sb_alloc*(sb_cum_pl-principal_balance*sb_alloc/100)/(principal_balance*sb_alloc/100):.2f} %", | |
) | |
with col3: | |
st.metric( | |
"Cosmic Cupcake Balance", | |
f"${cc_cum_pl:.2f}", | |
f"{cc_alloc*(cc_cum_pl-principal_balance*cc_alloc/100)/(principal_balance*cc_alloc/100):.2f} %", | |
) | |
with col4: | |
st.metric( | |
"Pure Bread Balance", | |
f"${pb_cum_pl:.2f}", | |
f"{pb_alloc*(pb_cum_pl-principal_balance*pb_alloc/100)/(principal_balance*pb_alloc/100):.2f} %", | |
) | |
ct_df.insert(1, 'Bot', ['CT']*len(ct_df)) | |
if ct_df.empty: | |
grouped_ct = pd.DataFrame([]) | |
else: | |
grouped_ct = ct_df.groupby('Exit Date').agg({'Bot': 'first', 'Signal':'min','Entry Date': 'min','Exit Date': 'max','Buy Price': 'mean', | |
'Sell Price' : 'max', | |
'Net P/L Per Trade': 'mean', | |
'Return Per Trade': 'mean', | |
'Calculated Return %' : lambda x: np.round(ct_alloc*ct_lev*x.sum(),2), | |
'DCA': lambda x: int(np.floor(x.max()))}) | |
sb_df.insert(1, 'Bot', ['SB']*len(sb_df)) | |
if sb_df.empty: | |
grouped_sb = pd.DataFrame([]) | |
else: | |
grouped_sb = sb_df.groupby('Exit Date').agg({'Bot': 'first', 'Signal':'min','Entry Date': 'min','Exit Date': 'max','Buy Price': 'mean', | |
'Sell Price' : 'max', | |
'Net P/L Per Trade': 'mean', | |
'Return Per Trade': 'mean', | |
'Calculated Return %' : lambda x: np.round(sb_alloc*sb_lev*x.sum(),2)}) | |
cc_df.insert(1, 'Bot', ['CC']*len(cc_df)) | |
if cc_df.empty: | |
grouped_cc = pd.DataFrame([]) | |
else: | |
grouped_cc = cc_df.groupby('Exit Date').agg({'Bot': 'first', 'Signal':'min','Entry Date': 'min','Exit Date': 'max','Buy Price': 'mean', | |
'Sell Price' : 'max', | |
'Net P/L Per Trade': 'mean', | |
'Return Per Trade': 'mean', | |
'Calculated Return %' : lambda x: np.round(cc_alloc*cc_lev*x.sum(),2)}) | |
pb_df.insert(1, 'Bot', ['PB']*len(pb_df)) | |
if pb_df.empty: | |
grouped_pb = pd.DataFrame([]) | |
else: | |
grouped_pb = pb_df.groupby('Exit Date').agg({'Bot': 'first', 'Signal':'min','Entry Date': 'min','Exit Date': 'max','Buy Price': 'mean', | |
'Sell Price' : 'max', | |
'Net P/L Per Trade': 'mean', | |
'Return Per Trade': 'mean', | |
'Calculated Return %' : lambda x: np.round(pb_alloc*pb_lev*x.sum(),2)}) | |
all_dfs = [grouped_ct, grouped_sb, grouped_cc, grouped_pb] | |
df = pd.concat([d for d in all_dfs if not d.empty]) | |
df['Entry Date'] = pd.to_datetime(df['Entry Date']) | |
df['Exit Date'] = pd.to_datetime(df['Exit Date']) | |
df.index = range(len(df)) | |
df.sort_values('Exit Date', ascending = True, inplace=True) | |
# Create figure | |
fig = go.Figure() | |
pyLogo = Image.open("logo.png") | |
# Add trace | |
fig.add_trace( | |
go.Scatter(x=df['Exit Date'], y=np.round(df['Net P/L Per Trade'].cumsum().values,2), line_shape='spline', | |
line = {'smoothing': 1.0, 'color' : 'rgba(31, 119, 200,.8)'}, | |
name='Cumulative P/L') | |
) | |
dfdata = df[(df['Bot'] != 'CC') & (df['Bot'] != 'PB')] | |
eth_buyhold = ((ct_alloc + sb_alloc)/100*principal_balance/dfdata['Buy Price'][dfdata.index[0]])*(dfdata['Buy Price']-dfdata['Buy Price'][dfdata.index[0]]) | |
fig.add_trace(go.Scatter(x=dfdata['Exit Date'], y=np.round(eth_buyhold.values,2), line_shape='spline', | |
line = {'smoothing': 1.0, 'color' :'red'}, name = 'ETH Buy & Hold Return') | |
) | |
dfdata = df[df['Bot'] == 'CC'] | |
atom_buyhold = ((cc_alloc)/100*principal_balance/dfdata['Buy Price'][dfdata.index[0]])*(dfdata['Buy Price']-dfdata['Buy Price'][dfdata.index[0]]) | |
fig.add_trace(go.Scatter(x=dfdata['Exit Date'], y=np.round(atom_buyhold.values,2), line_shape='spline', | |
line = {'smoothing': 1.0, 'color' :'orange'}, name = 'ATOM Buy & Hold Return') | |
) | |
dfdata = df[df['Bot'] == 'PB'] | |
doge_buyhold = ((pb_alloc)/100*principal_balance/dfdata['Buy Price'][dfdata.index[0]])*(dfdata['Buy Price']-dfdata['Buy Price'][dfdata.index[0]]) | |
fig.add_trace(go.Scatter(x=dfdata['Exit Date'], y=np.round(doge_buyhold.values,2), line_shape='spline', | |
line = {'smoothing': 1.0, 'color' :'green'}, name = 'DOGE Buy & Hold Return') | |
) | |
fig.add_layout_image( | |
dict( | |
source=pyLogo, | |
xref="paper", | |
yref="paper", | |
x = 0.05, #dfdata['Exit Date'].astype('int64').min() // 10**9, | |
y = .85, #dfdata['Cumulative P/L'].max(), | |
sizex= .9, #(dfdata['Exit Date'].astype('int64').max() - dfdata['Exit Date'].astype('int64').min()) // 10**9, | |
sizey= .9, #(dfdata['Cumulative P/L'].max() - dfdata['Cumulative P/L'].min()), | |
sizing="contain", | |
opacity=0.2, | |
layer = "below") | |
) | |
#style layout | |
fig.update_layout( | |
height = 600, | |
xaxis=dict( | |
title="Exit Date", | |
tickmode='array', | |
), | |
yaxis=dict( | |
title="Cumulative P/L" | |
) ) | |
st.plotly_chart(fig, theme=None, use_container_width=True,height=600) | |
df['Per Trade Return Rate'] = df['Return Per Trade']-1 | |
totals = pd.DataFrame([], columns = ['# of Trades', 'Wins', 'Losses', 'Win Rate', 'Profit Factor']) | |
data = get_hist_info(df, principal_balance,'Per Trade Return Rate') | |
totals.loc[len(totals)] = list(i for i in data) | |
totals['Cum. P/L'] = cum_pl-principal_balance | |
totals['Cum. P/L (%)'] = 100*(cum_pl-principal_balance)/principal_balance | |
if df.empty: | |
st.error("Oops! None of the data provided matches your selection(s). Please try again.") | |
else: | |
with st.container(): | |
for row in totals.itertuples(): | |
col1, col2, col3, col4= st.columns(4) | |
c1, c2, c3, c4 = st.columns(4) | |
with col1: | |
st.metric( | |
"Total Trades", | |
f"{row._1:.0f}", | |
) | |
with c1: | |
st.metric( | |
"Profit Factor", | |
f"{row._5:.2f}", | |
) | |
with col2: | |
st.metric( | |
"Wins", | |
f"{row.Wins:.0f}", | |
) | |
with c2: | |
st.metric( | |
"Cumulative P/L", | |
f"${row._6:.2f}", | |
f"{row._7:.2f} %", | |
) | |
with col3: | |
st.metric( | |
"Losses", | |
f"{row.Losses:.0f}", | |
) | |
with c3: | |
st.metric( | |
"Rolling 7 Days", | |
"",#f"{(1+get_rolling_stats(df,otimeheader, 30))*principal_balance:.2f}", | |
f"{get_rolling_stats(df,1, otimeheader, 7):.2f}%", | |
) | |
st.metric( | |
"Rolling 30 Days", | |
"",#f"{(1+get_rolling_stats(df,otimeheader, 30))*principal_balance:.2f}", | |
f"{get_rolling_stats(df,1, otimeheader, 30):.2f}%", | |
) | |
with col4: | |
st.metric( | |
"Win Rate", | |
f"{row._4:.1f}%", | |
) | |
with c4: | |
st.metric( | |
"Rolling 90 Days", | |
"",#f"{(1+get_rolling_stats(df,otimeheader, 30))*principal_balance:.2f}", | |
f"{get_rolling_stats(df,1, otimeheader, 90):.2f}%", | |
) | |
st.metric( | |
"Rolling 180 Days", | |
"",#f"{(1+get_rolling_stats(df,otimeheader, 30))*principal_balance:.2f}", | |
f"{get_rolling_stats(df,1, otimeheader, 180):.2f}%", | |
) | |
df.rename(columns={'DCA' : '# of DCAs', 'Buy Price':'Avg. Buy Price', 'Sell Price': 'Avg. Sell Price', | |
'Net P/L Per Trade':'Net P/L', | |
'Calculated Return %':'P/L %'}, inplace=True) | |
if '# of DCAs' in df.columns: | |
df['# of DCAs'] = df['# of DCAs'].fillna(1.0) | |
df['# of DCAs'] = [int(i) for i in df['# of DCAs'].values] | |
else: | |
df['# of DCAs'] = np.ones(len(df)) | |
df.sort_values('Entry Date', ascending = True, inplace=True) | |
df.insert(0,'Trade',np.arange(1, len(df)+1)) | |
df.index = range(len(df)) | |
df = df.drop('Per Trade Return Rate', axis=1) | |
df = df.drop('Return Per Trade', axis=1) | |
st.subheader("Trade Logs") | |
st.dataframe(df.style.format({'Entry Date':'{:%m-%d-%Y %H:%M:%S}','Exit Date':'{:%m-%d-%Y %H:%M:%S}','Avg. Buy Price': conditional_formatter, 'Avg. Sell Price': conditional_formatter, 'Net P/L':'${:.2f}', 'P/L %':'{:.2f}%'})\ | |
.apply(cc_coding, axis=1)\ | |
.applymap(my_style,subset=['Net P/L'])\ | |
.applymap(my_style,subset=['P/L %']), use_container_width=True) | |
# new_title = '<div style="text-align: right;"><span style="background-color:lightgrey;"> </span> Not Live Traded</div>' | |
# st.markdown(new_title, unsafe_allow_html=True) | |
no_errors = False | |
if __name__ == "__main__": | |
st.set_page_config( | |
"Bread Basket Dashboard", | |
layout="wide", | |
) | |
runapp() | |
# - | |