feat: add 7 alt-chart bar builders (B19) (#220)
Adds seven information-driven bar builders to the **Alt-Chart Bars** family, the final batch of the family-deepening run. Indicator count **507 → 514**. ## Builders All implement the `BarBuilder` trait (`update(Candle) -> Vec<Bar>`), emitting a data-dependent number of completed bars per candle. | Builder | Driver | Bar fields | |---------|--------|-----------| | `RangeBars` | close | open, close, direction | | `TickBars` | OHLCV | open, high, low, close, volume | | `VolumeBars` | OHLCV | open, high, low, close, volume | | `DollarBars` (Lopez de Prado) | OHLCV | + dollar | | `ImbalanceBars` | OHLC | + imbalance, direction | | `RunBars` | OHLC | + length, direction | | `ThreeLineBreakBars` | close | open, close, direction | ## Touchpoints Seven core modules (each with full unit tests), `mod.rs`/`lib.rs` (builders counted, bar element types on their own re-export lines), README family rows, Python/Node/WASM hand-written bindings for the variable-length output (Python tuples + `(k, N)` ndarray; Node `Vec<object>`; WASM array of objects), the `bar_builder_update_candle` fuzz target, dedicated Python + Node tests, the `BAR_BUILDERS` completeness exclusion, and CHANGELOG. ## Verification - `cargo test -p wickra-core --lib` — 4207 passed - `cargo test -p wickra-core --doc` — 464 passed - `cargo clippy --workspace --all-targets --all-features -- -D warnings` — clean - `npm test` (node) — 584 passed - `pytest` (python) — 957 passed
This commit is contained in:
@@ -14,7 +14,18 @@ const wickra = require('..');
|
||||
// but intentionally not isReady/warmupPeriod, so they are excluded from the
|
||||
// Indicator completeness contract below (their interface is covered by the
|
||||
// dedicated bar-builder tests).
|
||||
const BAR_BUILDERS = new Set(['RenkoBars', 'KagiBars', 'PointAndFigureBars']);
|
||||
const BAR_BUILDERS = new Set([
|
||||
'RenkoBars',
|
||||
'KagiBars',
|
||||
'PointAndFigureBars',
|
||||
'RangeBars',
|
||||
'TickBars',
|
||||
'VolumeBars',
|
||||
'DollarBars',
|
||||
'ImbalanceBars',
|
||||
'RunBars',
|
||||
'ThreeLineBreakBars',
|
||||
]);
|
||||
|
||||
// An "indicator class" is an exported constructor whose prototype carries the
|
||||
// streaming `update` method. This excludes `version` (a plain function), the bar
|
||||
|
||||
@@ -1769,3 +1769,72 @@ test('PointAndFigureBars closes a column on a 3-box reversal', () => {
|
||||
assert.equal(col[0].direction, 1);
|
||||
assert.ok(Math.abs(col[0].high - 15) < 1e-9 && Math.abs(col[0].low - 10) < 1e-9);
|
||||
});
|
||||
|
||||
test('RangeBars prints aligned bars on an up move', () => {
|
||||
const rb = new wickra.RangeBars(1.0);
|
||||
assert.deepEqual(rb.update(10), []); // seed
|
||||
const up = rb.update(13);
|
||||
assert.equal(up.length, 3);
|
||||
assert.ok(Math.abs(up[0].open - 10) < 1e-9 && Math.abs(up[2].close - 13) < 1e-9);
|
||||
assert.ok(up.every((b) => b.direction === 1));
|
||||
});
|
||||
|
||||
test('TickBars groups a fixed number of candles', () => {
|
||||
const tb = new wickra.TickBars(2);
|
||||
assert.deepEqual(tb.update(10, 11, 9, 10.5, 100), []);
|
||||
const out = tb.update(10.5, 12, 10, 11, 150);
|
||||
assert.equal(out.length, 1);
|
||||
assert.ok(Math.abs(out[0].open - 10) < 1e-9);
|
||||
assert.ok(Math.abs(out[0].high - 12) < 1e-9);
|
||||
assert.ok(Math.abs(out[0].low - 9) < 1e-9);
|
||||
assert.ok(Math.abs(out[0].close - 11) < 1e-9);
|
||||
assert.ok(Math.abs(out[0].volume - 250) < 1e-9);
|
||||
});
|
||||
|
||||
test('VolumeBars closes when accumulated volume crosses the threshold', () => {
|
||||
const vb = new wickra.VolumeBars(100);
|
||||
assert.deepEqual(vb.update(10, 10, 10, 10, 60), []);
|
||||
const out = vb.update(10.5, 10.5, 10.5, 10.5, 60);
|
||||
assert.equal(out.length, 1);
|
||||
assert.ok(Math.abs(out[0].volume - 120) < 1e-9);
|
||||
});
|
||||
|
||||
test('DollarBars closes when traded value crosses the threshold', () => {
|
||||
const db = new wickra.DollarBars(1000);
|
||||
assert.deepEqual(db.update(10, 10, 10, 10, 60), []);
|
||||
const out = db.update(10, 10, 10, 10, 60);
|
||||
assert.equal(out.length, 1);
|
||||
assert.ok(Math.abs(out[0].dollar - 1200) < 1e-9);
|
||||
assert.ok(Math.abs(out[0].volume - 120) < 1e-9);
|
||||
});
|
||||
|
||||
test('ImbalanceBars closes a buy bar at the threshold', () => {
|
||||
const ib = new wickra.ImbalanceBars(3.0);
|
||||
ib.update(10, 10, 10, 10);
|
||||
ib.update(11, 11, 11, 11);
|
||||
ib.update(12, 12, 12, 12);
|
||||
const out = ib.update(13, 13, 13, 13);
|
||||
assert.equal(out.length, 1);
|
||||
assert.equal(out[0].direction, 1);
|
||||
assert.ok(Math.abs(out[0].imbalance - 3) < 1e-9);
|
||||
});
|
||||
|
||||
test('RunBars closes a buy run at the run length', () => {
|
||||
const rb = new wickra.RunBars(3);
|
||||
rb.update(10, 10, 10, 10);
|
||||
rb.update(11, 11, 11, 11);
|
||||
rb.update(12, 12, 12, 12);
|
||||
const out = rb.update(13, 13, 13, 13);
|
||||
assert.equal(out.length, 1);
|
||||
assert.equal(out[0].direction, 1);
|
||||
assert.equal(out[0].length, 3);
|
||||
});
|
||||
|
||||
test('ThreeLineBreakBars draws a rising line', () => {
|
||||
const tlb = new wickra.ThreeLineBreakBars(3);
|
||||
assert.deepEqual(tlb.update(10), []); // seed
|
||||
const out = tlb.update(11);
|
||||
assert.equal(out.length, 1);
|
||||
assert.equal(out[0].direction, 1);
|
||||
assert.ok(Math.abs(out[0].open - 10) < 1e-9 && Math.abs(out[0].close - 11) < 1e-9);
|
||||
});
|
||||
|
||||
Vendored
+104
@@ -468,6 +468,54 @@ export interface PnfColumnValue {
|
||||
high: number
|
||||
low: number
|
||||
}
|
||||
export interface RangeBarValue {
|
||||
open: number
|
||||
close: number
|
||||
direction: number
|
||||
}
|
||||
export interface TickBarValue {
|
||||
open: number
|
||||
high: number
|
||||
low: number
|
||||
close: number
|
||||
volume: number
|
||||
}
|
||||
export interface VolumeBarValue {
|
||||
open: number
|
||||
high: number
|
||||
low: number
|
||||
close: number
|
||||
volume: number
|
||||
}
|
||||
export interface DollarBarValue {
|
||||
open: number
|
||||
high: number
|
||||
low: number
|
||||
close: number
|
||||
volume: number
|
||||
dollar: number
|
||||
}
|
||||
export interface ImbalanceBarValue {
|
||||
open: number
|
||||
high: number
|
||||
low: number
|
||||
close: number
|
||||
imbalance: number
|
||||
direction: number
|
||||
}
|
||||
export interface RunBarValue {
|
||||
open: number
|
||||
high: number
|
||||
low: number
|
||||
close: number
|
||||
length: number
|
||||
direction: number
|
||||
}
|
||||
export interface LineBreakBarValue {
|
||||
open: number
|
||||
close: number
|
||||
direction: number
|
||||
}
|
||||
export interface SessionHighLowValue {
|
||||
high: number
|
||||
low: number
|
||||
@@ -5028,6 +5076,62 @@ export declare class PointAndFigureBars {
|
||||
reversal(): number
|
||||
reset(): void
|
||||
}
|
||||
export type RangeBarsNode = RangeBars
|
||||
export declare class RangeBars {
|
||||
constructor(range: number)
|
||||
update(close: number): Array<RangeBarValue>
|
||||
batch(close: Array<number>): Array<RangeBarValue>
|
||||
range(): number
|
||||
reset(): void
|
||||
}
|
||||
export type TickBarsNode = TickBars
|
||||
export declare class TickBars {
|
||||
constructor(ticks: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number): Array<TickBarValue>
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>): Array<TickBarValue>
|
||||
ticks(): number
|
||||
reset(): void
|
||||
}
|
||||
export type VolumeBarsNode = VolumeBars
|
||||
export declare class VolumeBars {
|
||||
constructor(volumePerBar: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number): Array<VolumeBarValue>
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>): Array<VolumeBarValue>
|
||||
volumePerBar(): number
|
||||
reset(): void
|
||||
}
|
||||
export type DollarBarsNode = DollarBars
|
||||
export declare class DollarBars {
|
||||
constructor(dollarPerBar: number)
|
||||
update(open: number, high: number, low: number, close: number, volume: number): Array<DollarBarValue>
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>, volume: Array<number>): Array<DollarBarValue>
|
||||
dollarPerBar(): number
|
||||
reset(): void
|
||||
}
|
||||
export type ImbalanceBarsNode = ImbalanceBars
|
||||
export declare class ImbalanceBars {
|
||||
constructor(threshold: number)
|
||||
update(open: number, high: number, low: number, close: number): Array<ImbalanceBarValue>
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>): Array<ImbalanceBarValue>
|
||||
threshold(): number
|
||||
reset(): void
|
||||
}
|
||||
export type RunBarsNode = RunBars
|
||||
export declare class RunBars {
|
||||
constructor(runLength: number)
|
||||
update(open: number, high: number, low: number, close: number): Array<RunBarValue>
|
||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>): Array<RunBarValue>
|
||||
runLength(): number
|
||||
reset(): void
|
||||
}
|
||||
export type ThreeLineBreakBarsNode = ThreeLineBreakBars
|
||||
export declare class ThreeLineBreakBars {
|
||||
constructor(lines: number)
|
||||
update(close: number): Array<LineBreakBarValue>
|
||||
batch(close: Array<number>): Array<LineBreakBarValue>
|
||||
lines(): number
|
||||
reset(): void
|
||||
}
|
||||
export type AlphaNode = Alpha
|
||||
export declare class Alpha {
|
||||
constructor(period: number, riskFree: number)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -17818,6 +17818,559 @@ impl PointAndFigureBarsNode {
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct RangeBarValue {
|
||||
pub open: f64,
|
||||
pub close: f64,
|
||||
pub direction: i32,
|
||||
}
|
||||
|
||||
#[napi(js_name = "RangeBars")]
|
||||
pub struct RangeBarsNode {
|
||||
inner: wc::RangeBars,
|
||||
}
|
||||
#[napi]
|
||||
impl RangeBarsNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(range: f64) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::RangeBars::new(range).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, close: f64) -> napi::Result<Vec<RangeBarValue>> {
|
||||
let candle = wc::Candle::new(close, close, close, close, 1.0, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| RangeBarValue {
|
||||
open: b.open,
|
||||
close: b.close,
|
||||
direction: i32::from(b.direction),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(&mut self, close: Vec<f64>) -> napi::Result<Vec<RangeBarValue>> {
|
||||
let mut out = Vec::new();
|
||||
for price in close {
|
||||
let candle = wc::Candle::new(price, price, price, price, 1.0, 0).map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
out.push(RangeBarValue {
|
||||
open: b.open,
|
||||
close: b.close,
|
||||
direction: i32::from(b.direction),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi]
|
||||
pub fn range(&self) -> f64 {
|
||||
self.inner.range()
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct TickBarValue {
|
||||
pub open: f64,
|
||||
pub high: f64,
|
||||
pub low: f64,
|
||||
pub close: f64,
|
||||
pub volume: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "TickBars")]
|
||||
pub struct TickBarsNode {
|
||||
inner: wc::TickBars,
|
||||
}
|
||||
#[napi]
|
||||
impl TickBarsNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(ticks: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::TickBars::new(ticks as usize).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
) -> napi::Result<Vec<TickBarValue>> {
|
||||
let candle = wc::Candle::new(open, high, low, close, volume, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| TickBarValue {
|
||||
open: b.open,
|
||||
high: b.high,
|
||||
low: b.low,
|
||||
close: b.close,
|
||||
volume: b.volume,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
volume: Vec<f64>,
|
||||
) -> napi::Result<Vec<TickBarValue>> {
|
||||
if open.len() != high.len()
|
||||
|| high.len() != low.len()
|
||||
|| low.len() != close.len()
|
||||
|| close.len() != volume.len()
|
||||
{
|
||||
return Err(NapiError::from_reason(
|
||||
"open, high, low, close, volume must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
for i in 0..open.len() {
|
||||
let candle = wc::Candle::new(open[i], high[i], low[i], close[i], volume[i], 0)
|
||||
.map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
out.push(TickBarValue {
|
||||
open: b.open,
|
||||
high: b.high,
|
||||
low: b.low,
|
||||
close: b.close,
|
||||
volume: b.volume,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi]
|
||||
pub fn ticks(&self) -> u32 {
|
||||
self.inner.ticks() as u32
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct VolumeBarValue {
|
||||
pub open: f64,
|
||||
pub high: f64,
|
||||
pub low: f64,
|
||||
pub close: f64,
|
||||
pub volume: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "VolumeBars")]
|
||||
pub struct VolumeBarsNode {
|
||||
inner: wc::VolumeBars,
|
||||
}
|
||||
#[napi]
|
||||
impl VolumeBarsNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(volume_per_bar: f64) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::VolumeBars::new(volume_per_bar).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
) -> napi::Result<Vec<VolumeBarValue>> {
|
||||
let candle = wc::Candle::new(open, high, low, close, volume, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| VolumeBarValue {
|
||||
open: b.open,
|
||||
high: b.high,
|
||||
low: b.low,
|
||||
close: b.close,
|
||||
volume: b.volume,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
volume: Vec<f64>,
|
||||
) -> napi::Result<Vec<VolumeBarValue>> {
|
||||
if open.len() != high.len()
|
||||
|| high.len() != low.len()
|
||||
|| low.len() != close.len()
|
||||
|| close.len() != volume.len()
|
||||
{
|
||||
return Err(NapiError::from_reason(
|
||||
"open, high, low, close, volume must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
for i in 0..open.len() {
|
||||
let candle = wc::Candle::new(open[i], high[i], low[i], close[i], volume[i], 0)
|
||||
.map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
out.push(VolumeBarValue {
|
||||
open: b.open,
|
||||
high: b.high,
|
||||
low: b.low,
|
||||
close: b.close,
|
||||
volume: b.volume,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi(js_name = "volumePerBar")]
|
||||
pub fn volume_per_bar(&self) -> f64 {
|
||||
self.inner.volume_per_bar()
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct DollarBarValue {
|
||||
pub open: f64,
|
||||
pub high: f64,
|
||||
pub low: f64,
|
||||
pub close: f64,
|
||||
pub volume: f64,
|
||||
pub dollar: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "DollarBars")]
|
||||
pub struct DollarBarsNode {
|
||||
inner: wc::DollarBars,
|
||||
}
|
||||
#[napi]
|
||||
impl DollarBarsNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(dollar_per_bar: f64) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::DollarBars::new(dollar_per_bar).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
) -> napi::Result<Vec<DollarBarValue>> {
|
||||
let candle = wc::Candle::new(open, high, low, close, volume, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| DollarBarValue {
|
||||
open: b.open,
|
||||
high: b.high,
|
||||
low: b.low,
|
||||
close: b.close,
|
||||
volume: b.volume,
|
||||
dollar: b.dollar,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
volume: Vec<f64>,
|
||||
) -> napi::Result<Vec<DollarBarValue>> {
|
||||
if open.len() != high.len()
|
||||
|| high.len() != low.len()
|
||||
|| low.len() != close.len()
|
||||
|| close.len() != volume.len()
|
||||
{
|
||||
return Err(NapiError::from_reason(
|
||||
"open, high, low, close, volume must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
for i in 0..open.len() {
|
||||
let candle = wc::Candle::new(open[i], high[i], low[i], close[i], volume[i], 0)
|
||||
.map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
out.push(DollarBarValue {
|
||||
open: b.open,
|
||||
high: b.high,
|
||||
low: b.low,
|
||||
close: b.close,
|
||||
volume: b.volume,
|
||||
dollar: b.dollar,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi(js_name = "dollarPerBar")]
|
||||
pub fn dollar_per_bar(&self) -> f64 {
|
||||
self.inner.dollar_per_bar()
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct ImbalanceBarValue {
|
||||
pub open: f64,
|
||||
pub high: f64,
|
||||
pub low: f64,
|
||||
pub close: f64,
|
||||
pub imbalance: f64,
|
||||
pub direction: i32,
|
||||
}
|
||||
|
||||
#[napi(js_name = "ImbalanceBars")]
|
||||
pub struct ImbalanceBarsNode {
|
||||
inner: wc::ImbalanceBars,
|
||||
}
|
||||
#[napi]
|
||||
impl ImbalanceBarsNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(threshold: f64) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::ImbalanceBars::new(threshold).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
) -> napi::Result<Vec<ImbalanceBarValue>> {
|
||||
let candle = wc::Candle::new(open, high, low, close, 1.0, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| ImbalanceBarValue {
|
||||
open: b.open,
|
||||
high: b.high,
|
||||
low: b.low,
|
||||
close: b.close,
|
||||
imbalance: b.imbalance,
|
||||
direction: i32::from(b.direction),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
) -> napi::Result<Vec<ImbalanceBarValue>> {
|
||||
if open.len() != high.len() || high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(NapiError::from_reason(
|
||||
"open, high, low, close must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
for i in 0..open.len() {
|
||||
let candle =
|
||||
wc::Candle::new(open[i], high[i], low[i], close[i], 1.0, 0).map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
out.push(ImbalanceBarValue {
|
||||
open: b.open,
|
||||
high: b.high,
|
||||
low: b.low,
|
||||
close: b.close,
|
||||
imbalance: b.imbalance,
|
||||
direction: i32::from(b.direction),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi]
|
||||
pub fn threshold(&self) -> f64 {
|
||||
self.inner.threshold()
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct RunBarValue {
|
||||
pub open: f64,
|
||||
pub high: f64,
|
||||
pub low: f64,
|
||||
pub close: f64,
|
||||
pub length: u32,
|
||||
pub direction: i32,
|
||||
}
|
||||
|
||||
#[napi(js_name = "RunBars")]
|
||||
pub struct RunBarsNode {
|
||||
inner: wc::RunBars,
|
||||
}
|
||||
#[napi]
|
||||
impl RunBarsNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(run_length: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::RunBars::new(run_length as usize).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
) -> napi::Result<Vec<RunBarValue>> {
|
||||
let candle = wc::Candle::new(open, high, low, close, 1.0, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| RunBarValue {
|
||||
open: b.open,
|
||||
high: b.high,
|
||||
low: b.low,
|
||||
close: b.close,
|
||||
length: b.length as u32,
|
||||
direction: i32::from(b.direction),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: Vec<f64>,
|
||||
high: Vec<f64>,
|
||||
low: Vec<f64>,
|
||||
close: Vec<f64>,
|
||||
) -> napi::Result<Vec<RunBarValue>> {
|
||||
if open.len() != high.len() || high.len() != low.len() || low.len() != close.len() {
|
||||
return Err(NapiError::from_reason(
|
||||
"open, high, low, close must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
for i in 0..open.len() {
|
||||
let candle =
|
||||
wc::Candle::new(open[i], high[i], low[i], close[i], 1.0, 0).map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
out.push(RunBarValue {
|
||||
open: b.open,
|
||||
high: b.high,
|
||||
low: b.low,
|
||||
close: b.close,
|
||||
length: b.length as u32,
|
||||
direction: i32::from(b.direction),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi(js_name = "runLength")]
|
||||
pub fn run_length(&self) -> u32 {
|
||||
self.inner.run_length() as u32
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct LineBreakBarValue {
|
||||
pub open: f64,
|
||||
pub close: f64,
|
||||
pub direction: i32,
|
||||
}
|
||||
|
||||
#[napi(js_name = "ThreeLineBreakBars")]
|
||||
pub struct ThreeLineBreakBarsNode {
|
||||
inner: wc::ThreeLineBreakBars,
|
||||
}
|
||||
#[napi]
|
||||
impl ThreeLineBreakBarsNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(lines: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::ThreeLineBreakBars::new(lines as usize).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, close: f64) -> napi::Result<Vec<LineBreakBarValue>> {
|
||||
let candle = wc::Candle::new(close, close, close, close, 1.0, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| LineBreakBarValue {
|
||||
open: b.open,
|
||||
close: b.close,
|
||||
direction: i32::from(b.direction),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(&mut self, close: Vec<f64>) -> napi::Result<Vec<LineBreakBarValue>> {
|
||||
let mut out = Vec::new();
|
||||
for price in close {
|
||||
let candle = wc::Candle::new(price, price, price, price, 1.0, 0).map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
out.push(LineBreakBarValue {
|
||||
open: b.open,
|
||||
close: b.close,
|
||||
direction: i32::from(b.direction),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi]
|
||||
pub fn lines(&self) -> u32 {
|
||||
self.inner.lines() as u32
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(js_name = "Alpha")]
|
||||
pub struct AlphaNode {
|
||||
inner: wc::Alpha,
|
||||
|
||||
@@ -370,6 +370,13 @@ from ._wickra import (
|
||||
InitialBalance,
|
||||
OpeningRange,
|
||||
# Alt-Chart Bars
|
||||
ThreeLineBreakBars,
|
||||
RunBars,
|
||||
ImbalanceBars,
|
||||
DollarBars,
|
||||
VolumeBars,
|
||||
TickBars,
|
||||
RangeBars,
|
||||
RenkoBars,
|
||||
KagiBars,
|
||||
PointAndFigureBars,
|
||||
@@ -907,6 +914,13 @@ __all__ = [
|
||||
"InitialBalance",
|
||||
"OpeningRange",
|
||||
# Alt-Chart Bars
|
||||
"ThreeLineBreakBars",
|
||||
"RunBars",
|
||||
"ImbalanceBars",
|
||||
"DollarBars",
|
||||
"VolumeBars",
|
||||
"TickBars",
|
||||
"RangeBars",
|
||||
"RenkoBars",
|
||||
"KagiBars",
|
||||
"PointAndFigureBars",
|
||||
|
||||
@@ -38,6 +38,67 @@ fn map_err(e: wc::Error) -> PyErr {
|
||||
/// Raised instead of panicking when a `NumPy` input is not C-contiguous.
|
||||
const NON_CONTIGUOUS: &str = "array must be C-contiguous; pass np.ascontiguousarray(arr)";
|
||||
|
||||
/// Borrowed `(open, high, low, close)` columns for candle-driven bar builders.
|
||||
type OhlcCols<'a> = (&'a [f64], &'a [f64], &'a [f64], &'a [f64]);
|
||||
/// Borrowed `(open, high, low, close, volume)` columns for candle-driven bar builders.
|
||||
type OhlcvCols<'a> = (&'a [f64], &'a [f64], &'a [f64], &'a [f64], &'a [f64]);
|
||||
|
||||
/// `(open, high, low, close, volume)` rows from Tick/Volume bar builders.
|
||||
type OhlcvBarRows = Vec<(f64, f64, f64, f64, f64)>;
|
||||
/// `(open, high, low, close, volume, dollar)` rows from the Dollar bar builder.
|
||||
type DollarBarRows = Vec<(f64, f64, f64, f64, f64, f64)>;
|
||||
/// `(open, high, low, close, imbalance, direction)` rows from the Imbalance bar builder.
|
||||
type ImbalanceBarRows = Vec<(f64, f64, f64, f64, f64, i64)>;
|
||||
/// `(open, high, low, close, length, direction)` rows from the Run bar builder.
|
||||
type RunBarRows = Vec<(f64, f64, f64, f64, i64, i64)>;
|
||||
|
||||
/// Extract four equal-length OHLC slices, erroring on non-contiguous or mismatched input.
|
||||
fn ohlc_slices<'a, 'py>(
|
||||
open: &'a PyReadonlyArray1<'py, f64>,
|
||||
high: &'a PyReadonlyArray1<'py, f64>,
|
||||
low: &'a PyReadonlyArray1<'py, f64>,
|
||||
close: &'a PyReadonlyArray1<'py, f64>,
|
||||
) -> PyResult<OhlcCols<'a>> {
|
||||
let o = open
|
||||
.as_slice()
|
||||
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
|
||||
let h = high
|
||||
.as_slice()
|
||||
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
|
||||
let l = low
|
||||
.as_slice()
|
||||
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
|
||||
let c = close
|
||||
.as_slice()
|
||||
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
|
||||
if o.len() != h.len() || h.len() != l.len() || l.len() != c.len() {
|
||||
return Err(PyValueError::new_err(
|
||||
"open, high, low, close must be equal length",
|
||||
));
|
||||
}
|
||||
Ok((o, h, l, c))
|
||||
}
|
||||
|
||||
/// Extract five equal-length OHLCV slices, erroring on non-contiguous or mismatched input.
|
||||
fn ohlcv_slices<'a, 'py>(
|
||||
open: &'a PyReadonlyArray1<'py, f64>,
|
||||
high: &'a PyReadonlyArray1<'py, f64>,
|
||||
low: &'a PyReadonlyArray1<'py, f64>,
|
||||
close: &'a PyReadonlyArray1<'py, f64>,
|
||||
volume: &'a PyReadonlyArray1<'py, f64>,
|
||||
) -> PyResult<OhlcvCols<'a>> {
|
||||
let (o, h, l, c) = ohlc_slices(open, high, low, close)?;
|
||||
let v = volume
|
||||
.as_slice()
|
||||
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
|
||||
if v.len() != o.len() {
|
||||
return Err(PyValueError::new_err(
|
||||
"open, high, low, close, volume must be equal length",
|
||||
));
|
||||
}
|
||||
Ok((o, h, l, c, v))
|
||||
}
|
||||
|
||||
/// `(pp, r1, r2, r3, s1, s2, s3)` pivot levels returned by Classic/Fibonacci pivots.
|
||||
type PivotLevels = (f64, f64, f64, f64, f64, f64, f64);
|
||||
/// The five Fibonacci-extension levels returned by `FibExtension`.
|
||||
@@ -23459,6 +23520,486 @@ impl PyPointAndFigureBars {
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "RangeBars", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
struct PyRangeBars {
|
||||
inner: wc::RangeBars,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyRangeBars {
|
||||
#[new]
|
||||
fn new(range: f64) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::RangeBars::new(range).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Feed one close; returns bars completed on it as `(open, close, direction)`.
|
||||
fn update(&mut self, close: f64) -> PyResult<Vec<(f64, f64, i64)>> {
|
||||
let candle = wc::Candle::new(close, close, close, close, 1.0, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| (b.open, b.close, i64::from(b.direction)))
|
||||
.collect())
|
||||
}
|
||||
/// Batch over a close column. Returns shape `(k, 3)` of `[open, close, direction]`.
|
||||
fn batch<'py>(
|
||||
&mut self,
|
||||
py: Python<'py>,
|
||||
close: PyReadonlyArray1<'py, f64>,
|
||||
) -> PyResult<Bound<'py, PyArray2<f64>>> {
|
||||
let prices = close
|
||||
.as_slice()
|
||||
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
|
||||
let mut rows: Vec<f64> = Vec::new();
|
||||
let mut k = 0usize;
|
||||
for &price in prices {
|
||||
let candle = wc::Candle::new(price, price, price, price, 1.0, 0).map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
rows.push(b.open);
|
||||
rows.push(b.close);
|
||||
rows.push(f64::from(b.direction));
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
Ok(numpy::ndarray::Array2::from_shape_vec((k, 3), rows)
|
||||
.expect("shape consistent")
|
||||
.into_pyarray(py))
|
||||
}
|
||||
#[getter]
|
||||
fn range(&self) -> f64 {
|
||||
self.inner.range()
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
fn __repr__(&self) -> String {
|
||||
format!("RangeBars(range={})", self.inner.range())
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "TickBars", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
struct PyTickBars {
|
||||
inner: wc::TickBars,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyTickBars {
|
||||
#[new]
|
||||
fn new(ticks: usize) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::TickBars::new(ticks).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Feed one candle; returns bars completed as `(open, high, low, close, volume)`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
) -> PyResult<OhlcvBarRows> {
|
||||
let candle = wc::Candle::new(open, high, low, close, volume, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| (b.open, b.high, b.low, b.close, b.volume))
|
||||
.collect())
|
||||
}
|
||||
/// Batch over OHLCV columns. Returns shape `(k, 5)` of `[open, high, low, close, volume]`.
|
||||
fn batch<'py>(
|
||||
&mut self,
|
||||
py: Python<'py>,
|
||||
open: PyReadonlyArray1<'py, f64>,
|
||||
high: PyReadonlyArray1<'py, f64>,
|
||||
low: PyReadonlyArray1<'py, f64>,
|
||||
close: PyReadonlyArray1<'py, f64>,
|
||||
volume: PyReadonlyArray1<'py, f64>,
|
||||
) -> PyResult<Bound<'py, PyArray2<f64>>> {
|
||||
let (o, h, l, c, v) = ohlcv_slices(&open, &high, &low, &close, &volume)?;
|
||||
let mut rows: Vec<f64> = Vec::new();
|
||||
let mut k = 0usize;
|
||||
for i in 0..o.len() {
|
||||
let candle = wc::Candle::new(o[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
rows.extend_from_slice(&[b.open, b.high, b.low, b.close, b.volume]);
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
Ok(numpy::ndarray::Array2::from_shape_vec((k, 5), rows)
|
||||
.expect("shape consistent")
|
||||
.into_pyarray(py))
|
||||
}
|
||||
#[getter]
|
||||
fn ticks(&self) -> usize {
|
||||
self.inner.ticks()
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
fn __repr__(&self) -> String {
|
||||
format!("TickBars(ticks={})", self.inner.ticks())
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "VolumeBars", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
struct PyVolumeBars {
|
||||
inner: wc::VolumeBars,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyVolumeBars {
|
||||
#[new]
|
||||
fn new(volume_per_bar: f64) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::VolumeBars::new(volume_per_bar).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Feed one candle; returns bars completed as `(open, high, low, close, volume)`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
) -> PyResult<OhlcvBarRows> {
|
||||
let candle = wc::Candle::new(open, high, low, close, volume, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| (b.open, b.high, b.low, b.close, b.volume))
|
||||
.collect())
|
||||
}
|
||||
/// Batch over OHLCV columns. Returns shape `(k, 5)` of `[open, high, low, close, volume]`.
|
||||
fn batch<'py>(
|
||||
&mut self,
|
||||
py: Python<'py>,
|
||||
open: PyReadonlyArray1<'py, f64>,
|
||||
high: PyReadonlyArray1<'py, f64>,
|
||||
low: PyReadonlyArray1<'py, f64>,
|
||||
close: PyReadonlyArray1<'py, f64>,
|
||||
volume: PyReadonlyArray1<'py, f64>,
|
||||
) -> PyResult<Bound<'py, PyArray2<f64>>> {
|
||||
let (o, h, l, c, v) = ohlcv_slices(&open, &high, &low, &close, &volume)?;
|
||||
let mut rows: Vec<f64> = Vec::new();
|
||||
let mut k = 0usize;
|
||||
for i in 0..o.len() {
|
||||
let candle = wc::Candle::new(o[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
rows.extend_from_slice(&[b.open, b.high, b.low, b.close, b.volume]);
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
Ok(numpy::ndarray::Array2::from_shape_vec((k, 5), rows)
|
||||
.expect("shape consistent")
|
||||
.into_pyarray(py))
|
||||
}
|
||||
#[getter]
|
||||
fn volume_per_bar(&self) -> f64 {
|
||||
self.inner.volume_per_bar()
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
fn __repr__(&self) -> String {
|
||||
format!("VolumeBars(volume_per_bar={})", self.inner.volume_per_bar())
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "DollarBars", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
struct PyDollarBars {
|
||||
inner: wc::DollarBars,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyDollarBars {
|
||||
#[new]
|
||||
fn new(dollar_per_bar: f64) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::DollarBars::new(dollar_per_bar).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Feed one candle; returns bars completed as `(open, high, low, close, volume, dollar)`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
) -> PyResult<DollarBarRows> {
|
||||
let candle = wc::Candle::new(open, high, low, close, volume, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| (b.open, b.high, b.low, b.close, b.volume, b.dollar))
|
||||
.collect())
|
||||
}
|
||||
/// Batch over OHLCV columns. Returns shape `(k, 6)` of `[open, high, low, close, volume, dollar]`.
|
||||
fn batch<'py>(
|
||||
&mut self,
|
||||
py: Python<'py>,
|
||||
open: PyReadonlyArray1<'py, f64>,
|
||||
high: PyReadonlyArray1<'py, f64>,
|
||||
low: PyReadonlyArray1<'py, f64>,
|
||||
close: PyReadonlyArray1<'py, f64>,
|
||||
volume: PyReadonlyArray1<'py, f64>,
|
||||
) -> PyResult<Bound<'py, PyArray2<f64>>> {
|
||||
let (o, h, l, c, v) = ohlcv_slices(&open, &high, &low, &close, &volume)?;
|
||||
let mut rows: Vec<f64> = Vec::new();
|
||||
let mut k = 0usize;
|
||||
for i in 0..o.len() {
|
||||
let candle = wc::Candle::new(o[i], h[i], l[i], c[i], v[i], 0).map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
rows.extend_from_slice(&[b.open, b.high, b.low, b.close, b.volume, b.dollar]);
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
Ok(numpy::ndarray::Array2::from_shape_vec((k, 6), rows)
|
||||
.expect("shape consistent")
|
||||
.into_pyarray(py))
|
||||
}
|
||||
#[getter]
|
||||
fn dollar_per_bar(&self) -> f64 {
|
||||
self.inner.dollar_per_bar()
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
fn __repr__(&self) -> String {
|
||||
format!("DollarBars(dollar_per_bar={})", self.inner.dollar_per_bar())
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "ImbalanceBars", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
struct PyImbalanceBars {
|
||||
inner: wc::ImbalanceBars,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyImbalanceBars {
|
||||
#[new]
|
||||
fn new(threshold: f64) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::ImbalanceBars::new(threshold).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Feed one candle; returns bars completed as `(open, high, low, close, imbalance, direction)`.
|
||||
fn update(&mut self, open: f64, high: f64, low: f64, close: f64) -> PyResult<ImbalanceBarRows> {
|
||||
let candle = wc::Candle::new(open, high, low, close, 1.0, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| {
|
||||
(
|
||||
b.open,
|
||||
b.high,
|
||||
b.low,
|
||||
b.close,
|
||||
b.imbalance,
|
||||
i64::from(b.direction),
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
/// Batch over OHLC columns. Returns shape `(k, 6)` of `[open, high, low, close, imbalance, direction]`.
|
||||
fn batch<'py>(
|
||||
&mut self,
|
||||
py: Python<'py>,
|
||||
open: PyReadonlyArray1<'py, f64>,
|
||||
high: PyReadonlyArray1<'py, f64>,
|
||||
low: PyReadonlyArray1<'py, f64>,
|
||||
close: PyReadonlyArray1<'py, f64>,
|
||||
) -> PyResult<Bound<'py, PyArray2<f64>>> {
|
||||
let (o, h, l, c) = ohlc_slices(&open, &high, &low, &close)?;
|
||||
let mut rows: Vec<f64> = Vec::new();
|
||||
let mut k = 0usize;
|
||||
for i in 0..o.len() {
|
||||
let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 1.0, 0).map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
rows.extend_from_slice(&[
|
||||
b.open,
|
||||
b.high,
|
||||
b.low,
|
||||
b.close,
|
||||
b.imbalance,
|
||||
f64::from(b.direction),
|
||||
]);
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
Ok(numpy::ndarray::Array2::from_shape_vec((k, 6), rows)
|
||||
.expect("shape consistent")
|
||||
.into_pyarray(py))
|
||||
}
|
||||
#[getter]
|
||||
fn threshold(&self) -> f64 {
|
||||
self.inner.threshold()
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
fn __repr__(&self) -> String {
|
||||
format!("ImbalanceBars(threshold={})", self.inner.threshold())
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "RunBars", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
struct PyRunBars {
|
||||
inner: wc::RunBars,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyRunBars {
|
||||
#[new]
|
||||
fn new(run_length: usize) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::RunBars::new(run_length).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Feed one candle; returns bars completed as `(open, high, low, close, length, direction)`.
|
||||
fn update(&mut self, open: f64, high: f64, low: f64, close: f64) -> PyResult<RunBarRows> {
|
||||
let candle = wc::Candle::new(open, high, low, close, 1.0, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| {
|
||||
(
|
||||
b.open,
|
||||
b.high,
|
||||
b.low,
|
||||
b.close,
|
||||
i64::try_from(b.length).unwrap_or(i64::MAX),
|
||||
i64::from(b.direction),
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
/// Batch over OHLC columns. Returns shape `(k, 6)` of `[open, high, low, close, length, direction]`.
|
||||
fn batch<'py>(
|
||||
&mut self,
|
||||
py: Python<'py>,
|
||||
open: PyReadonlyArray1<'py, f64>,
|
||||
high: PyReadonlyArray1<'py, f64>,
|
||||
low: PyReadonlyArray1<'py, f64>,
|
||||
close: PyReadonlyArray1<'py, f64>,
|
||||
) -> PyResult<Bound<'py, PyArray2<f64>>> {
|
||||
let (o, h, l, c) = ohlc_slices(&open, &high, &low, &close)?;
|
||||
let mut rows: Vec<f64> = Vec::new();
|
||||
let mut k = 0usize;
|
||||
for i in 0..o.len() {
|
||||
let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 1.0, 0).map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
rows.extend_from_slice(&[
|
||||
b.open,
|
||||
b.high,
|
||||
b.low,
|
||||
b.close,
|
||||
b.length as f64,
|
||||
f64::from(b.direction),
|
||||
]);
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
Ok(numpy::ndarray::Array2::from_shape_vec((k, 6), rows)
|
||||
.expect("shape consistent")
|
||||
.into_pyarray(py))
|
||||
}
|
||||
#[getter]
|
||||
fn run_length(&self) -> usize {
|
||||
self.inner.run_length()
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
fn __repr__(&self) -> String {
|
||||
format!("RunBars(run_length={})", self.inner.run_length())
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(
|
||||
name = "ThreeLineBreakBars",
|
||||
module = "wickra._wickra",
|
||||
skip_from_py_object
|
||||
)]
|
||||
#[derive(Clone)]
|
||||
struct PyThreeLineBreakBars {
|
||||
inner: wc::ThreeLineBreakBars,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyThreeLineBreakBars {
|
||||
#[new]
|
||||
#[pyo3(signature = (lines=3))]
|
||||
fn new(lines: usize) -> PyResult<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::ThreeLineBreakBars::new(lines).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Feed one close; returns bars completed on it as `(open, close, direction)`.
|
||||
fn update(&mut self, close: f64) -> PyResult<Vec<(f64, f64, i64)>> {
|
||||
let candle = wc::Candle::new(close, close, close, close, 1.0, 0).map_err(map_err)?;
|
||||
Ok(self
|
||||
.inner
|
||||
.update(candle)
|
||||
.into_iter()
|
||||
.map(|b| (b.open, b.close, i64::from(b.direction)))
|
||||
.collect())
|
||||
}
|
||||
/// Batch over a close column. Returns shape `(k, 3)` of `[open, close, direction]`.
|
||||
fn batch<'py>(
|
||||
&mut self,
|
||||
py: Python<'py>,
|
||||
close: PyReadonlyArray1<'py, f64>,
|
||||
) -> PyResult<Bound<'py, PyArray2<f64>>> {
|
||||
let prices = close
|
||||
.as_slice()
|
||||
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
|
||||
let mut rows: Vec<f64> = Vec::new();
|
||||
let mut k = 0usize;
|
||||
for &price in prices {
|
||||
let candle = wc::Candle::new(price, price, price, price, 1.0, 0).map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
rows.push(b.open);
|
||||
rows.push(b.close);
|
||||
rows.push(f64::from(b.direction));
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
Ok(numpy::ndarray::Array2::from_shape_vec((k, 3), rows)
|
||||
.expect("shape consistent")
|
||||
.into_pyarray(py))
|
||||
}
|
||||
#[getter]
|
||||
fn lines(&self) -> usize {
|
||||
self.inner.lines()
|
||||
}
|
||||
fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
fn __repr__(&self) -> String {
|
||||
format!("ThreeLineBreakBars(lines={})", self.inner.lines())
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Module ==============================
|
||||
|
||||
// ====================== Seasonality & Session (full-candle) ======================
|
||||
@@ -26074,6 +26615,13 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<PyRenkoBars>()?;
|
||||
m.add_class::<PyKagiBars>()?;
|
||||
m.add_class::<PyPointAndFigureBars>()?;
|
||||
m.add_class::<PyRangeBars>()?;
|
||||
m.add_class::<PyTickBars>()?;
|
||||
m.add_class::<PyVolumeBars>()?;
|
||||
m.add_class::<PyDollarBars>()?;
|
||||
m.add_class::<PyImbalanceBars>()?;
|
||||
m.add_class::<PyRunBars>()?;
|
||||
m.add_class::<PyThreeLineBreakBars>()?;
|
||||
m.add_class::<PyInitialBalance>()?;
|
||||
m.add_class::<PyOpeningRange>()?;
|
||||
m.add_class::<PyNakedPoc>()?;
|
||||
|
||||
@@ -4237,3 +4237,83 @@ def test_bar_builders_reset():
|
||||
r.update(15.0)
|
||||
r.reset()
|
||||
assert r.update(50.0) == [] # re-seeds after reset
|
||||
|
||||
|
||||
def test_range_bars_reference():
|
||||
rb = ta.RangeBars(1.0)
|
||||
assert rb.update(10.0) == [] # seed
|
||||
assert rb.update(13.0) == [(10.0, 11.0, 1), (11.0, 12.0, 1), (12.0, 13.0, 1)]
|
||||
|
||||
|
||||
def test_range_bars_batch_shape():
|
||||
rb = ta.RangeBars(1.0)
|
||||
out = rb.batch(np.array([10.0, 11.0, 12.0, 13.0]))
|
||||
assert out.shape == (3, 3)
|
||||
np.testing.assert_allclose(out[:, 2], [1.0, 1.0, 1.0])
|
||||
|
||||
|
||||
def test_tick_bars_reference():
|
||||
tb = ta.TickBars(2)
|
||||
assert tb.update(10.0, 11.0, 9.0, 10.5, 100.0) == []
|
||||
out = tb.update(10.5, 12.0, 10.0, 11.0, 150.0)
|
||||
assert len(out) == 1
|
||||
assert out[0] == (10.0, 12.0, 9.0, 11.0, 250.0)
|
||||
|
||||
|
||||
def test_tick_bars_batch_shape():
|
||||
tb = ta.TickBars(2)
|
||||
col = np.array([10.0, 10.0, 10.0, 10.0])
|
||||
vol = np.array([1.0, 1.0, 1.0, 1.0])
|
||||
out = tb.batch(col, col, col, col, vol)
|
||||
assert out.shape == (2, 5)
|
||||
|
||||
|
||||
def test_volume_bars_reference():
|
||||
vb = ta.VolumeBars(100.0)
|
||||
assert vb.update(10.0, 10.0, 10.0, 10.0, 60.0) == []
|
||||
out = vb.update(10.5, 10.5, 10.5, 10.5, 60.0)
|
||||
assert len(out) == 1
|
||||
assert out[0][4] == 120.0 # accumulated volume
|
||||
|
||||
|
||||
def test_dollar_bars_reference():
|
||||
db = ta.DollarBars(1000.0)
|
||||
assert db.update(10.0, 10.0, 10.0, 10.0, 60.0) == [] # 600
|
||||
out = db.update(10.0, 10.0, 10.0, 10.0, 60.0) # 1200 >= 1000
|
||||
assert len(out) == 1
|
||||
assert out[0][4] == 120.0 # volume
|
||||
assert out[0][5] == 1200.0 # traded value
|
||||
|
||||
|
||||
def test_imbalance_bars_reference():
|
||||
ib = ta.ImbalanceBars(3.0)
|
||||
assert ib.update(10.0, 10.0, 10.0, 10.0) == [] # seed
|
||||
ib.update(11.0, 11.0, 11.0, 11.0) # +1
|
||||
ib.update(12.0, 12.0, 12.0, 12.0) # +2
|
||||
out = ib.update(13.0, 13.0, 13.0, 13.0) # +3 -> close
|
||||
assert len(out) == 1
|
||||
assert out[0][4] == 3.0 # imbalance
|
||||
assert out[0][5] == 1 # direction
|
||||
|
||||
|
||||
def test_run_bars_reference():
|
||||
rb = ta.RunBars(3)
|
||||
assert rb.update(10.0, 10.0, 10.0, 10.0) == [] # seed
|
||||
rb.update(11.0, 11.0, 11.0, 11.0) # run 1
|
||||
rb.update(12.0, 12.0, 12.0, 12.0) # run 2
|
||||
out = rb.update(13.0, 13.0, 13.0, 13.0) # run 3 -> close
|
||||
assert len(out) == 1
|
||||
assert out[0][4] == 3 # length
|
||||
assert out[0][5] == 1 # direction
|
||||
|
||||
|
||||
def test_three_line_break_bars_reference():
|
||||
tlb = ta.ThreeLineBreakBars(3)
|
||||
assert tlb.update(10.0) == [] # seed
|
||||
assert tlb.update(11.0) == [(10.0, 11.0, 1)]
|
||||
|
||||
|
||||
def test_three_line_break_bars_batch_shape():
|
||||
tlb = ta.ThreeLineBreakBars(3)
|
||||
out = tlb.batch(np.array([10.0, 11.0, 12.0, 13.0]))
|
||||
assert out.shape[1] == 3
|
||||
|
||||
@@ -13163,6 +13163,310 @@ impl WasmPointAndFigureBars {
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = RangeBars)]
|
||||
pub struct WasmRangeBars {
|
||||
inner: wc::RangeBars,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = RangeBars)]
|
||||
impl WasmRangeBars {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(range: f64) -> Result<WasmRangeBars, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::RangeBars::new(range).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Returns an array of `{ open, close, direction }` bars completed on this close.
|
||||
pub fn update(&mut self, close: f64) -> Result<Array, JsError> {
|
||||
let candle = wc::Candle::new(close, close, close, close, 1.0, 0).map_err(map_err)?;
|
||||
let arr = Array::new();
|
||||
for b in self.inner.update(candle) {
|
||||
let obj = Object::new();
|
||||
Reflect::set(&obj, &"open".into(), &b.open.into()).ok();
|
||||
Reflect::set(&obj, &"close".into(), &b.close.into()).ok();
|
||||
Reflect::set(&obj, &"direction".into(), &f64::from(b.direction).into()).ok();
|
||||
arr.push(&obj);
|
||||
}
|
||||
Ok(arr)
|
||||
}
|
||||
pub fn batch(&mut self, close: &[f64]) -> Result<Array, JsError> {
|
||||
let arr = Array::new();
|
||||
for &price in close {
|
||||
let candle = wc::Candle::new(price, price, price, price, 1.0, 0).map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
let obj = Object::new();
|
||||
Reflect::set(&obj, &"open".into(), &b.open.into()).ok();
|
||||
Reflect::set(&obj, &"close".into(), &b.close.into()).ok();
|
||||
Reflect::set(&obj, &"direction".into(), &f64::from(b.direction).into()).ok();
|
||||
arr.push(&obj);
|
||||
}
|
||||
}
|
||||
Ok(arr)
|
||||
}
|
||||
pub fn range(&self) -> f64 {
|
||||
self.inner.range()
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = TickBars)]
|
||||
pub struct WasmTickBars {
|
||||
inner: wc::TickBars,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = TickBars)]
|
||||
impl WasmTickBars {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(ticks: usize) -> Result<WasmTickBars, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::TickBars::new(ticks).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Returns an array of `{ open, high, low, close, volume }` bars completed on this candle.
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
) -> Result<Array, JsError> {
|
||||
let candle = wc::Candle::new(open, high, low, close, volume, 0).map_err(map_err)?;
|
||||
let arr = Array::new();
|
||||
for b in self.inner.update(candle) {
|
||||
let obj = Object::new();
|
||||
Reflect::set(&obj, &"open".into(), &b.open.into()).ok();
|
||||
Reflect::set(&obj, &"high".into(), &b.high.into()).ok();
|
||||
Reflect::set(&obj, &"low".into(), &b.low.into()).ok();
|
||||
Reflect::set(&obj, &"close".into(), &b.close.into()).ok();
|
||||
Reflect::set(&obj, &"volume".into(), &b.volume.into()).ok();
|
||||
arr.push(&obj);
|
||||
}
|
||||
Ok(arr)
|
||||
}
|
||||
pub fn ticks(&self) -> usize {
|
||||
self.inner.ticks()
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = VolumeBars)]
|
||||
pub struct WasmVolumeBars {
|
||||
inner: wc::VolumeBars,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = VolumeBars)]
|
||||
impl WasmVolumeBars {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(volume_per_bar: f64) -> Result<WasmVolumeBars, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::VolumeBars::new(volume_per_bar).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Returns an array of `{ open, high, low, close, volume }` bars completed on this candle.
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
) -> Result<Array, JsError> {
|
||||
let candle = wc::Candle::new(open, high, low, close, volume, 0).map_err(map_err)?;
|
||||
let arr = Array::new();
|
||||
for b in self.inner.update(candle) {
|
||||
let obj = Object::new();
|
||||
Reflect::set(&obj, &"open".into(), &b.open.into()).ok();
|
||||
Reflect::set(&obj, &"high".into(), &b.high.into()).ok();
|
||||
Reflect::set(&obj, &"low".into(), &b.low.into()).ok();
|
||||
Reflect::set(&obj, &"close".into(), &b.close.into()).ok();
|
||||
Reflect::set(&obj, &"volume".into(), &b.volume.into()).ok();
|
||||
arr.push(&obj);
|
||||
}
|
||||
Ok(arr)
|
||||
}
|
||||
#[wasm_bindgen(js_name = volumePerBar)]
|
||||
pub fn volume_per_bar(&self) -> f64 {
|
||||
self.inner.volume_per_bar()
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = DollarBars)]
|
||||
pub struct WasmDollarBars {
|
||||
inner: wc::DollarBars,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = DollarBars)]
|
||||
impl WasmDollarBars {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(dollar_per_bar: f64) -> Result<WasmDollarBars, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::DollarBars::new(dollar_per_bar).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Returns an array of `{ open, high, low, close, volume, dollar }` bars completed on this candle.
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
volume: f64,
|
||||
) -> Result<Array, JsError> {
|
||||
let candle = wc::Candle::new(open, high, low, close, volume, 0).map_err(map_err)?;
|
||||
let arr = Array::new();
|
||||
for b in self.inner.update(candle) {
|
||||
let obj = Object::new();
|
||||
Reflect::set(&obj, &"open".into(), &b.open.into()).ok();
|
||||
Reflect::set(&obj, &"high".into(), &b.high.into()).ok();
|
||||
Reflect::set(&obj, &"low".into(), &b.low.into()).ok();
|
||||
Reflect::set(&obj, &"close".into(), &b.close.into()).ok();
|
||||
Reflect::set(&obj, &"volume".into(), &b.volume.into()).ok();
|
||||
Reflect::set(&obj, &"dollar".into(), &b.dollar.into()).ok();
|
||||
arr.push(&obj);
|
||||
}
|
||||
Ok(arr)
|
||||
}
|
||||
#[wasm_bindgen(js_name = dollarPerBar)]
|
||||
pub fn dollar_per_bar(&self) -> f64 {
|
||||
self.inner.dollar_per_bar()
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = ImbalanceBars)]
|
||||
pub struct WasmImbalanceBars {
|
||||
inner: wc::ImbalanceBars,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = ImbalanceBars)]
|
||||
impl WasmImbalanceBars {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(threshold: f64) -> Result<WasmImbalanceBars, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::ImbalanceBars::new(threshold).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Returns an array of `{ open, high, low, close, imbalance, direction }` bars completed on this candle.
|
||||
pub fn update(&mut self, open: f64, high: f64, low: f64, close: f64) -> Result<Array, JsError> {
|
||||
let candle = wc::Candle::new(open, high, low, close, 1.0, 0).map_err(map_err)?;
|
||||
let arr = Array::new();
|
||||
for b in self.inner.update(candle) {
|
||||
let obj = Object::new();
|
||||
Reflect::set(&obj, &"open".into(), &b.open.into()).ok();
|
||||
Reflect::set(&obj, &"high".into(), &b.high.into()).ok();
|
||||
Reflect::set(&obj, &"low".into(), &b.low.into()).ok();
|
||||
Reflect::set(&obj, &"close".into(), &b.close.into()).ok();
|
||||
Reflect::set(&obj, &"imbalance".into(), &b.imbalance.into()).ok();
|
||||
Reflect::set(&obj, &"direction".into(), &f64::from(b.direction).into()).ok();
|
||||
arr.push(&obj);
|
||||
}
|
||||
Ok(arr)
|
||||
}
|
||||
pub fn threshold(&self) -> f64 {
|
||||
self.inner.threshold()
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = RunBars)]
|
||||
pub struct WasmRunBars {
|
||||
inner: wc::RunBars,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = RunBars)]
|
||||
impl WasmRunBars {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(run_length: usize) -> Result<WasmRunBars, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::RunBars::new(run_length).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Returns an array of `{ open, high, low, close, length, direction }` bars completed on this candle.
|
||||
pub fn update(&mut self, open: f64, high: f64, low: f64, close: f64) -> Result<Array, JsError> {
|
||||
let candle = wc::Candle::new(open, high, low, close, 1.0, 0).map_err(map_err)?;
|
||||
let arr = Array::new();
|
||||
for b in self.inner.update(candle) {
|
||||
let obj = Object::new();
|
||||
Reflect::set(&obj, &"open".into(), &b.open.into()).ok();
|
||||
Reflect::set(&obj, &"high".into(), &b.high.into()).ok();
|
||||
Reflect::set(&obj, &"low".into(), &b.low.into()).ok();
|
||||
Reflect::set(&obj, &"close".into(), &b.close.into()).ok();
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
Reflect::set(&obj, &"length".into(), &(b.length as f64).into()).ok();
|
||||
Reflect::set(&obj, &"direction".into(), &f64::from(b.direction).into()).ok();
|
||||
arr.push(&obj);
|
||||
}
|
||||
Ok(arr)
|
||||
}
|
||||
#[wasm_bindgen(js_name = runLength)]
|
||||
pub fn run_length(&self) -> usize {
|
||||
self.inner.run_length()
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = ThreeLineBreakBars)]
|
||||
pub struct WasmThreeLineBreakBars {
|
||||
inner: wc::ThreeLineBreakBars,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = ThreeLineBreakBars)]
|
||||
impl WasmThreeLineBreakBars {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(lines: usize) -> Result<WasmThreeLineBreakBars, JsError> {
|
||||
Ok(Self {
|
||||
inner: wc::ThreeLineBreakBars::new(lines).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
/// Returns an array of `{ open, close, direction }` bars completed on this close.
|
||||
pub fn update(&mut self, close: f64) -> Result<Array, JsError> {
|
||||
let candle = wc::Candle::new(close, close, close, close, 1.0, 0).map_err(map_err)?;
|
||||
let arr = Array::new();
|
||||
for b in self.inner.update(candle) {
|
||||
let obj = Object::new();
|
||||
Reflect::set(&obj, &"open".into(), &b.open.into()).ok();
|
||||
Reflect::set(&obj, &"close".into(), &b.close.into()).ok();
|
||||
Reflect::set(&obj, &"direction".into(), &f64::from(b.direction).into()).ok();
|
||||
arr.push(&obj);
|
||||
}
|
||||
Ok(arr)
|
||||
}
|
||||
pub fn batch(&mut self, close: &[f64]) -> Result<Array, JsError> {
|
||||
let arr = Array::new();
|
||||
for &price in close {
|
||||
let candle = wc::Candle::new(price, price, price, price, 1.0, 0).map_err(map_err)?;
|
||||
for b in self.inner.update(candle) {
|
||||
let obj = Object::new();
|
||||
Reflect::set(&obj, &"open".into(), &b.open.into()).ok();
|
||||
Reflect::set(&obj, &"close".into(), &b.close.into()).ok();
|
||||
Reflect::set(&obj, &"direction".into(), &f64::from(b.direction).into()).ok();
|
||||
arr.push(&obj);
|
||||
}
|
||||
}
|
||||
Ok(arr)
|
||||
}
|
||||
pub fn lines(&self) -> usize {
|
||||
self.inner.lines()
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = Alpha)]
|
||||
pub struct WasmAlpha {
|
||||
inner: wc::Alpha,
|
||||
|
||||
Reference in New Issue
Block a user