oakscriptpy 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.
oakscriptpy/ta.py ADDED
@@ -0,0 +1,1795 @@
1
+ """Technical Analysis (ta) namespace — mirrors PineScript ta.* functions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+
7
+ from ._types import series_float, series_bool, series_int, Source
8
+ from ._utils import is_na, nz, create_nan_series, validate_series_length
9
+
10
+
11
+ def sma(source: Source, length: int) -> series_float:
12
+ result: series_float = []
13
+ length = int(length)
14
+
15
+ for i in range(len(source)):
16
+ if i < length - 1:
17
+ result.append(float('nan'))
18
+ else:
19
+ s = 0.0
20
+ for j in range(length):
21
+ s += source[i - j]
22
+ result.append(s / length)
23
+
24
+ return result
25
+
26
+
27
+ def ema(source: Source, length: int) -> series_float:
28
+ result: series_float = []
29
+ length = int(length)
30
+ multiplier = 2 / (length + 1)
31
+
32
+ first_valid_index = -1
33
+ valid_count = 0
34
+ init_sum = 0.0
35
+
36
+ for i in range(len(source)):
37
+ val = source[i]
38
+ if val is not None and not math.isnan(val):
39
+ init_sum += val
40
+ valid_count += 1
41
+ if valid_count == length:
42
+ first_valid_index = i
43
+ break
44
+
45
+ ema_value = init_sum / valid_count if valid_count > 0 else float('nan')
46
+ ema_initialized = first_valid_index >= 0
47
+
48
+ for i in range(len(source)):
49
+ if not ema_initialized or i < first_valid_index:
50
+ result.append(float('nan'))
51
+ elif i == first_valid_index:
52
+ result.append(ema_value)
53
+ else:
54
+ val = source[i]
55
+ if val is not None and not math.isnan(val):
56
+ ema_value = (val - ema_value) * multiplier + ema_value
57
+ result.append(ema_value)
58
+
59
+ return result
60
+
61
+
62
+ def rsi(source: Source, length: int) -> series_float:
63
+ result: series_float = []
64
+
65
+ changes = []
66
+ for i in range(1, len(source)):
67
+ changes.append(source[i] - source[i - 1])
68
+
69
+ gains = [c if c > 0 else 0.0 for c in changes]
70
+ losses = [-c if c < 0 else 0.0 for c in changes]
71
+
72
+ avg_gains = rma(gains, length)
73
+ avg_losses = rma(losses, length)
74
+
75
+ result.append(float('nan'))
76
+
77
+ for i in range(len(avg_gains)):
78
+ if avg_losses[i] == 0:
79
+ result.append(100.0)
80
+ else:
81
+ rs = avg_gains[i] / avg_losses[i]
82
+ result.append(100.0 - (100.0 / (1.0 + rs)))
83
+
84
+ return result
85
+
86
+
87
+ def macd(source: Source, fast_length: int, slow_length: int, signal_length: int):
88
+ fast_ema = ema(source, fast_length)
89
+ slow_ema = ema(source, slow_length)
90
+
91
+ macd_line: series_float = []
92
+ for i in range(len(source)):
93
+ macd_line.append(fast_ema[i] - slow_ema[i])
94
+
95
+ signal_line = ema(macd_line, signal_length)
96
+
97
+ histogram: series_float = []
98
+ for i in range(len(source)):
99
+ histogram.append(macd_line[i] - signal_line[i])
100
+
101
+ return [macd_line, signal_line, histogram]
102
+
103
+
104
+ def bb(series: Source, length: int, mult: float):
105
+ basis = sma(series, length)
106
+ dev_values = stdev(series, length)
107
+
108
+ upper: series_float = []
109
+ lower: series_float = []
110
+
111
+ for i in range(len(series)):
112
+ upper.append(basis[i] + mult * dev_values[i])
113
+ lower.append(basis[i] - mult * dev_values[i])
114
+
115
+ return [basis, upper, lower]
116
+
117
+
118
+ def stdev(source: Source, length: int) -> series_float:
119
+ result: series_float = []
120
+ length = int(length)
121
+ avg = sma(source, length)
122
+
123
+ for i in range(len(source)):
124
+ if i < length - 1:
125
+ result.append(float('nan'))
126
+ else:
127
+ sum_squares = 0.0
128
+ for j in range(length):
129
+ diff = source[i - j] - avg[i]
130
+ sum_squares += diff * diff
131
+ result.append(math.sqrt(sum_squares / length))
132
+
133
+ return result
134
+
135
+
136
+ def crossover(series1: Source, series2: Source) -> series_bool:
137
+ result: series_bool = []
138
+
139
+ for i in range(len(series1)):
140
+ if i == 0:
141
+ result.append(False)
142
+ else:
143
+ result.append(series1[i] > series2[i] and series1[i - 1] <= series2[i - 1])
144
+
145
+ return result
146
+
147
+
148
+ def crossunder(series1: Source, series2: Source) -> series_bool:
149
+ result: series_bool = []
150
+
151
+ for i in range(len(series1)):
152
+ if i == 0:
153
+ result.append(False)
154
+ else:
155
+ result.append(series1[i] < series2[i] and series1[i - 1] >= series2[i - 1])
156
+
157
+ return result
158
+
159
+
160
+ def change(source: Source, length: int = 1) -> series_float:
161
+ result: series_float = []
162
+
163
+ for i in range(len(source)):
164
+ if i < length:
165
+ result.append(float('nan'))
166
+ else:
167
+ result.append(source[i] - source[i - length])
168
+
169
+ return result
170
+
171
+
172
+ def tr(handle_na: bool = False, high: Source = None, low: Source = None, close: Source = None) -> series_float:
173
+ if high is None or low is None or close is None:
174
+ raise ValueError(
175
+ 'ta.tr() requires high, low, and close series. '
176
+ 'Pass them explicitly.'
177
+ )
178
+
179
+ result: series_float = []
180
+
181
+ for i in range(len(high)):
182
+ if i == 0:
183
+ result.append(high[i] - low[i])
184
+ else:
185
+ prev_close = close[i - 1]
186
+
187
+ if math.isnan(prev_close):
188
+ if handle_na:
189
+ result.append(high[i] - low[i])
190
+ else:
191
+ result.append(float('nan'))
192
+ else:
193
+ tr_val = max(
194
+ high[i] - low[i],
195
+ abs(high[i] - prev_close),
196
+ abs(low[i] - prev_close)
197
+ )
198
+ result.append(tr_val)
199
+
200
+ return result
201
+
202
+
203
+ def atr(length: int, high: Source = None, low: Source = None, close: Source = None) -> series_float:
204
+ if high is None or low is None or close is None:
205
+ raise ValueError(
206
+ 'ta.atr() requires high, low, and close series. '
207
+ 'Pass them explicitly.'
208
+ )
209
+
210
+ true_range = tr(False, high, low, close)
211
+ return rma(true_range, length)
212
+
213
+
214
+ def supertrend(factor: float, atr_period: int, high: Source = None, low: Source = None, close: Source = None, wicks: bool = False):
215
+ if high is None or low is None or close is None:
216
+ raise ValueError(
217
+ 'ta.supertrend() requires high, low, and close series. '
218
+ 'Pass them explicitly.'
219
+ )
220
+
221
+ supertrend_values: series_float = []
222
+ directions: series_int = []
223
+
224
+ source: series_float = []
225
+ for i in range(len(high)):
226
+ source.append((high[i] + low[i]) / 2)
227
+
228
+ atr_values = atr(atr_period, high, low, close)
229
+
230
+ prev_lower_band = float('nan')
231
+ prev_upper_band = float('nan')
232
+ prev_super_trend = float('nan')
233
+
234
+ for i in range(len(source)):
235
+ atr_value = atr_values[i] * factor
236
+
237
+ if math.isnan(atr_value):
238
+ supertrend_values.append(float('nan'))
239
+ directions.append(1)
240
+ continue
241
+
242
+ upper_band = source[i] + atr_value
243
+ lower_band = source[i] - atr_value
244
+
245
+ high_price = high[i] if wicks else close[i]
246
+ low_price = low[i] if wicks else close[i]
247
+ prev_low_price = (low[i - 1] if wicks else close[i - 1]) if i > 0 else 0.0
248
+ prev_high_price = (high[i - 1] if wicks else close[i - 1]) if i > 0 else 0.0
249
+
250
+ if i > 0 and not math.isnan(prev_lower_band) and not math.isnan(prev_upper_band):
251
+ lower_band = lower_band if (lower_band > prev_lower_band or prev_low_price < prev_lower_band) else prev_lower_band
252
+ upper_band = upper_band if (upper_band < prev_upper_band or prev_high_price > prev_upper_band) else prev_upper_band
253
+
254
+ if math.isnan(prev_super_trend):
255
+ current_direction = 1
256
+ elif prev_super_trend == prev_upper_band:
257
+ current_direction = -1 if high_price > upper_band else 1
258
+ else:
259
+ current_direction = 1 if low_price < lower_band else -1
260
+
261
+ super_trend_value = lower_band if current_direction == -1 else upper_band
262
+
263
+ supertrend_values.append(super_trend_value)
264
+ directions.append(current_direction)
265
+
266
+ prev_lower_band = lower_band
267
+ prev_upper_band = upper_band
268
+ prev_super_trend = super_trend_value
269
+
270
+ return [supertrend_values, directions]
271
+
272
+
273
+ def rma(source: Source, length: int) -> series_float:
274
+ result: series_float = []
275
+ length = int(length)
276
+ alpha = 1.0 / length
277
+
278
+ first_valid_index = -1
279
+ valid_count = 0
280
+ init_sum = 0.0
281
+
282
+ for i in range(len(source)):
283
+ val = source[i]
284
+ if val is not None and not math.isnan(val):
285
+ init_sum += val
286
+ valid_count += 1
287
+ if valid_count == length:
288
+ first_valid_index = i
289
+ break
290
+
291
+ rma_value = init_sum / valid_count if valid_count > 0 else float('nan')
292
+ rma_initialized = first_valid_index >= 0
293
+
294
+ for i in range(len(source)):
295
+ if not rma_initialized or i < first_valid_index:
296
+ result.append(float('nan'))
297
+ elif i == first_valid_index:
298
+ result.append(rma_value)
299
+ else:
300
+ val = source[i]
301
+ if val is not None and not math.isnan(val):
302
+ rma_value = alpha * val + (1 - alpha) * rma_value
303
+ result.append(rma_value)
304
+
305
+ return result
306
+
307
+
308
+ def wma(source: Source, length: int) -> series_float:
309
+ result: series_float = []
310
+ length = int(length)
311
+
312
+ for i in range(len(source)):
313
+ if i < length - 1:
314
+ result.append(float('nan'))
315
+ else:
316
+ s = 0.0
317
+ weight_sum = 0
318
+
319
+ for j in range(length):
320
+ weight = length - j
321
+ s += source[i - j] * weight
322
+ weight_sum += weight
323
+
324
+ result.append(s / weight_sum)
325
+
326
+ return result
327
+
328
+
329
+ def highest(source: Source, length: int) -> series_float:
330
+ result: series_float = []
331
+
332
+ for i in range(len(source)):
333
+ if i < length - 1:
334
+ result.append(float('nan'))
335
+ else:
336
+ max_val = -math.inf
337
+ for j in range(length):
338
+ if not math.isnan(source[i - j]):
339
+ max_val = max(max_val, source[i - j])
340
+ result.append(float('nan') if max_val == -math.inf else max_val)
341
+
342
+ return result
343
+
344
+
345
+ def lowest(source: Source, length: int) -> series_float:
346
+ result: series_float = []
347
+
348
+ for i in range(len(source)):
349
+ if i < length - 1:
350
+ result.append(float('nan'))
351
+ else:
352
+ min_val = math.inf
353
+ for j in range(length):
354
+ if not math.isnan(source[i - j]):
355
+ min_val = min(min_val, source[i - j])
356
+ result.append(float('nan') if min_val == math.inf else min_val)
357
+
358
+ return result
359
+
360
+
361
+ def cum(source: Source) -> series_float:
362
+ result: series_float = []
363
+ s = 0.0
364
+
365
+ for i in range(len(source)):
366
+ s += source[i]
367
+ result.append(s)
368
+
369
+ return result
370
+
371
+
372
+ def cross(source1: Source, source2: Source) -> series_bool:
373
+ result: series_bool = []
374
+
375
+ for i in range(len(source1)):
376
+ if i == 0:
377
+ result.append(False)
378
+ else:
379
+ crossed_up = source1[i] > source2[i] and source1[i - 1] <= source2[i - 1]
380
+ crossed_down = source1[i] < source2[i] and source1[i - 1] >= source2[i - 1]
381
+ result.append(crossed_up or crossed_down)
382
+
383
+ return result
384
+
385
+
386
+ def rising(source: Source, length: int) -> series_bool:
387
+ result: series_bool = []
388
+
389
+ for i in range(len(source)):
390
+ if i < length:
391
+ result.append(False)
392
+ else:
393
+ is_rising = True
394
+ for j in range(1, length + 1):
395
+ if source[i - j + 1] <= source[i - j]:
396
+ is_rising = False
397
+ break
398
+ result.append(is_rising)
399
+
400
+ return result
401
+
402
+
403
+ def falling(source: Source, length: int) -> series_bool:
404
+ result: series_bool = []
405
+
406
+ for i in range(len(source)):
407
+ if i < length:
408
+ result.append(False)
409
+ else:
410
+ is_falling = True
411
+ for j in range(1, length + 1):
412
+ if source[i - j + 1] >= source[i - j]:
413
+ is_falling = False
414
+ break
415
+ result.append(is_falling)
416
+
417
+ return result
418
+
419
+
420
+ def roc(source: Source, length: int) -> series_float:
421
+ result: series_float = []
422
+
423
+ for i in range(len(source)):
424
+ if i < length:
425
+ result.append(float('nan'))
426
+ else:
427
+ old_value = source[i - length]
428
+ if old_value == 0 or math.isnan(old_value) or math.isnan(source[i]):
429
+ result.append(float('nan'))
430
+ else:
431
+ change_value = source[i] - old_value
432
+ result.append((100 * change_value) / old_value)
433
+
434
+ return result
435
+
436
+
437
+ def mom(source: Source, length: int) -> series_float:
438
+ result: series_float = []
439
+
440
+ for i in range(len(source)):
441
+ if i < length:
442
+ result.append(float('nan'))
443
+ else:
444
+ if math.isnan(source[i]) or math.isnan(source[i - length]):
445
+ result.append(float('nan'))
446
+ else:
447
+ result.append(source[i] - source[i - length])
448
+
449
+ return result
450
+
451
+
452
+ def dev(source: Source, length: int) -> series_float:
453
+ result: series_float = []
454
+ mean_values = sma(source, length)
455
+
456
+ for i in range(len(source)):
457
+ if i < length - 1 or math.isnan(mean_values[i]):
458
+ result.append(float('nan'))
459
+ else:
460
+ s = 0.0
461
+ for j in range(length):
462
+ if not math.isnan(source[i - j]):
463
+ s += abs(source[i - j] - mean_values[i])
464
+ result.append(s / length)
465
+
466
+ return result
467
+
468
+
469
+ def variance(source: Source, length: int, biased: bool = True) -> series_float:
470
+ result: series_float = []
471
+ mean_values = sma(source, length)
472
+
473
+ for i in range(len(source)):
474
+ if i < length - 1 or math.isnan(mean_values[i]):
475
+ result.append(float('nan'))
476
+ else:
477
+ sum_squares = 0.0
478
+ count = 0
479
+ for j in range(length):
480
+ if not math.isnan(source[i - j]):
481
+ diff = source[i - j] - mean_values[i]
482
+ sum_squares += diff * diff
483
+ count += 1
484
+ divisor = count if biased else count - 1
485
+ result.append(sum_squares / divisor if divisor > 0 else float('nan'))
486
+
487
+ return result
488
+
489
+
490
+ def median(source: Source, length: int) -> series_float:
491
+ result: series_float = []
492
+
493
+ for i in range(len(source)):
494
+ if i < length - 1:
495
+ result.append(float('nan'))
496
+ else:
497
+ values = []
498
+ for j in range(length):
499
+ if not math.isnan(source[i - j]):
500
+ values.append(source[i - j])
501
+
502
+ if len(values) == 0:
503
+ result.append(float('nan'))
504
+ else:
505
+ values.sort()
506
+ mid = len(values) // 2
507
+ if len(values) % 2 == 0:
508
+ result.append((values[mid - 1] + values[mid]) / 2)
509
+ else:
510
+ result.append(values[mid])
511
+
512
+ return result
513
+
514
+
515
+ def swma(source: Source) -> series_float:
516
+ result: series_float = []
517
+
518
+ for i in range(len(source)):
519
+ if i < 3:
520
+ result.append(float('nan'))
521
+ else:
522
+ if math.isnan(source[i]) or math.isnan(source[i - 1]) or math.isnan(source[i - 2]) or math.isnan(source[i - 3]):
523
+ result.append(float('nan'))
524
+ else:
525
+ value = (
526
+ source[i - 3] * (1 / 6) +
527
+ source[i - 2] * (2 / 6) +
528
+ source[i - 1] * (2 / 6) +
529
+ source[i] * (1 / 6)
530
+ )
531
+ result.append(value)
532
+
533
+ return result
534
+
535
+
536
+ def vwma(source: Source, length: int, volume: Source = None) -> series_float:
537
+ if volume is None:
538
+ raise ValueError(
539
+ 'ta.vwma() requires volume series. '
540
+ 'Pass it explicitly.'
541
+ )
542
+
543
+ source_times_volume: series_float = []
544
+ for i in range(len(source)):
545
+ source_times_volume.append(source[i] * volume[i])
546
+
547
+ numerator = sma(source_times_volume, length)
548
+ denominator = sma(volume, length)
549
+
550
+ result: series_float = []
551
+ for i in range(len(source)):
552
+ if denominator[i] == 0 or math.isnan(denominator[i]):
553
+ result.append(float('nan'))
554
+ else:
555
+ result.append(numerator[i] / denominator[i])
556
+
557
+ return result
558
+
559
+
560
+ def linreg(source: Source, length: int, offset: int = 0) -> series_float:
561
+ result: series_float = []
562
+ length = int(length)
563
+
564
+ for i in range(len(source)):
565
+ if i < length - 1:
566
+ result.append(float('nan'))
567
+ else:
568
+ has_nan = False
569
+ for j in range(length):
570
+ if math.isnan(source[i - j]):
571
+ has_nan = True
572
+ break
573
+
574
+ if has_nan:
575
+ result.append(float('nan'))
576
+ else:
577
+ sum_x = 0.0
578
+ sum_y = 0.0
579
+ sum_xy = 0.0
580
+ sum_x2 = 0.0
581
+
582
+ for j in range(length):
583
+ x = j
584
+ y = source[i - (length - 1 - j)]
585
+ sum_x += x
586
+ sum_y += y
587
+ sum_xy += x * y
588
+ sum_x2 += x * x
589
+
590
+ slope = (length * sum_xy - sum_x * sum_y) / (length * sum_x2 - sum_x * sum_x)
591
+ intercept = (sum_y - slope * sum_x) / length
592
+
593
+ x_val = length - 1 - offset
594
+ result.append(intercept + slope * x_val)
595
+
596
+ return result
597
+
598
+
599
+ def correlation(source1: Source, source2: Source, length: int) -> series_float:
600
+ result: series_float = []
601
+
602
+ for i in range(len(source1)):
603
+ if i < length - 1:
604
+ result.append(float('nan'))
605
+ else:
606
+ pairs = []
607
+ for j in range(length):
608
+ if not math.isnan(source1[i - j]) and not math.isnan(source2[i - j]):
609
+ pairs.append((source1[i - j], source2[i - j]))
610
+
611
+ if len(pairs) == 0:
612
+ result.append(float('nan'))
613
+ else:
614
+ sum1 = 0.0
615
+ sum2 = 0.0
616
+ for v1, v2 in pairs:
617
+ sum1 += v1
618
+ sum2 += v2
619
+ mean1 = sum1 / len(pairs)
620
+ mean2 = sum2 / len(pairs)
621
+
622
+ numerator = 0.0
623
+ sum1_sq = 0.0
624
+ sum2_sq = 0.0
625
+
626
+ for v1, v2 in pairs:
627
+ dev1 = v1 - mean1
628
+ dev2 = v2 - mean2
629
+ numerator += dev1 * dev2
630
+ sum1_sq += dev1 * dev1
631
+ sum2_sq += dev2 * dev2
632
+
633
+ denominator = math.sqrt(sum1_sq * sum2_sq)
634
+ if denominator == 0:
635
+ result.append(float('nan'))
636
+ else:
637
+ result.append(numerator / denominator)
638
+
639
+ return result
640
+
641
+
642
+ def percentrank(source: Source, length: int) -> series_float:
643
+ result: series_float = []
644
+
645
+ for i in range(len(source)):
646
+ if i < length:
647
+ result.append(float('nan'))
648
+ else:
649
+ if math.isnan(source[i]):
650
+ result.append(float('nan'))
651
+ continue
652
+
653
+ has_nan = False
654
+ for j in range(1, length + 1):
655
+ if math.isnan(source[i - j]):
656
+ has_nan = True
657
+ break
658
+
659
+ if has_nan:
660
+ result.append(float('nan'))
661
+ else:
662
+ current_value = source[i]
663
+ count_less_or_equal = 0
664
+
665
+ for j in range(1, length + 1):
666
+ if source[i - j] <= current_value:
667
+ count_less_or_equal += 1
668
+
669
+ result.append((count_less_or_equal / length) * 100)
670
+
671
+ return result
672
+
673
+
674
+ def cci(source: Source, length: int) -> series_float:
675
+ result: series_float = []
676
+ sma_values = sma(source, length)
677
+ dev_values = dev(source, length)
678
+
679
+ for i in range(len(source)):
680
+ if math.isnan(sma_values[i]) or math.isnan(dev_values[i]) or dev_values[i] == 0:
681
+ result.append(float('nan'))
682
+ else:
683
+ cci_val = (source[i] - sma_values[i]) / (0.015 * dev_values[i])
684
+ result.append(cci_val)
685
+
686
+ return result
687
+
688
+
689
+ def stoch(source: Source, high: Source, low: Source, length: int) -> series_float:
690
+ result: series_float = []
691
+ lowest_values = lowest(low, length)
692
+ highest_values = highest(high, length)
693
+
694
+ for i in range(len(source)):
695
+ if math.isnan(lowest_values[i]) or math.isnan(highest_values[i]):
696
+ result.append(float('nan'))
697
+ else:
698
+ range_val = highest_values[i] - lowest_values[i]
699
+ if range_val == 0:
700
+ result.append(float('nan'))
701
+ else:
702
+ stoch_value = 100 * (source[i] - lowest_values[i]) / range_val
703
+ result.append(stoch_value)
704
+
705
+ return result
706
+
707
+
708
+ def mfi(source: Source, length: int, volume: Source = None) -> series_float:
709
+ if volume is None:
710
+ raise ValueError(
711
+ 'ta.mfi() requires volume series. '
712
+ 'Pass it explicitly.'
713
+ )
714
+
715
+ result: series_float = []
716
+
717
+ changes = [float('nan')]
718
+ for i in range(1, len(source)):
719
+ changes.append(source[i] - source[i - 1])
720
+
721
+ positive_flow = []
722
+ negative_flow = []
723
+
724
+ for i in range(len(source)):
725
+ if i == 0 or math.isnan(changes[i]):
726
+ positive_flow.append(0.0)
727
+ negative_flow.append(0.0)
728
+ elif changes[i] > 0:
729
+ positive_flow.append(volume[i] * source[i])
730
+ negative_flow.append(0.0)
731
+ elif changes[i] < 0:
732
+ positive_flow.append(0.0)
733
+ negative_flow.append(volume[i] * source[i])
734
+ else:
735
+ positive_flow.append(0.0)
736
+ negative_flow.append(0.0)
737
+
738
+ for i in range(len(source)):
739
+ if i < length:
740
+ result.append(float('nan'))
741
+ else:
742
+ pos_sum = 0.0
743
+ neg_sum = 0.0
744
+
745
+ for j in range(length):
746
+ pos_sum += positive_flow[i - j]
747
+ neg_sum += negative_flow[i - j]
748
+
749
+ if neg_sum == 0:
750
+ result.append(100.0)
751
+ else:
752
+ money_ratio = pos_sum / neg_sum
753
+ mfi_value = 100.0 - (100.0 / (1.0 + money_ratio))
754
+ result.append(mfi_value)
755
+
756
+ return result
757
+
758
+
759
+ def hma(source: Source, length: int) -> series_float:
760
+ half_length = int(length / 2)
761
+ sqrt_length = int(math.sqrt(length))
762
+
763
+ wma_half = wma(source, half_length)
764
+ wma_full = wma(source, length)
765
+
766
+ diff: series_float = []
767
+ for i in range(len(source)):
768
+ diff.append(2 * wma_half[i] - wma_full[i])
769
+
770
+ return wma(diff, sqrt_length)
771
+
772
+
773
+ def sar(start: float, inc: float, max_val: float, high: Source = None, low: Source = None, close: Source = None) -> series_float:
774
+ if high is None or low is None or close is None:
775
+ raise ValueError(
776
+ 'ta.sar() requires high, low, and close series. '
777
+ 'Pass them explicitly.'
778
+ )
779
+
780
+ result: series_float = []
781
+ sar_value = float('nan')
782
+ extreme_point = float('nan')
783
+ acceleration = start
784
+ is_up_trend = False
785
+ is_first_trend_bar = False
786
+
787
+ for i in range(len(close)):
788
+ if i == 0:
789
+ result.append(float('nan'))
790
+ continue
791
+
792
+ if i == 1:
793
+ if close[i] > close[i - 1]:
794
+ is_up_trend = True
795
+ extreme_point = high[i]
796
+ sar_value = low[i - 1]
797
+ else:
798
+ is_up_trend = False
799
+ extreme_point = low[i]
800
+ sar_value = high[i - 1]
801
+ is_first_trend_bar = True
802
+ acceleration = start
803
+
804
+ sar_value = sar_value + acceleration * (extreme_point - sar_value)
805
+
806
+ if is_up_trend:
807
+ if sar_value > low[i]:
808
+ is_first_trend_bar = True
809
+ is_up_trend = False
810
+ sar_value = max(high[i], extreme_point)
811
+ extreme_point = low[i]
812
+ acceleration = start
813
+ else:
814
+ if sar_value < high[i]:
815
+ is_first_trend_bar = True
816
+ is_up_trend = True
817
+ sar_value = min(low[i], extreme_point)
818
+ extreme_point = high[i]
819
+ acceleration = start
820
+
821
+ if not is_first_trend_bar:
822
+ if is_up_trend:
823
+ if high[i] > extreme_point:
824
+ extreme_point = high[i]
825
+ acceleration = min(acceleration + inc, max_val)
826
+ else:
827
+ if low[i] < extreme_point:
828
+ extreme_point = low[i]
829
+ acceleration = min(acceleration + inc, max_val)
830
+
831
+ if is_up_trend:
832
+ sar_value = min(sar_value, low[i - 1])
833
+ if i > 1:
834
+ sar_value = min(sar_value, low[i - 2])
835
+ else:
836
+ sar_value = max(sar_value, high[i - 1])
837
+ if i > 1:
838
+ sar_value = max(sar_value, high[i - 2])
839
+
840
+ result.append(sar_value)
841
+ is_first_trend_bar = False
842
+
843
+ return result
844
+
845
+
846
+ def pivothigh(source_or_leftbars, leftbars_or_rightbars, rightbars=None, high=None):
847
+ if rightbars is None:
848
+ if high is None:
849
+ raise ValueError('ta.pivothigh() requires high series when using two-parameter version.')
850
+ source = high
851
+ leftbars = source_or_leftbars
852
+ right = leftbars_or_rightbars
853
+ else:
854
+ source = source_or_leftbars
855
+ leftbars = leftbars_or_rightbars
856
+ right = rightbars
857
+
858
+ result: series_float = []
859
+
860
+ for i in range(len(source)):
861
+ if i < leftbars or i + right >= len(source):
862
+ result.append(float('nan'))
863
+ continue
864
+
865
+ center_value = source[i]
866
+ is_pivot = True
867
+
868
+ for j in range(1, leftbars + 1):
869
+ if source[i - j] >= center_value:
870
+ is_pivot = False
871
+ break
872
+
873
+ if is_pivot:
874
+ for j in range(1, right + 1):
875
+ if source[i + j] >= center_value:
876
+ is_pivot = False
877
+ break
878
+
879
+ result.append(center_value if is_pivot else float('nan'))
880
+
881
+ return result
882
+
883
+
884
+ def pivotlow(source_or_leftbars, leftbars_or_rightbars, rightbars=None, low=None):
885
+ if rightbars is None:
886
+ if low is None:
887
+ raise ValueError('ta.pivotlow() requires low series when using two-parameter version.')
888
+ source = low
889
+ leftbars = source_or_leftbars
890
+ right = leftbars_or_rightbars
891
+ else:
892
+ source = source_or_leftbars
893
+ leftbars = leftbars_or_rightbars
894
+ right = rightbars
895
+
896
+ result: series_float = []
897
+
898
+ for i in range(len(source)):
899
+ if i < leftbars or i + right >= len(source):
900
+ result.append(float('nan'))
901
+ continue
902
+
903
+ center_value = source[i]
904
+ is_pivot = True
905
+
906
+ for j in range(1, leftbars + 1):
907
+ if source[i - j] <= center_value:
908
+ is_pivot = False
909
+ break
910
+
911
+ if is_pivot:
912
+ for j in range(1, right + 1):
913
+ if source[i + j] <= center_value:
914
+ is_pivot = False
915
+ break
916
+
917
+ result.append(center_value if is_pivot else float('nan'))
918
+
919
+ return result
920
+
921
+
922
+ def barssince(condition: series_bool) -> series_float:
923
+ result: series_float = []
924
+ bars_since_true = float('nan')
925
+
926
+ for i in range(len(condition)):
927
+ if condition[i]:
928
+ bars_since_true = 0
929
+ elif not math.isnan(bars_since_true):
930
+ bars_since_true += 1
931
+ result.append(bars_since_true)
932
+
933
+ return result
934
+
935
+
936
+ def valuewhen(condition: series_bool, source: Source, occurrence: int) -> series_float:
937
+ result: series_float = []
938
+
939
+ for i in range(len(condition)):
940
+ occurrence_count = 0
941
+ found_value = float('nan')
942
+
943
+ for j in range(i, -1, -1):
944
+ if condition[j]:
945
+ if occurrence_count == occurrence:
946
+ found_value = source[j]
947
+ break
948
+ occurrence_count += 1
949
+
950
+ result.append(found_value)
951
+
952
+ return result
953
+
954
+
955
+ def dmi(di_length: int, adx_smoothing: int, high: Source, low: Source, close: Source):
956
+ length = max(len(high), len(low), len(close))
957
+ plus_dm: series_float = []
958
+ minus_dm: series_float = []
959
+ true_range_values = tr(False, high, low, close)
960
+
961
+ for i in range(length):
962
+ if i == 0:
963
+ plus_dm.append(0.0)
964
+ minus_dm.append(0.0)
965
+ else:
966
+ up_move = high[i] - high[i - 1]
967
+ down_move = low[i - 1] - low[i]
968
+
969
+ plus_dm_val = 0.0
970
+ minus_dm_val = 0.0
971
+
972
+ if up_move > down_move and up_move > 0:
973
+ plus_dm_val = up_move
974
+ if down_move > up_move and down_move > 0:
975
+ minus_dm_val = down_move
976
+
977
+ plus_dm.append(plus_dm_val)
978
+ minus_dm.append(minus_dm_val)
979
+
980
+ smoothed_plus_dm = rma(plus_dm, di_length)
981
+ smoothed_minus_dm = rma(minus_dm, di_length)
982
+ smoothed_tr = rma(true_range_values, di_length)
983
+
984
+ plus_di: series_float = []
985
+ minus_di: series_float = []
986
+
987
+ for i in range(length):
988
+ if smoothed_tr[i] == 0:
989
+ plus_di.append(0.0)
990
+ minus_di.append(0.0)
991
+ else:
992
+ plus_di.append((smoothed_plus_dm[i] / smoothed_tr[i]) * 100)
993
+ minus_di.append((smoothed_minus_dm[i] / smoothed_tr[i]) * 100)
994
+
995
+ dx: series_float = []
996
+ for i in range(length):
997
+ total = plus_di[i] + minus_di[i]
998
+ if total == 0:
999
+ dx.append(0.0)
1000
+ else:
1001
+ dx.append((abs(plus_di[i] - minus_di[i]) / total) * 100)
1002
+
1003
+ adx_values = rma(dx, adx_smoothing)
1004
+
1005
+ return [plus_di, minus_di, adx_values]
1006
+
1007
+
1008
+ def tsi(source: Source, short_length: int, long_length: int) -> series_float:
1009
+ momentum: series_float = []
1010
+
1011
+ for i in range(len(source)):
1012
+ if i == 0:
1013
+ momentum.append(float('nan'))
1014
+ else:
1015
+ momentum.append(source[i] - source[i - 1])
1016
+
1017
+ smoothed_momentum = ema(ema(momentum, long_length), short_length)
1018
+
1019
+ abs_momentum = [abs(v) for v in momentum]
1020
+ smoothed_abs_momentum = ema(ema(abs_momentum, long_length), short_length)
1021
+
1022
+ result: series_float = []
1023
+ for i in range(len(source)):
1024
+ if smoothed_abs_momentum[i] == 0:
1025
+ result.append(0.0)
1026
+ else:
1027
+ result.append((smoothed_momentum[i] / smoothed_abs_momentum[i]) * 100)
1028
+
1029
+ return result
1030
+
1031
+
1032
+ def cmo(source: Source, length: int) -> series_float:
1033
+ result: series_float = []
1034
+
1035
+ for i in range(len(source)):
1036
+ if i < length:
1037
+ result.append(float('nan'))
1038
+ continue
1039
+
1040
+ sum_gains = 0.0
1041
+ sum_losses = 0.0
1042
+
1043
+ for j in range(length):
1044
+ change_val = source[i - j] - source[i - j - 1]
1045
+ if change_val > 0:
1046
+ sum_gains += change_val
1047
+ else:
1048
+ sum_losses += abs(change_val)
1049
+
1050
+ total_movement = sum_gains + sum_losses
1051
+ if total_movement == 0:
1052
+ result.append(0.0)
1053
+ else:
1054
+ cmo_value = ((sum_gains - sum_losses) / total_movement) * 100
1055
+ result.append(cmo_value)
1056
+
1057
+ return result
1058
+
1059
+
1060
+ def kc(source: Source, length: int, mult: float, use_true_range: bool = True, high: Source = None, low: Source = None, close: Source = None):
1061
+ middle = ema(source, length)
1062
+
1063
+ if use_true_range:
1064
+ if high is None or low is None or close is None:
1065
+ raise ValueError('ta.kc() with use_true_range=True requires high, low, and close data')
1066
+ range_values = tr(False, high, low, close)
1067
+ else:
1068
+ if high is None or low is None:
1069
+ raise ValueError('ta.kc() requires high and low data')
1070
+ range_values = []
1071
+ for i in range(len(high)):
1072
+ range_values.append(high[i] - low[i])
1073
+
1074
+ range_ema = ema(range_values, length)
1075
+
1076
+ upper: series_float = []
1077
+ lower: series_float = []
1078
+
1079
+ for i in range(len(middle)):
1080
+ upper.append(middle[i] + range_ema[i] * mult)
1081
+ lower.append(middle[i] - range_ema[i] * mult)
1082
+
1083
+ return [middle, upper, lower]
1084
+
1085
+
1086
+ def bbw(source: Source, length: int, mult: float) -> series_float:
1087
+ basis, upper, lower = bb(source, length, mult)
1088
+ result: series_float = []
1089
+
1090
+ for i in range(len(source)):
1091
+ if basis[i] == 0:
1092
+ result.append(float('nan'))
1093
+ else:
1094
+ width = ((upper[i] - lower[i]) / basis[i]) * 100
1095
+ result.append(width)
1096
+
1097
+ return result
1098
+
1099
+
1100
+ def wpr(high: Source, low: Source, close: Source, length: int = 14) -> series_float:
1101
+ result: series_float = []
1102
+
1103
+ for i in range(len(close)):
1104
+ if i < length - 1:
1105
+ result.append(float('nan'))
1106
+ continue
1107
+
1108
+ highest_high = high[i - length + 1]
1109
+ lowest_low = low[i - length + 1]
1110
+
1111
+ for j in range(i - length + 2, i + 1):
1112
+ if high[j] > highest_high:
1113
+ highest_high = high[j]
1114
+ if low[j] < lowest_low:
1115
+ lowest_low = low[j]
1116
+
1117
+ range_val = highest_high - lowest_low
1118
+ if range_val == 0:
1119
+ result.append(float('nan'))
1120
+ else:
1121
+ wpr_value = ((highest_high - close[i]) / range_val) * -100
1122
+ result.append(wpr_value)
1123
+
1124
+ return result
1125
+
1126
+
1127
+ def vwap(source: Source, volume: Source) -> series_float:
1128
+ if len(source) != len(volume):
1129
+ raise ValueError('ta.vwap: source and volume must have the same length')
1130
+
1131
+ result: series_float = []
1132
+ cumulative_pv = 0.0
1133
+ cumulative_volume = 0.0
1134
+
1135
+ for i in range(len(source)):
1136
+ if math.isnan(source[i]) or math.isnan(volume[i]):
1137
+ result.append(float('nan'))
1138
+ continue
1139
+
1140
+ cumulative_pv += source[i] * volume[i]
1141
+ cumulative_volume += volume[i]
1142
+
1143
+ if cumulative_volume == 0:
1144
+ result.append(float('nan'))
1145
+ else:
1146
+ result.append(cumulative_pv / cumulative_volume)
1147
+
1148
+ return result
1149
+
1150
+
1151
+ def alma(source: Source, length: int = 9, offset: float = 0.85, sigma: float = 6, floor: bool = False) -> series_float:
1152
+ result: series_float = []
1153
+ m = int(offset * (length - 1)) if floor else offset * (length - 1)
1154
+ s = length / sigma
1155
+
1156
+ weights = []
1157
+ weight_sum = 0.0
1158
+
1159
+ for i in range(length):
1160
+ weight = math.exp(-1 * (i - m) ** 2 / (2 * s ** 2))
1161
+ weights.append(weight)
1162
+ weight_sum += weight
1163
+
1164
+ for i in range(length):
1165
+ weights[i] /= weight_sum
1166
+
1167
+ for i in range(len(source)):
1168
+ if i < length - 1:
1169
+ result.append(float('nan'))
1170
+ continue
1171
+
1172
+ alma_value = 0.0
1173
+ for j in range(length):
1174
+ alma_value += source[i - length + 1 + j] * weights[j]
1175
+
1176
+ result.append(alma_value)
1177
+
1178
+ return result
1179
+
1180
+
1181
+ def kcw(source: Source, length: int = 20, mult: float = 2, use_true_range: bool = True, high: Source = None, low: Source = None, close: Source = None) -> series_float:
1182
+ basis, upper, lower = kc(source, length, mult, use_true_range, high, low, close)
1183
+ result: series_float = []
1184
+
1185
+ for i in range(len(source)):
1186
+ if math.isnan(basis[i]) or basis[i] == 0:
1187
+ result.append(float('nan'))
1188
+ else:
1189
+ width = ((upper[i] - lower[i]) / basis[i]) * 100
1190
+ result.append(width)
1191
+
1192
+ return result
1193
+
1194
+
1195
+ def range_(high: Source, low: Source) -> series_float:
1196
+ if len(high) != len(low):
1197
+ raise ValueError('ta.range: high and low must have the same length')
1198
+
1199
+ result: series_float = []
1200
+
1201
+ for i in range(len(high)):
1202
+ if math.isnan(high[i]) or math.isnan(low[i]):
1203
+ result.append(float('nan'))
1204
+ else:
1205
+ result.append(high[i] - low[i])
1206
+
1207
+ return result
1208
+
1209
+
1210
+ def highestbars(source: Source, length: int) -> series_int:
1211
+ result: series_int = []
1212
+
1213
+ for i in range(len(source)):
1214
+ if i < length - 1:
1215
+ result.append(float('nan'))
1216
+ continue
1217
+
1218
+ highest_value = source[i - length + 1]
1219
+ highest_offset = length - 1
1220
+
1221
+ for j in range(i - length + 2, i + 1):
1222
+ if source[j] > highest_value:
1223
+ highest_value = source[j]
1224
+ highest_offset = i - j
1225
+
1226
+ result.append(highest_offset)
1227
+
1228
+ return result
1229
+
1230
+
1231
+ def lowestbars(source: Source, length: int) -> series_int:
1232
+ result: series_int = []
1233
+
1234
+ for i in range(len(source)):
1235
+ if i < length - 1:
1236
+ result.append(float('nan'))
1237
+ continue
1238
+
1239
+ lowest_value = source[i - length + 1]
1240
+ lowest_offset = length - 1
1241
+
1242
+ for j in range(i - length + 2, i + 1):
1243
+ if source[j] < lowest_value:
1244
+ lowest_value = source[j]
1245
+ lowest_offset = i - j
1246
+
1247
+ result.append(lowest_offset)
1248
+
1249
+ return result
1250
+
1251
+
1252
+ def max_(source1: Source, source2: Source) -> series_float:
1253
+ if len(source1) != len(source2):
1254
+ raise ValueError('ta.max: source1 and source2 must have the same length')
1255
+
1256
+ result: series_float = []
1257
+
1258
+ for i in range(len(source1)):
1259
+ if math.isnan(source1[i]) or math.isnan(source2[i]):
1260
+ result.append(float('nan'))
1261
+ else:
1262
+ result.append(max(source1[i], source2[i]))
1263
+
1264
+ return result
1265
+
1266
+
1267
+ def min_(source1: Source, source2: Source) -> series_float:
1268
+ if len(source1) != len(source2):
1269
+ raise ValueError('ta.min: source1 and source2 must have the same length')
1270
+
1271
+ result: series_float = []
1272
+
1273
+ for i in range(len(source1)):
1274
+ if math.isnan(source1[i]) or math.isnan(source2[i]):
1275
+ result.append(float('nan'))
1276
+ else:
1277
+ result.append(min(source1[i], source2[i]))
1278
+
1279
+ return result
1280
+
1281
+
1282
+ def cog(source: Source, length: int = 10) -> series_float:
1283
+ result: series_float = []
1284
+
1285
+ for i in range(len(source)):
1286
+ if i < length - 1:
1287
+ result.append(float('nan'))
1288
+ continue
1289
+
1290
+ numerator = 0.0
1291
+ denominator = 0.0
1292
+
1293
+ for j in range(length):
1294
+ weight = j + 1
1295
+ price = source[i - length + 1 + j]
1296
+ numerator += weight * price
1297
+ denominator += price
1298
+
1299
+ if denominator == 0:
1300
+ result.append(float('nan'))
1301
+ else:
1302
+ cog_val = -1 * (numerator / denominator) + (length + 1) / 2
1303
+ result.append(cog_val)
1304
+
1305
+ return result
1306
+
1307
+
1308
+ def mode(source: Source, length: int) -> series_float:
1309
+ result: series_float = []
1310
+
1311
+ for i in range(len(source)):
1312
+ if i < length - 1:
1313
+ result.append(float('nan'))
1314
+ continue
1315
+
1316
+ values = []
1317
+ for j in range(length):
1318
+ value = source[i - j]
1319
+ if not math.isnan(value):
1320
+ values.append(value)
1321
+
1322
+ if len(values) == 0:
1323
+ result.append(float('nan'))
1324
+ continue
1325
+
1326
+ frequency_map = {}
1327
+ for value in values:
1328
+ frequency_map[value] = frequency_map.get(value, 0) + 1
1329
+
1330
+ max_frequency = max(frequency_map.values())
1331
+
1332
+ modes_with_max_freq = [v for v, freq in frequency_map.items() if freq == max_frequency]
1333
+
1334
+ result.append(min(modes_with_max_freq))
1335
+
1336
+ return result
1337
+
1338
+
1339
+ def percentile_linear_interpolation(source: Source, length: int, percentage: float) -> series_float:
1340
+ result: series_float = []
1341
+
1342
+ for i in range(len(source)):
1343
+ if i < length - 1:
1344
+ result.append(float('nan'))
1345
+ continue
1346
+
1347
+ values = []
1348
+ for j in range(length):
1349
+ values.append(source[i - j])
1350
+
1351
+ if any(math.isnan(v) for v in values):
1352
+ result.append(float('nan'))
1353
+ continue
1354
+
1355
+ sorted_vals = sorted(values)
1356
+
1357
+ position = (percentage / 100) * (len(sorted_vals) - 1)
1358
+ lower_index = int(position)
1359
+ upper_index = math.ceil(position)
1360
+
1361
+ if lower_index == upper_index:
1362
+ result.append(sorted_vals[lower_index])
1363
+ else:
1364
+ fraction = position - lower_index
1365
+ interpolated = sorted_vals[lower_index] + fraction * (sorted_vals[upper_index] - sorted_vals[lower_index])
1366
+ result.append(interpolated)
1367
+
1368
+ return result
1369
+
1370
+
1371
+ def percentile_nearest_rank(source: Source, length: int, percentage: float) -> series_float:
1372
+ result: series_float = []
1373
+
1374
+ for i in range(len(source)):
1375
+ if i < length - 1:
1376
+ result.append(float('nan'))
1377
+ continue
1378
+
1379
+ values = []
1380
+ for j in range(length):
1381
+ value = source[i - j]
1382
+ if not math.isnan(value):
1383
+ values.append(value)
1384
+
1385
+ if len(values) == 0:
1386
+ result.append(float('nan'))
1387
+ continue
1388
+
1389
+ sorted_vals = sorted(values)
1390
+
1391
+ if percentage >= 100:
1392
+ result.append(sorted_vals[-1])
1393
+ continue
1394
+
1395
+ rank = math.ceil((percentage / 100) * len(sorted_vals))
1396
+
1397
+ index = max(0, rank - 1)
1398
+
1399
+ result.append(sorted_vals[index])
1400
+
1401
+ return result
1402
+
1403
+
1404
+ def rci(source: Source, length: int) -> series_float:
1405
+ result: series_float = []
1406
+
1407
+ for i in range(len(source)):
1408
+ if i < length - 1:
1409
+ result.append(float('nan'))
1410
+ continue
1411
+
1412
+ values = []
1413
+ for j in range(length):
1414
+ values.append(source[i - length + 1 + j])
1415
+
1416
+ if any(math.isnan(v) for v in values):
1417
+ result.append(float('nan'))
1418
+ continue
1419
+
1420
+ indexed = [(value, idx) for idx, value in enumerate(values)]
1421
+
1422
+ sorted_indexed = sorted(indexed, key=lambda x: x[0])
1423
+
1424
+ ranks = [0.0] * length
1425
+ j = 0
1426
+ while j < len(sorted_indexed):
1427
+ tie_count = 1
1428
+ while j + tie_count < len(sorted_indexed) and sorted_indexed[j][0] == sorted_indexed[j + tie_count][0]:
1429
+ tie_count += 1
1430
+
1431
+ current_rank = j + 1
1432
+ avg_rank = (current_rank + (current_rank + tie_count - 1)) / 2
1433
+
1434
+ for k in range(tie_count):
1435
+ ranks[sorted_indexed[j + k][1]] = avg_rank
1436
+
1437
+ j += tie_count
1438
+
1439
+ sum_squared_diff = 0.0
1440
+ for j in range(length):
1441
+ time_rank = j + 1
1442
+ diff = ranks[j] - time_rank
1443
+ sum_squared_diff += diff * diff
1444
+
1445
+ n = length
1446
+ base = (n * n * n - n) / 12
1447
+
1448
+ tie_correction = 0.0
1449
+ j = 0
1450
+ while j < len(sorted_indexed):
1451
+ tie_count = 1
1452
+ while j + tie_count < len(sorted_indexed) and sorted_indexed[j][0] == sorted_indexed[j + tie_count][0]:
1453
+ tie_count += 1
1454
+ if tie_count > 1:
1455
+ tie_correction += (tie_count * tie_count * tie_count - tie_count) / 12
1456
+ j += tie_count
1457
+
1458
+ a = base - tie_correction
1459
+ b = base
1460
+
1461
+ if a == 0 or b == 0:
1462
+ rho = 0.0
1463
+ else:
1464
+ rho = (a + b - sum_squared_diff) / (2 * math.sqrt(a * b))
1465
+
1466
+ result.append(rho * 100)
1467
+
1468
+ return result
1469
+
1470
+
1471
+ def _calculate_pivot_levels(type_str, h, l, c, o):
1472
+ if math.isnan(h) or math.isnan(l) or math.isnan(c):
1473
+ return [float('nan')] * 11
1474
+
1475
+ levels = [float('nan')] * 11
1476
+
1477
+ if type_str in ('Traditional', 'Fibonacci', 'Classic'):
1478
+ p = (h + l + c) / 3
1479
+ elif type_str == 'Woodie':
1480
+ p = (h + l + 2 * c) / 4
1481
+ elif type_str == 'DM':
1482
+ p = (h + l + c) / 3
1483
+ elif type_str == 'Camarilla':
1484
+ p = (h + l + c) / 3
1485
+ else:
1486
+ p = (h + l + c) / 3
1487
+
1488
+ levels[0] = p
1489
+
1490
+ if type_str in ('Traditional', 'Classic'):
1491
+ levels[1] = 2 * p - l
1492
+ levels[2] = 2 * p - h
1493
+ levels[3] = p + (h - l)
1494
+ levels[4] = p - (h - l)
1495
+ levels[5] = h + 2 * (p - l)
1496
+ levels[6] = l - 2 * (h - p)
1497
+ levels[7] = levels[5] + (h - l)
1498
+ levels[8] = levels[6] - (h - l)
1499
+ levels[9] = levels[7] + (h - l)
1500
+ levels[10] = levels[8] - (h - l)
1501
+
1502
+ elif type_str == 'Fibonacci':
1503
+ levels[1] = p + 0.382 * (h - l)
1504
+ levels[2] = p - 0.382 * (h - l)
1505
+ levels[3] = p + 0.618 * (h - l)
1506
+ levels[4] = p - 0.618 * (h - l)
1507
+ levels[5] = p + (h - l)
1508
+ levels[6] = p - (h - l)
1509
+ levels[7] = levels[5] + 0.618 * (h - l)
1510
+ levels[8] = levels[6] - 0.618 * (h - l)
1511
+ levels[9] = levels[7] + 0.382 * (h - l)
1512
+ levels[10] = levels[8] - 0.382 * (h - l)
1513
+
1514
+ elif type_str == 'Woodie':
1515
+ levels[1] = 2 * p - l
1516
+ levels[2] = 2 * p - h
1517
+ levels[3] = p + (h - l)
1518
+ levels[4] = p - (h - l)
1519
+ levels[5] = h + 2 * (p - l)
1520
+ levels[6] = l - 2 * (h - p)
1521
+ levels[7] = levels[5] + (h - l)
1522
+ levels[8] = levels[6] - (h - l)
1523
+ levels[9] = levels[7] + (h - l)
1524
+ levels[10] = levels[8] - (h - l)
1525
+
1526
+ elif type_str == 'DM':
1527
+ x = h + l + (c * 2) + (c if math.isnan(o) else o)
1528
+ new_p = x / (4 if math.isnan(o) else 5)
1529
+ levels[0] = new_p
1530
+ levels[1] = x / 2 - l
1531
+ levels[2] = x / 2 - h
1532
+
1533
+ elif type_str == 'Camarilla':
1534
+ range_val = h - l
1535
+ levels[1] = c + range_val * 1.1 / 12
1536
+ levels[2] = c - range_val * 1.1 / 12
1537
+ levels[3] = c + range_val * 1.1 / 6
1538
+ levels[4] = c - range_val * 1.1 / 6
1539
+ levels[5] = c + range_val * 1.1 / 4
1540
+ levels[6] = c - range_val * 1.1 / 4
1541
+ levels[7] = c + range_val * 1.1 / 2
1542
+ levels[8] = c - range_val * 1.1 / 2
1543
+ levels[9] = h
1544
+ levels[10] = l
1545
+
1546
+ return levels
1547
+
1548
+
1549
+ def pivot_point_levels(type_val, anchor, developing=False, high=None, low=None, close=None, open_=None):
1550
+ type_str = type_val if isinstance(type_val, str) else str(type_val[0])
1551
+ is_developing = developing if isinstance(developing, bool) else developing[0]
1552
+
1553
+ if type_str == 'Woodie' and is_developing:
1554
+ raise ValueError('ta.pivot_point_levels: Woodie type cannot use developing=True')
1555
+
1556
+ length = len(anchor)
1557
+ if high is not None and len(high) != length:
1558
+ raise ValueError('High series length mismatch')
1559
+ if low is not None and len(low) != length:
1560
+ raise ValueError('Low series length mismatch')
1561
+ if close is not None and len(close) != length:
1562
+ raise ValueError('Close series length mismatch')
1563
+ if open_ is not None and len(open_) != length:
1564
+ raise ValueError('Open series length mismatch')
1565
+
1566
+ results = [[] for _ in range(11)]
1567
+
1568
+ last_h = float('nan')
1569
+ last_l = float('nan')
1570
+ last_c = float('nan')
1571
+ last_o = float('nan')
1572
+ last_anchor_index = -1
1573
+
1574
+ for i in range(length):
1575
+ if anchor[i]:
1576
+ last_anchor_index = i
1577
+ last_h = high[i] if high else float('nan')
1578
+ last_l = low[i] if low else float('nan')
1579
+ last_c = close[i] if close else float('nan')
1580
+ last_o = open_[i] if open_ else float('nan')
1581
+
1582
+ if is_developing and last_anchor_index >= 0:
1583
+ h = max(v for v in high[last_anchor_index:i + 1] if not math.isnan(v)) if high else float('nan')
1584
+ l = min(v for v in low[last_anchor_index:i + 1] if not math.isnan(v)) if low else float('nan')
1585
+ c = close[i] if close else float('nan')
1586
+ o = open_[last_anchor_index] if open_ and last_anchor_index >= 0 else float('nan')
1587
+ else:
1588
+ h = last_h
1589
+ l = last_l
1590
+ c = last_c
1591
+ o = last_o
1592
+
1593
+ levels = _calculate_pivot_levels(type_str, h, l, c, o)
1594
+
1595
+ for j in range(11):
1596
+ results[j].append(levels[j])
1597
+
1598
+ return results
1599
+
1600
+
1601
+ def ichimoku(conversion_periods: int, base_periods: int, lagging_span2_periods: int, displacement: int, high: Source, low: Source, close: Source):
1602
+ length = len(high)
1603
+
1604
+ highest_conversion = highest(high, conversion_periods)
1605
+ lowest_conversion = lowest(low, conversion_periods)
1606
+ tenkan_sen: series_float = []
1607
+ for i in range(length):
1608
+ if math.isnan(highest_conversion[i]) or math.isnan(lowest_conversion[i]):
1609
+ tenkan_sen.append(float('nan'))
1610
+ else:
1611
+ tenkan_sen.append((highest_conversion[i] + lowest_conversion[i]) / 2)
1612
+
1613
+ highest_base = highest(high, base_periods)
1614
+ lowest_base = lowest(low, base_periods)
1615
+ kijun_sen: series_float = []
1616
+ for i in range(length):
1617
+ if math.isnan(highest_base[i]) or math.isnan(lowest_base[i]):
1618
+ kijun_sen.append(float('nan'))
1619
+ else:
1620
+ kijun_sen.append((highest_base[i] + lowest_base[i]) / 2)
1621
+
1622
+ senkou_span_a: series_float = []
1623
+ for i in range(length):
1624
+ source_index = i - displacement
1625
+ if source_index < 0 or math.isnan(tenkan_sen[source_index]) or math.isnan(kijun_sen[source_index]):
1626
+ senkou_span_a.append(float('nan'))
1627
+ else:
1628
+ senkou_span_a.append((tenkan_sen[source_index] + kijun_sen[source_index]) / 2)
1629
+
1630
+ highest_lagging = highest(high, lagging_span2_periods)
1631
+ lowest_lagging = lowest(low, lagging_span2_periods)
1632
+ senkou_span_b: series_float = []
1633
+ for i in range(length):
1634
+ source_index = i - displacement
1635
+ if source_index < 0 or math.isnan(highest_lagging[source_index]) or math.isnan(lowest_lagging[source_index]):
1636
+ senkou_span_b.append(float('nan'))
1637
+ else:
1638
+ senkou_span_b.append((highest_lagging[source_index] + lowest_lagging[source_index]) / 2)
1639
+
1640
+ chikou_span: series_float = []
1641
+ for i in range(length):
1642
+ source_index = i + displacement
1643
+ if source_index >= length or math.isnan(close[source_index]):
1644
+ chikou_span.append(float('nan'))
1645
+ else:
1646
+ chikou_span.append(close[source_index])
1647
+
1648
+ return [tenkan_sen, kijun_sen, senkou_span_a, senkou_span_b, chikou_span]
1649
+
1650
+
1651
+ def zigzag(deviation: float = 5.0, depth: int = 10, backstep: int = 3, source: Source = None, high: Source = None, low: Source = None):
1652
+ if high is not None and low is not None:
1653
+ high_source = high
1654
+ low_source = low
1655
+ elif source is not None:
1656
+ high_source = source
1657
+ low_source = source
1658
+ else:
1659
+ raise ValueError(
1660
+ 'ta.zigzag() requires either source series or high/low series. '
1661
+ 'Pass them explicitly.'
1662
+ )
1663
+
1664
+ length = len(high_source)
1665
+ if len(low_source) != length:
1666
+ raise ValueError('ta.zigzag: high and low must have the same length')
1667
+
1668
+ zigzag_values: series_float = [float('nan')] * length
1669
+ directions: series_int = [0] * length
1670
+ is_pivot: series_bool = [False] * length
1671
+
1672
+ def get_deviation(price1, price2):
1673
+ if abs(price1) < 1e-10 or math.isnan(price1) or math.isnan(price2):
1674
+ return 0.0
1675
+ return abs((price2 - price1) / price1) * 100
1676
+
1677
+ last_confirmed_pivot = None
1678
+ potential_pivot = None
1679
+ current_direction = 0
1680
+
1681
+ confirmed_pivots = []
1682
+
1683
+ start_index = -1
1684
+ initial_highest = -math.inf
1685
+ initial_lowest = math.inf
1686
+ initial_high_index = -1
1687
+ initial_low_index = -1
1688
+
1689
+ for i in range(min(depth, length)):
1690
+ if not math.isnan(high_source[i]) and high_source[i] > initial_highest:
1691
+ initial_highest = high_source[i]
1692
+ initial_high_index = i
1693
+ if not math.isnan(low_source[i]) and low_source[i] < initial_lowest:
1694
+ initial_lowest = low_source[i]
1695
+ initial_low_index = i
1696
+
1697
+ if initial_high_index >= 0 and initial_low_index >= 0:
1698
+ if initial_low_index <= initial_high_index:
1699
+ last_confirmed_pivot = {'index': initial_low_index, 'price': initial_lowest, 'type': 'low'}
1700
+ current_direction = 1
1701
+ else:
1702
+ last_confirmed_pivot = {'index': initial_high_index, 'price': initial_highest, 'type': 'high'}
1703
+ current_direction = -1
1704
+ confirmed_pivots.append(last_confirmed_pivot)
1705
+ start_index = last_confirmed_pivot['index'] + 1
1706
+ else:
1707
+ start_index = depth
1708
+
1709
+ for i in range(max(start_index, depth), length):
1710
+ current_high = high_source[i]
1711
+ current_low = low_source[i]
1712
+
1713
+ if math.isnan(current_high) or math.isnan(current_low):
1714
+ directions[i] = current_direction
1715
+ continue
1716
+
1717
+ is_potential_high = True
1718
+ for j in range(1, backstep + 1):
1719
+ if i - j < 0:
1720
+ break
1721
+ if not math.isnan(high_source[i - j]) and high_source[i - j] >= current_high:
1722
+ is_potential_high = False
1723
+ break
1724
+
1725
+ is_potential_low = True
1726
+ for j in range(1, backstep + 1):
1727
+ if i - j < 0:
1728
+ break
1729
+ if not math.isnan(low_source[i - j]) and low_source[i - j] <= current_low:
1730
+ is_potential_low = False
1731
+ break
1732
+
1733
+ if current_direction == 1:
1734
+ if is_potential_high:
1735
+ if potential_pivot is None or potential_pivot['type'] != 'high':
1736
+ if last_confirmed_pivot and get_deviation(last_confirmed_pivot['price'], current_high) >= deviation:
1737
+ potential_pivot = {'index': i, 'price': current_high, 'type': 'high'}
1738
+ else:
1739
+ if current_high > potential_pivot['price']:
1740
+ potential_pivot = {'index': i, 'price': current_high, 'type': 'high'}
1741
+
1742
+ if potential_pivot and potential_pivot['type'] == 'high':
1743
+ deviation_from_potential = get_deviation(potential_pivot['price'], current_low)
1744
+ if deviation_from_potential >= deviation and i - potential_pivot['index'] >= backstep:
1745
+ confirmed_pivots.append(potential_pivot)
1746
+ last_confirmed_pivot = potential_pivot
1747
+ current_direction = -1
1748
+ potential_pivot = None
1749
+
1750
+ elif current_direction == -1:
1751
+ if is_potential_low:
1752
+ if potential_pivot is None or potential_pivot['type'] != 'low':
1753
+ if last_confirmed_pivot and get_deviation(last_confirmed_pivot['price'], current_low) >= deviation:
1754
+ potential_pivot = {'index': i, 'price': current_low, 'type': 'low'}
1755
+ else:
1756
+ if current_low < potential_pivot['price']:
1757
+ potential_pivot = {'index': i, 'price': current_low, 'type': 'low'}
1758
+
1759
+ if potential_pivot and potential_pivot['type'] == 'low':
1760
+ deviation_from_potential = get_deviation(potential_pivot['price'], current_high)
1761
+ if deviation_from_potential >= deviation and i - potential_pivot['index'] >= backstep:
1762
+ confirmed_pivots.append(potential_pivot)
1763
+ last_confirmed_pivot = potential_pivot
1764
+ current_direction = 1
1765
+ potential_pivot = None
1766
+
1767
+ else:
1768
+ if last_confirmed_pivot:
1769
+ if last_confirmed_pivot['type'] == 'low' and get_deviation(last_confirmed_pivot['price'], current_high) >= deviation:
1770
+ current_direction = 1
1771
+ elif last_confirmed_pivot['type'] == 'high' and get_deviation(last_confirmed_pivot['price'], current_low) >= deviation:
1772
+ current_direction = -1
1773
+
1774
+ directions[i] = current_direction
1775
+
1776
+ last_potential_was_added = potential_pivot is not None
1777
+ if potential_pivot:
1778
+ confirmed_pivots.append(potential_pivot)
1779
+
1780
+ for pivot in confirmed_pivots:
1781
+ zigzag_values[pivot['index']] = pivot['price']
1782
+ is_pivot[pivot['index']] = True
1783
+
1784
+ current_dir = 0
1785
+ pivot_idx = 0
1786
+ num_confirmed = len(confirmed_pivots) - 1 if last_potential_was_added else len(confirmed_pivots)
1787
+
1788
+ for i in range(length):
1789
+ while pivot_idx < num_confirmed and confirmed_pivots[pivot_idx]['index'] <= i:
1790
+ pivot = confirmed_pivots[pivot_idx]
1791
+ current_dir = 1 if pivot['type'] == 'low' else -1
1792
+ pivot_idx += 1
1793
+ directions[i] = current_dir
1794
+
1795
+ return [zigzag_values, directions, is_pivot]