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.
@@ -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
+