feat(indicators): A5b Fibonacci tools (geometric) (#172)

Completes the **Fibonacci** family with the four geometric/time tools (catalogue 373 -> 377). All extend the internal `pattern_swing` ZigZag tracker with a per-pivot bar index and a current-bar counter (additive — the chart/harmonic detectors are unaffected), and emit `Candle -> struct` outputs via custom Python/Node/WASM bindings.

| Tool | Output |
|------|--------|
| `FibFan` | three trendlines fanning from a swing start through its 38.2/50/61.8% retracement levels, extended to the current bar |
| `FibArcs` | semicircular retracement levels centred on the swing end, normalised by the leg's bar-width (chart-scale-free) |
| `FibChannel` | a sloped base trendline plus parallel lines at Fibonacci multiples of the channel width |
| `FibTimeZones` | markers at Fibonacci bar-distances (1/2/3/5/8/...) from the latest swing pivot |

The geometric tools are novel as streaming indicators; each normalises its geometry to the swing leg's bar-width so the output is chart-scale-free. Formulas are documented in each module and deep-dive.

Fully wired: core (100% unit-tested branches incl. the new `pattern_swing` bar tracking), Python/Node/WASM struct bindings, fuzz, reference + streaming-vs-batch tests.

Verification: `cargo test --workspace` green, clippy `-D warnings` clean, node 454 tests, python 768 tests.
This commit is contained in:
kingchenc
2026-06-04 01:12:09 +02:00
committed by GitHub
parent ea9da12d86
commit 5a1d607807
19 changed files with 1757 additions and 13 deletions
@@ -391,6 +391,10 @@ const multi = {
AutoFib: { make: () => new wickra.AutoFib(), fields: ['level0', 'level236', 'level382', 'level500', 'level618', 'level786', 'level1000'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
GoldenPocket: { make: () => new wickra.GoldenPocket(), fields: ['low', 'mid', 'high'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
FibConfluence: { make: () => new wickra.FibConfluence(), fields: ['price', 'strength'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
FibFan: { make: () => new wickra.FibFan(), fields: ['fan382', 'fan500', 'fan618'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
FibArcs: { make: () => new wickra.FibArcs(), fields: ['arc382', 'arc500', 'arc618'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
FibChannel: { make: () => new wickra.FibChannel(), fields: ['base', 'level618', 'level1000', 'level1618'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
FibTimeZones: { make: () => new wickra.FibTimeZones(), fields: ['onZone', 'barsToNext'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
};
for (const [name, d] of Object.entries(multi)) {
+56
View File
@@ -402,6 +402,26 @@ export interface FibConfluenceValue {
price: number
strength: number
}
export interface FibFanValue {
fan382: number
fan500: number
fan618: number
}
export interface FibArcsValue {
arc382: number
arc500: number
arc618: number
}
export interface FibChannelValue {
base: number
level618: number
level1000: number
level1618: number
}
export interface FibTimeZonesValue {
onZone: number
barsToNext: number
}
export type SmaNode = SMA
export declare class SMA {
constructor(period: number)
@@ -3926,3 +3946,39 @@ export declare class FibConfluence {
isReady(): boolean
warmupPeriod(): number
}
export type FibFanNode = FibFan
export declare class FibFan {
constructor()
update(high: number, low: number): FibFanValue | null
batch(high: Array<number>, low: Array<number>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type FibArcsNode = FibArcs
export declare class FibArcs {
constructor()
update(high: number, low: number): FibArcsValue | null
batch(high: Array<number>, low: Array<number>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type FibChannelNode = FibChannel
export declare class FibChannel {
constructor()
update(high: number, low: number): FibChannelValue | null
batch(high: Array<number>, low: Array<number>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
export type FibTimeZonesNode = FibTimeZones
export declare class FibTimeZones {
constructor()
update(high: number, low: number): FibTimeZonesValue | null
batch(high: Array<number>, low: Array<number>): Array<number>
reset(): void
isReady(): boolean
warmupPeriod(): number
}
File diff suppressed because one or more lines are too long
+276
View File
@@ -14482,3 +14482,279 @@ impl Default for FibConfluenceNode {
Self::new()
}
}
#[napi(object)]
pub struct FibFanValue {
pub fan_382: f64,
pub fan_500: f64,
pub fan_618: f64,
}
#[napi(js_name = "FibFan")]
pub struct FibFanNode {
inner: wc::FibFan,
}
#[napi]
impl FibFanNode {
#[napi(constructor)]
pub fn new() -> Self {
Self {
inner: wc::FibFan::new(),
}
}
#[napi]
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<FibFanValue>> {
Ok(self
.inner
.update(swing_cnd(high, low)?)
.map(|o| FibFanValue {
fan_382: o.fan_382,
fan_500: o.fan_500,
fan_618: o.fan_618,
}))
}
#[napi]
pub fn batch(&mut self, high: Vec<f64>, low: Vec<f64>) -> napi::Result<Vec<f64>> {
if high.len() != low.len() {
return Err(NapiError::from_reason(
"high and low must be equal length".to_string(),
));
}
let n = high.len();
let mut out = vec![f64::NAN; n * 3];
for i in 0..n {
if let Some(o) = self.inner.update(swing_cnd(high[i], low[i])?) {
out[i * 3] = o.fan_382;
out[i * 3 + 1] = o.fan_500;
out[i * 3 + 2] = o.fan_618;
}
}
Ok(out)
}
#[napi]
pub fn reset(&mut self) {
self.inner.reset();
}
#[napi(js_name = "isReady")]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[napi(js_name = "warmupPeriod")]
pub fn warmup_period(&self) -> u32 {
self.inner.warmup_period() as u32
}
}
impl Default for FibFanNode {
fn default() -> Self {
Self::new()
}
}
#[napi(object)]
pub struct FibArcsValue {
pub arc_382: f64,
pub arc_500: f64,
pub arc_618: f64,
}
#[napi(js_name = "FibArcs")]
pub struct FibArcsNode {
inner: wc::FibArcs,
}
#[napi]
impl FibArcsNode {
#[napi(constructor)]
pub fn new() -> Self {
Self {
inner: wc::FibArcs::new(),
}
}
#[napi]
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<FibArcsValue>> {
Ok(self
.inner
.update(swing_cnd(high, low)?)
.map(|o| FibArcsValue {
arc_382: o.arc_382,
arc_500: o.arc_500,
arc_618: o.arc_618,
}))
}
#[napi]
pub fn batch(&mut self, high: Vec<f64>, low: Vec<f64>) -> napi::Result<Vec<f64>> {
if high.len() != low.len() {
return Err(NapiError::from_reason(
"high and low must be equal length".to_string(),
));
}
let n = high.len();
let mut out = vec![f64::NAN; n * 3];
for i in 0..n {
if let Some(o) = self.inner.update(swing_cnd(high[i], low[i])?) {
out[i * 3] = o.arc_382;
out[i * 3 + 1] = o.arc_500;
out[i * 3 + 2] = o.arc_618;
}
}
Ok(out)
}
#[napi]
pub fn reset(&mut self) {
self.inner.reset();
}
#[napi(js_name = "isReady")]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[napi(js_name = "warmupPeriod")]
pub fn warmup_period(&self) -> u32 {
self.inner.warmup_period() as u32
}
}
impl Default for FibArcsNode {
fn default() -> Self {
Self::new()
}
}
#[napi(object)]
pub struct FibChannelValue {
pub base: f64,
pub level_618: f64,
pub level_1000: f64,
pub level_1618: f64,
}
#[napi(js_name = "FibChannel")]
pub struct FibChannelNode {
inner: wc::FibChannel,
}
#[napi]
impl FibChannelNode {
#[napi(constructor)]
pub fn new() -> Self {
Self {
inner: wc::FibChannel::new(),
}
}
#[napi]
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<FibChannelValue>> {
Ok(self
.inner
.update(swing_cnd(high, low)?)
.map(|o| FibChannelValue {
base: o.base,
level_618: o.level_618,
level_1000: o.level_1000,
level_1618: o.level_1618,
}))
}
#[napi]
pub fn batch(&mut self, high: Vec<f64>, low: Vec<f64>) -> napi::Result<Vec<f64>> {
if high.len() != low.len() {
return Err(NapiError::from_reason(
"high and low must be equal length".to_string(),
));
}
let n = high.len();
let mut out = vec![f64::NAN; n * 4];
for i in 0..n {
if let Some(o) = self.inner.update(swing_cnd(high[i], low[i])?) {
out[i * 4] = o.base;
out[i * 4 + 1] = o.level_618;
out[i * 4 + 2] = o.level_1000;
out[i * 4 + 3] = o.level_1618;
}
}
Ok(out)
}
#[napi]
pub fn reset(&mut self) {
self.inner.reset();
}
#[napi(js_name = "isReady")]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[napi(js_name = "warmupPeriod")]
pub fn warmup_period(&self) -> u32 {
self.inner.warmup_period() as u32
}
}
impl Default for FibChannelNode {
fn default() -> Self {
Self::new()
}
}
#[napi(object)]
pub struct FibTimeZonesValue {
pub on_zone: f64,
pub bars_to_next: f64,
}
#[napi(js_name = "FibTimeZones")]
pub struct FibTimeZonesNode {
inner: wc::FibTimeZones,
}
#[napi]
impl FibTimeZonesNode {
#[napi(constructor)]
pub fn new() -> Self {
Self {
inner: wc::FibTimeZones::new(),
}
}
#[napi]
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<FibTimeZonesValue>> {
Ok(self
.inner
.update(swing_cnd(high, low)?)
.map(|o| FibTimeZonesValue {
on_zone: o.on_zone,
bars_to_next: o.bars_to_next,
}))
}
#[napi]
pub fn batch(&mut self, high: Vec<f64>, low: Vec<f64>) -> napi::Result<Vec<f64>> {
if high.len() != low.len() {
return Err(NapiError::from_reason(
"high and low must be equal length".to_string(),
));
}
let n = high.len();
let mut out = vec![f64::NAN; n * 2];
for i in 0..n {
if let Some(o) = self.inner.update(swing_cnd(high[i], low[i])?) {
out[i * 2] = o.on_zone;
out[i * 2 + 1] = o.bars_to_next;
}
}
Ok(out)
}
#[napi]
pub fn reset(&mut self) {
self.inner.reset();
}
#[napi(js_name = "isReady")]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[napi(js_name = "warmupPeriod")]
pub fn warmup_period(&self) -> u32 {
self.inner.warmup_period() as u32
}
}
impl Default for FibTimeZonesNode {
fn default() -> Self {
Self::new()
}
}
@@ -340,6 +340,10 @@ from ._wickra import (
Gartley,
Abcd,
# Fibonacci
FibTimeZones,
FibChannel,
FibArcs,
FibFan,
FibConfluence,
GoldenPocket,
AutoFib,
@@ -742,6 +746,10 @@ __all__ = [
"Gartley",
"Abcd",
# Fibonacci
"FibTimeZones",
"FibChannel",
"FibArcs",
"FibFan",
"FibConfluence",
"GoldenPocket",
"AutoFib",
+273
View File
@@ -18293,6 +18293,275 @@ impl PyFibConfluence {
}
}
#[pyclass(name = "FibFan", module = "wickra._wickra", skip_from_py_object)]
#[derive(Clone)]
struct PyFibFan {
inner: wc::FibFan,
}
#[pymethods]
impl PyFibFan {
#[new]
fn new() -> Self {
Self {
inner: wc::FibFan::new(),
}
}
/// Returns `(fan_382, fan_500, fan_618)` at the current bar, or None.
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<(f64, f64, f64)>> {
let c = extract_candle(candle)?;
Ok(self
.inner
.update(c)
.map(|o| (o.fan_382, o.fan_500, o.fan_618)))
}
/// Batch over numpy columns high, low. Returns shape `(n, 3)`.
fn batch<'py>(
&mut self,
py: Python<'py>,
high: PyReadonlyArray1<'py, f64>,
low: PyReadonlyArray1<'py, f64>,
) -> PyResult<Bound<'py, PyArray2<f64>>> {
let h = high
.as_slice()
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
let l = low
.as_slice()
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
if h.len() != l.len() {
return Err(PyValueError::new_err("high and low must be equal length"));
}
let n = h.len();
let mut out = vec![f64::NAN; n * 3];
for i in 0..n {
if let Some(o) = self
.inner
.update(swing_candle(h[i], l[i]).map_err(map_err)?)
{
out[i * 3] = o.fan_382;
out[i * 3 + 1] = o.fan_500;
out[i * 3 + 2] = o.fan_618;
}
}
Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out)
.expect("shape consistent")
.into_pyarray(py))
}
fn reset(&mut self) {
self.inner.reset();
}
fn is_ready(&self) -> bool {
self.inner.is_ready()
}
fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
fn __repr__(&self) -> String {
"FibFan()".to_string()
}
}
#[pyclass(name = "FibArcs", module = "wickra._wickra", skip_from_py_object)]
#[derive(Clone)]
struct PyFibArcs {
inner: wc::FibArcs,
}
#[pymethods]
impl PyFibArcs {
#[new]
fn new() -> Self {
Self {
inner: wc::FibArcs::new(),
}
}
/// Returns `(arc_382, arc_500, arc_618)` at the current bar, or None.
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<(f64, f64, f64)>> {
let c = extract_candle(candle)?;
Ok(self
.inner
.update(c)
.map(|o| (o.arc_382, o.arc_500, o.arc_618)))
}
/// Batch over numpy columns high, low. Returns shape `(n, 3)`.
fn batch<'py>(
&mut self,
py: Python<'py>,
high: PyReadonlyArray1<'py, f64>,
low: PyReadonlyArray1<'py, f64>,
) -> PyResult<Bound<'py, PyArray2<f64>>> {
let h = high
.as_slice()
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
let l = low
.as_slice()
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
if h.len() != l.len() {
return Err(PyValueError::new_err("high and low must be equal length"));
}
let n = h.len();
let mut out = vec![f64::NAN; n * 3];
for i in 0..n {
if let Some(o) = self
.inner
.update(swing_candle(h[i], l[i]).map_err(map_err)?)
{
out[i * 3] = o.arc_382;
out[i * 3 + 1] = o.arc_500;
out[i * 3 + 2] = o.arc_618;
}
}
Ok(numpy::ndarray::Array2::from_shape_vec((n, 3), out)
.expect("shape consistent")
.into_pyarray(py))
}
fn reset(&mut self) {
self.inner.reset();
}
fn is_ready(&self) -> bool {
self.inner.is_ready()
}
fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
fn __repr__(&self) -> String {
"FibArcs()".to_string()
}
}
#[pyclass(name = "FibChannel", module = "wickra._wickra", skip_from_py_object)]
#[derive(Clone)]
struct PyFibChannel {
inner: wc::FibChannel,
}
#[pymethods]
impl PyFibChannel {
#[new]
fn new() -> Self {
Self {
inner: wc::FibChannel::new(),
}
}
/// Returns `(base, level_618, level_1000, level_1618)` at the current bar, or None.
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<(f64, f64, f64, f64)>> {
let c = extract_candle(candle)?;
Ok(self
.inner
.update(c)
.map(|o| (o.base, o.level_618, o.level_1000, o.level_1618)))
}
/// Batch over numpy columns high, low. Returns shape `(n, 4)`.
fn batch<'py>(
&mut self,
py: Python<'py>,
high: PyReadonlyArray1<'py, f64>,
low: PyReadonlyArray1<'py, f64>,
) -> PyResult<Bound<'py, PyArray2<f64>>> {
let h = high
.as_slice()
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
let l = low
.as_slice()
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
if h.len() != l.len() {
return Err(PyValueError::new_err("high and low must be equal length"));
}
let n = h.len();
let mut out = vec![f64::NAN; n * 4];
for i in 0..n {
if let Some(o) = self
.inner
.update(swing_candle(h[i], l[i]).map_err(map_err)?)
{
out[i * 4] = o.base;
out[i * 4 + 1] = o.level_618;
out[i * 4 + 2] = o.level_1000;
out[i * 4 + 3] = o.level_1618;
}
}
Ok(numpy::ndarray::Array2::from_shape_vec((n, 4), out)
.expect("shape consistent")
.into_pyarray(py))
}
fn reset(&mut self) {
self.inner.reset();
}
fn is_ready(&self) -> bool {
self.inner.is_ready()
}
fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
fn __repr__(&self) -> String {
"FibChannel()".to_string()
}
}
#[pyclass(name = "FibTimeZones", module = "wickra._wickra", skip_from_py_object)]
#[derive(Clone)]
struct PyFibTimeZones {
inner: wc::FibTimeZones,
}
#[pymethods]
impl PyFibTimeZones {
#[new]
fn new() -> Self {
Self {
inner: wc::FibTimeZones::new(),
}
}
/// Returns `(on_zone, bars_to_next)` at the current bar, or None during warmup.
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<(f64, f64)>> {
let c = extract_candle(candle)?;
Ok(self.inner.update(c).map(|o| (o.on_zone, o.bars_to_next)))
}
/// Batch over numpy columns high, low. Returns shape `(n, 2)`.
fn batch<'py>(
&mut self,
py: Python<'py>,
high: PyReadonlyArray1<'py, f64>,
low: PyReadonlyArray1<'py, f64>,
) -> PyResult<Bound<'py, PyArray2<f64>>> {
let h = high
.as_slice()
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
let l = low
.as_slice()
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
if h.len() != l.len() {
return Err(PyValueError::new_err("high and low must be equal length"));
}
let n = h.len();
let mut out = vec![f64::NAN; n * 2];
for i in 0..n {
if let Some(o) = self
.inner
.update(swing_candle(h[i], l[i]).map_err(map_err)?)
{
out[i * 2] = o.on_zone;
out[i * 2 + 1] = o.bars_to_next;
}
}
Ok(numpy::ndarray::Array2::from_shape_vec((n, 2), out)
.expect("shape consistent")
.into_pyarray(py))
}
fn reset(&mut self) {
self.inner.reset();
}
fn is_ready(&self) -> bool {
self.inner.is_ready()
}
fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
fn __repr__(&self) -> String {
"FibTimeZones()".to_string()
}
}
#[pymodule]
#[allow(clippy::too_many_lines)]
fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
@@ -18682,5 +18951,9 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyAutoFib>()?;
m.add_class::<PyGoldenPocket>()?;
m.add_class::<PyFibConfluence>()?;
m.add_class::<PyFibFan>()?;
m.add_class::<PyFibArcs>()?;
m.add_class::<PyFibChannel>()?;
m.add_class::<PyFibTimeZones>()?;
Ok(())
}
@@ -860,6 +860,26 @@ def test_candle_scalar_streaming_matches_batch(name, ohlcv):
# --- Candle-input, multi-output indicators --------------------------------
MULTI = {
"FibFan": (
lambda: ta.FibFan(),
lambda ind, h, l, c, v: ind.batch(h, l),
3,
),
"FibArcs": (
lambda: ta.FibArcs(),
lambda ind, h, l, c, v: ind.batch(h, l),
3,
),
"FibChannel": (
lambda: ta.FibChannel(),
lambda ind, h, l, c, v: ind.batch(h, l),
4,
),
"FibTimeZones": (
lambda: ta.FibTimeZones(),
lambda ind, h, l, c, v: ind.batch(h, l),
2,
),
"FibRetracement": (
lambda: ta.FibRetracement(),
lambda ind, h, l, c, v: ind.batch(h, l),
@@ -2652,6 +2672,41 @@ def test_fib_confluence_reference():
assert t.update((101.0, 160.0, 101.0, 101.0, 1.0, 2)) is None
assert t.update((144.0, 158.4, 144.0, 144.0, 1.0, 3)) == pytest.approx((137.64, 2.0))
def test_fib_fan_reference():
t = ta.FibFan()
assert t.update((199.0, 200.0, 199.0, 199.0, 1.0, 0)) is None
assert t.update((160.0, 190.0, 160.0, 160.0, 1.0, 1)) is None
assert t.update((100.0, 150.0, 100.0, 100.0, 1.0, 2)) is None
assert t.update((105.0, 110.0, 105.0, 105.0, 1.0, 3)) == pytest.approx((142.7, 125.0, 107.3))
def test_fib_arcs_reference():
t = ta.FibArcs()
assert t.update((199.0, 200.0, 199.0, 199.0, 1.0, 0)) is None
assert t.update((160.0, 190.0, 160.0, 160.0, 1.0, 1)) is None
assert t.update((100.0, 150.0, 100.0, 100.0, 1.0, 2)) is None
assert t.update((105.0, 110.0, 105.0, 105.0, 1.0, 3)) == pytest.approx((133.082181, 143.30127, 153.52037))
def test_fib_channel_reference():
t = ta.FibChannel()
assert t.update((199.0, 200.0, 199.0, 199.0, 1.0, 0)) is None
assert t.update((100.0, 190.0, 100.0, 100.0, 1.0, 1)) is None
assert t.update((108.0, 110.0, 108.0, 108.0, 1.0, 2)) is None
assert t.update((210.0, 220.0, 210.0, 210.0, 1.0, 3)) is None
assert t.update((150.0, 200.0, 150.0, 150.0, 1.0, 4)) == pytest.approx((226.666667, 160.746667, 120.0, 54.08))
def test_fib_time_zones_reference():
t = ta.FibTimeZones()
assert t.update((199.0, 200.0, 199.0, 199.0, 1.0, 0)) is None
assert t.update((150.0, 190.0, 150.0, 150.0, 1.0, 1)) == pytest.approx((1.0, 1.0))
assert t.update((151.0, 155.0, 151.0, 151.0, 1.0, 2)) == pytest.approx((1.0, 1.0))
assert t.update((151.0, 155.0, 151.0, 151.0, 1.0, 3)) == pytest.approx((1.0, 2.0))
assert t.update((151.0, 155.0, 151.0, 151.0, 1.0, 4)) == pytest.approx((0.0, 1.0))
assert t.update((151.0, 155.0, 151.0, 151.0, 1.0, 5)) == pytest.approx((1.0, 3.0))
# --- Lifecycle ------------------------------------------------------------
+240
View File
@@ -11017,3 +11017,243 @@ impl WasmFibConfluence {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = FibFan)]
pub struct WasmFibFan {
inner: wc::FibFan,
}
impl Default for WasmFibFan {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = FibFan)]
impl WasmFibFan {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmFibFan {
Self {
inner: wc::FibFan::new(),
}
}
pub fn update(&mut self, high: f64, low: f64) -> Result<JsValue, JsError> {
let c = swing_make_candle(high, low)?;
Ok(match self.inner.update(c) {
Some(o) => {
let obj = Object::new();
Reflect::set(&obj, &"fan382".into(), &o.fan_382.into()).ok();
Reflect::set(&obj, &"fan500".into(), &o.fan_500.into()).ok();
Reflect::set(&obj, &"fan618".into(), &o.fan_618.into()).ok();
obj.into()
}
None => JsValue::NULL,
})
}
pub fn batch(&mut self, high: &[f64], low: &[f64]) -> Result<Float64Array, JsError> {
if high.len() != low.len() {
return Err(JsError::new("high and low must be equal length"));
}
let n = high.len();
let mut out = vec![f64::NAN; n * 3];
for i in 0..n {
if let Some(o) = self.inner.update(swing_make_candle(high[i], low[i])?) {
out[i * 3] = o.fan_382;
out[i * 3 + 1] = o.fan_500;
out[i * 3 + 2] = o.fan_618;
}
}
Ok(Float64Array::from(out.as_slice()))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = FibArcs)]
pub struct WasmFibArcs {
inner: wc::FibArcs,
}
impl Default for WasmFibArcs {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = FibArcs)]
impl WasmFibArcs {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmFibArcs {
Self {
inner: wc::FibArcs::new(),
}
}
pub fn update(&mut self, high: f64, low: f64) -> Result<JsValue, JsError> {
let c = swing_make_candle(high, low)?;
Ok(match self.inner.update(c) {
Some(o) => {
let obj = Object::new();
Reflect::set(&obj, &"arc382".into(), &o.arc_382.into()).ok();
Reflect::set(&obj, &"arc500".into(), &o.arc_500.into()).ok();
Reflect::set(&obj, &"arc618".into(), &o.arc_618.into()).ok();
obj.into()
}
None => JsValue::NULL,
})
}
pub fn batch(&mut self, high: &[f64], low: &[f64]) -> Result<Float64Array, JsError> {
if high.len() != low.len() {
return Err(JsError::new("high and low must be equal length"));
}
let n = high.len();
let mut out = vec![f64::NAN; n * 3];
for i in 0..n {
if let Some(o) = self.inner.update(swing_make_candle(high[i], low[i])?) {
out[i * 3] = o.arc_382;
out[i * 3 + 1] = o.arc_500;
out[i * 3 + 2] = o.arc_618;
}
}
Ok(Float64Array::from(out.as_slice()))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = FibChannel)]
pub struct WasmFibChannel {
inner: wc::FibChannel,
}
impl Default for WasmFibChannel {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = FibChannel)]
impl WasmFibChannel {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmFibChannel {
Self {
inner: wc::FibChannel::new(),
}
}
pub fn update(&mut self, high: f64, low: f64) -> Result<JsValue, JsError> {
let c = swing_make_candle(high, low)?;
Ok(match self.inner.update(c) {
Some(o) => {
let obj = Object::new();
Reflect::set(&obj, &"base".into(), &o.base.into()).ok();
Reflect::set(&obj, &"level618".into(), &o.level_618.into()).ok();
Reflect::set(&obj, &"level1000".into(), &o.level_1000.into()).ok();
Reflect::set(&obj, &"level1618".into(), &o.level_1618.into()).ok();
obj.into()
}
None => JsValue::NULL,
})
}
pub fn batch(&mut self, high: &[f64], low: &[f64]) -> Result<Float64Array, JsError> {
if high.len() != low.len() {
return Err(JsError::new("high and low must be equal length"));
}
let n = high.len();
let mut out = vec![f64::NAN; n * 4];
for i in 0..n {
if let Some(o) = self.inner.update(swing_make_candle(high[i], low[i])?) {
out[i * 4] = o.base;
out[i * 4 + 1] = o.level_618;
out[i * 4 + 2] = o.level_1000;
out[i * 4 + 3] = o.level_1618;
}
}
Ok(Float64Array::from(out.as_slice()))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}
#[wasm_bindgen(js_name = FibTimeZones)]
pub struct WasmFibTimeZones {
inner: wc::FibTimeZones,
}
impl Default for WasmFibTimeZones {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen(js_class = FibTimeZones)]
impl WasmFibTimeZones {
#[wasm_bindgen(constructor)]
pub fn new() -> WasmFibTimeZones {
Self {
inner: wc::FibTimeZones::new(),
}
}
pub fn update(&mut self, high: f64, low: f64) -> Result<JsValue, JsError> {
let c = swing_make_candle(high, low)?;
Ok(match self.inner.update(c) {
Some(o) => {
let obj = Object::new();
Reflect::set(&obj, &"onZone".into(), &o.on_zone.into()).ok();
Reflect::set(&obj, &"barsToNext".into(), &o.bars_to_next.into()).ok();
obj.into()
}
None => JsValue::NULL,
})
}
pub fn batch(&mut self, high: &[f64], low: &[f64]) -> Result<Float64Array, JsError> {
if high.len() != low.len() {
return Err(JsError::new("high and low must be equal length"));
}
let n = high.len();
let mut out = vec![f64::NAN; n * 2];
for i in 0..n {
if let Some(o) = self.inner.update(swing_make_candle(high[i], low[i])?) {
out[i * 2] = o.on_zone;
out[i * 2 + 1] = o.bars_to_next;
}
}
Ok(Float64Array::from(out.as_slice()))
}
pub fn reset(&mut self) {
self.inner.reset();
}
#[wasm_bindgen(js_name = isReady)]
pub fn is_ready(&self) -> bool {
self.inner.is_ready()
}
#[wasm_bindgen(js_name = warmupPeriod)]
pub fn warmup_period(&self) -> usize {
self.inner.warmup_period()
}
}