feat(indicators): A5a Fibonacci tools (price-level) (#171)
Adds the six price-level Fibonacci tools as a new **Fibonacci** family (catalogue 367 -> 373, twenty-four families). All build on the internal `pattern_swing` ZigZag tracker, are parameter-free (baked 5% swing threshold), and emit `Candle -> struct` outputs via custom Python/Node/WASM bindings. | Tool | Output | |------|--------| | `FibRetracement` | seven levels (0/23.6/38.2/50/61.8/78.6/100%) of the last swing leg | | `FibExtension` | five extension ratios (127.2/141.4/161.8/200/261.8%) projected beyond the leg | | `FibProjection` | A-B-C measured-move target zone (61.8/100/161.8/261.8%) | | `AutoFib` | retracement anchored on the dominant (largest-magnitude) recent leg | | `GoldenPocket` | the 0.618-0.65 optimal-trade-entry band (low/mid/high) | | `FibConfluence` | densest cluster of retracement levels across recent legs (price + strength) | Fully wired: core (100% unit-tested branches), Python/Node/WASM struct bindings, fuzz driver, reference + streaming-vs-batch tests, README/docs counter. The four geometric/time tools (Fan, Arcs, Channel, Time Zones) follow in A5b. Verification: `cargo test --workspace` green, clippy `-D warnings` clean, node 450 tests, python 760 tests.
This commit is contained in:
@@ -385,6 +385,12 @@ const multi = {
|
||||
// Family 13: Ichimoku & alternative charts
|
||||
Ichimoku: { make: () => new wickra.Ichimoku(9, 26, 52, 26), fields: ['tenkan', 'kijun', 'senkouA', 'senkouB', 'chikou'], step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
HeikinAshi: { make: () => new wickra.HeikinAshi(), fields: ['open', 'high', 'low', 'close'], step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
|
||||
FibRetracement: { make: () => new wickra.FibRetracement(), fields: ['level0', 'level236', 'level382', 'level500', 'level618', 'level786', 'level1000'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
|
||||
FibExtension: { make: () => new wickra.FibExtension(), fields: ['level1272', 'level1414', 'level1618', 'level2000', 'level2618'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
|
||||
FibProjection: { make: () => new wickra.FibProjection(), fields: ['level618', 'level1000', 'level1618', 'level2618'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
|
||||
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) },
|
||||
};
|
||||
|
||||
for (const [name, d] of Object.entries(multi)) {
|
||||
|
||||
Vendored
+94
@@ -362,6 +362,46 @@ export interface OvernightIntradayReturnValue {
|
||||
overnight: number
|
||||
intraday: number
|
||||
}
|
||||
export interface FibRetracementValue {
|
||||
level0: number
|
||||
level236: number
|
||||
level382: number
|
||||
level500: number
|
||||
level618: number
|
||||
level786: number
|
||||
level1000: number
|
||||
}
|
||||
export interface FibExtensionValue {
|
||||
level1272: number
|
||||
level1414: number
|
||||
level1618: number
|
||||
level2000: number
|
||||
level2618: number
|
||||
}
|
||||
export interface FibProjectionValue {
|
||||
level618: number
|
||||
level1000: number
|
||||
level1618: number
|
||||
level2618: number
|
||||
}
|
||||
export interface AutoFibValue {
|
||||
level0: number
|
||||
level236: number
|
||||
level382: number
|
||||
level500: number
|
||||
level618: number
|
||||
level786: number
|
||||
level1000: number
|
||||
}
|
||||
export interface GoldenPocketValue {
|
||||
low: number
|
||||
mid: number
|
||||
high: number
|
||||
}
|
||||
export interface FibConfluenceValue {
|
||||
price: number
|
||||
strength: number
|
||||
}
|
||||
export type SmaNode = SMA
|
||||
export declare class SMA {
|
||||
constructor(period: number)
|
||||
@@ -3832,3 +3872,57 @@ export declare class OvernightIntradayReturn {
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type FibRetracementNode = FibRetracement
|
||||
export declare class FibRetracement {
|
||||
constructor()
|
||||
update(high: number, low: number): FibRetracementValue | null
|
||||
batch(high: Array<number>, low: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type FibExtensionNode = FibExtension
|
||||
export declare class FibExtension {
|
||||
constructor()
|
||||
update(high: number, low: number): FibExtensionValue | null
|
||||
batch(high: Array<number>, low: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type FibProjectionNode = FibProjection
|
||||
export declare class FibProjection {
|
||||
constructor()
|
||||
update(high: number, low: number): FibProjectionValue | null
|
||||
batch(high: Array<number>, low: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type AutoFibNode = AutoFib
|
||||
export declare class AutoFib {
|
||||
constructor()
|
||||
update(high: number, low: number): AutoFibValue | null
|
||||
batch(high: Array<number>, low: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type GoldenPocketNode = GoldenPocket
|
||||
export declare class GoldenPocket {
|
||||
constructor()
|
||||
update(high: number, low: number): GoldenPocketValue | null
|
||||
batch(high: Array<number>, low: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type FibConfluenceNode = FibConfluence
|
||||
export declare class FibConfluence {
|
||||
constructor()
|
||||
update(high: number, low: number): FibConfluenceValue | 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
@@ -14030,3 +14030,455 @@ impl OvernightIntradayReturnNode {
|
||||
self.inner.warmup_period() as u32
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Fibonacci ==============================
|
||||
|
||||
/// Build a candle for the swing-based Fibonacci tools from a `high`/`low` pair;
|
||||
/// only the high and low drive the swing tracker, so open/close are the midpoint.
|
||||
fn swing_cnd(high: f64, low: f64) -> napi::Result<wc::Candle> {
|
||||
cnd(high, low, f64::midpoint(high, low), 0.0)
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct FibRetracementValue {
|
||||
pub level_0: f64,
|
||||
pub level_236: f64,
|
||||
pub level_382: f64,
|
||||
pub level_500: f64,
|
||||
pub level_618: f64,
|
||||
pub level_786: f64,
|
||||
pub level_1000: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "FibRetracement")]
|
||||
pub struct FibRetracementNode {
|
||||
inner: wc::FibRetracement,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl FibRetracementNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::FibRetracement::new(),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<FibRetracementValue>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(swing_cnd(high, low)?)
|
||||
.map(|o| FibRetracementValue {
|
||||
level_0: o.level_0,
|
||||
level_236: o.level_236,
|
||||
level_382: o.level_382,
|
||||
level_500: o.level_500,
|
||||
level_618: o.level_618,
|
||||
level_786: o.level_786,
|
||||
level_1000: o.level_1000,
|
||||
}))
|
||||
}
|
||||
#[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 * 7];
|
||||
for i in 0..n {
|
||||
if let Some(o) = self.inner.update(swing_cnd(high[i], low[i])?) {
|
||||
out[i * 7] = o.level_0;
|
||||
out[i * 7 + 1] = o.level_236;
|
||||
out[i * 7 + 2] = o.level_382;
|
||||
out[i * 7 + 3] = o.level_500;
|
||||
out[i * 7 + 4] = o.level_618;
|
||||
out[i * 7 + 5] = o.level_786;
|
||||
out[i * 7 + 6] = o.level_1000;
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct FibExtensionValue {
|
||||
pub level_1272: f64,
|
||||
pub level_1414: f64,
|
||||
pub level_1618: f64,
|
||||
pub level_2000: f64,
|
||||
pub level_2618: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "FibExtension")]
|
||||
pub struct FibExtensionNode {
|
||||
inner: wc::FibExtension,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl FibExtensionNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::FibExtension::new(),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<FibExtensionValue>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(swing_cnd(high, low)?)
|
||||
.map(|o| FibExtensionValue {
|
||||
level_1272: o.level_1272,
|
||||
level_1414: o.level_1414,
|
||||
level_1618: o.level_1618,
|
||||
level_2000: o.level_2000,
|
||||
level_2618: o.level_2618,
|
||||
}))
|
||||
}
|
||||
#[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 * 5];
|
||||
for i in 0..n {
|
||||
if let Some(o) = self.inner.update(swing_cnd(high[i], low[i])?) {
|
||||
out[i * 5] = o.level_1272;
|
||||
out[i * 5 + 1] = o.level_1414;
|
||||
out[i * 5 + 2] = o.level_1618;
|
||||
out[i * 5 + 3] = o.level_2000;
|
||||
out[i * 5 + 4] = o.level_2618;
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct FibProjectionValue {
|
||||
pub level_618: f64,
|
||||
pub level_1000: f64,
|
||||
pub level_1618: f64,
|
||||
pub level_2618: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "FibProjection")]
|
||||
pub struct FibProjectionNode {
|
||||
inner: wc::FibProjection,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl FibProjectionNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::FibProjection::new(),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<FibProjectionValue>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(swing_cnd(high, low)?)
|
||||
.map(|o| FibProjectionValue {
|
||||
level_618: o.level_618,
|
||||
level_1000: o.level_1000,
|
||||
level_1618: o.level_1618,
|
||||
level_2618: o.level_2618,
|
||||
}))
|
||||
}
|
||||
#[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.level_618;
|
||||
out[i * 4 + 1] = o.level_1000;
|
||||
out[i * 4 + 2] = o.level_1618;
|
||||
out[i * 4 + 3] = o.level_2618;
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct AutoFibValue {
|
||||
pub level_0: f64,
|
||||
pub level_236: f64,
|
||||
pub level_382: f64,
|
||||
pub level_500: f64,
|
||||
pub level_618: f64,
|
||||
pub level_786: f64,
|
||||
pub level_1000: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "AutoFib")]
|
||||
pub struct AutoFibNode {
|
||||
inner: wc::AutoFib,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl AutoFibNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::AutoFib::new(),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<AutoFibValue>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(swing_cnd(high, low)?)
|
||||
.map(|o| AutoFibValue {
|
||||
level_0: o.level_0,
|
||||
level_236: o.level_236,
|
||||
level_382: o.level_382,
|
||||
level_500: o.level_500,
|
||||
level_618: o.level_618,
|
||||
level_786: o.level_786,
|
||||
level_1000: o.level_1000,
|
||||
}))
|
||||
}
|
||||
#[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 * 7];
|
||||
for i in 0..n {
|
||||
if let Some(o) = self.inner.update(swing_cnd(high[i], low[i])?) {
|
||||
out[i * 7] = o.level_0;
|
||||
out[i * 7 + 1] = o.level_236;
|
||||
out[i * 7 + 2] = o.level_382;
|
||||
out[i * 7 + 3] = o.level_500;
|
||||
out[i * 7 + 4] = o.level_618;
|
||||
out[i * 7 + 5] = o.level_786;
|
||||
out[i * 7 + 6] = o.level_1000;
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct GoldenPocketValue {
|
||||
pub low: f64,
|
||||
pub mid: f64,
|
||||
pub high: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "GoldenPocket")]
|
||||
pub struct GoldenPocketNode {
|
||||
inner: wc::GoldenPocket,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl GoldenPocketNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::GoldenPocket::new(),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<GoldenPocketValue>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(swing_cnd(high, low)?)
|
||||
.map(|o| GoldenPocketValue {
|
||||
low: o.low,
|
||||
mid: o.mid,
|
||||
high: o.high,
|
||||
}))
|
||||
}
|
||||
#[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.low;
|
||||
out[i * 3 + 1] = o.mid;
|
||||
out[i * 3 + 2] = o.high;
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
pub struct FibConfluenceValue {
|
||||
pub price: f64,
|
||||
pub strength: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "FibConfluence")]
|
||||
pub struct FibConfluenceNode {
|
||||
inner: wc::FibConfluence,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl FibConfluenceNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::FibConfluence::new(),
|
||||
}
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, high: f64, low: f64) -> napi::Result<Option<FibConfluenceValue>> {
|
||||
Ok(self
|
||||
.inner
|
||||
.update(swing_cnd(high, low)?)
|
||||
.map(|o| FibConfluenceValue {
|
||||
price: o.price,
|
||||
strength: o.strength,
|
||||
}))
|
||||
}
|
||||
#[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.price;
|
||||
out[i * 2 + 1] = o.strength;
|
||||
}
|
||||
}
|
||||
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 FibRetracementNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FibExtensionNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FibProjectionNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AutoFibNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GoldenPocketNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FibConfluenceNode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +339,13 @@ from ._wickra import (
|
||||
Butterfly,
|
||||
Gartley,
|
||||
Abcd,
|
||||
# Fibonacci
|
||||
FibConfluence,
|
||||
GoldenPocket,
|
||||
AutoFib,
|
||||
FibProjection,
|
||||
FibExtension,
|
||||
FibRetracement,
|
||||
# Microstructure: order book
|
||||
OrderBookImbalanceTop1,
|
||||
OrderBookImbalanceTopN,
|
||||
@@ -734,6 +741,13 @@ __all__ = [
|
||||
"Butterfly",
|
||||
"Gartley",
|
||||
"Abcd",
|
||||
# Fibonacci
|
||||
"FibConfluence",
|
||||
"GoldenPocket",
|
||||
"AutoFib",
|
||||
"FibProjection",
|
||||
"FibExtension",
|
||||
"FibRetracement",
|
||||
# Microstructure: order book
|
||||
"OrderBookImbalanceTop1",
|
||||
"OrderBookImbalanceTopN",
|
||||
|
||||
@@ -49,6 +49,8 @@ const NON_CONTIGUOUS: &str = "array must be C-contiguous; pass np.ascontiguousar
|
||||
|
||||
/// `(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`.
|
||||
type FibExtLevels = (f64, f64, f64, f64, f64);
|
||||
/// `(pp, r1, r2, s1, s2)` pivot levels returned by Woodie pivots.
|
||||
type WoodieLevels = (f64, f64, f64, f64, f64);
|
||||
/// `(tenkan, kijun, senkou_a, senkou_b, chikou)` Ichimoku lines, each optional during warmup.
|
||||
@@ -17846,6 +17848,451 @@ impl PyOvernightIntradayReturn {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Fibonacci ==============================
|
||||
|
||||
/// Build a candle for the swing-based Fibonacci tools from a `high`/`low` pair.
|
||||
/// Only the high and low drive the swing tracker, so open and close are pinned
|
||||
/// to the midpoint to keep the OHLC invariants valid.
|
||||
fn swing_candle(high: f64, low: f64) -> Result<wc::Candle, wc::Error> {
|
||||
let mid = f64::midpoint(high, low);
|
||||
wc::Candle::new(mid, high, low, mid, 0.0, 0)
|
||||
}
|
||||
|
||||
#[pyclass(
|
||||
name = "FibRetracement",
|
||||
module = "wickra._wickra",
|
||||
skip_from_py_object
|
||||
)]
|
||||
#[derive(Clone)]
|
||||
struct PyFibRetracement {
|
||||
inner: wc::FibRetracement,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyFibRetracement {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::FibRetracement::new(),
|
||||
}
|
||||
}
|
||||
/// Returns `(level_0, …, level_1000)` (seven levels) or None during warmup.
|
||||
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<PivotLevels>> {
|
||||
let c = extract_candle(candle)?;
|
||||
Ok(self.inner.update(c).map(|o| {
|
||||
(
|
||||
o.level_0,
|
||||
o.level_236,
|
||||
o.level_382,
|
||||
o.level_500,
|
||||
o.level_618,
|
||||
o.level_786,
|
||||
o.level_1000,
|
||||
)
|
||||
}))
|
||||
}
|
||||
/// Batch over numpy columns high, low. Returns shape `(n, 7)`.
|
||||
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 * 7];
|
||||
for i in 0..n {
|
||||
if let Some(o) = self
|
||||
.inner
|
||||
.update(swing_candle(h[i], l[i]).map_err(map_err)?)
|
||||
{
|
||||
out[i * 7] = o.level_0;
|
||||
out[i * 7 + 1] = o.level_236;
|
||||
out[i * 7 + 2] = o.level_382;
|
||||
out[i * 7 + 3] = o.level_500;
|
||||
out[i * 7 + 4] = o.level_618;
|
||||
out[i * 7 + 5] = o.level_786;
|
||||
out[i * 7 + 6] = o.level_1000;
|
||||
}
|
||||
}
|
||||
Ok(numpy::ndarray::Array2::from_shape_vec((n, 7), 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 {
|
||||
"FibRetracement()".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "FibExtension", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
struct PyFibExtension {
|
||||
inner: wc::FibExtension,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyFibExtension {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::FibExtension::new(),
|
||||
}
|
||||
}
|
||||
/// Returns `(level_1272, level_1414, level_1618, level_2000, level_2618)` or None.
|
||||
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<FibExtLevels>> {
|
||||
let c = extract_candle(candle)?;
|
||||
Ok(self.inner.update(c).map(|o| {
|
||||
(
|
||||
o.level_1272,
|
||||
o.level_1414,
|
||||
o.level_1618,
|
||||
o.level_2000,
|
||||
o.level_2618,
|
||||
)
|
||||
}))
|
||||
}
|
||||
/// Batch over numpy columns high, low. Returns shape `(n, 5)`.
|
||||
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 * 5];
|
||||
for i in 0..n {
|
||||
if let Some(o) = self
|
||||
.inner
|
||||
.update(swing_candle(h[i], l[i]).map_err(map_err)?)
|
||||
{
|
||||
out[i * 5] = o.level_1272;
|
||||
out[i * 5 + 1] = o.level_1414;
|
||||
out[i * 5 + 2] = o.level_1618;
|
||||
out[i * 5 + 3] = o.level_2000;
|
||||
out[i * 5 + 4] = o.level_2618;
|
||||
}
|
||||
}
|
||||
Ok(numpy::ndarray::Array2::from_shape_vec((n, 5), 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 {
|
||||
"FibExtension()".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "FibProjection", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
struct PyFibProjection {
|
||||
inner: wc::FibProjection,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyFibProjection {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::FibProjection::new(),
|
||||
}
|
||||
}
|
||||
/// Returns `(level_618, level_1000, level_1618, level_2618)` or None during warmup.
|
||||
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.level_618, o.level_1000, o.level_1618, o.level_2618)))
|
||||
}
|
||||
/// 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.level_618;
|
||||
out[i * 4 + 1] = o.level_1000;
|
||||
out[i * 4 + 2] = o.level_1618;
|
||||
out[i * 4 + 3] = o.level_2618;
|
||||
}
|
||||
}
|
||||
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 {
|
||||
"FibProjection()".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "AutoFib", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
struct PyAutoFib {
|
||||
inner: wc::AutoFib,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyAutoFib {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::AutoFib::new(),
|
||||
}
|
||||
}
|
||||
/// Returns `(level_0, …, level_1000)` for the dominant leg, or None during warmup.
|
||||
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<PivotLevels>> {
|
||||
let c = extract_candle(candle)?;
|
||||
Ok(self.inner.update(c).map(|o| {
|
||||
(
|
||||
o.level_0,
|
||||
o.level_236,
|
||||
o.level_382,
|
||||
o.level_500,
|
||||
o.level_618,
|
||||
o.level_786,
|
||||
o.level_1000,
|
||||
)
|
||||
}))
|
||||
}
|
||||
/// Batch over numpy columns high, low. Returns shape `(n, 7)`.
|
||||
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 * 7];
|
||||
for i in 0..n {
|
||||
if let Some(o) = self
|
||||
.inner
|
||||
.update(swing_candle(h[i], l[i]).map_err(map_err)?)
|
||||
{
|
||||
out[i * 7] = o.level_0;
|
||||
out[i * 7 + 1] = o.level_236;
|
||||
out[i * 7 + 2] = o.level_382;
|
||||
out[i * 7 + 3] = o.level_500;
|
||||
out[i * 7 + 4] = o.level_618;
|
||||
out[i * 7 + 5] = o.level_786;
|
||||
out[i * 7 + 6] = o.level_1000;
|
||||
}
|
||||
}
|
||||
Ok(numpy::ndarray::Array2::from_shape_vec((n, 7), 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 {
|
||||
"AutoFib()".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "GoldenPocket", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
struct PyGoldenPocket {
|
||||
inner: wc::GoldenPocket,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyGoldenPocket {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::GoldenPocket::new(),
|
||||
}
|
||||
}
|
||||
/// Returns `(low, mid, high)` of the golden-pocket band, or None during warmup.
|
||||
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.low, o.mid, o.high)))
|
||||
}
|
||||
/// 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.low;
|
||||
out[i * 3 + 1] = o.mid;
|
||||
out[i * 3 + 2] = o.high;
|
||||
}
|
||||
}
|
||||
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 {
|
||||
"GoldenPocket()".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(name = "FibConfluence", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
struct PyFibConfluence {
|
||||
inner: wc::FibConfluence,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyFibConfluence {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
inner: wc::FibConfluence::new(),
|
||||
}
|
||||
}
|
||||
/// Returns `(price, strength)` of the densest cluster, 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.price, o.strength)))
|
||||
}
|
||||
/// 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.price;
|
||||
out[i * 2 + 1] = o.strength;
|
||||
}
|
||||
}
|
||||
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 {
|
||||
"FibConfluence()".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
@@ -18228,5 +18675,12 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<PyShark>()?;
|
||||
m.add_class::<PyCypher>()?;
|
||||
m.add_class::<PyThreeDrives>()?;
|
||||
// Fibonacci.
|
||||
m.add_class::<PyFibRetracement>()?;
|
||||
m.add_class::<PyFibExtension>()?;
|
||||
m.add_class::<PyFibProjection>()?;
|
||||
m.add_class::<PyAutoFib>()?;
|
||||
m.add_class::<PyGoldenPocket>()?;
|
||||
m.add_class::<PyFibConfluence>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -860,6 +860,36 @@ def test_candle_scalar_streaming_matches_batch(name, ohlcv):
|
||||
# --- Candle-input, multi-output indicators --------------------------------
|
||||
|
||||
MULTI = {
|
||||
"FibRetracement": (
|
||||
lambda: ta.FibRetracement(),
|
||||
lambda ind, h, l, c, v: ind.batch(h, l),
|
||||
7,
|
||||
),
|
||||
"FibExtension": (
|
||||
lambda: ta.FibExtension(),
|
||||
lambda ind, h, l, c, v: ind.batch(h, l),
|
||||
5,
|
||||
),
|
||||
"FibProjection": (
|
||||
lambda: ta.FibProjection(),
|
||||
lambda ind, h, l, c, v: ind.batch(h, l),
|
||||
4,
|
||||
),
|
||||
"AutoFib": (
|
||||
lambda: ta.AutoFib(),
|
||||
lambda ind, h, l, c, v: ind.batch(h, l),
|
||||
7,
|
||||
),
|
||||
"GoldenPocket": (
|
||||
lambda: ta.GoldenPocket(),
|
||||
lambda ind, h, l, c, v: ind.batch(h, l),
|
||||
3,
|
||||
),
|
||||
"FibConfluence": (
|
||||
lambda: ta.FibConfluence(),
|
||||
lambda ind, h, l, c, v: ind.batch(h, l),
|
||||
2,
|
||||
),
|
||||
"Vortex": (
|
||||
lambda: ta.Vortex(14),
|
||||
lambda ind, h, l, c, v: ind.batch(h, l, c),
|
||||
@@ -2578,6 +2608,50 @@ def test_three_drives_reference():
|
||||
assert t.update((109.08, 136.0, 109.08, 109.08, 1.0, 4)) == pytest.approx(0.0)
|
||||
assert t.update((122.4, 134.64, 122.4, 122.4, 1.0, 5)) == pytest.approx(-1.0)
|
||||
|
||||
|
||||
def test_fib_retracement_reference():
|
||||
t = ta.FibRetracement()
|
||||
assert t.update((199.8, 200.0, 199.8, 199.8, 1.0, 0)) is None
|
||||
assert t.update((100.0, 198.0, 100.0, 100.0, 1.0, 1)) is None
|
||||
assert t.update((101.0, 110.0, 101.0, 101.0, 1.0, 2)) == pytest.approx((100.0, 123.6, 138.2, 150.0, 161.8, 178.6, 200.0))
|
||||
|
||||
|
||||
def test_fib_extension_reference():
|
||||
t = ta.FibExtension()
|
||||
assert t.update((199.8, 200.0, 199.8, 199.8, 1.0, 0)) is None
|
||||
assert t.update((100.0, 198.0, 100.0, 100.0, 1.0, 1)) is None
|
||||
assert t.update((101.0, 110.0, 101.0, 101.0, 1.0, 2)) == pytest.approx((72.8, 58.6, 38.2, 0.0, -61.8))
|
||||
|
||||
|
||||
def test_fib_projection_reference():
|
||||
t = ta.FibProjection()
|
||||
assert t.update((199.8, 200.0, 199.8, 199.8, 1.0, 0)) is None
|
||||
assert t.update((160.0, 198.0, 160.0, 160.0, 1.0, 1)) is None
|
||||
assert t.update((161.6, 190.0, 161.6, 161.6, 1.0, 2)) is None
|
||||
assert t.update((171.0, 188.1, 171.0, 171.0, 1.0, 3)) == pytest.approx((165.28, 150.0, 125.28, 85.28))
|
||||
|
||||
|
||||
def test_auto_fib_reference():
|
||||
t = ta.AutoFib()
|
||||
assert t.update((199.8, 200.0, 199.8, 199.8, 1.0, 0)) is None
|
||||
assert t.update((100.0, 198.0, 100.0, 100.0, 1.0, 1)) is None
|
||||
assert t.update((101.0, 110.0, 101.0, 101.0, 1.0, 2)) == pytest.approx((100.0, 123.6, 138.2, 150.0, 161.8, 178.6, 200.0))
|
||||
|
||||
|
||||
def test_golden_pocket_reference():
|
||||
t = ta.GoldenPocket()
|
||||
assert t.update((199.8, 200.0, 199.8, 199.8, 1.0, 0)) is None
|
||||
assert t.update((100.0, 198.0, 100.0, 100.0, 1.0, 1)) is None
|
||||
assert t.update((101.0, 110.0, 101.0, 101.0, 1.0, 2)) == pytest.approx((161.8, 163.4, 165.0))
|
||||
|
||||
|
||||
def test_fib_confluence_reference():
|
||||
t = ta.FibConfluence()
|
||||
assert t.update((199.8, 200.0, 199.8, 199.8, 1.0, 0)) is None
|
||||
assert t.update((100.0, 198.0, 100.0, 100.0, 1.0, 1)) is None
|
||||
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))
|
||||
|
||||
# --- Lifecycle ------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
@@ -10629,3 +10629,391 @@ impl WasmOvernightIntradayReturn {
|
||||
self.inner.warmup_period()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Fibonacci ==============================
|
||||
|
||||
/// Candle for the swing-based Fibonacci tools: only high/low drive the tracker,
|
||||
/// so open/close are pinned to the midpoint.
|
||||
fn swing_make_candle(high: f64, low: f64) -> Result<wc::Candle, JsError> {
|
||||
make_candle(high, low, f64::midpoint(high, low), 0.0)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = FibRetracement)]
|
||||
pub struct WasmFibRetracement {
|
||||
inner: wc::FibRetracement,
|
||||
}
|
||||
|
||||
impl Default for WasmFibRetracement {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = FibRetracement)]
|
||||
impl WasmFibRetracement {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> WasmFibRetracement {
|
||||
Self {
|
||||
inner: wc::FibRetracement::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, &"level0".into(), &o.level_0.into()).ok();
|
||||
Reflect::set(&obj, &"level236".into(), &o.level_236.into()).ok();
|
||||
Reflect::set(&obj, &"level382".into(), &o.level_382.into()).ok();
|
||||
Reflect::set(&obj, &"level500".into(), &o.level_500.into()).ok();
|
||||
Reflect::set(&obj, &"level618".into(), &o.level_618.into()).ok();
|
||||
Reflect::set(&obj, &"level786".into(), &o.level_786.into()).ok();
|
||||
Reflect::set(&obj, &"level1000".into(), &o.level_1000.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 * 7];
|
||||
for i in 0..n {
|
||||
if let Some(o) = self.inner.update(swing_make_candle(high[i], low[i])?) {
|
||||
out[i * 7] = o.level_0;
|
||||
out[i * 7 + 1] = o.level_236;
|
||||
out[i * 7 + 2] = o.level_382;
|
||||
out[i * 7 + 3] = o.level_500;
|
||||
out[i * 7 + 4] = o.level_618;
|
||||
out[i * 7 + 5] = o.level_786;
|
||||
out[i * 7 + 6] = o.level_1000;
|
||||
}
|
||||
}
|
||||
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 = FibExtension)]
|
||||
pub struct WasmFibExtension {
|
||||
inner: wc::FibExtension,
|
||||
}
|
||||
|
||||
impl Default for WasmFibExtension {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = FibExtension)]
|
||||
impl WasmFibExtension {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> WasmFibExtension {
|
||||
Self {
|
||||
inner: wc::FibExtension::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, &"level1272".into(), &o.level_1272.into()).ok();
|
||||
Reflect::set(&obj, &"level1414".into(), &o.level_1414.into()).ok();
|
||||
Reflect::set(&obj, &"level1618".into(), &o.level_1618.into()).ok();
|
||||
Reflect::set(&obj, &"level2000".into(), &o.level_2000.into()).ok();
|
||||
Reflect::set(&obj, &"level2618".into(), &o.level_2618.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 * 5];
|
||||
for i in 0..n {
|
||||
if let Some(o) = self.inner.update(swing_make_candle(high[i], low[i])?) {
|
||||
out[i * 5] = o.level_1272;
|
||||
out[i * 5 + 1] = o.level_1414;
|
||||
out[i * 5 + 2] = o.level_1618;
|
||||
out[i * 5 + 3] = o.level_2000;
|
||||
out[i * 5 + 4] = o.level_2618;
|
||||
}
|
||||
}
|
||||
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 = FibProjection)]
|
||||
pub struct WasmFibProjection {
|
||||
inner: wc::FibProjection,
|
||||
}
|
||||
|
||||
impl Default for WasmFibProjection {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = FibProjection)]
|
||||
impl WasmFibProjection {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> WasmFibProjection {
|
||||
Self {
|
||||
inner: wc::FibProjection::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, &"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();
|
||||
Reflect::set(&obj, &"level2618".into(), &o.level_2618.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.level_618;
|
||||
out[i * 4 + 1] = o.level_1000;
|
||||
out[i * 4 + 2] = o.level_1618;
|
||||
out[i * 4 + 3] = o.level_2618;
|
||||
}
|
||||
}
|
||||
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 = AutoFib)]
|
||||
pub struct WasmAutoFib {
|
||||
inner: wc::AutoFib,
|
||||
}
|
||||
|
||||
impl Default for WasmAutoFib {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = AutoFib)]
|
||||
impl WasmAutoFib {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> WasmAutoFib {
|
||||
Self {
|
||||
inner: wc::AutoFib::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, &"level0".into(), &o.level_0.into()).ok();
|
||||
Reflect::set(&obj, &"level236".into(), &o.level_236.into()).ok();
|
||||
Reflect::set(&obj, &"level382".into(), &o.level_382.into()).ok();
|
||||
Reflect::set(&obj, &"level500".into(), &o.level_500.into()).ok();
|
||||
Reflect::set(&obj, &"level618".into(), &o.level_618.into()).ok();
|
||||
Reflect::set(&obj, &"level786".into(), &o.level_786.into()).ok();
|
||||
Reflect::set(&obj, &"level1000".into(), &o.level_1000.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 * 7];
|
||||
for i in 0..n {
|
||||
if let Some(o) = self.inner.update(swing_make_candle(high[i], low[i])?) {
|
||||
out[i * 7] = o.level_0;
|
||||
out[i * 7 + 1] = o.level_236;
|
||||
out[i * 7 + 2] = o.level_382;
|
||||
out[i * 7 + 3] = o.level_500;
|
||||
out[i * 7 + 4] = o.level_618;
|
||||
out[i * 7 + 5] = o.level_786;
|
||||
out[i * 7 + 6] = o.level_1000;
|
||||
}
|
||||
}
|
||||
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 = GoldenPocket)]
|
||||
pub struct WasmGoldenPocket {
|
||||
inner: wc::GoldenPocket,
|
||||
}
|
||||
|
||||
impl Default for WasmGoldenPocket {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = GoldenPocket)]
|
||||
impl WasmGoldenPocket {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> WasmGoldenPocket {
|
||||
Self {
|
||||
inner: wc::GoldenPocket::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, &"low".into(), &o.low.into()).ok();
|
||||
Reflect::set(&obj, &"mid".into(), &o.mid.into()).ok();
|
||||
Reflect::set(&obj, &"high".into(), &o.high.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.low;
|
||||
out[i * 3 + 1] = o.mid;
|
||||
out[i * 3 + 2] = o.high;
|
||||
}
|
||||
}
|
||||
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 = FibConfluence)]
|
||||
pub struct WasmFibConfluence {
|
||||
inner: wc::FibConfluence,
|
||||
}
|
||||
|
||||
impl Default for WasmFibConfluence {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = FibConfluence)]
|
||||
impl WasmFibConfluence {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> WasmFibConfluence {
|
||||
Self {
|
||||
inner: wc::FibConfluence::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, &"price".into(), &o.price.into()).ok();
|
||||
Reflect::set(&obj, &"strength".into(), &o.strength.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.price;
|
||||
out[i * 2 + 1] = o.strength;
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user