Invicto69 commited on
Commit
aab0a9b
·
verified ·
1 Parent(s): 96e0391

Synced repo using 'sync_with_huggingface' Github Action

Browse files
app.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from indicators import SMC
3
+ from utils import smc_plot_backtest, smc_ema_plot_backtest, smc_structure_plot_backtest, fetch
4
+ import pandas as pd
5
+
6
+ symbols = pd.read_csv('data/Ticker_List_NSE_India.csv')
7
+ limits = pd.read_csv('data/yahoo_limits.csv')
8
+
9
+ def run(stock, interval, period, strategy, swing_hl, ema1=9, ema2=21, cross_close=False):
10
+ # Downloading ticker data.
11
+ ticker = symbols[symbols['NAME OF COMPANY'] == stock]['YahooEquiv'].values[0]
12
+ data = fetch(ticker, period, interval)
13
+
14
+ # Plotting signal plot based on strategy.
15
+ if strategy == "Order Block" or strategy == "Order Block with EMA":
16
+ signal_plot = (SMC(data=data, swing_hl_window_sz=swing_hl).
17
+ plot(order_blocks=True, swing_hl=True, show=False).
18
+ update_layout(title=dict(text=ticker)))
19
+ else:
20
+ signal_plot = (SMC(data=data, swing_hl_window_sz=swing_hl).
21
+ plot(swing_hl_v2=True, structure=True, show=False).
22
+ update_layout(title=dict(text=ticker)))
23
+
24
+ backtest_plot = gr.Plot()
25
+
26
+ # Plotting backtest plot based on strategy.
27
+ if strategy == "Order Block":
28
+ backtest_plot = smc_plot_backtest(data, 'test.html', swing_hl)
29
+ if strategy == "Order Block with EMA":
30
+ backtest_plot = smc_ema_plot_backtest(data, 'test.html', ema1, ema2, cross_close)
31
+ if strategy == "Structure trading":
32
+ backtest_plot = smc_structure_plot_backtest(data, 'test.html', swing_hl)
33
+
34
+ return signal_plot, backtest_plot
35
+
36
+
37
+ with gr.Blocks(fill_width=True) as app:
38
+ gr.Markdown(
39
+ '# Algorithmic Trading Dashboard'
40
+ )
41
+ stock = gr.Dropdown(symbols['NAME OF COMPANY'].unique().tolist(), label='Select Company', value=None)
42
+
43
+ with gr.Row():
44
+ interval = gr.Dropdown(limits['interval'].tolist(), label='Select Interval', value=None)
45
+
46
+ period_list = ['1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max']
47
+ period = gr.Dropdown(label = 'Select Period', choices=["max"], value="max")
48
+
49
+ # Updating period based on interval
50
+ def update_period(interval):
51
+ limit = limits[limits['interval'] == interval]['limit'].values[0]
52
+ idx = period_list.index(limit)
53
+ return gr.Dropdown(period_list[:idx+1]+['max'], interactive=True, label='Select Period')
54
+
55
+ interval.change(update_period, [interval], [period])
56
+
57
+ with gr.Row():
58
+ strategy = gr.Dropdown(['Order Block', 'Order Block with EMA', 'Structure trading'], label='Strategy', value=None)
59
+ swing_hl = gr.Number(label="Swing High/Low Window Size", value=10, interactive=True)
60
+
61
+ @gr.render(inputs=[strategy])
62
+ def show_extra(strat):
63
+ if strat == "Order Block with EMA":
64
+ with gr.Row():
65
+ ema1 = gr.Number(label='Fast EMA length', value=9)
66
+ ema2 = gr.Number(label='Slow EMA length', value=21)
67
+ cross_close = gr.Checkbox(label='Close trade on EMA crossover')
68
+ input = [stock, interval, period, strategy, swing_hl, ema1, ema2, cross_close]
69
+
70
+ elif strat == "Order Block" or strat == "Structure trading":
71
+ input = [stock, interval, period, strategy, swing_hl]
72
+ else:
73
+ input = []
74
+
75
+ btn.click(
76
+ run,
77
+ inputs=input,
78
+ outputs=[signal_plot, backtest_plot]
79
+ )
80
+
81
+ examples = gr.Examples(
82
+ examples=[
83
+ ["Reliance Industries Limited", "15m", "max", "Order Block", 10],
84
+ ["Reliance Industries Limited", "15m", "max", "Order Block with EMA", 10],
85
+ ["Reliance Industries Limited", "15m", "max", "Structure trading", 20],
86
+ ],
87
+ example_labels=['Order Block', 'Order Block with EMA', 'Structure trading'],
88
+ inputs=[stock, interval, period, strategy, swing_hl]
89
+ )
90
+
91
+ btn = gr.Button("Run")
92
+
93
+ with gr.Row():
94
+ signal_plot = gr.Plot(label='Signal plot')
95
+
96
+ with gr.Row():
97
+ backtest_plot = gr.Plot(label='Backtesting plot')
98
+
99
+ app.launch(debug=True)
data/Ticker_List_NSE_India.csv ADDED
The diff for this file is too large to render. See raw diff
 
data/ind_nifty50list.csv ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Company Name,Industry,Symbol,Series,ISIN Code
2
+ Adani Enterprises Ltd.,Metals & Mining,ADANIENT,EQ,INE423A01024
3
+ Adani Ports and Special Economic Zone Ltd.,Services,ADANIPORTS,EQ,INE742F01042
4
+ Apollo Hospitals Enterprise Ltd.,Healthcare,APOLLOHOSP,EQ,INE437A01024
5
+ Asian Paints Ltd.,Consumer Durables,ASIANPAINT,EQ,INE021A01026
6
+ Axis Bank Ltd.,Financial Services,AXISBANK,EQ,INE238A01034
7
+ Bajaj Auto Ltd.,Automobile and Auto Components,BAJAJ-AUTO,EQ,INE917I01010
8
+ Bajaj Finance Ltd.,Financial Services,BAJFINANCE,EQ,INE296A01024
9
+ Bajaj Finserv Ltd.,Financial Services,BAJAJFINSV,EQ,INE918I01026
10
+ Bharat Electronics Ltd.,Capital Goods,BEL,EQ,INE263A01024
11
+ Bharat Petroleum Corporation Ltd.,Oil Gas & Consumable Fuels,BPCL,EQ,INE029A01011
12
+ Bharti Airtel Ltd.,Telecommunication,BHARTIARTL,EQ,INE397D01024
13
+ Britannia Industries Ltd.,Fast Moving Consumer Goods,BRITANNIA,EQ,INE216A01030
14
+ Cipla Ltd.,Healthcare,CIPLA,EQ,INE059A01026
15
+ Coal India Ltd.,Oil Gas & Consumable Fuels,COALINDIA,EQ,INE522F01014
16
+ Dr. Reddy's Laboratories Ltd.,Healthcare,DRREDDY,EQ,INE089A01031
17
+ Dummy ITC Ltd.,Consumer Services,DUMMYITC,EQ,DUM154A01025
18
+ Eicher Motors Ltd.,Automobile and Auto Components,EICHERMOT,EQ,INE066A01021
19
+ Grasim Industries Ltd.,Construction Materials,GRASIM,EQ,INE047A01021
20
+ HCL Technologies Ltd.,Information Technology,HCLTECH,EQ,INE860A01027
21
+ HDFC Bank Ltd.,Financial Services,HDFCBANK,EQ,INE040A01034
22
+ HDFC Life Insurance Company Ltd.,Financial Services,HDFCLIFE,EQ,INE795G01014
23
+ Hero MotoCorp Ltd.,Automobile and Auto Components,HEROMOTOCO,EQ,INE158A01026
24
+ Hindalco Industries Ltd.,Metals & Mining,HINDALCO,EQ,INE038A01020
25
+ Hindustan Unilever Ltd.,Fast Moving Consumer Goods,HINDUNILVR,EQ,INE030A01027
26
+ ICICI Bank Ltd.,Financial Services,ICICIBANK,EQ,INE090A01021
27
+ ITC Ltd.,Fast Moving Consumer Goods,ITC,EQ,INE154A01025
28
+ IndusInd Bank Ltd.,Financial Services,INDUSINDBK,EQ,INE095A01012
29
+ Infosys Ltd.,Information Technology,INFY,EQ,INE009A01021
30
+ JSW Steel Ltd.,Metals & Mining,JSWSTEEL,EQ,INE019A01038
31
+ Kotak Mahindra Bank Ltd.,Financial Services,KOTAKBANK,EQ,INE237A01028
32
+ Larsen & Toubro Ltd.,Construction,LT,EQ,INE018A01030
33
+ Mahindra & Mahindra Ltd.,Automobile and Auto Components,M&M,EQ,INE101A01026
34
+ Maruti Suzuki India Ltd.,Automobile and Auto Components,MARUTI,EQ,INE585B01010
35
+ NTPC Ltd.,Power,NTPC,EQ,INE733E01010
36
+ Nestle India Ltd.,Fast Moving Consumer Goods,NESTLEIND,EQ,INE239A01024
37
+ Oil & Natural Gas Corporation Ltd.,Oil Gas & Consumable Fuels,ONGC,EQ,INE213A01029
38
+ Power Grid Corporation of India Ltd.,Power,POWERGRID,EQ,INE752E01010
39
+ Reliance Industries Ltd.,Oil Gas & Consumable Fuels,RELIANCE,EQ,INE002A01018
40
+ SBI Life Insurance Company Ltd.,Financial Services,SBILIFE,EQ,INE123W01016
41
+ Shriram Finance Ltd.,Financial Services,SHRIRAMFIN,EQ,INE721A01047
42
+ State Bank of India,Financial Services,SBIN,EQ,INE062A01020
43
+ Sun Pharmaceutical Industries Ltd.,Healthcare,SUNPHARMA,EQ,INE044A01036
44
+ Tata Consultancy Services Ltd.,Information Technology,TCS,EQ,INE467B01029
45
+ Tata Consumer Products Ltd.,Fast Moving Consumer Goods,TATACONSUM,EQ,INE192A01025
46
+ Tata Motors Ltd.,Automobile and Auto Components,TATAMOTORS,EQ,INE155A01022
47
+ Tata Steel Ltd.,Metals & Mining,TATASTEEL,EQ,INE081A01020
48
+ Tech Mahindra Ltd.,Information Technology,TECHM,EQ,INE669C01036
49
+ Titan Company Ltd.,Consumer Durables,TITAN,EQ,INE280A01028
50
+ Trent Ltd.,Consumer Services,TRENT,EQ,INE849A01020
51
+ UltraTech Cement Ltd.,Construction Materials,ULTRACEMCO,EQ,INE481G01011
52
+ Wipro Ltd.,Information Technology,WIPRO,EQ,INE075A01022
data/yahoo_limits.csv ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ interval,limit
2
+ 1m,5d
3
+ 2m,1mo
4
+ 5m,1mo
5
+ 15m,1mo
6
+ 30m,1mo
7
+ 1h,2y
8
+ 1d,ytd
9
+ 5d,ytd
10
+ 1wk,ytd
11
+ 1mo,ytd
12
+ 3mo,ytd
indicators.py ADDED
@@ -0,0 +1,630 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """
9
+ Smart Money Concept
10
+ :param data:
11
+ Should contain Open, High, Low, Close columns and 'Date' as index.
12
+ :type data: pd.DataFrame
13
+ :param swing_hl_window_sz: {int}
14
+ CHoCH Detection Period.
15
+ """
16
+ self.data = data
17
+ self.data['Date'] = self.data.index.to_series()
18
+ self.swing_hl_window_sz = swing_hl_window_sz
19
+ self.order_blocks = self.order_block()
20
+ self.swing_hl = self.swing_highs_lows_v2(self.swing_hl_window_sz)
21
+ self.structure_map = self.bos_choch(self.swing_hl)
22
+
23
+ def backtest_buy_signal_ob(self):
24
+ """
25
+ :return:
26
+ Get buy signals from order blocks mitigation index.
27
+ :rtype: np.ndarray
28
+ """
29
+ # Get only bullish order blocks which are mitigated.
30
+ bull_ob = self.order_blocks[(self.order_blocks['OB']==1) & (self.order_blocks['MitigatedIndex']!=0)]
31
+ arr = np.zeros(len(self.data))
32
+ # Mark the mitigated indices with 1.
33
+ arr[bull_ob['MitigatedIndex'].apply(lambda x: int(x))] = 1
34
+ return arr
35
+
36
+ def backtest_sell_signal_ob(self):
37
+ """
38
+ :return:
39
+ Get sell signals from order blocks mitigation index.
40
+ :rtype: np.ndarray
41
+ """
42
+ # Get only bearish order blocks which are mitigated.
43
+ bear_ob = self.order_blocks[(self.order_blocks['OB'] == -1) & (self.order_blocks['MitigatedIndex'] != 0)]
44
+ arr = np.zeros(len(self.data))
45
+ # Mark the mitigated indices with -1.
46
+ arr[bear_ob['MitigatedIndex'].apply(lambda x: int(x))] = -1
47
+ return arr
48
+
49
+ def backtest_buy_signal_structure(self):
50
+ """
51
+ :return:
52
+ Get buy signals from bullish structure broken index.
53
+ :rtype: np.ndarray
54
+ """
55
+ # Get only bullish structure.
56
+ bull_struct = self.structure_map[(self.structure_map['BOS'] == 1) | (self.structure_map['CHOCH'] == 1)]
57
+ arr = np.zeros(len(self.data))
58
+ # Mark the broken indices with 1.
59
+ arr[bull_struct['BrokenIndex'].apply(lambda x: int(x))] = 1
60
+ return arr
61
+
62
+ def backtest_sell_signal_structure(self):
63
+ """
64
+ :return:
65
+ Get buy signals from bullish structure broken index.
66
+ :rtype: np.ndarray
67
+ """
68
+ # Get only bearish structure.
69
+ bull_struct = self.structure_map[(self.structure_map['BOS'] == -1) | (self.structure_map['CHOCH'] == -1)]
70
+ arr = np.zeros(len(self.data))
71
+ # Mark the broken indices with -1.
72
+ arr[bull_struct['BrokenIndex'].apply(lambda x: int(x))] = 1
73
+ return arr
74
+
75
+ def swing_highs_lows(self, window_size):
76
+ """
77
+ Basic version of swing highs and lows. Suitable for finding swing order blocks.
78
+ :param window_size:
79
+ Window size for searching swing highs and lows
80
+ :type window_size: int
81
+ :return:
82
+ DataFrame with Date, highs(bool), lows(bool) columns
83
+ :rtype: pd.DataFrame
84
+ """
85
+ l = self.data['Low'].reset_index(drop=True)
86
+ h = self.data['High'].reset_index(drop=True)
87
+ swing_highs = (h.rolling(window_size, center=True).max() / h == 1.)
88
+ swing_lows = (l.rolling(window_size, center=True).min() / l == 1.)
89
+ return pd.DataFrame({'Date':self.data.index.to_series(), 'highs':swing_highs.values, 'lows':swing_lows.values})
90
+
91
+ def swing_highs_lows_v2(self, window_size):
92
+ """
93
+ Updated version of swing_highs_lows function. Suitable for BOS and CHoCH.
94
+ :param window_size:
95
+ Window size for searching swing highs and lows.
96
+ :type window_size: int
97
+ :return:
98
+ DataFrame with HighLow(1 for bull, -1 for bear), Level columns.
99
+ :rtype: pd.DataFrame
100
+ """
101
+ # Reversing the datapoints for .rolling() method with right to left.
102
+ l = self.data['Low'][::-1].reset_index(drop=True)
103
+ h = self.data['High'][::-1].reset_index(drop=True)
104
+ swing_highs = (h.rolling(window_size, min_periods=1).max() / h == 1.)[::-1]
105
+ swing_lows = (l.rolling(window_size, min_periods=1).min() / l == 1.)[::-1]
106
+
107
+ swing_highs.reset_index(drop=True, inplace=True)
108
+ swing_lows.reset_index(drop=True, inplace=True)
109
+
110
+ # Mark swing highs as 1 and swing lows as -1.
111
+ swings = np.where((swing_highs | swing_lows), np.where(swing_highs, 1, -1), 0)
112
+
113
+ # Filtering only one swing high between two swing lows and vice-versa.
114
+ state = 1
115
+ for i in range(1, swings.shape[0]):
116
+ if swings[i] == state or swings[i] == 0:
117
+ swings[i] = 0
118
+ else:
119
+ state *= -1
120
+
121
+ # Replace 0 with NaN.
122
+ swing_highs_lows = np.where(swings==0, np.nan, swings)
123
+
124
+ # Get positions of swing_highs_lows where elements are not np.nan
125
+ pos = np.where(~np.isnan(swing_highs_lows))[0]
126
+
127
+ # Set first position and last position of swing_highs_lows.
128
+ if len(pos) > 0:
129
+ if swing_highs_lows[pos[0]] == 1:
130
+ swing_highs_lows[0] = -1
131
+ if swing_highs_lows[pos[0]] == -1:
132
+ swing_highs_lows[0] = 1
133
+ if swing_highs_lows[pos[-1]] == -1:
134
+ swing_highs_lows[-1] = 1
135
+ if swing_highs_lows[pos[-1]] == 1:
136
+ swing_highs_lows[-1] = -1
137
+
138
+ level = np.where(
139
+ ~np.isnan(swing_highs_lows),
140
+ np.where(swing_highs_lows == 1, self.data.High, self.data.Low),
141
+ np.nan,
142
+ )
143
+
144
+ return pd.concat(
145
+ [
146
+ pd.Series(swing_highs_lows, name="HighLow"),
147
+ pd.Series(level, name="Level"),
148
+ ],
149
+ axis=1,
150
+ )
151
+
152
+ def bos_choch(self, swing_highs_lows):
153
+ """
154
+ Break of Structure and Change of Character
155
+ :param swing_highs_lows: A DataFrame which contains swing highs and lows.
156
+ Format should be same as swing_highs_lows_v2() function.
157
+ :type swing_highs_lows: pd.DataFrame
158
+ :return: A DataFrame with BOS(1 for bear, -1 for bull),
159
+ CHOCH(1 for bear, -1 for bull), Level, BrokenIndex as columns.
160
+ :rtype: pd.DataFrame
161
+ """
162
+ level_order = []
163
+ highs_lows_order = []
164
+
165
+ bos = np.zeros(len(self.data), dtype=np.int32)
166
+ choch = np.zeros(len(self.data), dtype=np.int32)
167
+ level = np.zeros(len(self.data), dtype=np.float32)
168
+
169
+ last_positions = []
170
+
171
+ for i in range(len(swing_highs_lows["HighLow"])):
172
+ if not np.isnan(swing_highs_lows["HighLow"][i]):
173
+ level_order.append(swing_highs_lows["Level"][i])
174
+ highs_lows_order.append(swing_highs_lows["HighLow"][i])
175
+ if len(level_order) >= 4:
176
+ # bullish bos
177
+ # -1
178
+ # -3 __BOS__ / \
179
+ # / \ / \
180
+ # / \ /
181
+ # \ / \ /
182
+ # \ / -2
183
+ # -4
184
+ bos[last_positions[-2]] = (
185
+ 1
186
+ if (
187
+ np.all(highs_lows_order[-4:] == [-1, 1, -1, 1])
188
+ and np.all(
189
+ level_order[-4]
190
+ < level_order[-2]
191
+ < level_order[-3]
192
+ < level_order[-1]
193
+ )
194
+ )
195
+ else 0
196
+ )
197
+ level[last_positions[-2]] = (
198
+ level_order[-3] if bos[last_positions[-2]] !=0 else 0
199
+ )
200
+
201
+ # bearish bos
202
+ # -4
203
+ # / \ -2
204
+ # / \ / \
205
+ # \ / \
206
+ # \ / \
207
+ # \ /__BOS__\ /
208
+ # -3 \ /
209
+ # -1
210
+ bos[last_positions[-2]] = (
211
+ -1
212
+ if(
213
+ np.all(highs_lows_order[-4:] == [1, -1, 1, -1])
214
+ and np.all(
215
+ level_order[-4]
216
+ > level_order[-2]
217
+ > level_order[-3]
218
+ > level_order[-1]
219
+ )
220
+ )
221
+ else bos[last_positions[-2]]
222
+ )
223
+ level[last_positions[-2]] = (
224
+ level_order[-3] if bos[last_positions[-2]] != 0 else 0
225
+ )
226
+
227
+ # bullish CHoCH
228
+ # -1
229
+ # -3 __CHoCH__ / \
230
+ # / \ / \
231
+ # / \ /
232
+ # \ / \ /
233
+ # \ / \ /
234
+ # -4 \ /
235
+ # -2
236
+ choch[last_positions[-2]] = (
237
+ 1
238
+ if (
239
+ np.all(highs_lows_order[-4:] == [-1, 1, -1, 1])
240
+ and np.all(
241
+ level_order[-1]
242
+ > level_order[-3]
243
+ > level_order[-4]
244
+ > level_order[-2]
245
+ )
246
+ )
247
+ else 0
248
+ )
249
+ level[last_positions[-2]] = (
250
+ level_order[-3]
251
+ if choch[last_positions[-2]] != 0
252
+ else level[last_positions[-2]]
253
+ )
254
+
255
+ # bearish CHoCH
256
+ # -2
257
+ # -4 / \
258
+ # / \ / \
259
+ # / \ / \
260
+ # \ / \
261
+ # \ / \
262
+ # -3__CHoCH__ \ /
263
+ # \ /
264
+ # -1
265
+ choch[last_positions[-2]] = (
266
+ -1
267
+ if (
268
+ np.all(highs_lows_order[-4:] == [1, -1, 1, -1])
269
+ and np.all(
270
+ level_order[-1]
271
+ < level_order[-3]
272
+ < level_order[-4]
273
+ < level_order[-2]
274
+ )
275
+ )
276
+ else choch[last_positions[-2]]
277
+ )
278
+ level[last_positions[-2]] = (
279
+ level_order[-3]
280
+ if choch[last_positions[-2]] != 0
281
+ else level[last_positions[-2]]
282
+ )
283
+
284
+ last_positions.append(i)
285
+
286
+ broken = np.zeros(len(self.data), dtype=np.int32)
287
+ for i in np.where(np.logical_or(bos != 0, choch != 0))[0]:
288
+ mask = np.zeros(len(self.data), dtype=np.bool_)
289
+ # if the bos is 1 then check if the candles high has gone above the level
290
+ if bos[i] == 1 or choch[i] == 1:
291
+ mask = self.data.Close[i + 2:] > level[i]
292
+ # if the bos is -1 then check if the candles low has gone below the level
293
+ elif bos[i] == -1 or choch[i] == -1:
294
+ mask = self.data.Close[i + 2:] < level[i]
295
+ if np.any(mask):
296
+ j = np.argmax(mask) + i + 2
297
+ broken[i] = j
298
+ # if there are any unbroken bos or CHoCH that started before this one and ended after this one then remove them
299
+ for k in np.where(np.logical_or(bos != 0, choch != 0))[0]:
300
+ if k < i and broken[k] >= j:
301
+ bos[k] = 0
302
+ choch[k] = 0
303
+ level[k] = 0
304
+
305
+ # remove the ones that aren't broken
306
+ for i in np.where(
307
+ np.logical_and(np.logical_or(bos != 0, choch != 0), broken == 0)
308
+ )[0]:
309
+ bos[i] = 0
310
+ choch[i] = 0
311
+ level[i] = 0
312
+
313
+ # replace all the 0s with np.nan
314
+ bos = np.where(bos != 0, bos, np.nan)
315
+ choch = np.where(choch != 0, choch, np.nan)
316
+ level = np.where(level != 0, level, np.nan)
317
+ broken = np.where(broken != 0, broken, np.nan)
318
+
319
+ bos = pd.Series(bos, name="BOS")
320
+ choch = pd.Series(choch, name="CHOCH")
321
+ level = pd.Series(level, name="Level")
322
+ broken = pd.Series(broken, name="BrokenIndex")
323
+
324
+ return pd.concat([bos, choch, level, broken], axis=1)
325
+
326
+ def fvg(self):
327
+ """
328
+ FVG - Fair Value Gap
329
+ A fair value gap is when the previous high is lower than the next low if the current candle is bullish.
330
+ Or when the previous low is higher than the next high if the current candle is bearish.
331
+
332
+ :return:\
333
+ FVG = 1 if bullish fair value gap, -1 if bearish fair value gap
334
+ Top = the top of the fair value gap
335
+ Bottom = the bottom of the fair value gap
336
+ MitigatedIndex = the index of the candle that mitigated the fair value gap
337
+ :rtype: pd.DataFrame
338
+ """
339
+
340
+ fvg = np.where(
341
+ (
342
+ (self.data["High"].shift(1) < self.data["Low"].shift(-1))
343
+ & (self.data["Close"] > self.data["Open"])
344
+ )
345
+ | (
346
+ (self.data["Low"].shift(1) > self.data["High"].shift(-1))
347
+ & (self.data["Close"] < self.data["Open"])
348
+ ),
349
+ np.where(self.data["Close"] > self.data["Open"], 1, -1),
350
+ np.nan,
351
+ )
352
+
353
+ top = np.where(
354
+ ~np.isnan(fvg),
355
+ np.where(
356
+ self.data["Close"] > self.data["Open"],
357
+ self.data["Low"].shift(-1),
358
+ self.data["Low"].shift(1),
359
+ ),
360
+ np.nan,
361
+ )
362
+
363
+ bottom = np.where(
364
+ ~np.isnan(fvg),
365
+ np.where(
366
+ self.data["Close"] > self.data["Open"],
367
+ self.data["High"].shift(1),
368
+ self.data["High"].shift(-1),
369
+ ),
370
+ np.nan,
371
+ )
372
+
373
+ mitigated_index = np.zeros(len(self.data), dtype=np.int32)
374
+ for i in np.where(~np.isnan(fvg))[0]:
375
+ mask = np.zeros(len(self.data), dtype=np.bool_)
376
+ if fvg[i] == 1:
377
+ mask = self.data["Low"][i + 2:] <= top[i]
378
+ elif fvg[i] == -1:
379
+ mask = self.data["High"][i + 2:] >= bottom[i]
380
+ if np.any(mask):
381
+ j = np.argmax(mask) + i + 2
382
+ mitigated_index[i] = j
383
+
384
+ mitigated_index = np.where(np.isnan(fvg), np.nan, mitigated_index)
385
+
386
+ return pd.concat(
387
+ [
388
+ pd.Series(fvg.flatten(), name="FVG"),
389
+ pd.Series(top.flatten(), name="Top"),
390
+ pd.Series(bottom.flatten(), name="Bottom"),
391
+ pd.Series(mitigated_index.flatten(), name="MitigatedIndex"),
392
+ ],
393
+ axis=1,
394
+ )
395
+
396
+ def order_block(self, imb_perc=.1, join_consecutive=True):
397
+ """
398
+ OB - Order Block
399
+ Order block is the presence of a chunk of market orders that results in a sudden rise or fall in the market
400
+
401
+ :return:\
402
+ OB = 1 if bullish order block, -1 if bearish order block
403
+ Top = the top of the order block
404
+ Bottom = the bottom of the order block
405
+ MitigatedIndex = the index of the candle that mitigated the order block
406
+ :rtype: pd.DataFrame
407
+ """
408
+ hl = self.swing_highs_lows(self.swing_hl_window_sz)
409
+
410
+ ob = np.where(
411
+ (
412
+ ((self.data["High"]*((100+imb_perc)/100)) < self.data["Low"].shift(-2))
413
+ & ((hl['lows']==True) | (hl['lows'].shift(1)==True))
414
+ )
415
+ | (
416
+ (self.data["Low"] > (self.data["High"].shift(-2)*((100+imb_perc)/100)))
417
+ & ((hl['highs']==True) | (hl['highs'].shift(1)==True))
418
+ ),
419
+ np.where(((hl['lows']==True) | (hl['lows'].shift(1)==True)), 1, -1),
420
+ np.nan,
421
+ )
422
+
423
+ # print(ob)
424
+
425
+ top = np.where(
426
+ ~np.isnan(ob),
427
+ np.where(
428
+ self.data["Close"] > self.data["Open"],
429
+ self.data["Low"].shift(-2),
430
+ self.data["Low"],
431
+ ),
432
+ np.nan,
433
+ )
434
+
435
+ bottom = np.where(
436
+ ~np.isnan(ob),
437
+ np.where(
438
+ self.data["Close"] > self.data["Open"],
439
+ self.data["High"],
440
+ self.data["High"].shift(-2),
441
+ ),
442
+ np.nan,
443
+ )
444
+
445
+ # if join_consecutive:
446
+ # for i in range(len(ob) - 1):
447
+ # if ob[i] == ob[i + 1]:
448
+ # top[i + 1] = max(top[i], top[i + 1])
449
+ # bottom[i + 1] = min(bottom[i], bottom[i + 1])
450
+ # ob[i] = top[i] = bottom[i] = np.nan
451
+
452
+ mitigated_index = np.zeros(len(self.data), dtype=np.int32)
453
+ for i in np.where(~np.isnan(ob))[0]:
454
+ mask = np.zeros(len(self.data), dtype=np.bool_)
455
+ if ob[i] == 1:
456
+ mask = self.data["Low"][i + 3:] <= top[i]
457
+ elif ob[i] == -1:
458
+ mask = self.data["High"][i + 3:] >= bottom[i]
459
+ if np.any(mask):
460
+ j = np.argmax(mask) + i + 3
461
+ mitigated_index[i] = int(j)
462
+ ob = ob.flatten()
463
+ mitigated_index1 = np.where(np.isnan(ob), np.nan, mitigated_index)
464
+
465
+ return pd.concat(
466
+ [
467
+ pd.Series(ob.flatten(), name="OB"),
468
+ pd.Series(top.flatten(), name="Top"),
469
+ pd.Series(bottom.flatten(), name="Bottom"),
470
+ pd.Series(mitigated_index1.flatten(), name="MitigatedIndex"),
471
+ ],
472
+ axis=1,
473
+ ).dropna(subset=['OB'])
474
+
475
+ def plot(self, order_blocks=False, swing_hl=False, swing_hl_v2=False, structure=False, show=True):
476
+ """
477
+ :param order_blocks:
478
+ :param swing_hl:
479
+ :param swing_hl_v2:
480
+ :param structure:
481
+ :param show:
482
+ :return:
483
+ """
484
+ fig = make_subplots(1, 1)
485
+
486
+ # plot the candle stick graph
487
+ fig.add_trace(go.Candlestick(x=self.data.index.to_series(),
488
+ open=self.data['Open'],
489
+ high=self.data['High'],
490
+ low=self.data['Low'],
491
+ close=self.data['Close'],
492
+ name='ohlc'))
493
+
494
+ # grab first and last observations from df.date and make a continuous date range from that
495
+ dt_all = pd.date_range(start=self.data['Date'].iloc[0], end=self.data['Date'].iloc[-1], freq='5min')
496
+
497
+ # check which dates from your source that also accur in the continuous date range
498
+ dt_obs = [d.strftime("%Y-%m-%d %H:%M:%S") for d in self.data['Date']]
499
+
500
+ # isolate missing timestamps
501
+ dt_breaks = [d for d in dt_all.strftime("%Y-%m-%d %H:%M:%S").tolist() if not d in dt_obs]
502
+
503
+ # adjust xaxis for rangebreaks
504
+ fig.update_xaxes(rangebreaks=[dict(dvalue=5 * 60 * 1000, values=dt_breaks)])
505
+
506
+ if order_blocks:
507
+ # print(self.order_blocks.head())
508
+ # print(self.order_blocks.index.to_list())
509
+
510
+ ob_df = self.data.iloc[self.order_blocks.index.to_list()]
511
+ # print(ob_df)
512
+
513
+ fig.add_trace(go.Scatter(
514
+ x=ob_df['Date'],
515
+ y=ob_df['Low'],
516
+ name="Order Block",
517
+ mode='markers',
518
+ marker_symbol='diamond-dot',
519
+ marker_size=13,
520
+ marker_line_width=2,
521
+ # offsetgroup=0,
522
+ ))
523
+
524
+ if swing_hl:
525
+ hl = self.swing_highs_lows(self.swing_hl_window_sz)
526
+ h = hl[(hl['highs']==True)]
527
+ l = hl[hl['lows']==True]
528
+
529
+ fig.add_trace(go.Scatter(
530
+ x=h['Date'],
531
+ y=self.data[self.data.Date.isin(h['Date'])]['High']*(100.1/100),
532
+ mode='markers',
533
+ marker_symbol="triangle-up-dot",
534
+ marker_size=10,
535
+ name='Swing High',
536
+ # offsetgroup=2,
537
+ ))
538
+ fig.add_trace(go.Scatter(
539
+ x=l['Date'],
540
+ y=self.data[self.data.Date.isin(l['Date'])]['Low']*(99.9/100),
541
+ mode='markers',
542
+ marker_symbol="triangle-down-dot",
543
+ marker_size=10,
544
+ name='Swing Low',
545
+ marker_color='red',
546
+ # offsetgroup=2,
547
+ ))
548
+
549
+ if swing_hl_v2:
550
+ hl = self.swing_hl
551
+ h = hl[hl['HighLow']==1]
552
+ l = hl[hl['HighLow']==-1]
553
+
554
+ fig.add_trace(go.Scatter(
555
+ x=self.data['Date'].iloc[h.index],
556
+ y=h['Level'],
557
+ mode='markers',
558
+ marker_symbol="triangle-up-dot",
559
+ marker_size=10,
560
+ name='Swing High',
561
+ marker_color='green',
562
+ ))
563
+ fig.add_trace(go.Scatter(
564
+ x=self.data['Date'].iloc[l.index],
565
+ y=l['Level'],
566
+ mode='markers',
567
+ marker_symbol="triangle-down-dot",
568
+ marker_size=10,
569
+ name='Swing Low',
570
+ marker_color='red',
571
+ ))
572
+
573
+ if structure:
574
+ struct = self.structure_map
575
+ struct.dropna(subset=['Level'], inplace=True)
576
+
577
+ for i in range(len(struct)):
578
+ x0 = self.data['Date'].iloc[struct.index[i]]
579
+ x1 = self.data['Date'].iloc[int(struct['BrokenIndex'].iloc[i])]
580
+ y = struct['Level'].iloc[i]
581
+ label = "BOS" if np.isnan(struct['CHOCH'].iloc[i]) else "CHOCH"
582
+ direction = struct[label].iloc[i]
583
+
584
+ # Add scatter trace for the line
585
+ fig.add_trace(go.Scatter(
586
+ x=[x0, x1], # x-coordinates of the line
587
+ y=[y, y], # y-coordinates of the line
588
+ mode="lines+text", # Line and optional label
589
+ line=dict(color="blue" if label=="BOS" else "orange"), # Customize line color
590
+ text=[None, label], # Add label only at one end
591
+ textposition="top left" if direction==1 else "bottom left", # Adjust label position
592
+ name=label, # Legend entry for this line
593
+ showlegend=False
594
+ ))
595
+
596
+ fig.update_layout(xaxis_rangeslider_visible=False)
597
+ if show:
598
+ fig.show()
599
+ return fig
600
+
601
+
602
+ def EMA(array, n):
603
+ """
604
+ :param array: price of the stock
605
+ :param n: window size
606
+ :type n: int
607
+ :return: Exponential moving average
608
+ :rtype: pd.Series
609
+ """
610
+ return pd.Series(array).ewm(span=n, adjust=False).mean()
611
+
612
+ if __name__ == "__main__":
613
+ from utils import fetch
614
+
615
+ data = fetch('ICICIBANK.NS', period='1mo', interval='15m')
616
+ data = fetch('RELIANCE.NS', period='1mo', interval='15m')
617
+ data['Date'] = data.index.to_series()
618
+ filter = pd.to_datetime('2024-12-17 09:50:00.0000000011',
619
+ format='%Y-%m-%d %H:%M:%S.%f')
620
+ # data = data[data['Date']<filter]
621
+ # print(SMC(data).backtest_buy_signal())
622
+ # print(SMC(data).swing_highs_lows_v3(10).to_string())
623
+ # print(data.tail())
624
+ SMC(data).plot(order_blocks=False, swing_hl=False, swing_hl_v2=True, structure=True, show=True)
625
+ # struct = SMC(data).structure_map
626
+ # print(struct)
627
+ #
628
+ # for i in range(len(data)):
629
+ # print(i, data['Date'][i], struct['BrokenIndex'].iloc[i])
630
+ # SMC(data).structure()
pages/complete_backtest.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ from streamlit.components import v1 as components
4
+ from utils import complete_test
5
+
6
+ def complete_backtest():
7
+ st.title("Evaluate Strategy")
8
+
9
+ limits = pd.read_csv('data/yahoo_limits.csv')
10
+ period_list = ['1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max']
11
+
12
+ c1, c2 = st.columns(2)
13
+ with c1:
14
+ # Select strategy
15
+ strategy = st.selectbox("Select Strategy", ['Order Block', 'Order Block with EMA', 'Structure trading'], index=2)
16
+ with c2:
17
+ # Swing High/Low window size
18
+ swing_hl = st.number_input("Swing High/Low Window Size", min_value=1, value=10)
19
+
20
+ c1, c2 = st.columns(2)
21
+ with c1:
22
+ # Select interval
23
+ interval = st.selectbox("Select Interval", limits['interval'].tolist(), index=3)
24
+ with c2:
25
+ # Update period options based on interval
26
+ limit = limits[limits['interval'] == interval]['limit'].values[0]
27
+ idx = period_list.index(limit)
28
+ period_options = period_list[:idx + 1] + ['max']
29
+ period = st.selectbox("Select Period", period_options, index=3)
30
+
31
+ # EMA parameters if "Order Block with EMA" is selected
32
+ if strategy == "Order Block with EMA":
33
+ c1, c2, c3 = st.columns(3)
34
+ with c1:
35
+ ema1 = st.number_input("Fast EMA Length", min_value=1, value=9)
36
+ with c2:
37
+ ema2 = st.number_input("Slow EMA Length", min_value=1, value=21)
38
+ with c3:
39
+ cross_close = st.checkbox("Close trade on EMA crossover", value=False)
40
+ else:
41
+ ema1, ema2, cross_close = None, None, None
42
+
43
+ # Button to run the analysis
44
+ if st.button("Run"):
45
+ st.session_state.results = complete_test(strategy, period, interval, swing_hl=swing_hl, ema1=ema1, ema2=ema2, cross_close=cross_close)
46
+
47
+ if "results" in st.session_state:
48
+ cols = ['stock', 'Start', 'End', 'Return [%]', 'Equity Final [$]', 'Buy & Hold Return [%]', '# Trades', 'Win Rate [%]', 'Best Trade [%]', 'Worst Trade [%]', 'Avg. Trade [%]']
49
+ df = st.dataframe(st.session_state.results, hide_index=True, column_order=cols, on_select="rerun", selection_mode="single-row")
50
+ if df.selection.rows:
51
+ row = df.selection.rows
52
+ plot = st.session_state.results['plot'].values[row]
53
+ components.html(plot[0], height=1067)
54
+
55
+ complete_backtest()
pages/dashboard.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import streamlit as st
3
+ import os
4
+ import random
5
+ from bokeh.io import output_file, save
6
+ from bokeh.plotting import figure
7
+ from streamlit.components import v1 as components
8
+
9
+ from indicators import SMC
10
+ from utils import fetch, smc_plot_backtest, smc_ema_plot_backtest, smc_structure_plot_backtest
11
+
12
+ def use_file_for_bokeh(chart: figure, chart_height=1067):
13
+ # Function used to replace st.boken_chart, because streamlit doesn't support bokeh v3
14
+ file_name = f'bokeh_graph_{random.getrandbits(8)}.html'
15
+ output_file(file_name)
16
+ save(chart)
17
+ with open(file_name, 'r', encoding='utf-8') as f:
18
+ html = f.read()
19
+ os.remove(file_name)
20
+ components.html(html, height=chart_height)
21
+
22
+ st.bokeh_chart = use_file_for_bokeh
23
+
24
+ def algorithmic_trading_dashboard():
25
+ # Load data
26
+ symbols = pd.read_csv('data/Ticker_List_NSE_India.csv')
27
+ limits = pd.read_csv('data/yahoo_limits.csv')
28
+
29
+ # Dropdown options
30
+ period_list = ['1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max']
31
+
32
+ # Input fields on the main page
33
+ st.title("Algorithmic Trading Dashboard")
34
+
35
+ # Select stock
36
+ stock = st.selectbox("Select Company", symbols['NAME OF COMPANY'].unique(), index=None)
37
+
38
+ c1, c2 = st.columns(2)
39
+
40
+ with c1:
41
+ # Select interval
42
+ interval = st.selectbox("Select Interval", limits['interval'].tolist(), index=3)
43
+
44
+ with c2:
45
+ # Update period options based on interval
46
+ limit = limits[limits['interval'] == interval]['limit'].values[0]
47
+ idx = period_list.index(limit)
48
+ period_options = period_list[:idx + 1] + ['max']
49
+ period = st.selectbox("Select Period", period_options, index=3)
50
+
51
+ c1, c2 = st.columns(2)
52
+
53
+ with c1:
54
+ # Select strategy
55
+ strategy = st.selectbox("Select Strategy", ['Order Block', 'Order Block with EMA', 'Structure trading'], index=2)
56
+
57
+ with c2:
58
+ # Swing High/Low window size
59
+ swing_hl = st.number_input("Swing High/Low Window Size", min_value=1, value=10)
60
+
61
+ # EMA parameters if "Order Block with EMA" is selected
62
+ if strategy == "Order Block with EMA":
63
+ c1, c2, c3 = st.columns(3)
64
+ with c1:
65
+ ema1 = st.number_input("Fast EMA Length", min_value=1, value=9)
66
+ with c2:
67
+ ema2 = st.number_input("Slow EMA Length", min_value=1, value=21)
68
+ with c3:
69
+ cross_close = st.checkbox("Close trade on EMA crossover", value=False)
70
+
71
+ # Button to run the analysis
72
+ if st.button("Run"):
73
+ # Fetch ticker data
74
+ ticker = symbols[symbols['NAME OF COMPANY'] == stock]['YahooEquiv'].values[0]
75
+ data = fetch(ticker, period, interval)
76
+
77
+ # Generate signal plot based on strategy
78
+ if strategy == "Order Block" or strategy == "Order Block with EMA":
79
+ signal_plot = (
80
+ SMC(data=data, swing_hl_window_sz=swing_hl)
81
+ .plot(order_blocks=True, swing_hl=True, show=False)
82
+ .update_layout(title=dict(text=ticker))
83
+ )
84
+ else:
85
+ signal_plot = (
86
+ SMC(data=data, swing_hl_window_sz=swing_hl)
87
+ .plot(swing_hl_v2=True, structure=True, show=False)
88
+ .update_layout(title=dict(text=ticker))
89
+ )
90
+
91
+ # Generate backtest plot
92
+ if strategy == "Order Block":
93
+ backtest_plot = smc_plot_backtest(data, 'test.html', swing_hl)
94
+ elif strategy == "Order Block with EMA":
95
+ backtest_plot = smc_ema_plot_backtest(data, 'test.html', ema1, ema2, cross_close)
96
+ elif strategy == "Structure trading":
97
+ backtest_plot = smc_structure_plot_backtest(data, 'test.html', swing_hl)
98
+
99
+ # Display plots
100
+ st.write("### Signal Plot")
101
+ st.plotly_chart(signal_plot, width=1200)
102
+
103
+ st.write("### Backtesting Plot")
104
+ st.bokeh_chart(backtest_plot)
105
+
106
+ algorithmic_trading_dashboard()
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
+ gradio==5.11.0
8
+ streamlit==1.41.1
strategies.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from backtesting import Backtest, Strategy
2
+ from backtesting.lib import SignalStrategy, TrailingStrategy
3
+ from indicators import SMC, EMA
4
+ import pandas as pd
5
+ import numpy as np
6
+
7
+ class SMC_test(Strategy):
8
+ swing_window = 10
9
+ def init(self):
10
+ super().init()
11
+
12
+ # Setting smc buy and sell indicators.
13
+ self.smc_b = self.I(self.smc_buy, data=self.data.df, swing_hl=self.swing_window)
14
+ self.smc_s = self.I(self.smc_sell, data=self.data.df, swing_hl=self.swing_window)
15
+
16
+ def next(self):
17
+ price = self.data.Close[-1]
18
+ current_time = self.data.index[-1]
19
+
20
+ # If buy signal, set target 5% above price and stoploss 5% below price.
21
+ if self.smc_b[-1] == 1:
22
+ self.buy(sl=.95 * price, tp=1.05 * price)
23
+ # If sell signal, set targe 5% below price and stoploss 5% above price.
24
+ if self.smc_s[-1] == -1:
25
+ self.sell(tp=.95 * price, sl=1.05 * price)
26
+
27
+ # Additionally, set aggressive stop-loss on trades that have been open
28
+ # for more than two days
29
+ for trade in self.trades:
30
+ if current_time - trade.entry_time > pd.Timedelta('2 days'):
31
+ if trade.is_long:
32
+ trade.sl = max(trade.sl, self.data.Low[-1])
33
+ else:
34
+ trade.sl = min(trade.sl, self.data.High[-1])
35
+
36
+ def smc_buy(self, data, swing_hl):
37
+ return SMC(data, swing_hl).backtest_buy_signal_ob()
38
+
39
+ def smc_sell(self, data, swing_hl):
40
+ return SMC(data, swing_hl).backtest_sell_signal_ob()
41
+
42
+
43
+ class SMC_ema(SignalStrategy, TrailingStrategy):
44
+ ema1 = 9
45
+ ema2 = 21
46
+ close_on_crossover = False
47
+
48
+ def init(self):
49
+ super().init()
50
+
51
+ # Setting smc buy and sell indicators.
52
+ self.smc_b = self.I(self.smc_buy, self.data.df)
53
+ self.smc_s = self.I(self.smc_sell, self.data.df)
54
+
55
+ close = self.data.Close
56
+
57
+ # Setting up EMAs.
58
+ self.ma1 = self.I(EMA, close, self.ema1)
59
+ self.ma2 = self.I(EMA, close, self.ema2)
60
+
61
+
62
+ def next(self):
63
+ price = self.data.Close[-1]
64
+ current_time = self.data.index[-1]
65
+
66
+ # If buy signal and short moving average is above long moving average.
67
+ if self.smc_b[-1] == 1 and self.ma1 > self.ma2:
68
+ self.buy(sl=.95 * price, tp=1.05 * price)
69
+ # If sell signal and short moving average is below long moving average.
70
+ if self.smc_s[-1] == -1 and self.ma1 < self.ma2:
71
+ self.sell(tp=.95 * price, sl=1.05 * price)
72
+
73
+ # Additionally, set aggressive stop-loss on trades that have been open
74
+ # for more than two days
75
+ for trade in self.trades:
76
+ if current_time - trade.entry_time > pd.Timedelta('2 days'):
77
+ if trade.is_long:
78
+ trade.sl = max(trade.sl, self.data.Low[-1])
79
+ else:
80
+ trade.sl = min(trade.sl, self.data.High[-1])
81
+
82
+ # Close the trade if there is a moving average crossover in opposite direction
83
+ if self.close_on_crossover:
84
+ for trade in self.trades:
85
+ if trade.is_long and self.ma1 < self.ma2:
86
+ trade.close()
87
+ if trade.is_short and self.ma1 > self.ma2:
88
+ trade.close()
89
+
90
+ def smc_buy(self, data):
91
+ return SMC(data).backtest_buy_signal_ob()
92
+
93
+ def smc_sell(self, data):
94
+ return SMC(data).backtest_sell_signal_ob()
95
+
96
+
97
+ class SMCStructure(TrailingStrategy):
98
+ swing_window = 20
99
+
100
+ def init(self):
101
+ super().init()
102
+ self.smc_b = self.I(self.smc_buy, data=self.data.df, swing_hl=self.swing_window)
103
+ self.smc_s = self.I(self.smc_sell, data=self.data.df, swing_hl=self.swing_window)
104
+ self.set_trailing_sl(2)
105
+ # self.swing = self.I(self.nearest_swing, data=self.data.df, swing_hl)
106
+
107
+ def next(self):
108
+ price = self.data.Close[-1]
109
+ current_time = self.data.index[-1]
110
+
111
+ if self.smc_b[-1] == 1:
112
+ nearest = self.nearest_swing(self.data.df, self.swing_window)
113
+ target = price + ((price - nearest)* .414)
114
+ stoploss = price - (target-price)
115
+ # print(f"buy: {current_time}, {price}, {nearest}, {target}, {stoploss}")
116
+ try:
117
+ self.buy(sl=stoploss, tp=target)
118
+ except:
119
+ print('Buying failed')
120
+ if self.smc_s[-1] == 1:
121
+ nearest = self.nearest_swing(self.data.df, self.swing_window)
122
+ print(self.data.df.iloc[-1])
123
+ if nearest > price:
124
+ target = price - ((nearest - price) * .414)
125
+ stoploss = price + (price - target)
126
+ # print(f"sell: {current_time}, {price}, {nearest}, {target}, {stoploss}")
127
+ try:
128
+ self.sell(sl=stoploss, tp=target, limit=float(price))
129
+ except:
130
+ print("Selling failed")
131
+
132
+ # Additionally, set aggressive stop-loss on trades that have been open
133
+ # for more than two days
134
+ for trade in self.trades:
135
+ if current_time - trade.entry_time > pd.Timedelta('2 days'):
136
+ if trade.is_long:
137
+ trade.sl = max(trade.sl, self.data.Low[-1])
138
+ else:
139
+ trade.sl = min(trade.sl, self.data.High[-1])
140
+
141
+ def smc_buy(self, data, swing_hl):
142
+ return SMC(data, swing_hl).backtest_buy_signal_structure()
143
+
144
+ def smc_sell(self, data, swing_hl):
145
+ return SMC(data, swing_hl).backtest_sell_signal_structure()
146
+
147
+ def nearest_swing(self, data, swing_hl):
148
+ # Get swing high/low nearest to current price.
149
+ swings = SMC(data, swing_hl).swing_hl
150
+ swings = swings[~np.isnan(swings['Level'])]
151
+ return swings['Level'].iloc[-2]
152
+
153
+ strategies = {'Order Block': SMC_test, 'Order Block with EMA': SMC_ema , 'Structure trading': SMCStructure}
154
+
155
+ if __name__ == "__main__":
156
+ from utils import fetch
157
+ # data = fetch('ICICIBANK.NS', period='1mo', interval='15m')
158
+ data = fetch('RELIANCE.NS', period='1mo', interval='15m')
159
+ # data = fetch('AXISBANK.NS', period='1mo', interval='15m')
160
+ # bt = Backtest(data, SMC_ema, commission=.002)
161
+ # bt.run(ema1 = 9, ema2 = 21, close_on_crossover=True)
162
+ bt = Backtest(data, SMCStructure, commission = .002, trade_on_close=True)
163
+ print(bt.run())
164
+
165
+ # bt.plot()
streamlit_app.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+ st.set_page_config(page_title="Algorithmic Trading Dashboard", layout="wide", initial_sidebar_state="auto",
4
+ menu_items=None, page_icon=":chart_with_upwards_trend:")
5
+
6
+ dashboard = st.Page("pages/dashboard.py", title="Dashboard")
7
+ complete_test = st.Page("pages/complete_backtest.py", title="Nifty50 Test")
8
+
9
+ pg = st.navigation([dashboard, complete_test])
10
+
11
+ pg.run()
utils.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yfinance as yf
2
+ from backtesting import Backtest
3
+ import pandas as pd
4
+ import random
5
+
6
+ from strategies import SMC_test, SMC_ema, SMCStructure
7
+
8
+ def fetch(symbol, period, interval):
9
+ df = yf.download(symbol, period=period, interval=interval)
10
+ df.columns =df.columns.get_level_values(0)
11
+ return df
12
+
13
+ def smc_plot_backtest(data, filename, swing_hl, **kwargs):
14
+ bt = Backtest(data, SMC_test, **kwargs)
15
+ bt.run(swing_window=swing_hl)
16
+ return bt.plot(filename=filename, open_browser=False)
17
+
18
+ def smc_ema_plot_backtest(data, filename, ema1, ema2, closecross, **kwargs):
19
+ bt = Backtest(data, SMC_ema, **kwargs)
20
+ bt.run(ema1=ema1, ema2=ema2, close_on_crossover=closecross)
21
+ return bt.plot(filename=filename, open_browser=False)
22
+
23
+ def smc_structure_plot_backtest(data, filename, swing_hl, **kwargs):
24
+ bt = Backtest(data, SMCStructure, **kwargs)
25
+ bt.run(swing_window=swing_hl)
26
+ return bt.plot(filename=filename, open_browser=False)
27
+
28
+ def smc_backtest(data, swing_hl, **kwargs):
29
+ bt = Backtest(data, SMC_test, **kwargs)
30
+ results = bt.run(swing_window=swing_hl)
31
+ bt.plot(filename='bokeh_graph.html', open_browser=False)
32
+ return results
33
+
34
+ def smc_ema_backtest(data, ema1, ema2, closecross, **kwargs):
35
+ bt = Backtest(data, SMC_ema, **kwargs)
36
+ results = bt.run(ema1=ema1, ema2=ema2, close_on_crossover=closecross)
37
+ bt.plot(filename='bokeh_graph.html', open_browser=False)
38
+ return results
39
+
40
+ def smc_structure_backtest(data, swing_hl, **kwargs):
41
+ bt = Backtest(data, SMCStructure, **kwargs)
42
+ results = bt.run(swing_window=swing_hl)
43
+ bt.plot(filename='bokeh_graph.html', open_browser=False)
44
+ return results
45
+
46
+ def random_test(strategy: str, period: str, interval: str, no_of_stocks: int = 5, **kwargs):
47
+ nifty50 = pd.read_csv("data/ind_nifty50list.csv")
48
+ ticker_list = pd.read_csv("data/Ticker_List_NSE_India.csv")
49
+
50
+ # Merging nifty50 and ticker_list dataframes to get 'YahooEquiv' column.
51
+ nifty50 = nifty50.merge(ticker_list, "inner", left_on=['Symbol'], right_on=['SYMBOL'])
52
+
53
+ # Generating random indices between 0 and len(nifty50).
54
+ random_indices = random.sample(range(0, len(nifty50)), no_of_stocks)
55
+
56
+ df = pd.DataFrame()
57
+
58
+ for i in random_indices:
59
+ # Fetching ohlc of random ticker_symbol.
60
+ ticker_symbol = nifty50['YahooEquiv'].values[i]
61
+ data = fetch(ticker_symbol, period, interval)
62
+
63
+ if strategy == "Order Block":
64
+ backtest_results = smc_backtest(data, kwargs['swing_hl'])
65
+ elif strategy == "Order Block with EMA":
66
+ backtest_results = smc_ema_backtest(data, kwargs['ema1'], kwargs['ema2'], kwargs['cross_close'])
67
+ elif strategy == "Structure trading":
68
+ backtest_results = smc_structure_backtest(data, kwargs['swing_hl'])
69
+ else:
70
+ raise Exception('Strategy not found')
71
+
72
+ with open("bokeh_graph.html", 'r', encoding='utf-8') as f:
73
+ plot = f.read()
74
+
75
+ # Converting pd.Series to pd.Dataframe
76
+ backtest_results = backtest_results.to_frame().transpose()
77
+
78
+ backtest_results['stock'] = ticker_symbol
79
+
80
+ # Reordering columns.
81
+ # cols = df.columns.tolist()
82
+ # cols = cols[-1:] + cols[:-1]
83
+ cols = ['stock', 'Start', 'End', 'Return [%]', 'Equity Final [$]', 'Buy & Hold Return [%]', '# Trades', 'Win Rate [%]', 'Best Trade [%]', 'Worst Trade [%]', 'Avg. Trade [%]']
84
+ backtest_results = backtest_results[cols]
85
+
86
+ df = pd.concat([df, backtest_results])
87
+
88
+ df = df.sort_values(by=['Return [%]'], ascending=False)
89
+
90
+ return df
91
+
92
+ def complete_test(strategy: str, period: str, interval: str, **kwargs):
93
+ nifty50 = pd.read_csv("data/ind_nifty50list.csv")
94
+ ticker_list = pd.read_csv("data/Ticker_List_NSE_India.csv")
95
+
96
+ # Merging nifty50 and ticker_list dataframes to get 'YahooEquiv' column.
97
+ nifty50 = nifty50.merge(ticker_list, "inner", left_on=['Symbol'], right_on=['SYMBOL'])
98
+
99
+ df = pd.DataFrame()
100
+
101
+ for i in range(len(nifty50)):
102
+ # for i in range(5):
103
+
104
+ # Fetching ohlc of random ticker_symbol.
105
+ ticker_symbol = nifty50['YahooEquiv'].values[i]
106
+ data = fetch(ticker_symbol, period, interval)
107
+
108
+ if strategy == "Order Block":
109
+ backtest_results = smc_backtest(data, kwargs['swing_hl'])
110
+ elif strategy == "Order Block with EMA":
111
+ backtest_results = smc_ema_backtest(data, kwargs['ema1'], kwargs['ema2'], kwargs['cross_close'])
112
+ elif strategy == "Structure trading":
113
+ backtest_results = smc_structure_backtest(data, kwargs['swing_hl'])
114
+ else:
115
+ raise Exception('Strategy not found')
116
+
117
+ with open("bokeh_graph.html", 'r', encoding='utf-8') as f:
118
+ plot = f.read()
119
+
120
+ # Converting pd.Series to pd.Dataframe
121
+ backtest_results = backtest_results.to_frame().transpose()
122
+
123
+ backtest_results['stock'] = ticker_symbol
124
+ backtest_results['plot'] = plot
125
+
126
+ # Reordering columns.
127
+ # cols = df.columns.tolist()
128
+ # cols = cols[-1:] + cols[:-1]
129
+ cols = ['stock', 'Start', 'End', 'Return [%]', 'Equity Final [$]', 'Buy & Hold Return [%]', '# Trades', 'Win Rate [%]', 'Best Trade [%]', 'Worst Trade [%]', 'Avg. Trade [%]', 'plot']
130
+ backtest_results = backtest_results[cols]
131
+
132
+ df = pd.concat([df, backtest_results])
133
+
134
+ df['plot'] = df['plot'].astype(str)
135
+ df = df.sort_values(by=['Return [%]'], ascending=False)
136
+
137
+ return df
138
+
139
+
140
+ if __name__ == "__main__":
141
+ # random_testing("")
142
+ # data = fetch('RELIANCE.NS', period='1y', interval='15m')
143
+ # df = yf.download('RELIANCE.NS', period='1yr', interval='15m')
144
+
145
+ rt = complete_test("Order Block", '1mo', '15m', swing_hl=20)
146
+ rt.to_excel('test/all_testing_1.xlsx', index=False)
147
+ print(rt)