Skip to content

Commit

Permalink
add kvo logic
Browse files Browse the repository at this point in the history
and sample code to interact with yahoo
  • Loading branch information
chungg committed Jul 3, 2024
1 parent e471d66 commit bad2e7e
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 7 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ categories = ["finance"]
itertools = "0.13.0"

[dev-dependencies]
chrono = "0.4.38"
criterion = "0.5.1"
serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117"
serde_json = "1.0.120"
ureq = { version = "2.9.7", features = ["json"] }

[[bench]]
name = "traquer"
Expand Down
60 changes: 60 additions & 0 deletions examples/yahoo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use chrono::{NaiveDateTime, Utc};
use serde::{Deserialize, Serialize};

use traquer::*;

#[derive(Deserialize, Serialize, Debug)]
struct SecStats {
high: Vec<f64>,
low: Vec<f64>,
open: Vec<f64>,
close: Vec<f64>,
volume: Vec<f64>,
}

fn get_prices(ticker: &str) -> SecStats {
const USER_AGENT: &str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) \
AppleWebKit/537.36 (KHTML, like Gecko) \
Chrome/39.0.2171.95 Safari/537.36";

let res = ureq::get(&format!(
"https://query2.finance.yahoo.com/v8/finance/chart/{ticker}"
))
.set("User-Agent", USER_AGENT)
.query_pairs(vec![
("interval", "1d"),
("events", "capitalGain|div|split"),
(
"period1",
&NaiveDateTime::parse_from_str("2023-01-01 00:00:00", "%Y-%m-%d %H:%M:%S")
.unwrap()
.and_utc()
.timestamp()
.to_string(),
),
("period2", &Utc::now().timestamp().to_string()),
])
.call()
.unwrap();

serde_json::from_value::<SecStats>(
res.into_json::<serde_json::Value>().unwrap()["chart"]["result"][0]["indicators"]["quote"]
[0]
.clone(),
)
.unwrap()
}

fn main() {
let stats = get_prices("SPY");
dbg!(volume::kvo(
&stats.high,
&stats.low,
&stats.close,
&stats.volume,
18,
30,
Some(false)
)
.collect::<Vec<_>>());
}
37 changes: 33 additions & 4 deletions src/volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,29 @@ fn vforce<'a>(
low: &'a [f64],
close: &'a [f64],
volume: &'a [f64],
) -> impl Iterator<Item = f64> + 'a {
izip!(&high[1..], &low[1..], &close[1..], &volume[1..]).scan(
(high[0], low[0], close[0], 0.0, 0.0, 0.0),
|state, (h, l, c, v)| {
// state = (h, l, c, trend, cm, dm)
let trend = ((h + l + c) - (state.0 + state.1 + state.2)).signum();
let dm = h - l;
let cm = if trend == state.3 {
state.4 + dm
} else {
state.5 + dm
};
*state = (*h, *l, *c, trend, cm, dm);
Some(v * (2.0 * ((dm / cm) - 1.0)) * trend * 100.0)
},
)
}

fn vforce_alt<'a>(
high: &'a [f64],
low: &'a [f64],
close: &'a [f64],
volume: &'a [f64],
) -> impl Iterator<Item = f64> + 'a {
izip!(&high[1..], &low[1..], &close[1..], &volume[1..]).scan(
(high[0], low[0], close[0], 0.0),
Expand All @@ -30,8 +53,8 @@ fn vforce<'a>(
/// Developed by Stephen Klinger. It helps determine the long-term trend of money flow
/// while remaining sensitive enough to detect short-term fluctuations.
///
/// Note: This is different from formula defined in source. The vforce value is simply
/// volume * trend
/// An alt algorithm is available which computes the vforce value as simply
/// volume * trend. This behaviour matches some existing popular tools.
///
/// ## Usage
///
Expand All @@ -51,7 +74,7 @@ fn vforce<'a>(
/// &vec![1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
/// &vec![1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
/// &vec![1.0,2.0,3.0,4.0,5.0,6.0,4.0,5.0],
/// 3, 6).collect::<Vec<f64>>();
/// 3, 6, None).collect::<Vec<f64>>();
///
/// ```
pub fn kvo<'a>(
Expand All @@ -61,8 +84,14 @@ pub fn kvo<'a>(
volume: &'a [f64],
short: usize,
long: usize,
alt: Option<bool>,
) -> impl Iterator<Item = f64> + 'a {
let vf = vforce(high, low, close, volume).collect::<Vec<f64>>();
let alt = alt.unwrap_or(true);
let vf = if alt {
vforce_alt(high, low, close, volume).collect::<Vec<f64>>()
} else {
vforce(high, low, close, volume).collect::<Vec<f64>>()
};
let short_ma = smooth::ewma(&vf, short);
let long_ma = smooth::ewma(&vf, long);
iter::once(f64::NAN).chain(
Expand Down
51 changes: 49 additions & 2 deletions tests/volume_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,55 @@ fn test_twiggs() {
#[test]
fn test_kvo() {
let stats = common::test_data();
let result = volume::kvo(&stats.high, &stats.low, &stats.close, &stats.volume, 10, 16)
.collect::<Vec<_>>();
let result = volume::kvo(
&stats.high,
&stats.low,
&stats.close,
&stats.volume,
10,
16,
Some(false),
)
.collect::<Vec<_>>();
assert_eq!(stats.close.len(), result.len());
assert_eq!(
vec![
-47653124.52312139,
-38871537.60795313,
-56551756.57617685,
-80719127.50187302,
-91381485.15827936,
-102487748.59386584,
-103133145.86686252,
-76544074.74621749,
-76362373.25709169,
-84632468.62962747,
-54766782.00020293,
-53496835.81130403,
-60063221.578486785,
-36156452.68826012,
-39470196.1311378,
-81568025.19964269,
-163166676.59742773,
-90486708.71792603
],
result[16..]
);
}

#[test]
fn test_kvo_alt() {
let stats = common::test_data();
let result = volume::kvo(
&stats.high,
&stats.low,
&stats.close,
&stats.volume,
10,
16,
None,
)
.collect::<Vec<_>>();
assert_eq!(stats.close.len(), result.len());
assert_eq!(
vec![
Expand Down

0 comments on commit bad2e7e

Please sign in to comment.