• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bukosabino / ta / 1170

31 Oct 2023 11:37PM UTC coverage: 97.856% (-0.2%) from 98.054%
1170

Pull #311

circle-ci

Groni3000
I guess I needed that change =)
Pull Request #311: something like this

97 of 100 branches covered (0.0%)

Branch coverage included in aggregate %.

9 of 9 new or added lines in 1 file covered. (100.0%)

1318 of 1346 relevant lines covered (97.92%)

0.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.84
/ta/volume.py
1
"""
2
.. module:: volume
3
   :synopsis: Volume Indicators.
4

5
.. moduleauthor:: Dario Lopez Padial (Bukosabino)
6

7
"""
8

9
import numpy as np
1✔
10
import pandas as pd
1✔
11

12
from ta.utils import IndicatorMixin, _ema
1✔
13

14

15
class AccDistIndexIndicator(IndicatorMixin):
1✔
16
    """Accumulation/Distribution Index (ADI)
17

18
    Acting as leading indicator of price movements.
19

20
    https://school.stockcharts.com/doku.php?id=technical_indicators:accumulation_distribution_line
21

22
    Args:
23
        high(pandas.Series): dataset 'High' column.
24
        low(pandas.Series): dataset 'Low' column.
25
        close(pandas.Series): dataset 'Close' column.
26
        volume(pandas.Series): dataset 'Volume' column.
27
        fillna(bool): if True, fill nan values.
28
    """
29

30
    def __init__(
1✔
31
        self,
32
        high: pd.Series,
33
        low: pd.Series,
34
        close: pd.Series,
35
        volume: pd.Series,
36
        fillna: bool = False,
37
    ):
38
        self._high = high
1✔
39
        self._low = low
1✔
40
        self._close = close
1✔
41
        self._volume = volume
1✔
42
        self._fillna = fillna
1✔
43
        self._run()
1✔
44

45
    def _run(self):
1✔
46
        clv = ((self._close - self._low) - (self._high - self._close)) / (
1✔
47
            self._high - self._low
48
        )
49
        clv = clv.fillna(0.0)  # float division by zero
1✔
50
        adi = clv * self._volume
1✔
51
        self._adi = adi.cumsum()
1✔
52

53
    def acc_dist_index(self) -> pd.Series:
1✔
54
        """Accumulation/Distribution Index (ADI)
55

56
        Returns:
57
            pandas.Series: New feature generated.
58
        """
59
        adi = self._check_fillna(self._adi, value=0)
1✔
60
        return pd.Series(adi, name="adi")
1✔
61

62

63
class OnBalanceVolumeIndicator(IndicatorMixin):
1✔
64
    """On-balance volume (OBV)
65

66
    It relates price and volume in the stock market. OBV is based on a
67
    cumulative total volume.
68

69
    https://en.wikipedia.org/wiki/On-balance_volume
70

71
    Args:
72
        close(pandas.Series): dataset 'Close' column.
73
        volume(pandas.Series): dataset 'Volume' column.
74
        fillna(bool): if True, fill nan values.
75
    """
76

77
    def __init__(self, close: pd.Series, volume: pd.Series, fillna: bool = False):
1✔
78
        self._close = close
1✔
79
        self._volume = volume
1✔
80
        self._fillna = fillna
1✔
81
        self._run()
1✔
82

83
    def _run(self):
1✔
84
        obv = np.where(self._close < self._close.shift(1), -self._volume, self._volume)
1✔
85
        self._obv = pd.Series(obv, index=self._close.index).cumsum()
1✔
86

87
    def on_balance_volume(self) -> pd.Series:
1✔
88
        """On-balance volume (OBV)
89

90
        Returns:
91
            pandas.Series: New feature generated.
92
        """
93
        obv = self._check_fillna(self._obv, value=0)
1✔
94
        return pd.Series(obv, name="obv")
1✔
95

96

97
class ChaikinMoneyFlowIndicator(IndicatorMixin):
1✔
98
    """Chaikin Money Flow (CMF)
99

100
    It measures the amount of Money Flow Volume over a specific period.
101

102
    http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:chaikin_money_flow_cmf
103

104
    Args:
105
        high(pandas.Series): dataset 'High' column.
106
        low(pandas.Series): dataset 'Low' column.
107
        close(pandas.Series): dataset 'Close' column.
108
        volume(pandas.Series): dataset 'Volume' column.
109
        window(int): n period.
110
        fillna(bool): if True, fill nan values.
111
    """
112

113
    def __init__(
1✔
114
        self,
115
        high: pd.Series,
116
        low: pd.Series,
117
        close: pd.Series,
118
        volume: pd.Series,
119
        window: int = 20,
120
        fillna: bool = False,
121
    ):
122
        self._high = high
1✔
123
        self._low = low
1✔
124
        self._close = close
1✔
125
        self._volume = volume
1✔
126
        self._window = window
1✔
127
        self._fillna = fillna
1✔
128
        self._run()
1✔
129

130
    def _run(self):
1✔
131
        mfv = ((self._close - self._low) - (self._high - self._close)) / (
1✔
132
            self._high - self._low
133
        )
134
        mfv = mfv.fillna(0.0)  # float division by zero
1✔
135
        mfv *= self._volume
1✔
136
        min_periods = 0 if self._fillna else self._window
1✔
137
        self._cmf = (
1✔
138
            mfv.rolling(self._window, min_periods=min_periods).sum()
139
            / self._volume.rolling(self._window, min_periods=min_periods).sum()
140
        )
141

142
    def chaikin_money_flow(self) -> pd.Series:
1✔
143
        """Chaikin Money Flow (CMF)
144

145
        Returns:
146
            pandas.Series: New feature generated.
147
        """
148
        cmf = self._check_fillna(self._cmf, value=0)
1✔
149
        return pd.Series(cmf, name="cmf")
1✔
150

151

152
class ForceIndexIndicator(IndicatorMixin):
1✔
153
    """Force Index (FI)
154

155
    It illustrates how strong the actual buying or selling pressure is. High
156
    positive values mean there is a strong rising trend, and low values signify
157
    a strong downward trend.
158

159
    http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:force_index
160

161
    Args:
162
        close(pandas.Series): dataset 'Close' column.
163
        volume(pandas.Series): dataset 'Volume' column.
164
        window(int): n period.
165
        fillna(bool): if True, fill nan values.
166
    """
167

168
    def __init__(
1✔
169
        self,
170
        close: pd.Series,
171
        volume: pd.Series,
172
        window: int = 13,
173
        fillna: bool = False,
174
    ):
175
        self._close = close
1✔
176
        self._volume = volume
1✔
177
        self._window = window
1✔
178
        self._fillna = fillna
1✔
179
        self._run()
1✔
180

181
    def _run(self):
1✔
182
        fi_series = (self._close - self._close.shift(1)) * self._volume
1✔
183
        self._fi = _ema(fi_series, self._window, fillna=self._fillna)
1✔
184

185
    def force_index(self) -> pd.Series:
1✔
186
        """Force Index (FI)
187

188
        Returns:
189
            pandas.Series: New feature generated.
190
        """
191
        fi_series = self._check_fillna(self._fi, value=0)
1✔
192
        return pd.Series(fi_series, name=f"fi_{self._window}")
1✔
193

194

195
class EaseOfMovementIndicator(IndicatorMixin):
1✔
196
    """Ease of movement (EoM, EMV)
197

198
    It relate an asset's price change to its volume and is particularly useful
199
    for assessing the strength of a trend.
200

201
    https://en.wikipedia.org/wiki/Ease_of_movement
202

203
    Args:
204
        high(pandas.Series): dataset 'High' column.
205
        low(pandas.Series): dataset 'Low' column.
206
        volume(pandas.Series): dataset 'Volume' column.
207
        window(int): n period.
208
        fillna(bool): if True, fill nan values.
209
    """
210

211
    def __init__(
1✔
212
        self,
213
        high: pd.Series,
214
        low: pd.Series,
215
        volume: pd.Series,
216
        window: int = 14,
217
        fillna: bool = False,
218
    ):
219
        self._high = high
1✔
220
        self._low = low
1✔
221
        self._volume = volume
1✔
222
        self._window = window
1✔
223
        self._fillna = fillna
1✔
224
        self._run()
1✔
225

226
    def _run(self):
1✔
227
        self._emv = (
1✔
228
            (self._high.diff(1) + self._low.diff(1))
229
            * (self._high - self._low)
230
            / (2 * self._volume)
231
        )
232
        self._emv *= 100000000
1✔
233

234
    def ease_of_movement(self) -> pd.Series:
1✔
235
        """Ease of movement (EoM, EMV)
236

237
        Returns:
238
            pandas.Series: New feature generated.
239
        """
240
        emv = self._check_fillna(self._emv, value=0)
1✔
241
        return pd.Series(emv, name=f"eom_{self._window}")
1✔
242

243
    def sma_ease_of_movement(self) -> pd.Series:
1✔
244
        """Signal Ease of movement (EoM, EMV)
245

246
        Returns:
247
            pandas.Series: New feature generated.
248
        """
249
        min_periods = 0 if self._fillna else self._window
1✔
250
        emv = self._emv.rolling(self._window, min_periods=min_periods).mean()
1✔
251
        emv = self._check_fillna(emv, value=0)
1✔
252
        return pd.Series(emv, name=f"sma_eom_{self._window}")
1✔
253

254

255
class VolumePriceTrendIndicator(IndicatorMixin):
1✔
256
    """Volume-price trend (VPT)
257

258
    Is based on a running cumulative volume that adds or substracts a multiple
259
    of the percentage change in share price trend and current volume, depending
260
    upon the investment's upward or downward movements.
261

262
    https://en.wikipedia.org/wiki/Volume%E2%80%93price_trend
263

264
    Args:
265
        close(pandas.Series): dataset 'Close' column.
266
        volume(pandas.Series): dataset 'Volume' column.
267
        fillna(bool)=False: if True, fill nan values. DO NOT RECCOMEND to set it True.
268
        smoothing_factor(int)=None: will smooth default VPT implementation with SMA.
269
        dropnans(bool)=False: drop nans after indicator calculated.
270
    """
271

272
    def __init__(self, close: pd.Series, volume: pd.Series, fillna: bool = False, smoothing_factor: int = None, dropnans:bool = False):
1✔
273
        self._close = close
1✔
274
        self._volume = volume
1✔
275
        self._fillna = fillna
1✔
276
        self._smoothing_factor = smoothing_factor
1✔
277
        self._dropnans = dropnans
1✔
278
        self._run()
1✔
279

280
    def _run(self):
1✔
281
        self._vpt = (self._close.pct_change() * self._volume).cumsum()
1✔
282
        if self._smoothing_factor:
1!
283
            min_periods = 0 if self._fillna else self._window 
×
284
            self._vpt = self._vpt.rolling(self._smoothing_factor, min_periods=min_periods).mean()
×
285
        if self._dropnans: self._vpt = self._vpt.dropna()
1✔
286

287
    def volume_price_trend(self) -> pd.Series:
1✔
288
        """Volume-price trend (VPT)
289

290
        Returns:
291
            pandas.Series: New feature generated.
292
        """
293
        vpt = self._check_fillna(self._vpt, value=0)
1✔
294
        return pd.Series(vpt, name="vpt")
1✔
295

296

297
class NegativeVolumeIndexIndicator(IndicatorMixin):
1✔
298
    """Negative Volume Index (NVI)
299

300
    http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:negative_volume_inde
301

302
    Args:
303
        close(pandas.Series): dataset 'Close' column.
304
        volume(pandas.Series): dataset 'Volume' column.
305
        fillna(bool): if True, fill nan values with 1000.
306
    """
307

308
    def __init__(self, close: pd.Series, volume: pd.Series, fillna: bool = False):
1✔
309
        self._close = close
1✔
310
        self._volume = volume
1✔
311
        self._fillna = fillna
1✔
312
        self._run()
1✔
313

314
    def _run(self):
1✔
315
        price_change = self._close.pct_change()
1✔
316
        vol_decrease = self._volume.shift(1) > self._volume
1✔
317
        self._nvi = pd.Series(
1✔
318
            data=np.nan, index=self._close.index, dtype="float64", name="nvi"
319
        )
320
        self._nvi.iloc[0] = 1000
1✔
321
        for i in range(1, len(self._nvi)):
1✔
322
            if vol_decrease.iloc[i]:
1✔
323
                self._nvi.iloc[i] = self._nvi.iloc[i - 1] * (1.0 + price_change.iloc[i])
1✔
324
            else:
325
                self._nvi.iloc[i] = self._nvi.iloc[i - 1]
1✔
326

327
    def negative_volume_index(self) -> pd.Series:
1✔
328
        """Negative Volume Index (NVI)
329

330
        Returns:
331
            pandas.Series: New feature generated.
332
        """
333
        # IDEA: There shouldn't be any na; might be better to throw exception
334
        nvi = self._check_fillna(self._nvi, value=1000)
1✔
335
        return pd.Series(nvi, name="nvi")
1✔
336

337

338
class MFIIndicator(IndicatorMixin):
1✔
339
    """Money Flow Index (MFI)
340

341
    Uses both price and volume to measure buying and selling pressure. It is
342
    positive when the typical price rises (buying pressure) and negative when
343
    the typical price declines (selling pressure). A ratio of positive and
344
    negative money flow is then plugged into an RSI formula to create an
345
    oscillator that moves between zero and one hundred.
346

347
    http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:money_flow_index_mfi
348

349
    Args:
350
        high(pandas.Series): dataset 'High' column.
351
        low(pandas.Series): dataset 'Low' column.
352
        close(pandas.Series): dataset 'Close' column.
353
        volume(pandas.Series): dataset 'Volume' column.
354
        window(int): n period.
355
        fillna(bool): if True, fill nan values.
356
    """
357

358
    def __init__(
1✔
359
        self,
360
        high: pd.Series,
361
        low: pd.Series,
362
        close: pd.Series,
363
        volume: pd.Series,
364
        window: int = 14,
365
        fillna: bool = False,
366
    ):
367
        self._high = high
1✔
368
        self._low = low
1✔
369
        self._close = close
1✔
370
        self._volume = volume
1✔
371
        self._window = window
1✔
372
        self._fillna = fillna
1✔
373
        self._run()
1✔
374

375
    def _run(self):
1✔
376
        typical_price = (self._high + self._low + self._close) / 3.0
1✔
377
        up_down = np.where(
1✔
378
            typical_price > typical_price.shift(1),
379
            1,
380
            np.where(typical_price < typical_price.shift(1), -1, 0),
381
        )
382
        mfr = typical_price * self._volume * up_down
1✔
383

384
        # Positive and negative money flow with n periods
385
        min_periods = 0 if self._fillna else self._window
1✔
386
        n_positive_mf = mfr.rolling(self._window, min_periods=min_periods).apply(
1✔
387
            lambda x: np.sum(np.where(x >= 0.0, x, 0.0)), raw=True
388
        )
389
        n_negative_mf = abs(
1✔
390
            mfr.rolling(self._window, min_periods=min_periods).apply(
391
                lambda x: np.sum(np.where(x < 0.0, x, 0.0)), raw=True
392
            )
393
        )
394

395
        # n_positive_mf = np.where(mf.rolling(self._window).sum() >= 0.0, mf, 0.0)
396
        # n_negative_mf = abs(np.where(mf.rolling(self._window).sum() < 0.0, mf, 0.0))
397

398
        # Money flow index
399
        mfi = n_positive_mf / n_negative_mf
1✔
400
        self._mfi = 100 - (100 / (1 + mfi))
1✔
401

402
    def money_flow_index(self) -> pd.Series:
1✔
403
        """Money Flow Index (MFI)
404

405
        Returns:
406
            pandas.Series: New feature generated.
407
        """
408
        mfi = self._check_fillna(self._mfi, value=50)
1✔
409
        return pd.Series(mfi, name=f"mfi_{self._window}")
1✔
410

411

412
class VolumeWeightedAveragePrice(IndicatorMixin):
1✔
413
    """Volume Weighted Average Price (VWAP)
414

415
    VWAP equals the dollar value of all trading periods divided
416
    by the total trading volume for the current day.
417
    The calculation starts when trading opens and ends when it closes.
418
    Because it is good for the current trading day only,
419
    intraday periods and data are used in the calculation.
420

421
    https://school.stockcharts.com/doku.php?id=technical_indicators:vwap_intraday
422

423
    Args:
424
        high(pandas.Series): dataset 'High' column.
425
        low(pandas.Series): dataset 'Low' column.
426
        close(pandas.Series): dataset 'Close' column.
427
        volume(pandas.Series): dataset 'Volume' column.
428
        window(int): n period.
429
        fillna(bool): if True, fill nan values.
430

431
    Returns:
432
        pandas.Series: New feature generated.
433
    """
434

435
    def __init__(
1✔
436
        self,
437
        high: pd.Series,
438
        low: pd.Series,
439
        close: pd.Series,
440
        volume: pd.Series,
441
        window: int = 14,
442
        fillna: bool = False,
443
    ):
444
        self._high = high
1✔
445
        self._low = low
1✔
446
        self._close = close
1✔
447
        self._volume = volume
1✔
448
        self._window = window
1✔
449
        self._fillna = fillna
1✔
450
        self._run()
1✔
451

452
    def _run(self):
1✔
453
        # 1 typical price
454
        typical_price = (self._high + self._low + self._close) / 3.0
1✔
455

456
        # 2 typical price * volume
457
        typical_price_volume = typical_price * self._volume
1✔
458

459
        # 3 total price * volume
460
        min_periods = 0 if self._fillna else self._window
1✔
461
        total_pv = typical_price_volume.rolling(
1✔
462
            self._window, min_periods=min_periods
463
        ).sum()
464

465
        # 4 total volume
466
        total_volume = self._volume.rolling(self._window, min_periods=min_periods).sum()
1✔
467

468
        self.vwap = total_pv / total_volume
1✔
469

470
    def volume_weighted_average_price(self) -> pd.Series:
1✔
471
        """Volume Weighted Average Price (VWAP)
472

473
        Returns:
474
            pandas.Series: New feature generated.
475
        """
476
        vwap = self._check_fillna(self.vwap)
1✔
477
        return pd.Series(vwap, name=f"vwap_{self._window}")
1✔
478

479

480
def acc_dist_index(high, low, close, volume, fillna=False):
1✔
481
    """Accumulation/Distribution Index (ADI)
482

483
    Acting as leading indicator of price movements.
484

485
    https://en.wikipedia.org/wiki/Accumulation/distribution_index
486

487
    Args:
488
        high(pandas.Series): dataset 'High' column.
489
        low(pandas.Series): dataset 'Low' column.
490
        close(pandas.Series): dataset 'Close' column.
491
        volume(pandas.Series): dataset 'Volume' column.
492
        fillna(bool): if True, fill nan values.
493

494
    Returns:
495
        pandas.Series: New feature generated.
496
    """
497
    return AccDistIndexIndicator(
1✔
498
        high=high, low=low, close=close, volume=volume, fillna=fillna
499
    ).acc_dist_index()
500

501

502
def on_balance_volume(close, volume, fillna=False):
1✔
503
    """On-balance volume (OBV)
504

505
    It relates price and volume in the stock market. OBV is based on a
506
    cumulative total volume.
507

508
    https://en.wikipedia.org/wiki/On-balance_volume
509

510
    Args:
511
        close(pandas.Series): dataset 'Close' column.
512
        volume(pandas.Series): dataset 'Volume' column.
513
        fillna(bool): if True, fill nan values.
514

515
    Returns:
516
        pandas.Series: New feature generated.
517
    """
518
    return OnBalanceVolumeIndicator(
1✔
519
        close=close, volume=volume, fillna=fillna
520
    ).on_balance_volume()
521

522

523
def chaikin_money_flow(high, low, close, volume, window=20, fillna=False):
1✔
524
    """Chaikin Money Flow (CMF)
525

526
    It measures the amount of Money Flow Volume over a specific period.
527

528
    http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:chaikin_money_flow_cmf
529

530
    Args:
531
        high(pandas.Series): dataset 'High' column.
532
        low(pandas.Series): dataset 'Low' column.
533
        close(pandas.Series): dataset 'Close' column.
534
        volume(pandas.Series): dataset 'Volume' column.
535
        window(int): n period.
536
        fillna(bool): if True, fill nan values.
537

538
    Returns:
539
        pandas.Series: New feature generated.
540
    """
541
    return ChaikinMoneyFlowIndicator(
×
542
        high=high, low=low, close=close, volume=volume, window=window, fillna=fillna
543
    ).chaikin_money_flow()
544

545

546
def force_index(close, volume, window=13, fillna=False):
1✔
547
    """Force Index (FI)
548

549
    It illustrates how strong the actual buying or selling pressure is. High
550
    positive values mean there is a strong rising trend, and low values signify
551
    a strong downward trend.
552

553
    http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:force_index
554

555
    Args:
556
        close(pandas.Series): dataset 'Close' column.
557
        volume(pandas.Series): dataset 'Volume' column.
558
        window(int): n period.
559
        fillna(bool): if True, fill nan values.
560

561
    Returns:
562
        pandas.Series: New feature generated.
563
    """
564
    return ForceIndexIndicator(
1✔
565
        close=close, volume=volume, window=window, fillna=fillna
566
    ).force_index()
567

568

569
def ease_of_movement(high, low, volume, window=14, fillna=False):
1✔
570
    """Ease of movement (EoM, EMV)
571

572
    It relate an asset's price change to its volume and is particularly useful
573
    for assessing the strength of a trend.
574

575
    https://en.wikipedia.org/wiki/Ease_of_movement
576

577
    Args:
578
        high(pandas.Series): dataset 'High' column.
579
        low(pandas.Series): dataset 'Low' column.
580
        volume(pandas.Series): dataset 'Volume' column.
581
        window(int): n period.
582
        fillna(bool): if True, fill nan values.
583

584
    Returns:
585
        pandas.Series: New feature generated.
586
    """
587
    return EaseOfMovementIndicator(
1✔
588
        high=high, low=low, volume=volume, window=window, fillna=fillna
589
    ).ease_of_movement()
590

591

592
def sma_ease_of_movement(high, low, volume, window=14, fillna=False):
1✔
593
    """Ease of movement (EoM, EMV)
594

595
    It relate an asset's price change to its volume and is particularly useful
596
    for assessing the strength of a trend.
597

598
    https://en.wikipedia.org/wiki/Ease_of_movement
599

600
    Args:
601
        high(pandas.Series): dataset 'High' column.
602
        low(pandas.Series): dataset 'Low' column.
603
        volume(pandas.Series): dataset 'Volume' column.
604
        window(int): n period.
605
        fillna(bool): if True, fill nan values.
606

607
    Returns:
608
        pandas.Series: New feature generated.
609
    """
610
    return EaseOfMovementIndicator(
1✔
611
        high=high, low=low, volume=volume, window=window, fillna=fillna
612
    ).sma_ease_of_movement()
613

614

615
def volume_price_trend(close, volume, fillna=False, smoothing_factor: int=None, dropnans: bool=False):
1✔
616
    """Volume-price trend (VPT)
617

618
    Is based on a running cumulative volume that adds or substracts a multiple
619
    of the percentage change in share price trend and current volume, depending
620
    upon the investment's upward or downward movements.
621

622
    https://en.wikipedia.org/wiki/Volume%E2%80%93price_trend
623

624
    Args:
625
        close(pandas.Series): dataset 'Close' column.
626
        volume(pandas.Series): dataset 'Volume' column.
627
        fillna(bool)=False: if True, fill nan values. DO NOT RECCOMEND to set it True.
628
        smoothing_factor(int)=None: will smooth default VPT implementation with SMA.
629
        dropnans(bool)=False: drop nans after indicator calculated.
630

631
    Returns:
632
        pandas.Series: New feature generated.
633
    """
634
    return VolumePriceTrendIndicator(
×
635
        close=close, volume=volume, fillna=fillna, smoothing_factor=smoothing_factor, dropnans=dropnans
636
    ).volume_price_trend()
637

638

639
def negative_volume_index(close, volume, fillna=False):
1✔
640
    """Negative Volume Index (NVI)
641

642
    http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:negative_volume_inde
643

644
    The Negative Volume Index (NVI) is a cumulative indicator that uses the
645
    change in volume to decide when the smart money is active. Paul Dysart
646
    first developed this indicator in the 1930s. [...] Dysart's Negative Volume
647
    Index works under the assumption that the smart money is active on days
648
    when volume decreases and the not-so-smart money is active on days when
649
    volume increases.
650

651
    The cumulative NVI line was unchanged when volume increased from one
652
    period to the other. In other words, nothing was done. Norman Fosback, of
653
    Stock Market Logic, adjusted the indicator by substituting the percentage
654
    price change for Net Advances.
655

656
    This implementation is the Fosback version.
657

658
    If today's volume is less than yesterday's volume then:
659
        nvi(t) = nvi(t-1) * ( 1 + (close(t) - close(t-1)) / close(t-1) )
660
    Else
661
        nvi(t) = nvi(t-1)
662

663
    Please note: the "stockcharts.com" example calculation just adds the
664
    percentange change of price to previous NVI when volumes decline; other
665
    sources indicate that the same percentage of the previous NVI value should
666
    be added, which is what is implemented here.
667

668
    Args:
669
        close(pandas.Series): dataset 'Close' column.
670
        volume(pandas.Series): dataset 'Volume' column.
671
        fillna(bool): if True, fill nan values with 1000.
672

673
    Returns:
674
        pandas.Series: New feature generated.
675

676
    See also:
677
        https://en.wikipedia.org/wiki/Negative_volume_index
678
    """
679
    return NegativeVolumeIndexIndicator(
×
680
        close=close, volume=volume, fillna=fillna
681
    ).negative_volume_index()
682

683

684
def money_flow_index(high, low, close, volume, window=14, fillna=False):
1✔
685
    """Money Flow Index (MFI)
686

687
    Uses both price and volume to measure buying and selling pressure. It is
688
    positive when the typical price rises (buying pressure) and negative when
689
    the typical price declines (selling pressure). A ratio of positive and
690
    negative money flow is then plugged into an RSI formula to create an
691
    oscillator that moves between zero and one hundred.
692

693
    http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:money_flow_index_mfi
694

695
    Args:
696
        high(pandas.Series): dataset 'High' column.
697
        low(pandas.Series): dataset 'Low' column.
698
        close(pandas.Series): dataset 'Close' column.
699
        volume(pandas.Series): dataset 'Volume' column.
700
        window(int): n period.
701
        fillna(bool): if True, fill nan values.
702

703
    Returns:
704
        pandas.Series: New feature generated.
705

706
    """
707
    indicator = MFIIndicator(
1✔
708
        high=high, low=low, close=close, volume=volume, window=window, fillna=fillna
709
    )
710
    return indicator.money_flow_index()
1✔
711

712

713
def volume_weighted_average_price(
1✔
714
    high: pd.Series,
715
    low: pd.Series,
716
    close: pd.Series,
717
    volume: pd.Series,
718
    window: int = 14,
719
    fillna: bool = False,
720
):
721
    """Volume Weighted Average Price (VWAP)
722

723
    VWAP equals the dollar value of all trading periods divided
724
    by the total trading volume for the current day.
725
    The calculation starts when trading opens and ends when it closes.
726
    Because it is good for the current trading day only,
727
    intraday periods and data are used in the calculation.
728

729
    https://school.stockcharts.com/doku.php?id=technical_indicators:vwap_intraday
730

731
    Args:
732
        high(pandas.Series): dataset 'High' column.
733
        low(pandas.Series): dataset 'Low' column.
734
        close(pandas.Series): dataset 'Close' column.
735
        volume(pandas.Series): dataset 'Volume' column.
736
        window(int): n period.
737
        fillna(bool): if True, fill nan values.
738

739
    Returns:
740
        pandas.Series: New feature generated.
741
    """
742

743
    indicator = VolumeWeightedAveragePrice(
1✔
744
        high=high, low=low, close=close, volume=volume, window=window, fillna=fillna
745
    )
746
    return indicator.volume_weighted_average_price()
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc