Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
polakowo
GitHub Repository: polakowo/vectorbt
Path: blob/master/apps/candlestick-patterns/app.py
1073 views
1
# -*- coding: utf-8 -*-
2
3
# Copyright (c) 2021 Oleg Polakow. All rights reserved.
4
# This code is licensed under Apache 2.0 with Commons Clause license (see LICENSE.md for details)
5
6
# Run this app with `python app.py` and
7
# visit http://127.0.0.1:8050/ in your web browser.
8
9
import dash
10
import dash_table
11
import dash_core_components as dcc
12
import dash_bootstrap_components as dbc
13
import dash_html_components as html
14
from dash.dependencies import State, Input, Output
15
from flask_caching import Cache
16
import plotly.graph_objects as go
17
18
import os
19
import numpy as np
20
import pandas as pd
21
import json
22
import random
23
import yfinance as yf
24
import talib
25
from talib import abstract
26
from talib._ta_lib import (
27
CandleSettingType,
28
RangeType,
29
_ta_set_candle_settings
30
)
31
from vectorbt import settings
32
from vectorbt.utils.config import merge_dicts
33
from vectorbt.utils.colors import adjust_opacity
34
from vectorbt.portfolio.enums import Direction, DirectionConflictMode
35
from vectorbt.portfolio.base import Portfolio
36
37
USE_CACHING = os.environ.get(
38
"USE_CACHING",
39
"True",
40
) == "True"
41
HOST = os.environ.get(
42
"HOST",
43
"127.0.0.1",
44
)
45
PORT = int(os.environ.get(
46
"PORT",
47
8050,
48
))
49
DEBUG = os.environ.get(
50
"DEBUG",
51
"True",
52
) == "True"
53
GITHUB_LINK = os.environ.get(
54
"GITHUB_LINK",
55
"https://github.com/polakowo/vectorbt/tree/master/apps/candlestick-patterns",
56
)
57
58
app = dash.Dash(
59
__name__,
60
meta_tags=[
61
{
62
"name": "viewport",
63
"content": "width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no",
64
}
65
],
66
external_stylesheets=[dbc.themes.GRID]
67
)
68
CACHE_CONFIG = {
69
'CACHE_TYPE': 'filesystem' if USE_CACHING else 'null',
70
'CACHE_DIR': 'data',
71
'CACHE_DEFAULT_TIMEOUT': 0,
72
'CACHE_THRESHOLD': 50
73
}
74
cache = Cache()
75
cache.init_app(app.server, config=CACHE_CONFIG)
76
77
# Settings
78
periods = ['1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max']
79
intervals = ['1m', '2m', '5m', '15m', '30m', '60m', '90m', '1d', '5d', '1wk', '1mo', '3mo']
80
patterns = talib.get_function_groups()['Pattern Recognition']
81
stats_table_columns = ["Metric", "Buy & Hold", "Random (Median)", "Strategy", "Z-Score"]
82
directions = Direction._fields
83
conflict_modes = DirectionConflictMode._fields
84
plot_types = ['OHLC', 'Candlestick']
85
86
# Colors
87
color_schema = settings['plotting']['color_schema']
88
bgcolor = "#272a32"
89
dark_bgcolor = "#1d2026"
90
fontcolor = "#9fa6b7"
91
dark_fontcolor = "#7b7d8d"
92
gridcolor = "#323b56"
93
loadcolor = "#387c9e"
94
active_color = "#88ccee"
95
96
# Defaults
97
data_path = 'data/data.h5'
98
default_metric = 'Total Return [%]'
99
default_symbol = 'BTC-USD'
100
default_period = '1y'
101
default_interval = '1d'
102
default_date_range = [0, 1]
103
default_fees = 0.1
104
default_fixed_fees = 0.
105
default_slippage = 5.
106
default_yf_options = ['auto_adjust']
107
default_exit_n_random = default_entry_n_random = 5
108
default_prob_options = ['mimic_strategy']
109
default_entry_prob = 0.1
110
default_exit_prob = 0.1
111
default_entry_patterns = [
112
'CDLHAMMER',
113
'CDLINVERTEDHAMMER',
114
'CDLPIERCING',
115
'CDLMORNINGSTAR',
116
'CDL3WHITESOLDIERS'
117
]
118
default_exit_options = []
119
default_exit_patterns = [
120
'CDLHANGINGMAN',
121
'CDLSHOOTINGSTAR',
122
'CDLEVENINGSTAR',
123
'CDL3BLACKCROWS',
124
'CDLDARKCLOUDCOVER'
125
]
126
default_candle_settings = pd.DataFrame({
127
'SettingType': [
128
'BodyLong',
129
'BodyVeryLong',
130
'BodyShort',
131
'BodyDoji',
132
'ShadowLong',
133
'ShadowVeryLong',
134
'ShadowShort',
135
'ShadowVeryShort',
136
'Near',
137
'Far',
138
'Equal'
139
],
140
'RangeType': [
141
'RealBody',
142
'RealBody',
143
'RealBody',
144
'HighLow',
145
'RealBody',
146
'RealBody',
147
'Shadows',
148
'HighLow',
149
'HighLow',
150
'HighLow',
151
'HighLow'
152
],
153
'AvgPeriod': [
154
10,
155
10,
156
10,
157
10,
158
0,
159
0,
160
10,
161
10,
162
5,
163
5,
164
5
165
],
166
'Factor': [
167
1.0,
168
3.0,
169
1.0,
170
0.1,
171
1.0,
172
2.0,
173
1.0,
174
0.1,
175
0.2,
176
0.6,
177
0.05
178
]
179
})
180
default_entry_dates = []
181
default_exit_dates = []
182
default_direction = directions[0]
183
default_conflict_mode = conflict_modes[0]
184
default_sim_options = ['allow_accumulate']
185
default_n_random_strat = 50
186
default_stats_options = ['incl_open']
187
default_layout = dict(
188
autosize=True,
189
margin=dict(b=40, t=20),
190
font=dict(
191
color=fontcolor
192
),
193
plot_bgcolor=bgcolor,
194
paper_bgcolor=bgcolor,
195
legend=dict(
196
font=dict(size=10),
197
orientation="h",
198
yanchor="bottom",
199
y=1.02,
200
xanchor="right",
201
x=1
202
),
203
)
204
default_subplots = ['orders', 'trade_pnl', 'cum_returns']
205
default_plot_type = 'OHLC'
206
207
app.layout = html.Div(
208
children=[
209
html.Div(
210
className="banner",
211
children=[
212
html.H6("vectorbt: candlestick patterns"),
213
html.Div(
214
html.A(
215
"View on GitHub",
216
href=GITHUB_LINK,
217
target="_blank",
218
className="button",
219
)
220
),
221
],
222
),
223
dbc.Row(
224
children=[
225
dbc.Col(
226
lg=8, sm=12,
227
children=[
228
html.Div(
229
className="pretty-container",
230
children=[
231
html.Div(
232
className="banner",
233
children=[
234
html.H6("OHLCV and signals")
235
],
236
),
237
dbc.Row(
238
children=[
239
dbc.Col(
240
lg=4, sm=12,
241
children=[
242
html.Label("Select plot type:"),
243
dcc.Dropdown(
244
id="plot_type_dropdown",
245
options=[{"value": i, "label": i} for i in plot_types],
246
value=default_plot_type,
247
),
248
]
249
)
250
],
251
),
252
dcc.Loading(
253
id="ohlcv_loading",
254
type="default",
255
color=loadcolor,
256
children=[
257
dcc.Graph(
258
id="ohlcv_graph",
259
figure={
260
"layout": default_layout
261
}
262
)
263
],
264
),
265
html.Small("Hint: Use Box and Lasso Select to filter signals"),
266
],
267
),
268
dbc.Row(
269
children=[
270
dbc.Col(
271
children=[
272
html.Div(
273
className="pretty-container",
274
children=[
275
html.Div(
276
className="banner",
277
children=[
278
html.H6("Portfolio")
279
],
280
),
281
dbc.Row(
282
children=[
283
dbc.Col(
284
lg=6, sm=12,
285
children=[
286
html.Label("Select subplots:"),
287
dcc.Dropdown(
288
id="subplot_dropdown",
289
options=[
290
{"value": k, "label": v['title']}
291
for k, v in Portfolio.subplots.items()
292
],
293
multi=True,
294
value=default_subplots,
295
),
296
]
297
)
298
],
299
),
300
dcc.Loading(
301
id="portfolio_loading",
302
type="default",
303
color=loadcolor,
304
children=[
305
dcc.Graph(
306
id="portfolio_graph",
307
figure={
308
"layout": default_layout
309
}
310
)
311
],
312
),
313
],
314
),
315
316
]
317
)
318
]
319
),
320
dbc.Row(
321
children=[
322
dbc.Col(
323
children=[
324
html.Div(
325
className="pretty-container",
326
children=[
327
html.Div(
328
className="banner",
329
children=[
330
html.H6("Stats")
331
],
332
),
333
dcc.Loading(
334
id="stats_loading",
335
type="default",
336
color=loadcolor,
337
children=[
338
dash_table.DataTable(
339
id="stats_table",
340
columns=[
341
{
342
"name": c,
343
"id": c,
344
}
345
for c in stats_table_columns
346
],
347
style_data_conditional=[{
348
"if": {"column_id": stats_table_columns[1]},
349
"fontWeight": "bold",
350
"borderLeft": "1px solid dimgrey"
351
}, {
352
"if": {"column_id": stats_table_columns[2]},
353
"fontWeight": "bold",
354
}, {
355
"if": {"column_id": stats_table_columns[3]},
356
"fontWeight": "bold",
357
}, {
358
"if": {"column_id": stats_table_columns[4]},
359
"fontWeight": "bold",
360
}, {
361
"if": {"state": "selected"},
362
"backgroundColor": dark_bgcolor,
363
"color": active_color,
364
"border": "1px solid " + active_color,
365
}, {
366
"if": {"state": "active"},
367
"backgroundColor": dark_bgcolor,
368
"color": active_color,
369
"border": "1px solid " + active_color,
370
}],
371
style_header={
372
"border": "none",
373
"backgroundColor": bgcolor,
374
"fontWeight": "bold",
375
"padding": "0px 5px"
376
},
377
style_data={
378
"border": "none",
379
"backgroundColor": bgcolor,
380
"color": dark_fontcolor,
381
"paddingRight": "10px"
382
},
383
style_table={
384
'overflowX': 'scroll',
385
},
386
style_as_list_view=False,
387
editable=False,
388
),
389
],
390
),
391
],
392
),
393
]
394
)
395
]
396
),
397
dbc.Row(
398
children=[
399
dbc.Col(
400
children=[
401
html.Div(
402
className="pretty-container",
403
children=[
404
html.Div(
405
className="banner",
406
children=[
407
html.H6("Metric stats")
408
],
409
),
410
dcc.Loading(
411
id="metric_stats_loading",
412
type="default",
413
color=loadcolor,
414
children=[
415
html.Label("Metric:"),
416
dbc.Row(
417
children=[
418
dbc.Col(
419
lg=4, sm=12,
420
children=[
421
dcc.Dropdown(
422
id="metric_dropdown"
423
),
424
]
425
),
426
dbc.Col(
427
lg=8, sm=12,
428
children=[
429
dcc.Graph(
430
id="metric_graph",
431
figure={
432
"layout": default_layout
433
}
434
)
435
]
436
)
437
],
438
),
439
],
440
),
441
],
442
),
443
]
444
)
445
]
446
),
447
448
]
449
),
450
dbc.Col(
451
lg=4, sm=12,
452
children=[
453
html.Div(
454
className="pretty-container",
455
children=[
456
html.Div(
457
className="banner",
458
children=[
459
html.H6("Settings")
460
],
461
),
462
html.Button(
463
"Reset",
464
id="reset_button"
465
),
466
html.Details(
467
open=True,
468
children=[
469
html.Summary(
470
className="section-title",
471
children="Data",
472
),
473
dbc.Row(
474
children=[
475
dbc.Col(
476
children=[
477
html.Label("Yahoo! Finance symbol:"),
478
dcc.Input(
479
id="symbol_input",
480
className="input-control",
481
type="text",
482
value=default_symbol,
483
placeholder="Enter symbol...",
484
debounce=True
485
),
486
]
487
),
488
dbc.Col()
489
],
490
),
491
dbc.Row(
492
children=[
493
dbc.Col(
494
children=[
495
html.Label("Period:"),
496
dcc.Dropdown(
497
id="period_dropdown",
498
options=[{"value": i, "label": i} for i in periods],
499
value=default_period,
500
),
501
]
502
),
503
dbc.Col(
504
children=[
505
html.Label("Interval:"),
506
dcc.Dropdown(
507
id="interval_dropdown",
508
options=[{"value": i, "label": i} for i in intervals],
509
value=default_interval,
510
),
511
]
512
)
513
],
514
),
515
html.Label("Filter period:"),
516
dcc.RangeSlider(
517
id="date_slider",
518
min=0.,
519
max=1.,
520
value=default_date_range,
521
allowCross=False,
522
tooltip={
523
'placement': 'bottom'
524
}
525
),
526
dcc.Checklist(
527
id="yf_checklist",
528
options=[{
529
"label": "Adjust all OHLC automatically",
530
"value": "auto_adjust"
531
}, {
532
"label": "Use back-adjusted data to mimic true historical prices",
533
"value": "back_adjust"
534
}],
535
value=default_yf_options,
536
style={
537
"color": dark_fontcolor
538
}
539
),
540
],
541
),
542
html.Details(
543
open=True,
544
children=[
545
html.Summary(
546
className="section-title",
547
children="Entry patterns",
548
),
549
html.Div(
550
id='entry_settings',
551
children=[
552
html.Button(
553
"All",
554
id="entry_all_button"
555
),
556
html.Button(
557
"Random",
558
id="entry_random_button"
559
),
560
html.Button(
561
"Clear",
562
id="entry_clear_button"
563
),
564
html.Label("Number of random patterns:"),
565
dbc.Row(
566
children=[
567
dbc.Col(
568
children=[
569
dcc.Input(
570
id="entry_n_random_input",
571
className="input-control",
572
value=default_entry_n_random,
573
placeholder="Enter number...",
574
debounce=True,
575
type="number",
576
min=1, max=len(patterns), step=1
577
),
578
]
579
),
580
dbc.Col()
581
],
582
),
583
html.Label("Select patterns:"),
584
dcc.Dropdown(
585
id="entry_pattern_dropdown",
586
options=[{"value": i, "label": i} for i in patterns],
587
multi=True,
588
value=default_entry_patterns,
589
),
590
],
591
),
592
],
593
),
594
html.Details(
595
open=True,
596
children=[
597
html.Summary(
598
className="section-title",
599
children="Exit patterns",
600
),
601
dcc.Checklist(
602
id="exit_checklist",
603
options=[{
604
"label": "Same as entry patterns",
605
"value": "same_as_entry"
606
}],
607
value=default_exit_options,
608
style={
609
"color": dark_fontcolor
610
}
611
),
612
html.Div(
613
id='exit_settings',
614
hidden="same_as_entry" in default_exit_options,
615
children=[
616
html.Button(
617
"All",
618
id="exit_all_button"
619
),
620
html.Button(
621
"Random",
622
id="exit_random_button"
623
),
624
html.Button(
625
"Clear",
626
id="exit_clear_button"
627
),
628
html.Label("Number of random patterns:"),
629
dbc.Row(
630
children=[
631
dbc.Col(
632
children=[
633
dcc.Input(
634
id="exit_n_random_input",
635
className="input-control",
636
value=default_exit_n_random,
637
placeholder="Enter number...",
638
debounce=True,
639
type="number",
640
min=1, max=len(patterns), step=1
641
),
642
]
643
),
644
dbc.Col()
645
],
646
),
647
html.Label("Select patterns:"),
648
dcc.Dropdown(
649
id="exit_pattern_dropdown",
650
options=[{"value": i, "label": i} for i in patterns],
651
multi=True,
652
value=default_exit_patterns,
653
),
654
],
655
),
656
],
657
),
658
html.Details(
659
children=[
660
html.Summary(
661
className="section-title",
662
children="Candle settings",
663
),
664
dash_table.DataTable(
665
id="candle_settings_table",
666
columns=[
667
{
668
"name": c,
669
"id": c,
670
"editable": i in (2, 3),
671
"type": "numeric" if i in (2, 3) else "any"
672
}
673
for i, c in enumerate(default_candle_settings.columns)
674
],
675
data=default_candle_settings.to_dict("records"),
676
style_data_conditional=[{
677
"if": {"column_editable": True},
678
"backgroundColor": dark_bgcolor,
679
"border": "1px solid dimgrey"
680
}, {
681
"if": {"state": "selected"},
682
"backgroundColor": dark_bgcolor,
683
"color": active_color,
684
"border": "1px solid " + active_color,
685
}, {
686
"if": {"state": "active"},
687
"backgroundColor": dark_bgcolor,
688
"color": active_color,
689
"border": "1px solid " + active_color,
690
}],
691
style_header={
692
"border": "none",
693
"backgroundColor": bgcolor,
694
"fontWeight": "bold",
695
"padding": "0px 5px"
696
},
697
style_data={
698
"border": "none",
699
"backgroundColor": bgcolor,
700
"color": dark_fontcolor
701
},
702
style_table={
703
'overflowX': 'scroll',
704
},
705
style_as_list_view=False,
706
editable=True,
707
),
708
],
709
),
710
html.Details(
711
open=False,
712
children=[
713
html.Summary(
714
className="section-title",
715
children="Custom pattern",
716
),
717
html.Div(
718
id='custom_settings',
719
children=[
720
html.Label("Select entry dates:"),
721
dcc.Dropdown(
722
id="custom_entry_dropdown",
723
options=[],
724
multi=True,
725
value=default_entry_dates,
726
),
727
html.Label("Select exit dates:"),
728
dcc.Dropdown(
729
id="custom_exit_dropdown",
730
options=[],
731
multi=True,
732
value=default_exit_dates,
733
),
734
],
735
),
736
],
737
),
738
html.Details(
739
open=True,
740
children=[
741
html.Summary(
742
className="section-title",
743
children="Simulation settings",
744
),
745
dbc.Row(
746
children=[
747
dbc.Col(
748
children=[
749
html.Label("Fees (in %):"),
750
dcc.Input(
751
id="fees_input",
752
className="input-control",
753
type="number",
754
value=default_fees,
755
placeholder="Enter fees...",
756
debounce=True,
757
min=0, max=100
758
),
759
]
760
),
761
dbc.Col(
762
children=[
763
html.Label("Fixed fees:"),
764
dcc.Input(
765
id="fixed_fees_input",
766
className="input-control",
767
type="number",
768
value=default_fixed_fees,
769
placeholder="Enter fixed fees...",
770
debounce=True,
771
min=0
772
),
773
]
774
),
775
]
776
),
777
dbc.Row(
778
children=[
779
dbc.Col(
780
children=[
781
html.Label("Slippage (in % of H-O):"),
782
dcc.Input(
783
id="slippage_input",
784
className="input-control",
785
type="number",
786
value=default_slippage,
787
placeholder="Enter slippage...",
788
debounce=True,
789
min=0, max=100
790
),
791
]
792
),
793
dbc.Col()
794
],
795
),
796
dbc.Row(
797
children=[
798
dbc.Col(
799
children=[
800
html.Label("Direction:"),
801
dcc.Dropdown(
802
id="direction_dropdown",
803
options=[{"value": i, "label": i} for i in directions],
804
value=default_direction,
805
),
806
]
807
),
808
dbc.Col(
809
children=[
810
html.Label("Conflict Mode:"),
811
dcc.Dropdown(
812
id="conflict_mode_dropdown",
813
options=[{"value": i, "label": i} for i in conflict_modes],
814
value=default_conflict_mode
815
),
816
]
817
),
818
],
819
),
820
dcc.Checklist(
821
id="sim_checklist",
822
options=[{
823
"label": "Allow signal accumulation",
824
"value": "allow_accumulate"
825
}],
826
value=default_sim_options,
827
style={
828
"color": dark_fontcolor
829
}
830
),
831
html.Label("Number of random strategies to test against:"),
832
dbc.Row(
833
children=[
834
dbc.Col(
835
children=[
836
dcc.Input(
837
id="n_random_strat_input",
838
className="input-control",
839
value=default_n_random_strat,
840
placeholder="Enter number...",
841
debounce=True,
842
type="number",
843
min=10, max=1000, step=1
844
),
845
]
846
),
847
dbc.Col()
848
],
849
),
850
dcc.Checklist(
851
id="prob_checklist",
852
options=[{
853
"label": "Mimic strategy by shuffling",
854
"value": "mimic_strategy"
855
}],
856
value=default_prob_options,
857
style={
858
"color": dark_fontcolor
859
}
860
),
861
html.Div(
862
id='prob_settings',
863
hidden="mimic_strategy" in default_prob_options,
864
children=[
865
dbc.Row(
866
children=[
867
dbc.Col(
868
children=[
869
html.Label("Entry probability (in %):"),
870
dcc.Input(
871
id="entry_prob_input",
872
className="input-control",
873
value=default_entry_prob,
874
placeholder="Enter number...",
875
debounce=True,
876
type="number",
877
min=0, max=100
878
),
879
]
880
),
881
dbc.Col(
882
children=[
883
html.Label("Exit probability (in %):"),
884
dcc.Input(
885
id="exit_prob_input",
886
className="input-control",
887
value=default_exit_prob,
888
placeholder="Enter number...",
889
debounce=True,
890
type="number",
891
min=0, max=100
892
),
893
]
894
),
895
],
896
),
897
]
898
),
899
dcc.Checklist(
900
id="stats_checklist",
901
options=[{
902
"label": "Include open trades in stats",
903
"value": "incl_open"
904
}, {
905
"label": "Use positions instead of trades in stats",
906
"value": "use_positions"
907
}],
908
value=default_stats_options,
909
style={
910
"color": dark_fontcolor
911
}
912
),
913
],
914
),
915
],
916
),
917
]
918
)
919
]
920
),
921
html.Div(id='data_signal', style={'display': 'none'}),
922
html.Div(id='index_signal', style={'display': 'none'}),
923
html.Div(id='candle_settings_signal', style={'display': 'none'}),
924
html.Div(id='stats_signal', style={'display': 'none'}),
925
html.Div(id="window_width", style={'display': 'none'}),
926
dcc.Location(id="url")
927
],
928
)
929
930
app.clientside_callback(
931
"""
932
function(href) {
933
return window.innerWidth;
934
}
935
""",
936
Output("window_width", "children"),
937
[Input("url", "href")],
938
)
939
940
941
@cache.memoize()
942
def fetch_data(symbol, period, interval, auto_adjust, back_adjust):
943
"""Fetch OHLCV data from Yahoo! Finance."""
944
return yf.Ticker(symbol).history(
945
period=period,
946
interval=interval,
947
actions=False,
948
auto_adjust=auto_adjust,
949
back_adjust=back_adjust
950
)
951
952
953
@app.callback(
954
[Output('data_signal', 'children'),
955
Output('index_signal', 'children')],
956
[Input('symbol_input', 'value'),
957
Input('period_dropdown', 'value'),
958
Input('interval_dropdown', 'value'),
959
Input("yf_checklist", "value")]
960
)
961
def update_data(symbol, period, interval, yf_options):
962
"""Store data into a hidden DIV to avoid repeatedly calling Yahoo's API."""
963
auto_adjust = 'auto_adjust' in yf_options
964
back_adjust = 'back_adjust' in yf_options
965
df = fetch_data(symbol, period, interval, auto_adjust, back_adjust)
966
return df.to_json(date_format='iso', orient='split'), df.index.tolist()
967
968
969
@app.callback(
970
[Output('date_slider', 'min'),
971
Output('date_slider', 'max'),
972
Output('date_slider', 'value')],
973
[Input('index_signal', 'children')]
974
)
975
def update_date_slider(date_list):
976
"""Once index (dates) has changed, reset the date slider."""
977
return 0, len(date_list) - 1, [0, len(date_list) - 1]
978
979
980
@app.callback(
981
[Output('custom_entry_dropdown', 'options'),
982
Output('custom_exit_dropdown', 'options')],
983
[Input('index_signal', 'children'),
984
Input('date_slider', 'value')]
985
)
986
def update_custom_options(date_list, date_range):
987
"""Once dates have changed, update entry/exit dates in custom pattern section.
988
989
If selected dates cannot be found in new dates, they will be automatically removed."""
990
filtered_dates = np.asarray(date_list)[date_range[0]:date_range[1] + 1].tolist()
991
custom_options = [{"value": i, "label": i} for i in filtered_dates]
992
return custom_options, custom_options
993
994
995
@app.callback(
996
Output('entry_pattern_dropdown', 'value'),
997
[Input("entry_all_button", "n_clicks"),
998
Input("entry_random_button", "n_clicks"),
999
Input("entry_clear_button", "n_clicks"),
1000
Input("reset_button", "n_clicks")],
1001
[State("entry_n_random_input", "value")]
1002
)
1003
def select_entry_patterns(_1, _2, _3, _4, n_random):
1004
"""Select all/random entry patterns or clear."""
1005
ctx = dash.callback_context
1006
if ctx.triggered:
1007
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
1008
if button_id == 'entry_all_button':
1009
return patterns
1010
elif button_id == 'entry_random_button':
1011
return random.sample(patterns, n_random)
1012
elif button_id == 'entry_clear_button':
1013
return []
1014
elif button_id == 'reset_button':
1015
return default_entry_patterns
1016
return dash.no_update
1017
1018
1019
@app.callback(
1020
[Output("exit_settings", "hidden"),
1021
Output("exit_n_random_input", "value"),
1022
Output('exit_pattern_dropdown', 'value')],
1023
[Input("exit_checklist", "value"),
1024
Input("exit_all_button", "n_clicks"),
1025
Input("exit_random_button", "n_clicks"),
1026
Input("exit_clear_button", "n_clicks"),
1027
Input("reset_button", "n_clicks"),
1028
Input("entry_n_random_input", "value"),
1029
Input('entry_pattern_dropdown', 'value')],
1030
[State("exit_n_random_input", "value")]
1031
)
1032
def select_exit_patterns(exit_options, _1, _2, _3, _4, entry_n_random, entry_patterns, exit_n_random):
1033
"""Select all/random exit patterns, clear, or configure the same way as entry patterns."""
1034
ctx = dash.callback_context
1035
same_as_entry = 'same_as_entry' in exit_options
1036
if ctx.triggered:
1037
control_id = ctx.triggered[0]['prop_id'].split('.')[0]
1038
if control_id == 'exit_checklist':
1039
return same_as_entry, entry_n_random, entry_patterns
1040
elif control_id == 'exit_all_button':
1041
return same_as_entry, exit_n_random, patterns
1042
elif control_id == 'exit_random_button':
1043
return same_as_entry, exit_n_random, random.sample(patterns, exit_n_random)
1044
elif control_id == 'exit_clear_button':
1045
return same_as_entry, exit_n_random, []
1046
elif control_id == 'reset_button':
1047
default_same_as_entry = 'same_as_entry' in default_exit_options
1048
return default_same_as_entry, default_exit_n_random, default_exit_patterns
1049
elif control_id in ('entry_n_random_input', 'entry_pattern_dropdown'):
1050
if same_as_entry:
1051
return same_as_entry, entry_n_random, entry_patterns
1052
return dash.no_update
1053
1054
1055
@app.callback(
1056
Output('candle_settings_signal', 'children'),
1057
[Input('candle_settings_table', 'data')]
1058
)
1059
def set_candle_settings(data):
1060
"""Update candle settings in TA-Lib."""
1061
for d in data:
1062
AvgPeriod = d["AvgPeriod"]
1063
if isinstance(AvgPeriod, float) and float.is_integer(AvgPeriod):
1064
AvgPeriod = int(AvgPeriod)
1065
Factor = float(d["Factor"])
1066
_ta_set_candle_settings(
1067
getattr(CandleSettingType, d["SettingType"]),
1068
getattr(RangeType, d["RangeType"]),
1069
AvgPeriod,
1070
Factor
1071
)
1072
1073
1074
@app.callback(
1075
[Output('ohlcv_graph', 'figure'),
1076
Output("prob_settings", "hidden"),
1077
Output("entry_prob_input", "value"),
1078
Output("exit_prob_input", "value")],
1079
[Input('window_width', 'children'),
1080
Input('plot_type_dropdown', 'value'),
1081
Input('data_signal', 'children'),
1082
Input('date_slider', 'value'),
1083
Input('entry_pattern_dropdown', 'value'),
1084
Input('exit_pattern_dropdown', 'value'),
1085
Input('candle_settings_signal', 'children'),
1086
Input('custom_entry_dropdown', 'value'),
1087
Input('custom_exit_dropdown', 'value'),
1088
Input('prob_checklist', 'value'),
1089
Input("reset_button", "n_clicks")],
1090
[State("entry_prob_input", "value"),
1091
State("exit_prob_input", "value")]
1092
)
1093
def update_ohlcv(window_width, plot_type, df_json, date_range, entry_patterns, exit_patterns, _1,
1094
entry_dates, exit_dates, prob_options, _2, entry_prob, exit_prob):
1095
"""Update OHLCV graph.
1096
1097
Also update probability settings, as they also depend upon conversion of patterns into signals."""
1098
df = pd.read_json(df_json, orient='split')
1099
1100
# Filter by date
1101
df = df.iloc[date_range[0]:date_range[1] + 1]
1102
1103
# Run pattern recognition indicators and combine results
1104
talib_inputs = {
1105
'open': df['Open'].values,
1106
'high': df['High'].values,
1107
'low': df['Low'].values,
1108
'close': df['Close'].values,
1109
'volume': df['Volume'].values
1110
}
1111
entry_patterns += ['CUSTOM']
1112
exit_patterns += ['CUSTOM']
1113
all_patterns = list(set(entry_patterns + exit_patterns))
1114
signal_df = pd.DataFrame.vbt.empty(
1115
(len(df.index), len(all_patterns)),
1116
fill_value=0.,
1117
index=df.index,
1118
columns=all_patterns
1119
)
1120
for pattern in all_patterns:
1121
if pattern != 'CUSTOM':
1122
signal_df[pattern] = abstract.Function(pattern)(talib_inputs)
1123
signal_df['CUSTOM'].loc[entry_dates] += 100.
1124
signal_df['CUSTOM'].loc[exit_dates] += -100.
1125
entry_signal_df = signal_df[entry_patterns]
1126
exit_signal_df = signal_df[exit_patterns]
1127
1128
# Entry patterns
1129
entry_df = entry_signal_df[(entry_signal_df > 0).any(axis=1)]
1130
entry_patterns = []
1131
for row_i, row in entry_df.iterrows():
1132
entry_patterns.append('<br>'.join(row.index[row != 0]))
1133
entry_patterns = np.asarray(entry_patterns)
1134
1135
# Exit patterns
1136
exit_df = exit_signal_df[(exit_signal_df < 0).any(axis=1)]
1137
exit_patterns = []
1138
for row_i, row in exit_df.iterrows():
1139
exit_patterns.append('<br>'.join(row.index[row != 0]))
1140
exit_patterns = np.asarray(exit_patterns)
1141
1142
# Prepare scatter data
1143
highest_high = df['High'].max()
1144
lowest_low = df['Low'].min()
1145
distance = (highest_high - lowest_low) / 5
1146
entry_y = df.loc[entry_df.index, 'Low'] - distance
1147
entry_y.index = pd.to_datetime(entry_y.index)
1148
exit_y = df.loc[exit_df.index, 'High'] + distance
1149
exit_y.index = pd.to_datetime(exit_y.index)
1150
1151
# Prepare signals
1152
entry_signals = pd.Series.vbt.empty_like(entry_y, True)
1153
exit_signals = pd.Series.vbt.empty_like(exit_y, True)
1154
1155
# Build graph
1156
height = int(9 / 21 * 2 / 3 * window_width)
1157
fig = df.vbt.ohlcv.plot(
1158
plot_type=plot_type,
1159
**merge_dicts(
1160
default_layout,
1161
dict(
1162
width=None,
1163
height=max(500, height),
1164
margin=dict(r=40),
1165
hovermode="closest",
1166
xaxis2=dict(
1167
title='Date'
1168
),
1169
yaxis2=dict(
1170
title='Volume'
1171
),
1172
yaxis=dict(
1173
title='Price',
1174
)
1175
)
1176
)
1177
)
1178
entry_signals.vbt.signals.plot_as_entry_markers(
1179
y=entry_y,
1180
trace_kwargs=dict(
1181
customdata=entry_patterns[:, None],
1182
hovertemplate='%{x}<br>%{customdata[0]}',
1183
name='Bullish signal'
1184
),
1185
add_trace_kwargs=dict(row=1, col=1),
1186
fig=fig
1187
)
1188
exit_signals.vbt.signals.plot_as_exit_markers(
1189
y=exit_y,
1190
trace_kwargs=dict(
1191
customdata=exit_patterns[:, None],
1192
hovertemplate='%{x}<br>%{customdata[0]}',
1193
name='Bearish signal'
1194
),
1195
add_trace_kwargs=dict(row=1, col=1),
1196
fig=fig
1197
)
1198
fig.update_xaxes(gridcolor=gridcolor)
1199
fig.update_yaxes(gridcolor=gridcolor, zerolinecolor=gridcolor)
1200
figure = dict(data=fig.data, layout=fig.layout)
1201
1202
mimic_strategy = 'mimic_strategy' in prob_options
1203
ctx = dash.callback_context
1204
if ctx.triggered:
1205
control_id = ctx.triggered[0]['prop_id'].split('.')[0]
1206
if control_id == 'reset_button':
1207
mimic_strategy = 'mimic_strategy' in default_prob_options
1208
entry_prob = default_entry_prob
1209
exit_prob = default_exit_prob
1210
if mimic_strategy:
1211
entry_prob = np.round(len(entry_df.index) / len(df.index) * 100, 4)
1212
exit_prob = np.round(len(exit_df.index) / len(df.index) * 100, 4)
1213
return figure, mimic_strategy, entry_prob, exit_prob
1214
1215
1216
def simulate_portfolio(df, interval, date_range, selected_data, entry_patterns, exit_patterns,
1217
entry_dates, exit_dates, fees, fixed_fees, slippage, direction, conflict_mode,
1218
sim_options, n_random_strat, prob_options, entry_prob, exit_prob):
1219
"""Simulate portfolio of the main strategy, buy & hold strategy, and a bunch of random strategies."""
1220
# Filter by date
1221
df = df.iloc[date_range[0]:date_range[1] + 1]
1222
1223
# Run pattern recognition indicators and combine results
1224
talib_inputs = {
1225
'open': df['Open'].values,
1226
'high': df['High'].values,
1227
'low': df['Low'].values,
1228
'close': df['Close'].values,
1229
'volume': df['Volume'].values
1230
}
1231
entry_patterns += ['CUSTOM']
1232
exit_patterns += ['CUSTOM']
1233
all_patterns = list(set(entry_patterns + exit_patterns))
1234
entry_i = [all_patterns.index(p) for p in entry_patterns]
1235
exit_i = [all_patterns.index(p) for p in exit_patterns]
1236
signals = np.full((len(df.index), len(all_patterns)), 0., dtype=np.float64)
1237
for i, pattern in enumerate(all_patterns):
1238
if pattern != 'CUSTOM':
1239
signals[:, i] = abstract.Function(pattern)(talib_inputs)
1240
signals[np.flatnonzero(df.index.isin(entry_dates)), all_patterns.index('CUSTOM')] += 100.
1241
signals[np.flatnonzero(df.index.isin(exit_dates)), all_patterns.index('CUSTOM')] += -100.
1242
signals /= 100. # TA-Lib functions have output in increments of 100
1243
1244
# Filter signals
1245
if selected_data is not None:
1246
new_signals = np.full_like(signals, 0.)
1247
for point in selected_data['points']:
1248
if 'customdata' in point:
1249
point_patterns = point['customdata'][0].split('<br>')
1250
pi = df.index.get_loc(point['x'])
1251
for p in point_patterns:
1252
pc = all_patterns.index(p)
1253
new_signals[pi, pc] = signals[pi, pc]
1254
signals = new_signals
1255
1256
# Generate size for main
1257
def _generate_size(signals):
1258
entry_signals = signals[:, entry_i]
1259
exit_signals = signals[:, exit_i]
1260
return np.where(entry_signals > 0, entry_signals, 0).sum(axis=1) + \
1261
np.where(exit_signals < 0, exit_signals, 0).sum(axis=1)
1262
1263
main_size = np.empty((len(df.index),), dtype=np.float64)
1264
main_size[0] = 0 # avoid looking into future
1265
main_size[1:] = _generate_size(signals)[:-1]
1266
1267
# Generate size for buy & hold
1268
hold_size = np.full_like(main_size, 0.)
1269
hold_size[0] = np.inf
1270
1271
# Generate size for random
1272
def _shuffle_along_axis(a, axis):
1273
idx = np.random.rand(*a.shape).argsort(axis=axis)
1274
return np.take_along_axis(a, idx, axis=axis)
1275
1276
rand_size = np.empty((len(df.index), n_random_strat), dtype=np.float64)
1277
rand_size[0] = 0 # avoid looking into future
1278
if 'mimic_strategy' in prob_options:
1279
for i in range(n_random_strat):
1280
rand_signals = _shuffle_along_axis(signals, 0)
1281
rand_size[1:, i] = _generate_size(rand_signals)[:-1]
1282
else:
1283
entry_signals = pd.DataFrame.vbt.signals.generate_random(
1284
(rand_size.shape[0] - 1, rand_size.shape[1]), prob=entry_prob / 100).values
1285
exit_signals = pd.DataFrame.vbt.signals.generate_random(
1286
(rand_size.shape[0] - 1, rand_size.shape[1]), prob=exit_prob / 100).values
1287
rand_size[1:, :] = np.where(entry_signals, 1., 0.) - np.where(exit_signals, 1., 0.)
1288
1289
# Simulate portfolio
1290
def _simulate_portfolio(size, init_cash='autoalign'):
1291
return Portfolio.from_signals(
1292
close=df['Close'],
1293
entries=size > 0,
1294
exits=size < 0,
1295
price=df['Open'],
1296
size=np.abs(size),
1297
direction=direction,
1298
upon_dir_conflict=conflict_mode,
1299
accumulate='allow_accumulate' in sim_options,
1300
init_cash=init_cash,
1301
fees=float(fees) / 100,
1302
fixed_fees=float(fixed_fees),
1303
slippage=(float(slippage) / 100) * (df['High'] / df['Open'] - 1),
1304
freq=interval
1305
)
1306
1307
# Align initial cash across main and random strategies
1308
aligned_portfolio = _simulate_portfolio(np.hstack((main_size[:, None], rand_size)))
1309
# Fixate initial cash for indexing
1310
aligned_portfolio = aligned_portfolio.replace(
1311
init_cash=aligned_portfolio.init_cash
1312
)
1313
# Separate portfolios
1314
main_portfolio = aligned_portfolio.iloc[0]
1315
rand_portfolio = aligned_portfolio.iloc[1:]
1316
1317
# Simulate buy & hold portfolio
1318
hold_portfolio = _simulate_portfolio(hold_size, init_cash=main_portfolio.init_cash)
1319
1320
return main_portfolio, hold_portfolio, rand_portfolio
1321
1322
1323
@app.callback(
1324
[Output('portfolio_graph', 'figure'),
1325
Output('stats_table', 'data'),
1326
Output('stats_signal', 'children'),
1327
Output('metric_dropdown', 'options'),
1328
Output('metric_dropdown', 'value')],
1329
[Input('window_width', 'children'),
1330
Input('subplot_dropdown', 'value'),
1331
Input('data_signal', 'children'),
1332
Input('symbol_input', 'value'),
1333
Input('interval_dropdown', 'value'),
1334
Input('date_slider', 'value'),
1335
Input('ohlcv_graph', 'selectedData'),
1336
Input('entry_pattern_dropdown', 'value'),
1337
Input('exit_pattern_dropdown', 'value'),
1338
Input('candle_settings_signal', 'children'),
1339
Input('custom_entry_dropdown', 'value'),
1340
Input('custom_exit_dropdown', 'value'),
1341
Input('fees_input', 'value'),
1342
Input('fixed_fees_input', 'value'),
1343
Input('slippage_input', 'value'),
1344
Input('direction_dropdown', 'value'),
1345
Input('conflict_mode_dropdown', 'value'),
1346
Input('sim_checklist', 'value'),
1347
Input('n_random_strat_input', 'value'),
1348
Input('prob_checklist', 'value'),
1349
Input("entry_prob_input", "value"),
1350
Input("exit_prob_input", "value"),
1351
Input('stats_checklist', 'value'),
1352
Input("reset_button", "n_clicks")],
1353
[State('metric_dropdown', 'value')]
1354
)
1355
def update_stats(window_width, subplots, df_json, symbol, interval, date_range, selected_data,
1356
entry_patterns, exit_patterns, _1, entry_dates, exit_dates, fees, fixed_fees,
1357
slippage, direction, conflict_mode, sim_options, n_random_strat, prob_options,
1358
entry_prob, exit_prob, stats_options, _2, curr_metric):
1359
"""Final stage where we calculate key performance metrics and compare strategies."""
1360
df = pd.read_json(df_json, orient='split')
1361
1362
# Simulate portfolio
1363
main_portfolio, hold_portfolio, rand_portfolio = simulate_portfolio(
1364
df, interval, date_range, selected_data, entry_patterns, exit_patterns,
1365
entry_dates, exit_dates, fees, fixed_fees, slippage, direction, conflict_mode,
1366
sim_options, n_random_strat, prob_options, entry_prob, exit_prob)
1367
1368
subplot_settings = dict()
1369
if 'cum_returns' in subplots:
1370
subplot_settings['cum_returns'] = dict(
1371
benchmark_kwargs=dict(
1372
trace_kwargs=dict(
1373
line=dict(
1374
color=adjust_opacity(color_schema['yellow'], 0.5)
1375
),
1376
name=symbol
1377
)
1378
)
1379
)
1380
height = int(6 / 21 * 2 / 3 * window_width)
1381
fig = main_portfolio.plot(
1382
subplots=subplots,
1383
subplot_settings=subplot_settings,
1384
**merge_dicts(
1385
default_layout,
1386
dict(
1387
width=None,
1388
height=len(subplots) * max(300, height) if len(subplots) > 1 else max(350, height)
1389
)
1390
)
1391
)
1392
fig.update_traces(xaxis="x" if len(subplots) == 1 else "x" + str(len(subplots)))
1393
fig.update_xaxes(
1394
gridcolor=gridcolor
1395
)
1396
fig.update_yaxes(
1397
gridcolor=gridcolor,
1398
zerolinecolor=gridcolor
1399
)
1400
1401
def _chop_microseconds(delta):
1402
return delta - pd.Timedelta(microseconds=delta.microseconds, nanoseconds=delta.nanoseconds)
1403
1404
def _metric_to_str(x):
1405
if isinstance(x, float):
1406
return '%.2f' % x
1407
if isinstance(x, pd.Timedelta):
1408
return str(_chop_microseconds(x))
1409
return str(x)
1410
1411
incl_open = 'incl_open' in stats_options
1412
use_positions = 'use_positions' in stats_options
1413
main_stats = main_portfolio.stats(settings=dict(incl_open=incl_open, use_positions=use_positions))
1414
hold_stats = hold_portfolio.stats(settings=dict(incl_open=True, use_positions=use_positions))
1415
rand_stats = rand_portfolio.stats(settings=dict(incl_open=incl_open, use_positions=use_positions), agg_func=None)
1416
rand_stats_median = rand_stats.iloc[:, 3:].median(axis=0)
1417
rand_stats_mean = rand_stats.iloc[:, 3:].mean(axis=0)
1418
rand_stats_std = rand_stats.iloc[:, 3:].std(axis=0, ddof=0)
1419
stats_mean_diff = main_stats.iloc[3:] - rand_stats_mean
1420
1421
def _to_float(x):
1422
if pd.isnull(x):
1423
return np.nan
1424
if isinstance(x, float):
1425
if np.allclose(x, 0):
1426
return 0.
1427
if isinstance(x, pd.Timedelta):
1428
return float(x.total_seconds())
1429
return float(x)
1430
1431
z = stats_mean_diff.apply(_to_float) / rand_stats_std.apply(_to_float)
1432
1433
table_data = pd.DataFrame(columns=stats_table_columns)
1434
table_data.iloc[:, 0] = main_stats.index
1435
table_data.iloc[:, 1] = hold_stats.apply(_metric_to_str).values
1436
table_data.iloc[:3, 2] = table_data.iloc[:3, 1]
1437
table_data.iloc[3:, 2] = rand_stats_median.apply(_metric_to_str).values
1438
table_data.iloc[:, 3] = main_stats.apply(_metric_to_str).values
1439
table_data.iloc[3:, 4] = z.apply(_metric_to_str).values
1440
1441
metric = curr_metric
1442
ctx = dash.callback_context
1443
if ctx.triggered:
1444
control_id = ctx.triggered[0]['prop_id'].split('.')[0]
1445
if control_id == 'reset_button':
1446
metric = default_metric
1447
if metric is None:
1448
metric = default_metric
1449
return dict(data=fig.data, layout=fig.layout), \
1450
table_data.to_dict("records"), \
1451
json.dumps({
1452
'main': {m: [_to_float(main_stats[m])] for m in main_stats.index[3:]},
1453
'hold': {m: [_to_float(hold_stats[m])] for m in main_stats.index[3:]},
1454
'rand': {m: rand_stats[m].apply(_to_float).values.tolist() for m in main_stats.index[3:]}
1455
}), \
1456
[{"value": i, "label": i} for i in main_stats.index[3:]], \
1457
metric
1458
1459
1460
@app.callback(
1461
Output('metric_graph', 'figure'),
1462
[Input('window_width', 'children'),
1463
Input('stats_signal', 'children'),
1464
Input('metric_dropdown', 'value')]
1465
)
1466
def update_metric_stats(window_width, stats_json, metric):
1467
"""Once a new metric has been selected, plot its distribution."""
1468
stats_dict = json.loads(stats_json)
1469
height = int(9 / 21 * 2 / 3 * 2 / 3 * window_width)
1470
return dict(
1471
data=[
1472
go.Box(
1473
x=stats_dict['rand'][metric],
1474
quartilemethod="linear",
1475
jitter=0.3,
1476
pointpos=1.8,
1477
boxpoints='all',
1478
boxmean='sd',
1479
hoveron="points",
1480
hovertemplate='%{x}<br>Random',
1481
name='',
1482
marker=dict(
1483
color=color_schema['blue'],
1484
opacity=0.5,
1485
size=8,
1486
),
1487
),
1488
go.Box(
1489
x=stats_dict['hold'][metric],
1490
quartilemethod="linear",
1491
boxpoints="all",
1492
jitter=0,
1493
pointpos=1.8,
1494
hoveron="points",
1495
hovertemplate='%{x}<br>Buy & Hold',
1496
fillcolor="rgba(0,0,0,0)",
1497
line=dict(color="rgba(0,0,0,0)"),
1498
name='',
1499
marker=dict(
1500
color=color_schema['orange'],
1501
size=8,
1502
),
1503
),
1504
go.Box(
1505
x=stats_dict['main'][metric],
1506
quartilemethod="linear",
1507
boxpoints="all",
1508
jitter=0,
1509
pointpos=1.8,
1510
hoveron="points",
1511
hovertemplate='%{x}<br>Strategy',
1512
fillcolor="rgba(0,0,0,0)",
1513
line=dict(color="rgba(0,0,0,0)"),
1514
name='',
1515
marker=dict(
1516
color=color_schema['green'],
1517
size=8,
1518
),
1519
),
1520
],
1521
layout=merge_dicts(
1522
default_layout,
1523
dict(
1524
height=max(350, height),
1525
showlegend=False,
1526
margin=dict(l=60, r=20, t=40, b=20),
1527
hovermode="closest",
1528
xaxis=dict(
1529
gridcolor=gridcolor,
1530
title=metric,
1531
side='top'
1532
),
1533
yaxis=dict(
1534
gridcolor=gridcolor
1535
),
1536
)
1537
)
1538
)
1539
1540
1541
@app.callback(
1542
[Output('symbol_input', 'value'),
1543
Output('period_dropdown', 'value'),
1544
Output('interval_dropdown', 'value'),
1545
Output("yf_checklist", "value"),
1546
Output("entry_n_random_input", "value"),
1547
Output("exit_checklist", "value"),
1548
Output('candle_settings_table', 'data'),
1549
Output('custom_entry_dropdown', 'value'),
1550
Output('custom_exit_dropdown', 'value'),
1551
Output('fees_input', 'value'),
1552
Output('fixed_fees_input', 'value'),
1553
Output('slippage_input', 'value'),
1554
Output('conflict_mode_dropdown', 'value'),
1555
Output('direction_dropdown', 'value'),
1556
Output('sim_checklist', 'value'),
1557
Output('n_random_strat_input', 'value'),
1558
Output("prob_checklist", "value"),
1559
Output('stats_checklist', 'value')],
1560
[Input("reset_button", "n_clicks")],
1561
prevent_initial_call=True
1562
)
1563
def reset_settings(_):
1564
"""Reset most settings. Other settings are reset in their callbacks."""
1565
return default_symbol, \
1566
default_period, \
1567
default_interval, \
1568
default_yf_options, \
1569
default_entry_n_random, \
1570
default_exit_options, \
1571
default_candle_settings.to_dict("records"), \
1572
default_entry_dates, \
1573
default_exit_dates, \
1574
default_fees, \
1575
default_fixed_fees, \
1576
default_slippage, \
1577
default_conflict_mode, \
1578
default_direction, \
1579
default_sim_options, \
1580
default_n_random_strat, \
1581
default_prob_options, \
1582
default_stats_options
1583
1584
1585
if __name__ == '__main__':
1586
app.run_server(host=HOST, port=PORT, debug=DEBUG)
1587
1588