Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VolumeWeightAdjustedPrice #66

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion benches/indicators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use ta::indicators::{
ExponentialMovingAverage, FastStochastic, KeltnerChannel, Maximum, MeanAbsoluteDeviation,
Minimum, MoneyFlowIndex, MovingAverageConvergenceDivergence, OnBalanceVolume,
PercentagePriceOscillator, RateOfChange, RelativeStrengthIndex, SimpleMovingAverage,
SlowStochastic, StandardDeviation, TrueRange, WeightedMovingAverage,
SlowStochastic, StandardDeviation, TrueRange, VolumeWeightedAveragePrice, WeightedMovingAverage,
};
use ta::{DataItem, Next};

Expand Down Expand Up @@ -65,6 +65,7 @@ bench_indicators!(
MoneyFlowIndex,
MovingAverageConvergenceDivergence,
OnBalanceVolume,
VolumeWeightedAveragePrice,
PercentagePriceOscillator,
CommodityChannelIndex,
RateOfChange,
Expand Down
3 changes: 3 additions & 0 deletions src/indicators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,6 @@ pub use self::money_flow_index::MoneyFlowIndex;

mod on_balance_volume;
pub use self::on_balance_volume::OnBalanceVolume;

mod volume_weighted_average_price;
pub use self::volume_weighted_average_price::VolumeWeightedAveragePrice;
175 changes: 175 additions & 0 deletions src/indicators/volume_weighted_average_price.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use std::fmt;

use crate::{Close, Next, Volume, Reset};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// Volume weight average price (VWAP).
///
/// In finance, volume-weighted average price (VWAP) is the ratio of the value of a security or
/// financial asset traded to the total volume of transactions during a trading session.
/// It is a measure of the average trading price for the period.
///
/// # Formula
///
/// ![VWAP](https://wikimedia.org/api/rest_v1/media/math/render/svg/6c0a822a0a9e58a127105e818a07061a02851685)
///
/// Where:
///
/// vwap - volume weight average price
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you pls add explanations for Pj and Qj from the formula?

///
/// # Example
///
/// ```
/// use ta::indicators::VolumeWeightedAveragePrice;
/// use ta::{Next, DataItem};
///
/// let mut vwap = VolumeWeightedAveragePrice::new(1.0);
///
/// let di1 = DataItem::builder()
/// .high(3.0)
/// .low(1.0)
/// .close(2.0)
/// .open(1.5)
/// .volume(1000.0)
/// .build().unwrap();
///
/// let di2 = DataItem::builder()
/// .high(3.0)
/// .low(1.0)
/// .close(1.5)
/// .open(1.5)
/// .volume(300.0)
/// .build().unwrap();
///
/// assert_eq!(vwap.next(&di1), (2.0, 2.0, 2.0));
/// assert_eq!(vwap.next(&di2), (1.8846153846153846, 1.6739528624980127, 2.0952779067327563));
/// ```
///
/// # Links
///
/// * [Volume weight average price, Wikipedia](https://en.wikipedia.org/wiki/Volume-weighted_average_price)
///
#[doc(alias = "VWAP")]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct VolumeWeightedAveragePrice {
std_dev_multiplier: f64,
cumulative_volume: f64,
cumulative_traded: f64,
cumulative_traded_squared: f64
}

impl VolumeWeightedAveragePrice {
pub fn new(std_dev_multiplier: f64) -> Self {
Self {
std_dev_multiplier: std_dev_multiplier,
cumulative_volume: 0.0,
cumulative_traded: 0.0,
cumulative_traded_squared: 0.0
}
}
}

impl<T: Close + Volume> Next<&T> for VolumeWeightedAveragePrice {
type Output = (f64, f64, f64);

/*
computeVWAP(src, isNewPeriod, stDevMultiplier) =>
var float cumulative_traded = na
var float cumulative_volume = na
var float cumulative_traded_squared = na

cumulative_traded += src * volume
cumulative_volume += volume
cumulative_traded_squared += volume * pow(src, 2)

_vwap = cumulative_traded / cumulative_volume
variance = cumulative_traded_squared / cumulative_volume - pow(_vwap, 2)
variance := variance < 0 ? 0 : variance
stDev = sqrt(variance)

lowerBand = _vwap - stDev * stDevMultiplier
upperBand = _vwap + stDev * stDevMultiplier

[_vwap, lowerBand, upperBand]
*/
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you pleas remove it?


fn next(&mut self, input: &T) -> (f64, f64, f64) {
let price = input.close();
let volume = input.volume();
self.cumulative_volume += volume;
self.cumulative_traded += price * volume;
self.cumulative_traded_squared += volume * price.powf(2.0);
let vwap = self.cumulative_traded / self.cumulative_volume;
let variance = self.cumulative_traded_squared / self.cumulative_volume - vwap.powf(2.0);
let variance = if variance < 0.0 { 0.0 } else { variance };
let std_dev = variance.sqrt();
let lower_band = vwap - std_dev * self.std_dev_multiplier;
let upper_band = vwap + std_dev * self.std_dev_multiplier;
(vwap, lower_band, upper_band)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation differs very much from the formula you provided in the documentation.
I've read about this indicator but I cannot find anything related to the standard deviation. If you need Bollinger Bands, please use the Bollinger Bands indicator

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//@version=5
indicator(title="Volume Weighted Average Price", shorttitle="VWAP", overlay=true, timeframe="", timeframe_gaps=true)

hideonDWM = input(false, title="Hide VWAP on 1D or Above", group="VWAP Settings")
var anchor = input.string(defval = "Session", title="Anchor Period",
 options=["Session", "Week", "Month", "Quarter", "Year", "Decade", "Century", "Earnings", "Dividends", "Splits"], group="VWAP Settings")
src = input(title = "Source", defval = hlc3, group="VWAP Settings")
offset = input(0, title="Offset", group="VWAP Settings")

showBand_1 = input(true, title="", group="Standard Deviation Bands Settings", inline="band_1")
stdevMult_1 = input(1.0, title="Bands Multiplier #1", group="Standard Deviation Bands Settings", inline="band_1")
showBand_2 = input(false, title="", group="Standard Deviation Bands Settings", inline="band_2")
stdevMult_2 = input(2.0, title="Bands Multiplier #2", group="Standard Deviation Bands Settings", inline="band_2")
showBand_3 = input(false, title="", group="Standard Deviation Bands Settings", inline="band_3")
stdevMult_3 = input(3.0, title="Bands Multiplier #3", group="Standard Deviation Bands Settings", inline="band_3")

if barstate.islast and ta.cum(volume) == 0
    runtime.error("No volume is provided by the data vendor.")

new_earnings = request.earnings(syminfo.tickerid, earnings.actual, barmerge.gaps_on, barmerge.lookahead_on, ignore_invalid_symbol=true)
new_dividends = request.dividends(syminfo.tickerid, dividends.gross, barmerge.gaps_on, barmerge.lookahead_on, ignore_invalid_symbol=true)
new_split = request.splits(syminfo.tickerid, splits.denominator, barmerge.gaps_on, barmerge.lookahead_on, ignore_invalid_symbol=true)

isNewPeriod = switch anchor
	"Earnings"  => not na(new_earnings)
	"Dividends" => not na(new_dividends)
	"Splits"    => not na(new_split)
	"Session"   => timeframe.change("D")
	"Week"      => timeframe.change("W")
	"Month"     => timeframe.change("M")
	"Quarter"   => timeframe.change("3M")
	"Year"      => timeframe.change("12M")
	"Decade"    => timeframe.change("12M") and year % 10 == 0
	"Century"   => timeframe.change("12M") and year % 100 == 0
	=> false

isEsdAnchor = anchor == "Earnings" or anchor == "Dividends" or anchor == "Splits"
if na(src[1]) and not isEsdAnchor
	isNewPeriod := true

float vwapValue = na
float upperBandValue1 = na
float lowerBandValue1 = na
float upperBandValue2 = na
float lowerBandValue2 = na
float upperBandValue3 = na
float lowerBandValue3 = na

if not (hideonDWM and timeframe.isdwm)
    [_vwap, _stdevUpper, _] = ta.vwap(src, isNewPeriod, 1)
	vwapValue := _vwap
    stdevAbs = _stdevUpper - _vwap
	upperBandValue1 := _vwap + stdevAbs * stdevMult_1
	lowerBandValue1 := _vwap - stdevAbs * stdevMult_1
	upperBandValue2 := _vwap + stdevAbs * stdevMult_2
	lowerBandValue2 := _vwap - stdevAbs * stdevMult_2
	upperBandValue3 := _vwap + stdevAbs * stdevMult_3
	lowerBandValue3 := _vwap - stdevAbs * stdevMult_3

plot(vwapValue, title="VWAP", color=#2962FF, offset=offset)

upperBand_1 = plot(upperBandValue1, title="Upper Band #1", color=color.green, offset=offset, display = showBand_1 ? display.all : display.none)
lowerBand_1 = plot(lowerBandValue1, title="Lower Band #1", color=color.green, offset=offset, display = showBand_1 ? display.all : display.none)
fill(upperBand_1, lowerBand_1, title="Bands Fill #1", color= color.new(color.green, 95)    , display = showBand_1 ? display.all : display.none)

upperBand_2 = plot(upperBandValue2, title="Upper Band #2", color=color.olive, offset=offset, display = showBand_2 ? display.all : display.none)
lowerBand_2 = plot(lowerBandValue2, title="Lower Band #2", color=color.olive, offset=offset, display = showBand_2 ? display.all : display.none)
fill(upperBand_2, lowerBand_2, title="Bands Fill #2", color= color.new(color.olive, 95)    , display = showBand_2 ? display.all : display.none)

upperBand_3 = plot(upperBandValue3, title="Upper Band #3", color=color.teal, offset=offset, display = showBand_3 ? display.all : display.none)
lowerBand_3 = plot(lowerBandValue3, title="Lower Band #3", color=color.teal, offset=offset, display = showBand_3 ? display.all : display.none)
fill(upperBand_3, lowerBand_3, title="Bands Fill #3", color= color.new(color.teal, 95)    , display = showBand_3 ? display.all : display.none)

Code comes straight from TradingView's official VWAP indicator

}
}

impl Default for VolumeWeightedAveragePrice {
fn default() -> Self {
Self::new(1.0)
}
}

impl fmt::Display for VolumeWeightedAveragePrice {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "VWAP")
}
}

impl Reset for VolumeWeightedAveragePrice {
fn reset(&mut self) {
self.cumulative_volume = 0.0;
self.cumulative_traded = 0.0;
self.cumulative_traded_squared = 0.0;
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::test_helper::*;

#[test]
fn test_next_bar() {
let mut vwap = VolumeWeightedAveragePrice::new(1.25);
let bar1 = Bar::new().close(245.0504667).volume(103033.0);
let bar2 = Bar::new().close(244.7635667).volume(21168.0);
let bar3 = Bar::new().close(245.3166667).volume(36544.0);
assert_eq!(vwap.next(&bar1), (245.0504667, 245.0504667, 245.0504667));
assert_eq!(vwap.next(&bar2), (245.001569354568, 244.86672165111835, 245.13641705801766));
assert_eq!(vwap.next(&bar3), (245.07320403926406, 244.86997872595126, 245.27642935257686));
}

#[test]
fn test_reset() {
let mut vwap = VolumeWeightedAveragePrice::new(1.25);
let bar1 = Bar::new().close(245.0504667).volume(103033.0);
let bar2 = Bar::new().close(244.7635667).volume(21168.0);
let bar3 = Bar::new().close(245.3166667).volume(36544.0);
assert_eq!(vwap.next(&bar1), (245.0504667, 245.0504667, 245.0504667));
assert_eq!(vwap.next(&bar2), (245.001569354568, 244.86672165111835, 245.13641705801766));
assert_eq!(vwap.next(&bar3), (245.07320403926406, 244.86997872595126, 245.27642935257686));
vwap.reset();
assert_eq!(vwap.next(&bar1), (245.0504667, 245.0504667, 245.0504667));
assert_eq!(vwap.next(&bar2), (245.001569354568, 244.86672165111835, 245.13641705801766));
assert_eq!(vwap.next(&bar3), (245.07320403926406, 244.86997872595126, 245.27642935257686));
}

#[test]
fn test_default() {
VolumeWeightedAveragePrice::default();
}

#[test]
fn test_display() {
let vwap = VolumeWeightedAveragePrice::new(1.0);
assert_eq!(format!("{}", vwap), "VWAP");
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
//! * [Efficiency Ratio (ER)](indicators/struct.EfficiencyRatio.html)
//! * [Rate of Change (ROC)](indicators/struct.RateOfChange.html)
//! * [On Balance Volume (OBV)](indicators/struct.OnBalanceVolume.html)
//! * [Volume Weight Average Price (VWAP)](indicators/struct.VolumeWeightedAveragePrice.html)
//!
#[cfg(test)]
#[macro_use]
Expand Down