Upload 6 files
Browse files- app.py +148 -0
- data_fetcher.py +6 -0
- gradio_app.py +65 -0
- indicators.py +252 -0
- requirements.txt +8 -0
- strategies.py +108 -0
app.py
ADDED
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from dash import Dash, Input, Output, State, callback, dcc, html
|
2 |
+
import dash_bootstrap_components as dbc
|
3 |
+
import pandas as pd
|
4 |
+
from indicators import SMC
|
5 |
+
from data_fetcher import fetch
|
6 |
+
from strategies import smc_plot_backtest, smc_ema_plot_backtest
|
7 |
+
|
8 |
+
# Load symbols data
|
9 |
+
symbols = pd.read_csv('data/Ticker_List_NSE_India.csv')
|
10 |
+
|
11 |
+
# Initialize the app with a Bootstrap theme
|
12 |
+
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
|
13 |
+
|
14 |
+
# Function to create the layout
|
15 |
+
app.layout = dbc.Container([
|
16 |
+
dbc.Row([
|
17 |
+
html.H1("Algorithmic Trading Dashboard", className="text-center mb-4")
|
18 |
+
]),
|
19 |
+
|
20 |
+
dbc.Row([
|
21 |
+
html.Label("Select Company Name", className="form-label"),
|
22 |
+
dcc.Dropdown(
|
23 |
+
id="name",
|
24 |
+
options=[{"label": name, "value": name} for name in symbols['NAME OF COMPANY'].unique()],
|
25 |
+
value='',
|
26 |
+
placeholder="Select a company",
|
27 |
+
className="mb-3"
|
28 |
+
),
|
29 |
+
|
30 |
+
html.Label("Select Strategy", className="form-label"),
|
31 |
+
dcc.Dropdown(
|
32 |
+
id="strategy",
|
33 |
+
options=['SMC', 'SMC with EMA'],
|
34 |
+
value='',
|
35 |
+
placeholder="Select Strategy",
|
36 |
+
className="mb-3"
|
37 |
+
),
|
38 |
+
|
39 |
+
html.Label("Swing High/Low Window Size", className="form-label"),
|
40 |
+
dcc.Input(
|
41 |
+
id="window",
|
42 |
+
type="number",
|
43 |
+
value=10,
|
44 |
+
placeholder="Enter window size",
|
45 |
+
className="form-control mb-3"
|
46 |
+
),
|
47 |
+
|
48 |
+
]),
|
49 |
+
|
50 |
+
html.Div([
|
51 |
+
dbc.Row([
|
52 |
+
dbc.Col([
|
53 |
+
html.Label("Fast EMA Length: ", className="form-label"),
|
54 |
+
dcc.Input(
|
55 |
+
id="ema1",
|
56 |
+
type="number",
|
57 |
+
value=9,
|
58 |
+
placeholder="Enter EMA Length",
|
59 |
+
# className="form-control mb-3"
|
60 |
+
className = "text-nowrap"
|
61 |
+
),
|
62 |
+
], md=8),
|
63 |
+
|
64 |
+
dbc.Col([
|
65 |
+
html.Label("Slow EMA Length: ", className="form-label"),
|
66 |
+
dcc.Input(
|
67 |
+
id="ema2",
|
68 |
+
type="number",
|
69 |
+
value=21,
|
70 |
+
placeholder="Enter EMA size",
|
71 |
+
# className="form-control mb-3"
|
72 |
+
className="text-nowrap"
|
73 |
+
),
|
74 |
+
|
75 |
+
dbc.Col([dcc.Checklist(['Close on EMA crossover'], id='closecross', className="text-nowrap")]),
|
76 |
+
|
77 |
+
]),
|
78 |
+
]),
|
79 |
+
], style={'display': 'block'}, id='smc_ema'
|
80 |
+
),
|
81 |
+
|
82 |
+
dbc.Button("Run", id="submit-button", color="primary", className="w-100 mb-4"),
|
83 |
+
|
84 |
+
dbc.Row([
|
85 |
+
html.H5("Order Block Chart", className="text-center mb-3"),
|
86 |
+
html.Iframe(
|
87 |
+
src="assets/SMC.html",
|
88 |
+
style={"height": "450px", "width": "95%", "border": "none"},
|
89 |
+
className="mb-4"
|
90 |
+
),
|
91 |
+
|
92 |
+
html.H5("Backtest Results", className="text-center mb-3"),
|
93 |
+
html.Iframe(
|
94 |
+
src="assets/backtest_results.html",
|
95 |
+
style={"height": "1067px", "width": "95%", "border": "none"}
|
96 |
+
),
|
97 |
+
])
|
98 |
+
], fluid=True)
|
99 |
+
|
100 |
+
|
101 |
+
@callback(
|
102 |
+
Output("smc_ema", 'style'),
|
103 |
+
Input("strategy", 'value')
|
104 |
+
)
|
105 |
+
def update_layout(strategy):
|
106 |
+
if strategy=='SMC with EMA':
|
107 |
+
return {'display':'block'}
|
108 |
+
else:
|
109 |
+
return {'display':'none'}
|
110 |
+
|
111 |
+
|
112 |
+
# Callback for updating the visualizations
|
113 |
+
@callback(
|
114 |
+
Input("submit-button", "n_clicks"),
|
115 |
+
State("name", "value"),
|
116 |
+
State("window", "value"),
|
117 |
+
State("strategy", "value"),
|
118 |
+
State("ema1", "value"),
|
119 |
+
State("ema2", "value"),
|
120 |
+
State("closecross", "value")
|
121 |
+
)
|
122 |
+
def update_visuals(n_clicks, name, window, strategy, ema1, ema2, closecross):
|
123 |
+
if n_clicks <= 0 or not name:
|
124 |
+
return
|
125 |
+
|
126 |
+
# Clear existing files
|
127 |
+
open('assets/backtest_results.html', 'w').close()
|
128 |
+
open('assets/SMC.html', 'w').close()
|
129 |
+
|
130 |
+
ticker = symbols[symbols['NAME OF COMPANY'] == name]['YahooEquiv'].values[0]
|
131 |
+
data = fetch(ticker, '1mo', '15m')
|
132 |
+
|
133 |
+
fig = SMC(data=data, swing_hl_window_sz=window).plot(show=False).update_layout(title=dict(text=ticker))
|
134 |
+
|
135 |
+
print(strategy)
|
136 |
+
if strategy=='SMC':
|
137 |
+
smc_plot_backtest(data, 'assets/backtest_results.html', swing_hl=window)
|
138 |
+
elif strategy=='SMC with EMA':
|
139 |
+
smc_ema_plot_backtest(data, 'assets/backtest_results.html', ema1, ema2, closecross)
|
140 |
+
|
141 |
+
fig.write_html('assets/SMC.html')
|
142 |
+
|
143 |
+
if __name__ == "__main__":
|
144 |
+
# Clear initial files
|
145 |
+
open('assets/backtest_results.html', 'w').close()
|
146 |
+
open('assets/SMC.html', 'w').close()
|
147 |
+
|
148 |
+
app.run(debug=True)
|
data_fetcher.py
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import yfinance as yf
|
2 |
+
|
3 |
+
def fetch(symbol, period, interval):
|
4 |
+
df = yf.download(symbol, period=period, interval=interval)
|
5 |
+
df.columns =df.columns.get_level_values(0)
|
6 |
+
return df
|
gradio_app.py
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from data_fetcher import fetch
|
3 |
+
from indicators import SMC
|
4 |
+
from strategies import smc_plot_backtest, smc_ema_plot_backtest
|
5 |
+
import pandas as pd
|
6 |
+
|
7 |
+
symbols = pd.read_csv('data/Ticker_List_NSE_India.csv')
|
8 |
+
|
9 |
+
def run(stock, strategy, swing_hl, ema1, ema2, cross_close):
|
10 |
+
ticker = symbols[symbols['NAME OF COMPANY'] == stock]['YahooEquiv'].values[0]
|
11 |
+
data = fetch(ticker, period='1mo', interval='15m')
|
12 |
+
|
13 |
+
smc_plot_backtest(data, 'test.html', swing_hl)
|
14 |
+
|
15 |
+
signal_plot = SMC(data=data, swing_hl_window_sz=swing_hl).plot(show=False).update_layout(title=dict(text=ticker))
|
16 |
+
backtest_plot = gr.Plot()
|
17 |
+
|
18 |
+
if strategy == "SMC":
|
19 |
+
backtest_plot = smc_plot_backtest(data, 'test.html', swing_hl)
|
20 |
+
|
21 |
+
if strategy == "SMC with EMA":
|
22 |
+
backtest_plot = smc_ema_plot_backtest(data, 'test.html', ema1, ema2, cross_close)
|
23 |
+
|
24 |
+
return signal_plot, backtest_plot
|
25 |
+
|
26 |
+
|
27 |
+
with gr.Blocks(fill_width=True) as app:
|
28 |
+
gr.Markdown(
|
29 |
+
'# Algorithmic Trading Dashboard'
|
30 |
+
)
|
31 |
+
stock = gr.Dropdown(symbols['NAME OF COMPANY'].unique().tolist(), label='Select Company', value=None)
|
32 |
+
|
33 |
+
with gr.Row():
|
34 |
+
strategy = gr.Dropdown(['SMC', 'SMC with EMA'], label='Strategy', value=None)
|
35 |
+
swing_hl = gr.Number(label="Swing High/Low Window Size", value=10, interactive=True)
|
36 |
+
|
37 |
+
@gr.render(inputs=[strategy])
|
38 |
+
def show_extra(strat):
|
39 |
+
if strat == "SMC with EMA":
|
40 |
+
with gr.Row():
|
41 |
+
ema1 = gr.Number(label='Fast EMA length', value=9)
|
42 |
+
ema2 = gr.Number(label='Slow EMA length', value=21)
|
43 |
+
cross_close = gr.Checkbox(label='Close trade on EMA crossover')
|
44 |
+
input = [stock, strategy, swing_hl, ema1, ema2, cross_close]
|
45 |
+
|
46 |
+
elif strat == "SMC":
|
47 |
+
input = [stock, strategy, swing_hl]
|
48 |
+
else:
|
49 |
+
input = []
|
50 |
+
|
51 |
+
btn.click(
|
52 |
+
run,
|
53 |
+
inputs=input,
|
54 |
+
outputs=[signal_plot, backtest_plot]
|
55 |
+
)
|
56 |
+
|
57 |
+
btn = gr.Button("Run")
|
58 |
+
|
59 |
+
with gr.Row():
|
60 |
+
signal_plot = gr.Plot(label='Signal plot')
|
61 |
+
|
62 |
+
with gr.Row():
|
63 |
+
backtest_plot = gr.Plot(label='Backtesting plot')
|
64 |
+
|
65 |
+
app.launch(debug=True)
|
indicators.py
ADDED
@@ -0,0 +1,252 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pandas as pd
|
2 |
+
import numpy as np
|
3 |
+
import plotly.graph_objects as go
|
4 |
+
from plotly.subplots import make_subplots
|
5 |
+
|
6 |
+
class SMC:
|
7 |
+
def __init__(self, data, swing_hl_window_sz=10):
|
8 |
+
self.data = data
|
9 |
+
self.data['Date'] = self.data.index.to_series()
|
10 |
+
self.swing_hl_window_sz = swing_hl_window_sz
|
11 |
+
self.order_blocks = self.order_block()
|
12 |
+
|
13 |
+
def backtest_buy_signal(self):
|
14 |
+
bull_ob = self.order_blocks[(self.order_blocks['OB']==1) & (self.order_blocks['MitigatedIndex']!=0)]
|
15 |
+
arr = np.zeros(len(self.data))
|
16 |
+
arr[bull_ob['MitigatedIndex'].apply(lambda x: int(x))] = 1
|
17 |
+
return arr
|
18 |
+
|
19 |
+
def backtest_sell_signal(self):
|
20 |
+
bear_ob = self.order_blocks[(self.order_blocks['OB'] == -1) & (self.order_blocks['MitigatedIndex'] != 0)]
|
21 |
+
arr = np.zeros(len(self.data))
|
22 |
+
arr[bear_ob['MitigatedIndex'].apply(lambda x: int(x))] = -1
|
23 |
+
return arr
|
24 |
+
|
25 |
+
def swing_highs_lows(self, window_size):
|
26 |
+
l = self.data['Low'].reset_index(drop=True)
|
27 |
+
h = self.data['High'].reset_index(drop=True)
|
28 |
+
swing_highs = (h.rolling(window_size, center=True).max() / h == 1.)
|
29 |
+
swing_lows = (l.rolling(window_size, center=True).min() / l == 1.)
|
30 |
+
return pd.DataFrame({'Date':self.data.index.to_series(), 'highs':swing_highs.values, 'lows':swing_lows.values})
|
31 |
+
|
32 |
+
def fvg(self):
|
33 |
+
"""
|
34 |
+
FVG - Fair Value Gap
|
35 |
+
A fair value gap is when the previous high is lower than the next low if the current candle is bullish.
|
36 |
+
Or when the previous low is higher than the next high if the current candle is bearish.
|
37 |
+
|
38 |
+
parameters:
|
39 |
+
|
40 |
+
returns:
|
41 |
+
FVG = 1 if bullish fair value gap, -1 if bearish fair value gap
|
42 |
+
Top = the top of the fair value gap
|
43 |
+
Bottom = the bottom of the fair value gap
|
44 |
+
MitigatedIndex = the index of the candle that mitigated the fair value gap
|
45 |
+
"""
|
46 |
+
|
47 |
+
fvg = np.where(
|
48 |
+
(
|
49 |
+
(self.data["High"].shift(1) < self.data["Low"].shift(-1))
|
50 |
+
& (self.data["Close"] > self.data["Open"])
|
51 |
+
)
|
52 |
+
| (
|
53 |
+
(self.data["Low"].shift(1) > self.data["High"].shift(-1))
|
54 |
+
& (self.data["Close"] < self.data["Open"])
|
55 |
+
),
|
56 |
+
np.where(self.data["Close"] > self.data["Open"], 1, -1),
|
57 |
+
np.nan,
|
58 |
+
)
|
59 |
+
|
60 |
+
top = np.where(
|
61 |
+
~np.isnan(fvg),
|
62 |
+
np.where(
|
63 |
+
self.data["Close"] > self.data["Open"],
|
64 |
+
self.data["Low"].shift(-1),
|
65 |
+
self.data["Low"].shift(1),
|
66 |
+
),
|
67 |
+
np.nan,
|
68 |
+
)
|
69 |
+
|
70 |
+
bottom = np.where(
|
71 |
+
~np.isnan(fvg),
|
72 |
+
np.where(
|
73 |
+
self.data["Close"] > self.data["Open"],
|
74 |
+
self.data["High"].shift(1),
|
75 |
+
self.data["High"].shift(-1),
|
76 |
+
),
|
77 |
+
np.nan,
|
78 |
+
)
|
79 |
+
|
80 |
+
mitigated_index = np.zeros(len(self.data), dtype=np.int32)
|
81 |
+
for i in np.where(~np.isnan(fvg))[0]:
|
82 |
+
mask = np.zeros(len(self.data), dtype=np.bool_)
|
83 |
+
if fvg[i] == 1:
|
84 |
+
mask = self.data["Low"][i + 2:] <= top[i]
|
85 |
+
elif fvg[i] == -1:
|
86 |
+
mask = self.data["High"][i + 2:] >= bottom[i]
|
87 |
+
if np.any(mask):
|
88 |
+
j = np.argmax(mask) + i + 2
|
89 |
+
mitigated_index[i] = j
|
90 |
+
|
91 |
+
mitigated_index = np.where(np.isnan(fvg), np.nan, mitigated_index)
|
92 |
+
|
93 |
+
return pd.concat(
|
94 |
+
[
|
95 |
+
pd.Series(fvg.flatten(), name="FVG"),
|
96 |
+
pd.Series(top.flatten(), name="Top"),
|
97 |
+
pd.Series(bottom.flatten(), name="Bottom"),
|
98 |
+
pd.Series(mitigated_index.flatten(), name="MitigatedIndex"),
|
99 |
+
],
|
100 |
+
axis=1,
|
101 |
+
)
|
102 |
+
|
103 |
+
def order_block(self, imb_perc=.1, join_consecutive=True):
|
104 |
+
hl = self.swing_highs_lows(self.swing_hl_window_sz)
|
105 |
+
|
106 |
+
ob = np.where(
|
107 |
+
(
|
108 |
+
((self.data["High"]*((100+imb_perc)/100)) < self.data["Low"].shift(-2))
|
109 |
+
& ((hl['lows']==True) | (hl['lows'].shift(1)==True))
|
110 |
+
)
|
111 |
+
| (
|
112 |
+
(self.data["Low"] > (self.data["High"].shift(-2)*((100+imb_perc)/100)))
|
113 |
+
& ((hl['highs']==True) | (hl['highs'].shift(1)==True))
|
114 |
+
),
|
115 |
+
np.where(((hl['lows']==True) | (hl['lows'].shift(1)==True)), 1, -1),
|
116 |
+
np.nan,
|
117 |
+
)
|
118 |
+
|
119 |
+
# print(ob)
|
120 |
+
|
121 |
+
top = np.where(
|
122 |
+
~np.isnan(ob),
|
123 |
+
np.where(
|
124 |
+
self.data["Close"] > self.data["Open"],
|
125 |
+
self.data["Low"].shift(-2),
|
126 |
+
self.data["Low"],
|
127 |
+
),
|
128 |
+
np.nan,
|
129 |
+
)
|
130 |
+
|
131 |
+
bottom = np.where(
|
132 |
+
~np.isnan(ob),
|
133 |
+
np.where(
|
134 |
+
self.data["Close"] > self.data["Open"],
|
135 |
+
self.data["High"],
|
136 |
+
self.data["High"].shift(-2),
|
137 |
+
),
|
138 |
+
np.nan,
|
139 |
+
)
|
140 |
+
|
141 |
+
# if join_consecutive:
|
142 |
+
# for i in range(len(ob) - 1):
|
143 |
+
# if ob[i] == ob[i + 1]:
|
144 |
+
# top[i + 1] = max(top[i], top[i + 1])
|
145 |
+
# bottom[i + 1] = min(bottom[i], bottom[i + 1])
|
146 |
+
# ob[i] = top[i] = bottom[i] = np.nan
|
147 |
+
|
148 |
+
mitigated_index = np.zeros(len(self.data), dtype=np.int32)
|
149 |
+
for i in np.where(~np.isnan(ob))[0]:
|
150 |
+
mask = np.zeros(len(self.data), dtype=np.bool_)
|
151 |
+
if ob[i] == 1:
|
152 |
+
mask = self.data["Low"][i + 3:] <= top[i]
|
153 |
+
elif ob[i] == -1:
|
154 |
+
mask = self.data["High"][i + 3:] >= bottom[i]
|
155 |
+
if np.any(mask):
|
156 |
+
j = np.argmax(mask) + i + 3
|
157 |
+
mitigated_index[i] = int(j)
|
158 |
+
ob = ob.flatten()
|
159 |
+
mitigated_index1 = np.where(np.isnan(ob), np.nan, mitigated_index)
|
160 |
+
|
161 |
+
return pd.concat(
|
162 |
+
[
|
163 |
+
pd.Series(ob.flatten(), name="OB"),
|
164 |
+
pd.Series(top.flatten(), name="Top"),
|
165 |
+
pd.Series(bottom.flatten(), name="Bottom"),
|
166 |
+
pd.Series(mitigated_index1.flatten(), name="MitigatedIndex"),
|
167 |
+
],
|
168 |
+
axis=1,
|
169 |
+
).dropna(subset=['OB'])
|
170 |
+
|
171 |
+
def plot(self, swing_hl=True, show=True):
|
172 |
+
fig = make_subplots(1, 1)
|
173 |
+
|
174 |
+
# plot the candle stick graph
|
175 |
+
fig.add_trace(go.Candlestick(x=self.data.index.to_series(),
|
176 |
+
open=self.data['Open'],
|
177 |
+
high=self.data['High'],
|
178 |
+
low=self.data['Low'],
|
179 |
+
close=self.data['Close'],
|
180 |
+
name='ohlc'))
|
181 |
+
|
182 |
+
# grab first and last observations from df.date and make a continuous date range from that
|
183 |
+
dt_all = pd.date_range(start=self.data['Date'].iloc[0], end=self.data['Date'].iloc[-1], freq='5min')
|
184 |
+
|
185 |
+
# check which dates from your source that also accur in the continuous date range
|
186 |
+
dt_obs = [d.strftime("%Y-%m-%d %H:%M:%S") for d in self.data['Date']]
|
187 |
+
|
188 |
+
# isolate missing timestamps
|
189 |
+
dt_breaks = [d for d in dt_all.strftime("%Y-%m-%d %H:%M:%S").tolist() if not d in dt_obs]
|
190 |
+
|
191 |
+
# adjust xaxis for rangebreaks
|
192 |
+
fig.update_xaxes(rangebreaks=[dict(dvalue=5 * 60 * 1000, values=dt_breaks)])
|
193 |
+
|
194 |
+
print(self.order_blocks.head())
|
195 |
+
print(self.order_blocks.index.to_list())
|
196 |
+
|
197 |
+
ob_df = self.data.iloc[self.order_blocks.index.to_list()]
|
198 |
+
# print(ob_df)
|
199 |
+
|
200 |
+
fig.add_trace(go.Scatter(
|
201 |
+
x=ob_df['Date'],
|
202 |
+
y=ob_df['Low'],
|
203 |
+
name="Order Block",
|
204 |
+
mode='markers',
|
205 |
+
marker_symbol='diamond-dot',
|
206 |
+
marker_size=13,
|
207 |
+
marker_line_width=2,
|
208 |
+
# offsetgroup=0,
|
209 |
+
))
|
210 |
+
|
211 |
+
if swing_hl:
|
212 |
+
hl = self.swing_highs_lows(self.swing_hl_window_sz)
|
213 |
+
h = hl[(hl['highs']==True)]
|
214 |
+
l = hl[hl['lows']==True]
|
215 |
+
# print(h)
|
216 |
+
# exit(0)
|
217 |
+
fig.add_trace(go.Scatter(
|
218 |
+
x=h['Date'],
|
219 |
+
y=self.data[self.data.Date.isin(h['Date'])]['High']*(100.1/100),
|
220 |
+
mode='markers',
|
221 |
+
marker_symbol="triangle-up-dot",
|
222 |
+
marker_size=10,
|
223 |
+
name='Swing High',
|
224 |
+
# offsetgroup=2,
|
225 |
+
))
|
226 |
+
fig.add_trace(go.Scatter(
|
227 |
+
x=l['Date'],
|
228 |
+
y=self.data[self.data.Date.isin(l['Date'])]['Low']*(99.9/100),
|
229 |
+
mode='markers',
|
230 |
+
marker_symbol="triangle-down-dot",
|
231 |
+
marker_size=10,
|
232 |
+
name='Swing Low',
|
233 |
+
marker_color='red',
|
234 |
+
# offsetgroup=2,
|
235 |
+
))
|
236 |
+
|
237 |
+
fig.update_layout(xaxis_rangeslider_visible=False)
|
238 |
+
if show:
|
239 |
+
fig.show()
|
240 |
+
return fig
|
241 |
+
|
242 |
+
|
243 |
+
def EMA(array, n):
|
244 |
+
return pd.Series(array).ewm(span=n, adjust=False).mean()
|
245 |
+
|
246 |
+
if __name__ == "__main__":
|
247 |
+
from data_fetcher import fetch
|
248 |
+
data = fetch('ICICIBANK.NS', period='1mo', interval='15m')
|
249 |
+
# data = fetch('RELIANCE.NS', period='1mo', interval='15m')
|
250 |
+
|
251 |
+
# print(SMC(data).backtest_buy_signal())
|
252 |
+
SMC(data).plot()
|
requirements.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
backtesting==0.3.3
|
2 |
+
numpy==2.2.0
|
3 |
+
pandas==2.2.3
|
4 |
+
bokeh==3.1.0
|
5 |
+
yfinance==0.2.50
|
6 |
+
plotly==5.24.1
|
7 |
+
dash==2.18.2
|
8 |
+
dash-bootstrap-components==1.6.0
|
strategies.py
ADDED
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from backtesting import Backtest, Strategy
|
2 |
+
from backtesting.lib import SignalStrategy, TrailingStrategy
|
3 |
+
from indicators import SMC, EMA
|
4 |
+
from data_fetcher import fetch
|
5 |
+
import pandas as pd
|
6 |
+
|
7 |
+
class SMC_test(Strategy):
|
8 |
+
swing_hl = 10
|
9 |
+
def init(self):
|
10 |
+
super().init()
|
11 |
+
|
12 |
+
self.smc_b = self.I(self.smc_buy, data=self.data.df, swing_hl=self.swing_hl)
|
13 |
+
self.smc_s = self.I(self.smc_sell, data=self.data.df, swing_hl=self.swing_hl)
|
14 |
+
|
15 |
+
|
16 |
+
def next(self):
|
17 |
+
price = self.data.Close[-1]
|
18 |
+
current_time = self.data.index[-1]
|
19 |
+
|
20 |
+
if self.smc_b[-1] == 1:
|
21 |
+
self.buy(sl=.95 * price, tp=1.05 * price)
|
22 |
+
if self.smc_s[-1] == -1:
|
23 |
+
self.sell(tp=.95 * price, sl=1.05 * price)
|
24 |
+
|
25 |
+
# Additionally, set aggressive stop-loss on trades that have been open
|
26 |
+
# for more than two days
|
27 |
+
for trade in self.trades:
|
28 |
+
if current_time - trade.entry_time > pd.Timedelta('2 days'):
|
29 |
+
if trade.is_long:
|
30 |
+
trade.sl = max(trade.sl, self.data.Low[-1])
|
31 |
+
else:
|
32 |
+
trade.sl = min(trade.sl, self.data.High[-1])
|
33 |
+
|
34 |
+
|
35 |
+
def smc_buy(self, data, swing_hl):
|
36 |
+
return SMC(data, swing_hl).backtest_buy_signal()
|
37 |
+
|
38 |
+
def smc_sell(self, data, swing_hl):
|
39 |
+
return SMC(data, swing_hl).backtest_sell_signal()
|
40 |
+
|
41 |
+
class SMC_ema(SignalStrategy, TrailingStrategy):
|
42 |
+
ema1 = 9
|
43 |
+
ema2 = 21
|
44 |
+
close_on_crossover = False
|
45 |
+
|
46 |
+
def init(self):
|
47 |
+
super().init()
|
48 |
+
|
49 |
+
self.smc_b = self.I(self.smc_buy, self.data.df)
|
50 |
+
self.smc_s = self.I(self.smc_sell, self.data.df)
|
51 |
+
|
52 |
+
|
53 |
+
close = self.data.Close
|
54 |
+
|
55 |
+
self.ma1 = self.I(EMA, close, self.ema1)
|
56 |
+
self.ma2 = self.I(EMA, close, self.ema2)
|
57 |
+
|
58 |
+
|
59 |
+
def next(self):
|
60 |
+
price = self.data.Close[-1]
|
61 |
+
current_time = self.data.index[-1]
|
62 |
+
|
63 |
+
if self.smc_b[-1] == 1 and self.ma1 > self.ma2:
|
64 |
+
self.buy(sl=.95 * price, tp=1.05 * price)
|
65 |
+
if self.smc_s[-1] == -1 and self.ma1 < self.ma2:
|
66 |
+
self.sell(tp=.95 * price, sl=1.05 * price)
|
67 |
+
|
68 |
+
# Additionally, set aggressive stop-loss on trades that have been open
|
69 |
+
# for more than two days
|
70 |
+
for trade in self.trades:
|
71 |
+
if current_time - trade.entry_time > pd.Timedelta('2 days'):
|
72 |
+
if trade.is_long:
|
73 |
+
trade.sl = max(trade.sl, self.data.Low[-1])
|
74 |
+
else:
|
75 |
+
trade.sl = min(trade.sl, self.data.High[-1])
|
76 |
+
|
77 |
+
# Close the trade if there is a moving average crossover in opposite direction
|
78 |
+
if self.close_on_crossover:
|
79 |
+
for trade in self.trades:
|
80 |
+
if trade.is_long and self.ma1 < self.ma2:
|
81 |
+
trade.close()
|
82 |
+
if trade.is_short and self.ma1 > self.ma2:
|
83 |
+
trade.close()
|
84 |
+
|
85 |
+
def smc_buy(self, data):
|
86 |
+
return SMC(data).backtest_buy_signal()
|
87 |
+
|
88 |
+
def smc_sell(self, data):
|
89 |
+
return SMC(data).backtest_sell_signal()
|
90 |
+
|
91 |
+
def smc_plot_backtest(data, filename, swing_hl, **kwargs):
|
92 |
+
bt = Backtest(data, SMC_test, **kwargs)
|
93 |
+
bt.run(swing_hl=swing_hl)
|
94 |
+
print('runned')
|
95 |
+
return bt.plot(filename=filename, open_browser=False)
|
96 |
+
|
97 |
+
def smc_ema_plot_backtest(data, filename, ema1, ema2, closecross, **kwargs):
|
98 |
+
bt = Backtest(data, SMC_ema, **kwargs)
|
99 |
+
bt.run(ema1=ema1, ema2=ema2, close_on_crossover=closecross)
|
100 |
+
return bt.plot(filename=filename, open_browser=False)
|
101 |
+
|
102 |
+
|
103 |
+
if __name__ == "__main__":
|
104 |
+
data = fetch('ICICIBANK.NS', period='1mo', interval='15m')
|
105 |
+
bt = Backtest(data, SMC_ema, commission=.002)
|
106 |
+
|
107 |
+
bt.run(ema1 = 9, ema2 = 21, close_on_crossover=True)
|
108 |
+
bt.plot()
|