├── requirements.txt └── app_server.py /requirements.txt: -------------------------------------------------------------------------------- 1 | asn1crypto==1.2.0 2 | atomicwrites==1.3.0 3 | attrs==19.3.0 4 | backcall==0.1.0 5 | bleach==3.1.0 6 | boto3==1.10.5 7 | botocore==1.13.5 8 | certifi==2022.12.7 9 | cffi==1.13.1 10 | Click==7.0 11 | colorama==0.4.1 12 | cryptography==2.7 13 | dash==1.4.1 14 | dash-core-components==1.3.1 15 | dash-html-components==1.0.1 16 | dash-renderer==1.1.2 17 | dash-table==4.4.1 18 | decorator==4.4.0 19 | defusedxml==0.6.0 20 | docutils==0.15.2 21 | entrypoints==0.3 22 | Flask==1.1.1 23 | Flask-Compress==1.4.0 24 | future==0.18.1 25 | geographiclib==1.50 26 | geopy==1.20.0 27 | idna==2.8 28 | importlib-metadata==0.23 29 | ipykernel==5.1.2 30 | ipython==7.8.0 31 | ipython-genutils==0.2.0 32 | itsdangerous==1.1.0 33 | jedi==0.15.1 34 | Jinja2==2.10.3 35 | jmespath==0.9.4 36 | joblib==0.13.2 37 | json5==0.8.5 38 | jsonschema==3.1.1 39 | jupyter-client==5.3.4 40 | jupyter-contrib-core==0.3.3 41 | jupyter-contrib-nbextensions==0.5.1 42 | jupyter-core==4.6.0 43 | jupyter-highlight-selected-word==0.2.0 44 | jupyter-latex-envs==1.4.6 45 | jupyter-nbextensions-configurator==0.4.1 46 | jupyterlab==1.1.4 47 | jupyterlab-server==1.0.6 48 | lxml==4.4.1 49 | MarkupSafe==1.1.1 50 | mistune==0.8.4 51 | more-itertools==7.2.0 52 | nbconvert==5.6.0 53 | nbformat==4.4.0 54 | notebook==6.0.1 55 | numpy==1.16.5 56 | packaging==19.2 57 | pandas==0.25.2 58 | pandocfilters==1.4.2 59 | parso==0.5.1 60 | pickleshare==0.7.5 61 | plotly==4.2.1 62 | pluggy==0.13.0 63 | progressbar2==3.47.0 64 | prometheus-client==0.7.1 65 | prompt-toolkit==2.0.10 66 | py==1.8.0 67 | pycparser==2.19 68 | Pygments==2.4.2 69 | pyOpenSSL==19.0.0 70 | pyparsing==2.4.2 71 | pyrsistent==0.15.4 72 | PySocks==1.7.1 73 | pytest==5.2.2 74 | pytest-runner==5.2 75 | python-dateutil==2.8.0 76 | python-utils==2.3.0 77 | pytz==2019.3 78 | PyYAML==5.1.2 79 | pyzmq==18.1.0 80 | retrying==1.3.3 81 | s3transfer==0.2.1 82 | scikit-learn==0.21.3 83 | scipy==1.3.1 84 | Send2Trash==1.5.0 85 | Shapely==1.6.4.post2 86 | six==1.12.0 87 | SQLAlchemy==1.3.10 88 | terminado==0.8.2 89 | testpath==0.4.2 90 | tornado==6.0.3 91 | traitlets==4.3.3 92 | urllib3==1.25.6 93 | wcwidth==0.1.7 94 | webencodings==0.5.1 95 | Werkzeug==0.16.0 96 | win-inet-pton==1.1.0 97 | wincertstore==0.2 98 | zipp==0.6.0 99 | -------------------------------------------------------------------------------- /app_server.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | import pandas as pd 3 | import dash 4 | import dash_table 5 | import dash_core_components as dcc 6 | import dash_html_components as html 7 | import plotly.graph_objects as go 8 | 9 | 10 | # Read data 11 | engine = create_engine('postgresql://postgres:week4data@strategy-week4.cgdnfokybmc9.us-east-2.rds.amazonaws.com/postgres') 12 | df = pd.read_sql("SELECT * from trades", engine.connect(), parse_dates=('Entry time',)) 13 | #df = pd.read_csv('aggr.csv', parse_dates=['Entry time']) 14 | # add year month Column 15 | df['YearMonth'] = df.apply(lambda x: str(x['Entry time'].year) +'-'+ str(x['Entry time'].month) ,axis=1) 16 | 17 | # Instantiate dash app 18 | 19 | app = dash.Dash(__name__, external_stylesheets=['https://codepen.io/uditagarwal/pen/oNvwKNP.css', 20 | 'https://codepen.io/uditagarwal/pen/YzKbqyV.css']) 21 | # Define layout 22 | 23 | app.layout = html.Div(children=[ 24 | html.Div( 25 | children=[ 26 | html.H2(children="Bitcoin Leveraged Trading Backtest Analysis by Jose", className='h1-title'), 27 | ], 28 | className='study-browser-banner row' 29 | ), 30 | html.Div( 31 | className="row app-body", 32 | children=[ 33 | html.Div( 34 | className="twelve columns card", 35 | children=[ 36 | html.Div( 37 | className="padding row", 38 | children=[ 39 | html.Div( 40 | className="two columns card", 41 | children=[ 42 | html.H6("Select Exchange",), 43 | dcc.RadioItems( 44 | id="exchange-select", 45 | options=[ 46 | {'label': label, 'value': label} for label in df['Exchange'].unique() 47 | ], 48 | value='Bitmex', 49 | labelStyle={'display': 'inline-block'} 50 | ) 51 | ] 52 | ), 53 | # Leverage Selector 54 | html.Div( 55 | className="two columns card", 56 | children=[ 57 | html.H6("Select Leverage"), 58 | dcc.RadioItems( 59 | id="leverage-select", 60 | options=[ 61 | {'label': str(label), 'value': int(label)} for label in df['Margin'].unique() 62 | ], 63 | value=1, 64 | labelStyle={'display': 'inline-block'} 65 | ), 66 | ] 67 | ), 68 | html.Div( 69 | className="three columns card", 70 | children=[ 71 | html.H6("Select a Date Range"), 72 | dcc.DatePickerRange( 73 | id="date-range-select", 74 | display_format="MMM YY", 75 | start_date=df['Entry time'].min(), 76 | end_date=df['Entry time'].max(), 77 | initial_visible_month=df['Entry time'].max() 78 | ), 79 | ] 80 | ), 81 | html.Div( 82 | id="strat-returns-div", 83 | className="two columns indicator pretty_container", 84 | children=[ 85 | html.P(id="strat-returns", className="indicator_value"), 86 | html.P('Strategy Returns', className="twelve columns indicator_text"), 87 | ] 88 | ), 89 | html.Div( 90 | id="market-returns-div", 91 | className="two columns indicator pretty_container", 92 | children=[ 93 | html.P(id="market-returns", className="indicator_value"), 94 | html.P('Market Returns', className="twelve columns indicator_text"), 95 | ] 96 | ), 97 | html.Div( 98 | id="strat-vs-market-div", 99 | className="two columns indicator pretty_container", 100 | children=[ 101 | html.P(id="strat-vs-market", className="indicator_value"), 102 | html.P('Strategy vs. Market Returns', className="twelve columns indicator_text"), 103 | ] 104 | ), 105 | ] 106 | ) 107 | ]), 108 | html.Div( 109 | className="twelve columns card", 110 | children=[ 111 | dcc.Graph( 112 | id="monthly-chart", 113 | figure={ 114 | 'data': [] 115 | } 116 | ) 117 | ] 118 | ), 119 | html.Div( 120 | className="padding row", 121 | children=[ 122 | html.Div( 123 | className="six columns card", 124 | children=[ 125 | dash_table.DataTable( 126 | id='table', 127 | columns=[ 128 | {'name': 'Number', 'id': 'Number'}, 129 | {'name': 'Trade type', 'id': 'Trade type'}, 130 | {'name': 'Exposure', 'id': 'Exposure'}, 131 | {'name': 'Entry balance', 'id': 'Entry balance'}, 132 | {'name': 'Exit balance', 'id': 'Exit balance'}, 133 | {'name': 'Pnl (incl fees)', 'id': 'Pnl (incl fees)'}, 134 | ], 135 | style_cell={'width': '50px'}, 136 | style_table={ 137 | 'maxHeight': '450px', 138 | 'overflowY': 'scroll' 139 | }, 140 | ) 141 | ] 142 | ), 143 | dcc.Graph( 144 | id="pnl-types", 145 | className="six columns card", 146 | figure={} 147 | ) 148 | ] 149 | ), 150 | html.Div( 151 | className="padding row", 152 | children=[ 153 | dcc.Graph( 154 | id="daily-btc", 155 | className="six columns card", 156 | figure={} 157 | ), 158 | dcc.Graph( 159 | id="balance", 160 | className="six columns card", 161 | figure={} 162 | ) 163 | ] 164 | ) 165 | ] 166 | ) 167 | ]) 168 | 169 | 170 | # callback ex 3 171 | 172 | 173 | @app.callback( 174 | # date picker outputs 175 | [ 176 | dash.dependencies.Output('date-range-select', 'start_date'), 177 | dash.dependencies.Output('date-range-select', 'end_date') 178 | ], 179 | # exchange selector inputs 180 | [ 181 | dash.dependencies.Input('exchange-select', 'value'), 182 | ] 183 | ) 184 | def update_dates(exchange_type): 185 | df_filter = df[df['Exchange'] == exchange_type] 186 | start_date_ex = df_filter['Entry time'].min() 187 | end_date_ex = df_filter['Entry time'].max() 188 | return start_date_ex, end_date_ex 189 | 190 | # filter df function 191 | 192 | 193 | def filter_df(df,exchange,margin,start_date,end_date): 194 | df_filt = df[df['Exchange'] == exchange] 195 | df_filt = df_filt[df_filt['Margin'] == margin] 196 | df_filt = df_filt[df_filt['Entry time'].between(start_date,end_date)] 197 | return df_filt 198 | 199 | # callback multioutput 200 | 201 | def calc_returns_over_month(dff): 202 | 203 | out = [] 204 | 205 | for name, group in dff.groupby('YearMonth'): 206 | exit_balance = group.head(1)['Exit balance'].values[0] 207 | entry_balance = group.tail(1)['Entry balance'].values[0] 208 | monthly_return = (exit_balance*100 / entry_balance)-100 209 | out.append({ 210 | 'month': name, 211 | 'entry': entry_balance, 212 | 'exit': exit_balance, 213 | 'monthly_return': monthly_return 214 | }) 215 | return out 216 | 217 | 218 | def calc_btc_returns(dff): 219 | btc_start_value = dff.tail(1)['BTC Price'].values[0] 220 | btc_end_value = dff.head(1)['BTC Price'].values[0] 221 | btc_returns = (btc_end_value * 100/ btc_start_value)-100 222 | return btc_returns 223 | 224 | 225 | def calc_strat_returns(dff): 226 | start_value = dff.tail(1)['Exit balance'].values[0] 227 | end_value = dff.head(1)['Entry balance'].values[0] 228 | returns = (end_value * 100/ start_value)-100 229 | return returns 230 | 231 | 232 | @app.callback( 233 | [ 234 | dash.dependencies.Output('monthly-chart', 'figure'), 235 | dash.dependencies.Output('market-returns', 'children'), 236 | dash.dependencies.Output('strat-returns', 'children'), 237 | dash.dependencies.Output('strat-vs-market', 'children'), 238 | ], 239 | ( 240 | dash.dependencies.Input('exchange-select', 'value'), 241 | dash.dependencies.Input('leverage-select', 'value'), 242 | dash.dependencies.Input('date-range-select', 'start_date'), 243 | dash.dependencies.Input('date-range-select', 'end_date'), 244 | 245 | ) 246 | ) 247 | def update_monthly(exchange, leverage, start_date, end_date): 248 | dff = filter_df(df, exchange, leverage, start_date, end_date) 249 | data = calc_returns_over_month(dff) 250 | btc_returns = calc_btc_returns(dff) 251 | strat_returns = calc_strat_returns(dff) 252 | strat_vs_market = strat_returns - btc_returns 253 | 254 | return { 255 | 'data': [ 256 | go.Candlestick( 257 | open=[each['entry'] for each in data], 258 | close=[each['exit'] for each in data], 259 | x=[each['month'] for each in data], 260 | low=[each['entry'] for each in data], 261 | high=[each['exit'] for each in data] 262 | ) 263 | ], 264 | 'layout': { 265 | 'title': 'Overview of Monthly performance' 266 | } 267 | }, f'{btc_returns:0.2f}%', f'{strat_returns:0.2f}%', f'{strat_vs_market:0.2f}%' 268 | 269 | # table callback 270 | 271 | @app.callback( 272 | dash.dependencies.Output('table', 'data'), 273 | ( 274 | dash.dependencies.Input('exchange-select', 'value'), 275 | dash.dependencies.Input('leverage-select', 'value'), 276 | dash.dependencies.Input('date-range-select', 'start_date'), 277 | dash.dependencies.Input('date-range-select', 'end_date'), 278 | ) 279 | ) 280 | def update_table(exchange, leverage, start_date, end_date): 281 | dff = filter_df(df, exchange, leverage, start_date, end_date) 282 | return dff.to_dict('records') 283 | 284 | ########################################################################## 285 | # bar callback 286 | ########################################################################## 287 | # https://plot.ly/python/bar-charts/ reference 288 | 289 | 290 | @app.callback( 291 | dash.dependencies.Output('pnl-types', 'figure'), 292 | ( 293 | dash.dependencies.Input('exchange-select', 'value'), 294 | dash.dependencies.Input('leverage-select', 'value'), 295 | dash.dependencies.Input('date-range-select', 'start_date'), 296 | dash.dependencies.Input('date-range-select', 'end_date'), 297 | ) 298 | ) 299 | def update_bar_chart(exchange, leverage, start_date, end_date): 300 | dff = filter_df(df, exchange, leverage, start_date, end_date) 301 | df1 = dff[dff['Trade type'] == 'Long'] 302 | df2 = dff[dff['Trade type'] == 'Short'] 303 | trace1 = go.Bar(x=df1['Entry time'], y=dff['Pnl (incl fees)'], name='Long', ) 304 | trace2 = go.Bar(x=df2['Entry time'], y=dff['Pnl (incl fees)'], name='Short', ) 305 | 306 | return { 307 | 'data': [trace1, trace2], 308 | 'layout': go.Layout(title='PnL vs Trade type', 309 | height=450, 310 | colorway=["#EF963B", "#EF533B"], 311 | xaxis={'title': "Date", 'titlefont': {'color': 'black', 'size': 14}, 312 | 'tickfont': {'size': 9, 'color': 'black'}}, 313 | yaxis={'title': "Pnl (incl fees)", 'titlefont': {'color': 'black', 'size': 14, }, 314 | 'tickfont': {'color': 'black'}})} 315 | 316 | ##################################################################################################################### 317 | # line plot balance call back 318 | ##################################################################################################################### 319 | 320 | 321 | @app.callback( 322 | dash.dependencies.Output('balance', 'figure'), 323 | ( 324 | dash.dependencies.Input('exchange-select', 'value'), 325 | dash.dependencies.Input('leverage-select', 'value'), 326 | dash.dependencies.Input('date-range-select', 'start_date'), 327 | dash.dependencies.Input('date-range-select', 'end_date'), 328 | ) 329 | ) 330 | def update_line_price(exchange, leverage, start_date, end_date): 331 | dff = filter_df(df, exchange, leverage, start_date, end_date) 332 | trace1 = go.Scatter(x=dff['Entry time'], y=dff['Exit balance'], name='Balance', ) 333 | 334 | return { 335 | 'data': [trace1], 336 | 'layout': go.Layout(title='Balance', 337 | height=450, 338 | xaxis={'title': "Date", 'titlefont': {'color': 'black', 'size': 14}, 339 | 'tickfont': {'size': 9, 'color': 'black'}}, 340 | yaxis={'title': "Price(USD)", 'titlefont': {'color': 'black', 'size': 14, }, 341 | 'tickfont': {'color': 'black'}})} 342 | 343 | 344 | ##################################################################################################################### 345 | # line plot btc price callback 346 | ##################################################################################################################### 347 | 348 | 349 | @app.callback( 350 | dash.dependencies.Output('daily-btc', 'figure'), 351 | ( 352 | dash.dependencies.Input('exchange-select', 'value'), 353 | dash.dependencies.Input('leverage-select', 'value'), 354 | dash.dependencies.Input('date-range-select', 'start_date'), 355 | dash.dependencies.Input('date-range-select', 'end_date'), 356 | ) 357 | ) 358 | def update_line_price(exchange, leverage, start_date, end_date): 359 | dff = filter_df(df, exchange, leverage, start_date, end_date) 360 | trace1 = go.Scatter(x=dff['Entry time'], y=dff['BTC Price'], name='Btc Price', ) 361 | 362 | return { 363 | 'data': [trace1], 364 | 'layout': go.Layout(title='Btc price', 365 | height=450, 366 | xaxis={'title': "Date", 'titlefont': {'color': 'black', 'size': 14}, 367 | 'tickfont': {'size': 9, 'color': 'black'}}, 368 | yaxis={'title': "Price(USD)", 'titlefont': {'color': 'black', 'size': 14, }, 369 | 'tickfont': {'color': 'black'}})} 370 | 371 | 372 | # Run server 373 | if __name__ == "__main__": 374 | app.run_server(debug=True,host= '0.0.0.0') --------------------------------------------------------------------------------