sysstra 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- src/__init__.py +6 -0
- src/custom_indicators.py +903 -0
- src/data/__init__.py +2 -0
- src/data/historical.py +85 -0
- src/data/live.py +77 -0
- src/orders/__init__.py +0 -0
- src/orders/kite.py +36 -0
- src/orders/orders.py +489 -0
- src/utils.py +1465 -0
- sysstra-0.1.0.dist-info/LICENSE +21 -0
- sysstra-0.1.0.dist-info/METADATA +37 -0
- sysstra-0.1.0.dist-info/RECORD +14 -0
- sysstra-0.1.0.dist-info/WHEEL +5 -0
- sysstra-0.1.0.dist-info/top_level.txt +1 -0
src/custom_indicators.py
ADDED
|
@@ -0,0 +1,903 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
from pandas_ta.utils import get_offset, non_zero_range, verify_series
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas_ta as ta
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def stochastic_oscillator(dataframe, k=14, d=3, smooth_k=3, mamode='sma'):
|
|
8
|
+
""" Function to calculate stochastic oscillator """
|
|
9
|
+
try:
|
|
10
|
+
# Calculate the rolling lowest low and highest high over the K period
|
|
11
|
+
k = k if k and k > 0 else 14
|
|
12
|
+
d = d if d and d > 0 else 3
|
|
13
|
+
|
|
14
|
+
smooth_k = smooth_k if smooth_k and smooth_k > 0 else 3
|
|
15
|
+
_length = max(k, d, smooth_k)
|
|
16
|
+
|
|
17
|
+
high = verify_series(dataframe["high"], _length)
|
|
18
|
+
low = verify_series(dataframe["low"], _length)
|
|
19
|
+
close = verify_series(dataframe["close"], _length)
|
|
20
|
+
|
|
21
|
+
if high is None or low is None or close is None: return
|
|
22
|
+
|
|
23
|
+
# Calculate Result
|
|
24
|
+
lowest_low = low.rolling(k).min()
|
|
25
|
+
highest_high = high.rolling(k).max()
|
|
26
|
+
|
|
27
|
+
stoch = 100 * (close - lowest_low)
|
|
28
|
+
stoch /= non_zero_range(highest_high, lowest_low)
|
|
29
|
+
stoch_k = stoch.rolling(smooth_k).mean()
|
|
30
|
+
stoch_d = stoch_k.rolling(d).mean()
|
|
31
|
+
|
|
32
|
+
new_df = pd.DataFrame()
|
|
33
|
+
new_df["k"] = stoch_k
|
|
34
|
+
new_df["d"] = stoch_d
|
|
35
|
+
return new_df
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print("Exception in calculating stochastic oscillator : {}".format(e))
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def volume_flow_indicator(dataframe, length, coef, vcoef, signal_length, smooth_vfi=False):
|
|
42
|
+
""" Function to calculate volume Flow Indicator Values """
|
|
43
|
+
try:
|
|
44
|
+
print("Calculating Volume Flow")
|
|
45
|
+
df = pd.DataFrame()
|
|
46
|
+
# Calculate 'hlc3' (Typical Price)
|
|
47
|
+
df['typical'] = (dataframe['high'] + dataframe['low'] + dataframe['close']) / 3
|
|
48
|
+
|
|
49
|
+
# Calculate intermediary variables
|
|
50
|
+
df['inter'] = np.log(df['typical']) - np.log(df['typical'].shift(1))
|
|
51
|
+
df['vinter'] = df['inter'].rolling(window=30).std()
|
|
52
|
+
df['cutoff'] = coef * df['vinter'] * dataframe['close']
|
|
53
|
+
df['vave'] = ta.sma(dataframe['volume'], length).shift(1)
|
|
54
|
+
df['vmax'] = df['vave'] * vcoef
|
|
55
|
+
|
|
56
|
+
# Calculate volume cutoff (vcp)
|
|
57
|
+
df['vc'] = np.where(dataframe['volume'] < df['vmax'], dataframe['volume'], df['vmax'])
|
|
58
|
+
|
|
59
|
+
# Calculate Money Flow (mf)
|
|
60
|
+
df['mf'] = df['typical'] - df['typical'].shift(1)
|
|
61
|
+
df['vcp'] = np.where(df['mf'] > df['cutoff'], df['vc'], np.where(df['mf'] < -df['cutoff'], -df['vc'], 0))
|
|
62
|
+
|
|
63
|
+
# Calculate VFI
|
|
64
|
+
df['vfi'] = df['vcp'].rolling(window=length).sum() / df['vave']
|
|
65
|
+
|
|
66
|
+
# Smooth VFI if specified
|
|
67
|
+
if smooth_vfi:
|
|
68
|
+
df['vfi'] = ta.sma(df['vfi'], 3)
|
|
69
|
+
|
|
70
|
+
# Calculate EMA of VFI
|
|
71
|
+
df['vfi_ma'] = ta.ema(df['vfi'], signal_length)
|
|
72
|
+
|
|
73
|
+
# Calculate difference
|
|
74
|
+
df['vfi_d'] = df['vfi'] - df['vfi_ma']
|
|
75
|
+
|
|
76
|
+
return df
|
|
77
|
+
except Exception as e:
|
|
78
|
+
print(f"Exception in calculating volume flow indicator : {e}")
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def williams_fractal(dataframe, fractal_window=2):
|
|
83
|
+
""" Function to calculate williams fractal"""
|
|
84
|
+
try:
|
|
85
|
+
# Initialize empty columns for bullish (up) and bearish (down) fractals
|
|
86
|
+
dataframe['fractal_up'] = np.nan
|
|
87
|
+
dataframe['fractal_down'] = np.nan
|
|
88
|
+
|
|
89
|
+
# Iterate over the dataframe and check for fractals
|
|
90
|
+
for i in range(fractal_window, len(dataframe) - fractal_window):
|
|
91
|
+
# Bullish Fractal (Local High)
|
|
92
|
+
if dataframe['high'][i] == max(dataframe['high'][i - fractal_window:i + fractal_window + 1]):
|
|
93
|
+
dataframe.loc[i, 'fractal_up'] = dataframe['high'][i]
|
|
94
|
+
|
|
95
|
+
# Bearish Fractal (Local Low)
|
|
96
|
+
if dataframe['low'][i] == min(dataframe['low'][i - fractal_window:i + fractal_window + 1]):
|
|
97
|
+
dataframe.loc[i, 'fractal_down'] = dataframe['low'][i]
|
|
98
|
+
|
|
99
|
+
return dataframe
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"Exception in Williams Fractal : {e}")
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def mcgd(close, length=None, offset=None, c=None, **kwargs):
|
|
106
|
+
""" Indicator: McGinley Dynamic Indicator """
|
|
107
|
+
# Validate arguments
|
|
108
|
+
length = int(length) if length and length > 0 else 10
|
|
109
|
+
c = float(c) if c and 0 < c <= 1 else 1
|
|
110
|
+
close = verify_series(close, length)
|
|
111
|
+
offset = get_offset(offset)
|
|
112
|
+
|
|
113
|
+
if close is None: return
|
|
114
|
+
|
|
115
|
+
# Calculate Result
|
|
116
|
+
close = close.copy()
|
|
117
|
+
|
|
118
|
+
def mcg_(series):
|
|
119
|
+
denom = (c * length * (series.iloc[1] / series.iloc[0]) ** 4)
|
|
120
|
+
series.iloc[1] = (series.iloc[0] + ((series.iloc[1] - series.iloc[0]) / denom))
|
|
121
|
+
return series.iloc[1]
|
|
122
|
+
|
|
123
|
+
mcg_cell = close[0:].rolling(2, min_periods=2).apply(mcg_, raw=False)
|
|
124
|
+
mcg_ds = close[:1]._append(mcg_cell[1:])
|
|
125
|
+
|
|
126
|
+
# Offset
|
|
127
|
+
if offset != 0:
|
|
128
|
+
mcg_ds = mcg_ds.shift(offset)
|
|
129
|
+
|
|
130
|
+
# Handle fills
|
|
131
|
+
if "fillna" in kwargs:
|
|
132
|
+
mcg_ds.fillna(kwargs["fillna"], inplace=True)
|
|
133
|
+
if "fill_method" in kwargs:
|
|
134
|
+
mcg_ds.fillna(method=kwargs["fill_method"], inplace=True)
|
|
135
|
+
|
|
136
|
+
# Name & Category
|
|
137
|
+
mcg_ds.name = f"MCGD_{length}"
|
|
138
|
+
mcg_ds.category = "overlap"
|
|
139
|
+
|
|
140
|
+
return mcg_ds
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def squeeze_momentum(dataframe, bb_length=20, bb_mult=2.0, kc_length=20, kc_mult=1.5, use_true_range=True):
|
|
144
|
+
""" Function to calculate squeeze momentum """
|
|
145
|
+
try:
|
|
146
|
+
df = dataframe.copy()
|
|
147
|
+
|
|
148
|
+
# Bollinger Bands (BB) Calculation
|
|
149
|
+
source = df['close']
|
|
150
|
+
basis = ta.sma(source, length=bb_length)
|
|
151
|
+
dev = kc_mult * source.rolling(window=bb_length).std()
|
|
152
|
+
df['upperBB'] = basis + dev
|
|
153
|
+
df['lowerBB'] = basis - dev
|
|
154
|
+
|
|
155
|
+
# Keltner Channels (KC) Calculation
|
|
156
|
+
range_kc = ta.true_range(df['high'], df['low'], df['close']) if use_true_range else df['high'] - df['low']
|
|
157
|
+
range_ma = ta.sma(range_kc, kc_length)
|
|
158
|
+
|
|
159
|
+
df['maKC'] = ta.sma(df['close'], kc_length)
|
|
160
|
+
df['upperKC'] = df['maKC'] + range_ma * kc_mult
|
|
161
|
+
df['lowerKC'] = df['maKC'] - range_ma * kc_mult
|
|
162
|
+
|
|
163
|
+
# Squeeze conditions
|
|
164
|
+
df['sqzOn'] = (df['lowerBB'] > df['lowerKC']) & (df['upperBB'] < df['upperKC'])
|
|
165
|
+
df['sqzOff'] = (df['lowerBB'] < df['lowerKC']) & (df['upperBB'] > df['upperKC'])
|
|
166
|
+
df['noSqz'] = ~(df['sqzOn'] | df['sqzOff'])
|
|
167
|
+
|
|
168
|
+
highest_high = df['high'].rolling(window=kc_length).max()
|
|
169
|
+
lowest_low = df['low'].rolling(window=kc_length).min()
|
|
170
|
+
hh_ll_avg = (highest_high + lowest_low)/2
|
|
171
|
+
avg_2 = (hh_ll_avg + ta.sma(source, kc_length))/2
|
|
172
|
+
df['val'] = ta.linreg((source - avg_2), length=kc_length, offset=0)
|
|
173
|
+
|
|
174
|
+
return df[['val']]
|
|
175
|
+
except Exception as e:
|
|
176
|
+
print(f"Exception in Squeeze Momentum : {e}")
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def tv_rsi(dataframe, rsi_length=14, source="close", ma_type="sma", ma_length=14, bb_mult=2.0):
|
|
181
|
+
""" Function to calculate RSI similar to Trading View """
|
|
182
|
+
try:
|
|
183
|
+
df = dataframe.copy()
|
|
184
|
+
lookback_right = 5
|
|
185
|
+
lookback_left = 5
|
|
186
|
+
|
|
187
|
+
# Calculate RSI
|
|
188
|
+
df['rsi'] = ta.rsi(df[source], timeperiod=rsi_length)
|
|
189
|
+
|
|
190
|
+
# Moving Average of RSI
|
|
191
|
+
df['rsi_ma'] = ta.sma(close=df['rsi'], length=ma_length)
|
|
192
|
+
|
|
193
|
+
# Calculate Bollinger Bands
|
|
194
|
+
df['basis'] = ta.sma(df['close'], timeperiod=ma_length)
|
|
195
|
+
df['stddev'] = ta.stdev(df['close'], timeperiod=ma_length)
|
|
196
|
+
df['upper_bb'] = df['basis'] + bb_mult * df['stddev']
|
|
197
|
+
df['lower_bb'] = df['basis'] - bb_mult * df['stddev']
|
|
198
|
+
|
|
199
|
+
# KC Calculation (simplified as ATR-based KC)
|
|
200
|
+
df['atr'] = ta.atr(df['high'], df['low'], df['close'], timeperiod=ma_length)
|
|
201
|
+
df['upper_kc'] = df['basis'] + df['atr'] * bb_mult
|
|
202
|
+
df['lower_kc'] = df['basis'] - df['atr'] * bb_mult
|
|
203
|
+
|
|
204
|
+
# Squeeze conditions
|
|
205
|
+
df['sqz_on'] = (df['lower_bb'] > df['lower_kc']) & (df['upper_bb'] < df['upper_kc'])
|
|
206
|
+
df['sqz_off'] = (df['lower_bb'] < df['lower_kc']) & (df['upper_bb'] > df['upper_kc'])
|
|
207
|
+
|
|
208
|
+
# Pivot-based divergence detection
|
|
209
|
+
df['pivot_low'] = df['low'].rolling(window=lookback_left + lookback_right).apply(lambda x: x.idxmin(), raw=False)
|
|
210
|
+
df['pivot_high'] = df['high'].rolling(window=lookback_left + lookback_right).apply(lambda x: x.idxmax(), raw=False)
|
|
211
|
+
|
|
212
|
+
# Define bullish and bearish divergence conditions
|
|
213
|
+
df['rsi_lbr'] = df['rsi'].shift(lookback_right)
|
|
214
|
+
df['bullish_div'] = (df['low'] < df['low'].shift(lookback_right)) & (df['rsi'] > df['rsi'].shift(lookback_right))
|
|
215
|
+
df['bearish_div'] = (df['high'] > df['high'].shift(lookback_right)) & (df['rsi'] < df['rsi'].shift(lookback_right))
|
|
216
|
+
|
|
217
|
+
return df[['rsi', 'rsi_ma']]
|
|
218
|
+
except Exception as e:
|
|
219
|
+
print(f"Exception in TV RSI : {e}")
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def chaikin_volatility(dataframe, length=10, roc_length=10):
|
|
224
|
+
""" Function to calculate Chaikin Volatility """
|
|
225
|
+
try:
|
|
226
|
+
price_diff = dataframe['high'] - dataframe['low']
|
|
227
|
+
ema_price_diff = ta.ema(price_diff, timeperiod=length)
|
|
228
|
+
chaikin_volatility = ta.roc(ema_price_diff, timeperiod=roc_length)
|
|
229
|
+
return chaikin_volatility
|
|
230
|
+
except Exception as e:
|
|
231
|
+
print(f"Exception in chaikin volatility : {e}")
|
|
232
|
+
pass
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def calculate_dmi(dataframe, adx_smoothing=14, di_length=14):
|
|
236
|
+
""" Function to calculate Directional Moving Index """
|
|
237
|
+
try:
|
|
238
|
+
df = dataframe.copy()
|
|
239
|
+
|
|
240
|
+
# Calculate price changes
|
|
241
|
+
df['up'] = df['high'].diff()
|
|
242
|
+
df['down'] = -df['low'].diff()
|
|
243
|
+
|
|
244
|
+
# Initialize +DM and -DM
|
|
245
|
+
df['dm_plus'] = np.where((df['up'] > df['down']) & (df['up'] > 0), df['up'], 0)
|
|
246
|
+
df['dm_minus'] = np.where((df['down'] > df['up']) & (df['down'] > 0), df['down'], 0)
|
|
247
|
+
|
|
248
|
+
# Calculate True Range (TR)
|
|
249
|
+
tr1 = df['high'] - df['low']
|
|
250
|
+
tr2 = np.abs(df['high'] - df['close'].shift())
|
|
251
|
+
tr3 = np.abs(df['low'] - df['close'].shift())
|
|
252
|
+
df['tr'] = np.max([tr1, tr2, tr3], axis=0)
|
|
253
|
+
|
|
254
|
+
# Smooth True Range (TR) using the exponential moving average (EMA)
|
|
255
|
+
df['tr_smooth'] = df['tr'].rolling(window=di_length).mean()
|
|
256
|
+
|
|
257
|
+
# Smooth +DM and -DM using rolling mean (EMA)
|
|
258
|
+
df['dm_plus_smooth'] = df['dm_plus'].rolling(window=di_length).mean()
|
|
259
|
+
df['dm_minus_smooth'] = df['dm_minus'].rolling(window=di_length).mean()
|
|
260
|
+
|
|
261
|
+
# Calculate +DI and -DI
|
|
262
|
+
df['di_plus'] = 100 * (df['dm_plus_smooth'] / df['tr_smooth'])
|
|
263
|
+
df['di_minus'] = 100 * (df['dm_minus_smooth'] / df['tr_smooth'])
|
|
264
|
+
|
|
265
|
+
# Calculate DX (Directional Movement Index)
|
|
266
|
+
df['dx'] = 100 * np.abs(df['di_plus'] - df['di_minus']) / (df['di_plus'] + df['di_minus'])
|
|
267
|
+
|
|
268
|
+
# Calculate ADX by smoothing DX using the specified lensig period
|
|
269
|
+
df['di_adx'] = df['dx'].rolling(window=adx_smoothing).mean()
|
|
270
|
+
|
|
271
|
+
# Return only the relevant columns
|
|
272
|
+
return df[['di_plus', 'di_minus', 'di_adx']]
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
print(f"Exception in calculating DMI : {e}")
|
|
276
|
+
pass
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def calculate_vwap(dataframe, anchor='session'):
|
|
280
|
+
""" Function to calculate VWAP """
|
|
281
|
+
try:
|
|
282
|
+
df = dataframe.copy()
|
|
283
|
+
df['cumulative_volume'] = df['volume'].cumsum()
|
|
284
|
+
df['cumulative_volume_price'] = (df['close'] * df['volume']).cumsum()
|
|
285
|
+
df['vwap'] = df['cumulative_volume_price'] / df['cumulative_volume']
|
|
286
|
+
return df[['vwap']]
|
|
287
|
+
except Exception as e:
|
|
288
|
+
print(f"Exception in calculating vwap : {e}")
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def direction_change(dataframe, depth=12, deviation=5, backstep=2):
|
|
293
|
+
""" Function to calculate Direction Changes """
|
|
294
|
+
try:
|
|
295
|
+
df = dataframe.copy()
|
|
296
|
+
# ta.barssince(not (higher[-ta.highestbars(depth)] - higher > deviation)[1])
|
|
297
|
+
|
|
298
|
+
df['highest_high'] = df['high'].rolling(window=depth).max()
|
|
299
|
+
df['lowest_low'] = df['low'].rolling(window=depth).min()
|
|
300
|
+
|
|
301
|
+
# Calculating deviation from last value
|
|
302
|
+
df['dev_high'] = (df['highest_high'].shift(1) - df['high']) > deviation
|
|
303
|
+
df['dev_low'] = (df['low'] - df['lowest_low'].shift(1)) > deviation
|
|
304
|
+
|
|
305
|
+
# Calculate direction
|
|
306
|
+
df['hr'] = df['dev_high'].apply(lambda x: 1 if x else 0)
|
|
307
|
+
df['lr'] = df['dev_low'].apply(lambda x: 1 if x else 0)
|
|
308
|
+
|
|
309
|
+
df['direction'] = 0
|
|
310
|
+
last_direction = 0
|
|
311
|
+
|
|
312
|
+
for i in range(len(df)):
|
|
313
|
+
if last_direction == df['lr'][i] == df['hr'][i] == 0:
|
|
314
|
+
df.loc[i, 'direction'] = -1
|
|
315
|
+
last_direction = -1
|
|
316
|
+
|
|
317
|
+
# elif last_direction == -1 and df['llr'][i] == 1 and df['llr'][i-1] == 1 and df['hhr'][i] == 0:
|
|
318
|
+
elif last_direction == -1 and df['lr'][i] == 1 and df['lr'][i-1] == 0:
|
|
319
|
+
df.loc[i, 'direction'] = 1
|
|
320
|
+
last_direction = 1
|
|
321
|
+
|
|
322
|
+
# elif last_direction == 1 and df['hhr'][i] == 1 and df['hhr'][i-1] == 1:
|
|
323
|
+
elif last_direction == 1 and df['hr'][i] == 0 and df['hr'][i-1] == 1:
|
|
324
|
+
df.loc[i, 'direction'] = -1
|
|
325
|
+
last_direction = -1
|
|
326
|
+
|
|
327
|
+
# elif last_direction == 1 and df['lr'][i] == 0 and df['lr'][i-1] == 1:
|
|
328
|
+
# df.loc[i, 'direction'] = -1
|
|
329
|
+
# last_direction = -1
|
|
330
|
+
else:
|
|
331
|
+
df.loc[i, 'direction'] = last_direction
|
|
332
|
+
|
|
333
|
+
print("current df")
|
|
334
|
+
print(df)
|
|
335
|
+
|
|
336
|
+
except Exception as e:
|
|
337
|
+
print(f"Exception in Direction Change : {e}")
|
|
338
|
+
pass
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def yono(dataframe, depth=12, deviation=5, backstep=2, tick_size=0.05):
|
|
342
|
+
try:
|
|
343
|
+
df = dataframe.copy()
|
|
344
|
+
df['highest_high'] = df['high'].rolling(window=depth).max()
|
|
345
|
+
df['lowest_low'] = df['low'].rolling(window=depth).min()
|
|
346
|
+
|
|
347
|
+
candles_list = df.to_dict('records')
|
|
348
|
+
last_direction = 0
|
|
349
|
+
|
|
350
|
+
for i in range(0, len(candles_list)):
|
|
351
|
+
print(f"indx : {i} | candle : {candles_list[i]}")
|
|
352
|
+
candles_list[i]['hr'] = 0
|
|
353
|
+
candles_list[i]['lr'] = 0
|
|
354
|
+
candles_list[i]["direction"] = 0
|
|
355
|
+
hr_idx = []
|
|
356
|
+
lr_idx = []
|
|
357
|
+
if i < depth:
|
|
358
|
+
continue
|
|
359
|
+
else:
|
|
360
|
+
highest_high = candles_list[i]["highest_high"]
|
|
361
|
+
past_highs = [i['high'] for i in candles_list[i+1 - depth: i+1]]
|
|
362
|
+
highest_high_index = past_highs.index(highest_high)
|
|
363
|
+
|
|
364
|
+
for idx, high_val in enumerate(past_highs[highest_high_index+1:]):
|
|
365
|
+
if not (highest_high - high_val) > deviation * tick_size:
|
|
366
|
+
hr_idx.append(idx)
|
|
367
|
+
|
|
368
|
+
if hr_idx:
|
|
369
|
+
candles_list[i]['hr'] = len(past_highs[highest_high_index+1:]) - hr_idx[-1]
|
|
370
|
+
else:
|
|
371
|
+
candles_list[i]['hr'] = len(past_highs[highest_high_index + 1:])
|
|
372
|
+
|
|
373
|
+
lowest_low = candles_list[i]["lowest_low"]
|
|
374
|
+
past_lows = [i['low'] for i in candles_list[(i+1) - depth: i+1]]
|
|
375
|
+
lowest_low_index = past_lows.index(lowest_low)
|
|
376
|
+
|
|
377
|
+
for l_idx, low_val in enumerate(past_lows[lowest_low_index+1:]):
|
|
378
|
+
if not (low_val - lowest_low) > deviation * tick_size:
|
|
379
|
+
lr_idx.append(l_idx)
|
|
380
|
+
|
|
381
|
+
if lr_idx:
|
|
382
|
+
candles_list[i]['lr'] = int(len(past_lows[lowest_low_index + 1:]) - lr_idx[-1])
|
|
383
|
+
else:
|
|
384
|
+
candles_list[i]['lr'] = int(len(past_lows[lowest_low_index + 1:]))
|
|
385
|
+
|
|
386
|
+
print(f"hr : {candles_list[i]['hr']} | lr : {candles_list[i]['lr']}")
|
|
387
|
+
|
|
388
|
+
if last_direction == 0:
|
|
389
|
+
if candles_list[i]['hr'] == 8 and candles_list[i]['lr'] == 0:
|
|
390
|
+
candles_list[i]['direction'] = -1
|
|
391
|
+
last_direction = -1
|
|
392
|
+
|
|
393
|
+
elif candles_list[i]['hr'] == 0 and candles_list[i]['lr'] == 8:
|
|
394
|
+
candles_list[i]['direction'] = 1
|
|
395
|
+
last_direction = 1
|
|
396
|
+
|
|
397
|
+
elif last_direction == -1 and candles_list[i]['hr'] >= 5 and candles_list[i-1]['hr'] >= 5 and candles_list[i-1]['lr'] == 0 and candles_list[i]['lr'] == 1:
|
|
398
|
+
candles_list[i]['direction'] = 1
|
|
399
|
+
last_direction = 1
|
|
400
|
+
|
|
401
|
+
elif last_direction == 1 and candles_list[i]['lr'] >= 5 and candles_list[i-1]['lr'] >= 5 and candles_list[i-1]['hr'] == 0 and candles_list[i]['hr'] == 1:
|
|
402
|
+
candles_list[i]['direction'] = -1
|
|
403
|
+
last_direction = -1
|
|
404
|
+
|
|
405
|
+
# elif last_direction == 1 and candles_list[i-1]['hr'] == 0 and candles_list[i]['hr'] == 1:
|
|
406
|
+
# candles_list[i]['direction'] = -1
|
|
407
|
+
# last_direction = -1
|
|
408
|
+
else:
|
|
409
|
+
candles_list[i]['direction'] = last_direction
|
|
410
|
+
|
|
411
|
+
# if not candles_list[i]["hr"] > candles_list[i]["lr"] and not candles_list[i-1]["hr"] > candles_list[i-1]["lr"]:
|
|
412
|
+
# candles_list[i]["direction"] = -1
|
|
413
|
+
# else:
|
|
414
|
+
# candles_list[i]["direction"] = 1
|
|
415
|
+
|
|
416
|
+
df = pd.DataFrame(candles_list)
|
|
417
|
+
print(df)
|
|
418
|
+
|
|
419
|
+
except Exception as e:
|
|
420
|
+
print(f"Exception in signal lib : {e}")
|
|
421
|
+
pass
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def calculate_direction(dataframe, depth, deviation, backstep, tick_size=0.05):
|
|
425
|
+
df = dataframe.copy()
|
|
426
|
+
high = df['high']
|
|
427
|
+
low = df['low']
|
|
428
|
+
|
|
429
|
+
# Calculate highest high and lowest low over the specified depth
|
|
430
|
+
highest_high = high.rolling(window=depth).max()
|
|
431
|
+
lowest_low = low.rolling(window=depth).min()
|
|
432
|
+
|
|
433
|
+
# Calculate hr and lr
|
|
434
|
+
hr = (highest_high.shift(1) - high) > (deviation * 0.05) # Assuming syminfo.mintick is 1e-5 for example
|
|
435
|
+
lr = (low - lowest_low.shift(1)) > (deviation * 0.05)
|
|
436
|
+
df["hr"] = hr
|
|
437
|
+
df["lr"] = lr
|
|
438
|
+
|
|
439
|
+
# Count bars since condition was true
|
|
440
|
+
hr_bars_since = hr[::-1].cumsum()[::-1] # Reverse cumulative sum to count bars since
|
|
441
|
+
lr_bars_since = lr[::-1].cumsum()[::-1]
|
|
442
|
+
df["hr_bars_since"] = hr_bars_since
|
|
443
|
+
df["lr_bars_since"] = lr_bars_since
|
|
444
|
+
|
|
445
|
+
# Calculate direction
|
|
446
|
+
direction = (hr_bars_since > lr_bars_since).astype(int) # 1 if hr > lr, else 0
|
|
447
|
+
direction = direction.rolling(window=backstep).sum() # Sum over the backstep period
|
|
448
|
+
|
|
449
|
+
# Final direction value
|
|
450
|
+
final_direction = direction.apply(lambda x: -1 if x >= backstep else 1)
|
|
451
|
+
|
|
452
|
+
df["direction"] = final_direction
|
|
453
|
+
print("final_direction :")
|
|
454
|
+
print(df)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def jurik_moving_average(dataframe, length=20, phase=50, power=2, source='close', highlight_movements=True):
|
|
458
|
+
""" Function to calculate Juring Moving Average """
|
|
459
|
+
try:
|
|
460
|
+
df = dataframe.copy()
|
|
461
|
+
# Initialize necessary columns and constants
|
|
462
|
+
df['src'] = df[source] # By default, using 'close' as the source
|
|
463
|
+
phase_ratio = np.where(phase < -100, 0.5, np.where(phase > 100, 2.5, phase / 100 + 1.5))
|
|
464
|
+
|
|
465
|
+
beta = 0.45 * (length - 1) / (0.45 * (length - 1) + 2)
|
|
466
|
+
alpha = beta ** power
|
|
467
|
+
|
|
468
|
+
# Create placeholder columns
|
|
469
|
+
df['e0'] = 0.0
|
|
470
|
+
df['e1'] = 0.0
|
|
471
|
+
df['e2'] = 0.0
|
|
472
|
+
df['jma'] = 0.0
|
|
473
|
+
|
|
474
|
+
# Calculate JMA
|
|
475
|
+
for i in range(1, len(df)):
|
|
476
|
+
df.at[i, 'e0'] = (1 - alpha) * df.at[i, 'src'] + alpha * df.at[i - 1, 'e0']
|
|
477
|
+
df.at[i, 'e1'] = (df.at[i, 'src'] - df.at[i, 'e0']) * (1 - beta) + beta * df.at[i - 1, 'e1']
|
|
478
|
+
df.at[i, 'e2'] = (df.at[i, 'e0'] + phase_ratio * df.at[i, 'e1'] - df.at[i - 1, 'jma']) * (1 - alpha) ** 2 + (alpha ** 2) * df.at[i - 1, 'e2']
|
|
479
|
+
df.at[i, 'jma'] = df.at[i, 'e2'] + df.at[i - 1, 'jma']
|
|
480
|
+
|
|
481
|
+
# Highlight movements by coloring JMA based on its trend
|
|
482
|
+
df['jmaColor'] = np.where((df['jma'] > df['jma'].shift(1)) & highlight_movements, 'green', np.where(highlight_movements, 'red', '#6d1e7f'))
|
|
483
|
+
|
|
484
|
+
return df[['jma', 'jmaColor']]
|
|
485
|
+
except Exception as e:
|
|
486
|
+
print(f"Exception in calculating Jurik Moving Average : {e}")
|
|
487
|
+
pass
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def heikin_ashi_smoothed(dataframe, ema_length=55):
|
|
491
|
+
""" Function to calculate Heikin-Ashi Smoothed"""
|
|
492
|
+
try:
|
|
493
|
+
df = dataframe.copy()
|
|
494
|
+
# Calculate ohlc4 and hlc3
|
|
495
|
+
df['ohlc4'] = (df['open'] + df['high'] + df['low'] + df['close']) / 4
|
|
496
|
+
df['hlc3'] = (df['high'] + df['low'] + df['close']) / 3
|
|
497
|
+
|
|
498
|
+
# Calculate haOpen
|
|
499
|
+
df['haOpen'] = (df['ohlc4'] + df['ohlc4'].shift(1).fillna(df['ohlc4'].iloc[0])) / 2
|
|
500
|
+
|
|
501
|
+
# Calculate haC (similar to Heikin Ashi Close)
|
|
502
|
+
df['haC'] = (df['ohlc4'] + df['haOpen'] + df[['high', 'haOpen']].max(axis=1) + df[['low', 'haOpen']].min(axis=1)) / 4
|
|
503
|
+
|
|
504
|
+
# EMA calculations for Heikin Ashi Smoothed
|
|
505
|
+
df['EMA1'] = ta.ema(df['haC'], ema_length)
|
|
506
|
+
df['EMA2'] = ta.ema(df['EMA1'], ema_length)
|
|
507
|
+
df['EMA3'] = ta.ema(df['EMA2'], ema_length)
|
|
508
|
+
|
|
509
|
+
# TMA1 calculations
|
|
510
|
+
df['TMA1'] = 3 * df['EMA1'] - 3 * df['EMA2'] + df['EMA3']
|
|
511
|
+
|
|
512
|
+
# Further EMA calculations for TMA2
|
|
513
|
+
df['EMA4'] = ta.ema(df['TMA1'], ema_length)
|
|
514
|
+
df['EMA5'] = ta.ema(df['EMA4'], ema_length)
|
|
515
|
+
df['EMA6'] = ta.ema(df['EMA5'], ema_length)
|
|
516
|
+
|
|
517
|
+
# TMA2 calculations
|
|
518
|
+
df['TMA2'] = 3 * df['EMA4'] - 3 * df['EMA5'] + df['EMA6']
|
|
519
|
+
|
|
520
|
+
# Calculate IPEK and YASIN
|
|
521
|
+
df['IPEK'] = df['TMA1'] - df['TMA2']
|
|
522
|
+
df['YASIN'] = df['TMA1'] + df['IPEK']
|
|
523
|
+
|
|
524
|
+
# EMA calculations for TMA3 and TMA4
|
|
525
|
+
df['EMA7'] = ta.ema(df['hlc3'], ema_length)
|
|
526
|
+
df['EMA8'] = ta.ema(df['EMA7'], ema_length)
|
|
527
|
+
df['EMA9'] = ta.ema(df['EMA8'], ema_length)
|
|
528
|
+
|
|
529
|
+
# TMA3 calculations
|
|
530
|
+
df['TMA3'] = 3 * df['EMA7'] - 3 * df['EMA8'] + df['EMA9']
|
|
531
|
+
|
|
532
|
+
# Further EMA calculations for TMA4
|
|
533
|
+
df['EMA10'] = ta.ema(df['TMA3'], ema_length)
|
|
534
|
+
df['EMA11'] = ta.ema(df['EMA10'], ema_length)
|
|
535
|
+
df['EMA12'] = ta.ema(df['EMA11'], ema_length)
|
|
536
|
+
|
|
537
|
+
# TMA4 calculations
|
|
538
|
+
df['TMA4'] = 3 * df['EMA10'] - 3 * df['EMA11'] + df['EMA12']
|
|
539
|
+
|
|
540
|
+
# Calculate IPEK1 and YASIN1
|
|
541
|
+
df['IPEK1'] = df['TMA3'] - df['TMA4']
|
|
542
|
+
df['YASIN1'] = df['TMA3'] + df['IPEK1']
|
|
543
|
+
return df[['YASIN', 'YASIN1']]
|
|
544
|
+
except Exception as e:
|
|
545
|
+
print(f"Exception in calculating HeikinAshi Smoothed : {e}")
|
|
546
|
+
pass
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def average_volume(volume, length):
|
|
550
|
+
""" Function to calculate Average Volume on Every Candle """
|
|
551
|
+
try:
|
|
552
|
+
print("Calculating Average Volume")
|
|
553
|
+
if length == "full":
|
|
554
|
+
avg_vol = volume.expanding().mean()
|
|
555
|
+
elif isinstance(length, int) and length > 0:
|
|
556
|
+
avg_vol = volume.rolling(window=length).mean()
|
|
557
|
+
else:
|
|
558
|
+
raise ValueError("Invalid length parameter. Must be 'full' or a positive integer.")
|
|
559
|
+
return avg_vol
|
|
560
|
+
except Exception as e:
|
|
561
|
+
print(f"Exception in calculating Average Volume: {e}")
|
|
562
|
+
return None
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def bb_sideways(dataframe, bb_length=50, bb_mult=4.0, bbr_len=21, bbr_std_thresh=0.05):
|
|
566
|
+
""" Function to calculate Bollinger Band Percent Sideways Indicator """
|
|
567
|
+
try:
|
|
568
|
+
df = dataframe.copy()
|
|
569
|
+
|
|
570
|
+
# Bollinger Bands calculation
|
|
571
|
+
df['basis'] = ta.sma(df['close'], length=bb_length)
|
|
572
|
+
df['dev'] = bb_mult * ta.stdev(df['close'], bb_length)
|
|
573
|
+
|
|
574
|
+
# df['std_dev'] = df['close'].rolling(window=bb_length).std()
|
|
575
|
+
df['upper'] = df['basis'] + df['dev']
|
|
576
|
+
df['lower'] = df['basis'] - df['dev']
|
|
577
|
+
|
|
578
|
+
# Bollinger Bands % (BB%) calculation
|
|
579
|
+
df['bbr'] = (df['close'] - df['lower']) / (df['upper'] - df['lower'])
|
|
580
|
+
|
|
581
|
+
# Smoothing BB% with a rolling window standard deviation
|
|
582
|
+
df['bbr_std'] = ta.stdev(df['bbr'], bbr_len)
|
|
583
|
+
|
|
584
|
+
# Sideways detection based on BB% and threshold
|
|
585
|
+
df['is_sideways'] = ((df['bbr'] > 0.0) & (df['bbr'] < 1.0)) & (df['bbr_std'] <= bbr_std_thresh)
|
|
586
|
+
|
|
587
|
+
# Background color indicator (green for sideways, red for trending)
|
|
588
|
+
df['color'] = np.where(df['is_sideways'], 'green', 'red')
|
|
589
|
+
return df[['is_sideways', 'color']]
|
|
590
|
+
except Exception as e:
|
|
591
|
+
print(f"Exception in calculating BB Percent Sideways : {e}")
|
|
592
|
+
pass
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def calculate_rdx(dataframe):
|
|
596
|
+
""" Function to Calculate RDX by trader hari krishna """
|
|
597
|
+
try:
|
|
598
|
+
df = dataframe.copy()
|
|
599
|
+
|
|
600
|
+
# Calculate RSI and DMI (14-period for each)
|
|
601
|
+
df['rsi'] = ta.rsi(df['close'], timeperiod=14)
|
|
602
|
+
dmi_df = calculate_dmi_2(dataframe, period=14)
|
|
603
|
+
|
|
604
|
+
df['plus_di'] = dmi_df['plus_di']
|
|
605
|
+
df['minus_di'] = dmi_df['minus_di']
|
|
606
|
+
df['adx'] = dmi_df['adx']
|
|
607
|
+
|
|
608
|
+
# Conditional ADX-based bands
|
|
609
|
+
df['s1'] = np.where(df['adx'] > 20, np.nan, 45)
|
|
610
|
+
df['s2'] = np.where(df['adx'] > 20, np.nan, 55)
|
|
611
|
+
|
|
612
|
+
# Plot filling based on ADX and RSI values
|
|
613
|
+
# df['str'] = np.where(df['adx'] > 20, (df['adx'] - 20) / 5, 0)
|
|
614
|
+
df['str'] = np.where(df['adx'] > 20, (df['adx'] - 25) / 5, 0)
|
|
615
|
+
df['shifted_rsi'] = df['str'] + df['rsi']
|
|
616
|
+
|
|
617
|
+
# Identify trend direction
|
|
618
|
+
df['color'] = np.where(df['plus_di'] > df['minus_di'], 'green', 'red')
|
|
619
|
+
|
|
620
|
+
# Buy and Sell Signals
|
|
621
|
+
df['buy'] = (df['plus_di'] > df['minus_di']) & (df['plus_di'].shift(1) <= df['minus_di'].shift(1))
|
|
622
|
+
df['sell'] = (df['plus_di'] < df['minus_di']) & (df['plus_di'].shift(1) >= df['minus_di'].shift(1))
|
|
623
|
+
|
|
624
|
+
return df[["shifted_rsi"]]
|
|
625
|
+
|
|
626
|
+
except Exception as e:
|
|
627
|
+
print(f"Exception in Calculating RDX : {e}")
|
|
628
|
+
pass
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def calculate_dmi_2(dataframe, period):
|
|
632
|
+
""" Function to Calculate Directional Movement Index """
|
|
633
|
+
try:
|
|
634
|
+
df = dataframe.copy()
|
|
635
|
+
|
|
636
|
+
# Calculate +DM and -DM
|
|
637
|
+
df['high_diff'] = df['high'].diff()
|
|
638
|
+
df['low_diff'] = -df['low'].diff()
|
|
639
|
+
df['plus_dm'] = np.where((df['high_diff'] > df['low_diff']) & (df['high_diff'] > 0), df['high_diff'], 0.0)
|
|
640
|
+
df['minus_dm'] = np.where((df['low_diff'] > df['high_diff']) & (df['low_diff'] > 0), df['low_diff'], 0.0)
|
|
641
|
+
|
|
642
|
+
# Calculate True Range (TR)
|
|
643
|
+
df['tr1'] = df['high'] - df['low']
|
|
644
|
+
df['tr2'] = np.abs(df['high'] - df['close'].shift(1))
|
|
645
|
+
df['tr3'] = np.abs(df['low'] - df['close'].shift(1))
|
|
646
|
+
df['tr'] = df[['tr1', 'tr2', 'tr3']].max(axis=1)
|
|
647
|
+
|
|
648
|
+
# Smooth +DM,-DM, and TR with an exponential moving average
|
|
649
|
+
df['plus_dm_smoothed'] = df['plus_dm'].rolling(window=period).mean()
|
|
650
|
+
df['minus_dm_smoothed'] = df['minus_dm'].rolling(window=period).mean()
|
|
651
|
+
df['tr_smoothed'] = df['tr'].rolling(window=period).mean()
|
|
652
|
+
|
|
653
|
+
# Calculate +DI and -DI
|
|
654
|
+
df['plus_di'] = 100 * (df['plus_dm_smoothed'] / df['tr_smoothed'])
|
|
655
|
+
df['minus_di'] = 100 * (df['minus_dm_smoothed'] / df['tr_smoothed'])
|
|
656
|
+
|
|
657
|
+
# Calculate DX (Directional Movement Index)
|
|
658
|
+
df['dx'] = 100 * np.abs(df['plus_di'] - df['minus_di']) / (df['plus_di'] + df['minus_di'])
|
|
659
|
+
|
|
660
|
+
# Calculate ADX by smoothing the DX values
|
|
661
|
+
df['adx'] = df['dx'].rolling(window=period).mean()
|
|
662
|
+
return df[['plus_di', 'minus_di', 'adx']]
|
|
663
|
+
except Exception as e:
|
|
664
|
+
print(f"Exception in Calculating DMI : {e}")
|
|
665
|
+
pass
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
def calculate_mti(dataframe, bb_length=20, bb_mult=2.0, adx_length=14, rsi_length=14):
|
|
669
|
+
""" Function to Calculate Market Trend Indicator by FinnoVent """
|
|
670
|
+
try:
|
|
671
|
+
print("Calculating MTI")
|
|
672
|
+
df = dataframe.copy()
|
|
673
|
+
|
|
674
|
+
# EMA Settings
|
|
675
|
+
df['shortEma'] = ta.ema(df['close'], timeperiod=3)
|
|
676
|
+
df['longEma'] = ta.ema(df['close'], timeperiod=30)
|
|
677
|
+
|
|
678
|
+
# Bollinger Bands Settings
|
|
679
|
+
df['basis'] = ta.sma(df['close'], timeperiod=bb_length)
|
|
680
|
+
df['dev'] = bb_mult * ta.stdev(df['close'], timeperiod=bb_length, nbdev=1)
|
|
681
|
+
df['upper'] = df['basis'] + df['dev']
|
|
682
|
+
df['lower'] = df['basis'] - df['dev']
|
|
683
|
+
|
|
684
|
+
# ADX Calculation
|
|
685
|
+
plus_dm = np.where((df['high'].diff() > df['low'].diff()) & (df['high'].diff() > 0), df['high'].diff(), 0)
|
|
686
|
+
minus_dm = np.where((df['low'].diff() > df['high'].diff()) & (df['low'].diff() > 0), df['low'].diff(), 0)
|
|
687
|
+
tr = ta.adx(df['high'], df['low'], df['close'], timeperiod=adx_length)
|
|
688
|
+
df['plusDI'] = 100 * ta.sma(plus_dm, timeperiod=adx_length) / tr
|
|
689
|
+
df['minusDI'] = 100 * ta.sma(minus_dm, timeperiod=adx_length) / tr
|
|
690
|
+
dx = 100 * np.abs(df['plusDI'] - df['minusDI']) / (df['plusDI'] + df['minusDI'])
|
|
691
|
+
df['adx'] = ta.sma(dx, timeperiod=adx_length)
|
|
692
|
+
|
|
693
|
+
# RSI Settings
|
|
694
|
+
|
|
695
|
+
df['rsi'] = ta.rsi(df['close'], timeperiod=rsi_length)
|
|
696
|
+
|
|
697
|
+
# Sideways Condition
|
|
698
|
+
df['sidewaysCondition'] = (
|
|
699
|
+
(df['close'] > df['lower'] + (df['upper'] - df['lower']) * 0.20) &
|
|
700
|
+
(df['close'] < df['upper'] - (df['upper'] - df['lower']) * 0.20) &
|
|
701
|
+
(df['adx'] < 30) &
|
|
702
|
+
(df['rsi'] > 40) & (df['rsi'] < 60)
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
# Trend Determination
|
|
706
|
+
df['uptrend'] = (df['shortEma'] > df['longEma']) & (~df['sidewaysCondition'])
|
|
707
|
+
df['downtrend'] = (df['shortEma'] < df['longEma']) & (~df['sidewaysCondition'])
|
|
708
|
+
|
|
709
|
+
# Bar color based on trend
|
|
710
|
+
df['color'] = np.where(df['uptrend'], 'green', np.where(df['downtrend'], 'red', np.where(df['sidewaysCondition'], 'gray', 'na')))
|
|
711
|
+
print("mti_df")
|
|
712
|
+
print(df)
|
|
713
|
+
except Exception as e:
|
|
714
|
+
print(f"Exception in Calculating MTI : {e}")
|
|
715
|
+
pass
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def smoothed_adx(dataframe, adx_length=14, di_length=14, smoothing_length=9, mamode="sma"):
|
|
719
|
+
""" Function to Calculated Smoothed ADX """
|
|
720
|
+
try:
|
|
721
|
+
print("Calculating Smoothed ADX")
|
|
722
|
+
out_df = pd.DataFrame()
|
|
723
|
+
adx_df = ta.adx(high=dataframe["high"], low=dataframe["low"], close=dataframe["close"], length=adx_length, lensig=di_length)
|
|
724
|
+
adx = round(adx_df.iloc[:, 0], 2).fillna(0)
|
|
725
|
+
out_df["adx"] = adx
|
|
726
|
+
s_adx = None
|
|
727
|
+
if mamode == "ema":
|
|
728
|
+
s_adx = ta.ema(adx, length=smoothing_length)
|
|
729
|
+
else:
|
|
730
|
+
s_adx = ta.sma(adx, length=smoothing_length)
|
|
731
|
+
out_df["s_adx"] = s_adx
|
|
732
|
+
|
|
733
|
+
return out_df
|
|
734
|
+
except Exception as e:
|
|
735
|
+
print(f"Exception in Calculating Smoothed ADX : {e}")
|
|
736
|
+
pass
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def calculate_net_volume(dataframe):
|
|
740
|
+
""" Function to Calculate the Net Volume indicator based on price and volume data."""
|
|
741
|
+
try:
|
|
742
|
+
print("Calculating Net Volume")
|
|
743
|
+
df = dataframe.copy()
|
|
744
|
+
|
|
745
|
+
# Shift the close column to get the previous close prices
|
|
746
|
+
df['prev_close'] = df['close'].shift(1)
|
|
747
|
+
|
|
748
|
+
# Calculate up and down volume based on the change in 'close' prices
|
|
749
|
+
df['up_volume'] = df['volume'].where(df['close'] > df['prev_close'], 0)
|
|
750
|
+
df['down_volume'] = df['volume'].where(df['close'] < df['prev_close'], 0)
|
|
751
|
+
|
|
752
|
+
# Calculate net volume as the difference between up and down volume
|
|
753
|
+
df['net_volume'] = df['up_volume'] - df['down_volume']
|
|
754
|
+
|
|
755
|
+
# Check if volume data is available
|
|
756
|
+
cum_vol = df['volume'].sum()
|
|
757
|
+
if cum_vol == 0:
|
|
758
|
+
raise ValueError("The data vendor doesn't provide volume data for this symbol.")
|
|
759
|
+
|
|
760
|
+
# Drop intermediate columns if not needed
|
|
761
|
+
df.drop(columns=['up_volume', 'down_volume', 'prev_close'], inplace=True)
|
|
762
|
+
|
|
763
|
+
return df[['net_volume']]
|
|
764
|
+
except Exception as e:
|
|
765
|
+
print(f"Exception in calculating net volume : {e}")
|
|
766
|
+
pass
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
def volume_oscillator(dataframe, short_length=5, long_length=10):
|
|
770
|
+
""" Function to Calculate Volume Oscillator Values """
|
|
771
|
+
try:
|
|
772
|
+
print("Calculating Volume Oscillator")
|
|
773
|
+
df = dataframe.copy()
|
|
774
|
+
|
|
775
|
+
# Check if volume data is available
|
|
776
|
+
if df['volume'].sum() == 0:
|
|
777
|
+
return None
|
|
778
|
+
|
|
779
|
+
# Calculate short and long EMAs of volume
|
|
780
|
+
df['short_ema'] = ta.ema(df["volume"], short_length)
|
|
781
|
+
df['long_ema'] = ta.ema(df["volume"], long_length)
|
|
782
|
+
|
|
783
|
+
# Calculate Volume Oscillator
|
|
784
|
+
df['v_osc'] = 100 * (df['short_ema'] - df['long_ema']) / df['long_ema']
|
|
785
|
+
|
|
786
|
+
# Drop intermediate columns if not needed
|
|
787
|
+
df.drop(columns=['short_ema', 'long_ema'], inplace=True)
|
|
788
|
+
|
|
789
|
+
return df[['v_osc']]
|
|
790
|
+
except Exception as e:
|
|
791
|
+
print(f"Exception in calculating volume oscillator : {e}")
|
|
792
|
+
pass
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
def volume_slope(volume, length=9, mamode="ema"):
|
|
796
|
+
""" Function to calculate Volume slope """
|
|
797
|
+
try:
|
|
798
|
+
out_df = pd.DataFrame()
|
|
799
|
+
if mamode == "sma":
|
|
800
|
+
out_df["vol_ma"] = ta.sma(volume, length=length)
|
|
801
|
+
elif mamode == "ema":
|
|
802
|
+
out_df["vol_ma"] = ta.ema(volume, length=length)
|
|
803
|
+
|
|
804
|
+
out_df["vol_slope"] = out_df['vol_ma'].gt(out_df['vol_ma'].shift(1).fillna(-np.inf)).map({True: '+ve', False: '-ve'})
|
|
805
|
+
out_df.loc[out_df['vol_ma'].isna(), 'vol_slope'] = 'NA'
|
|
806
|
+
return out_df[["vol_ma", "vol_slope"]]
|
|
807
|
+
except Exception as e:
|
|
808
|
+
print(f"Exception in calculating Volume Slope : {e}")
|
|
809
|
+
pass
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
def oi_slope(oi, length=9, mamode="ema"):
|
|
813
|
+
""" Function to calculate OI Slope """
|
|
814
|
+
try:
|
|
815
|
+
out_df = pd.DataFrame()
|
|
816
|
+
if mamode == "sma":
|
|
817
|
+
out_df["oi_ma"] = ta.sma(oi, length=length)
|
|
818
|
+
elif mamode == "ema":
|
|
819
|
+
out_df["oi_ma"] = ta.ema(oi, length=length)
|
|
820
|
+
|
|
821
|
+
out_df["oi_slope"] = out_df['oi_ma'].gt(out_df['oi_ma'].shift(1).fillna(-np.inf)).map({True: '+ve', False: '-ve'})
|
|
822
|
+
out_df.loc[out_df['oi_ma'].isna(), 'oi_slope'] = 'NA'
|
|
823
|
+
return out_df[["oi_ma", "oi_slope"]]
|
|
824
|
+
except Exception as e:
|
|
825
|
+
print(f"Exception in calculating OI Slope : {e}")
|
|
826
|
+
pass
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def custom_macd(dataframe, fast_length=12, slow_length=26, signal_length=9, source='close', sma_source='EMA', sma_signal='EMA'):
|
|
830
|
+
""" Function to calculate Custom MACD """
|
|
831
|
+
try:
|
|
832
|
+
print()
|
|
833
|
+
df = dataframe.copy()
|
|
834
|
+
if sma_source == 'SMA':
|
|
835
|
+
fast_ma = df[source].rolling(window=fast_length).mean()
|
|
836
|
+
slow_ma = df[source].rolling(window=slow_length).mean()
|
|
837
|
+
elif sma_source == 'EMA':
|
|
838
|
+
fast_ma = df[source].ewm(span=fast_length, adjust=False).mean()
|
|
839
|
+
slow_ma = df[source].ewm(span=slow_length, adjust=False).mean()
|
|
840
|
+
else:
|
|
841
|
+
raise ValueError("Invalid sma_source. Choose 'SMA' or 'EMA'.")
|
|
842
|
+
|
|
843
|
+
macd = fast_ma - slow_ma
|
|
844
|
+
|
|
845
|
+
if sma_signal == 'SMA':
|
|
846
|
+
signal = macd.rolling(window=signal_length).mean()
|
|
847
|
+
elif sma_signal == 'EMA':
|
|
848
|
+
signal = macd.ewm(span=signal_length, adjust=False).mean()
|
|
849
|
+
else:
|
|
850
|
+
raise ValueError("Invalid sma_signal. Choose 'SMA' or 'EMA'.")
|
|
851
|
+
|
|
852
|
+
hist = macd - signal
|
|
853
|
+
|
|
854
|
+
# Add MACD components to the DataFrame
|
|
855
|
+
df['macd'] = macd
|
|
856
|
+
df['macd_h'] = hist
|
|
857
|
+
df['macd_s'] = signal
|
|
858
|
+
return df[["macd", "macd_h", "macd_s"]]
|
|
859
|
+
except Exception as e:
|
|
860
|
+
print(f"Exception in Custom MACD : {e}")
|
|
861
|
+
pass
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
def bollinger_bandwidth(dataframe, length=20, source="close", std_dev=2, he_length=125, lc_length=125):
|
|
865
|
+
""" Function to calculate bollinger bandwidth"""
|
|
866
|
+
try:
|
|
867
|
+
basis = ta.sma(dataframe[source], length)
|
|
868
|
+
dev = std_dev * ta.stdev(dataframe[source], length)
|
|
869
|
+
upper = basis + dev
|
|
870
|
+
lower = basis - dev
|
|
871
|
+
bbw = ((upper - lower) / basis) * 100
|
|
872
|
+
return bbw
|
|
873
|
+
except Exception as e:
|
|
874
|
+
print(f"Exception in calculating bollinger bandwidth : {e}")
|
|
875
|
+
pass
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def calculate_bbw_range(dataframe, length, deviation=0.02):
|
|
879
|
+
""" Function to calculate Bollinger Band Range"""
|
|
880
|
+
try:
|
|
881
|
+
df = dataframe.copy()
|
|
882
|
+
df["bbw_ma"] = round(ta.sma(df["bbw"], length), 2)
|
|
883
|
+
df["bbw_diff"] = abs(round(df["bbw"] - df["bbw_ma"], 2))
|
|
884
|
+
df["bbw_range"] = np.where(df["bbw_diff"] > deviation, False, True)
|
|
885
|
+
return df[['bbw_range']]
|
|
886
|
+
except Exception as e:
|
|
887
|
+
print(f"Exception in calculating BB Range : {e}")
|
|
888
|
+
pass
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def calculate_tsi(close, period=14):
|
|
892
|
+
""" Calculate Trend Strength Index (TSI) using Correlation. """
|
|
893
|
+
try:
|
|
894
|
+
bar_index = pd.Series(range(len(close)), index=close.index)
|
|
895
|
+
|
|
896
|
+
# Calculate rolling correlation
|
|
897
|
+
tsi = close.rolling(window=period).corr(bar_index)
|
|
898
|
+
|
|
899
|
+
return tsi
|
|
900
|
+
except Exception as e:
|
|
901
|
+
print(f"Exception in calculating TSI : {e}")
|
|
902
|
+
pass
|
|
903
|
+
|