feat: signed candlestick directional ±1 encoding (Doji signed mode) (#111)
* feat(core): add signed dragonfly/gravestone encoding to Doji Doji gains an opt-in `.signed()` mode that classifies a detected Doji by the position of its body within the bar range: dragonfly (long lower shadow) emits +1.0 (bullish), gravestone (long upper shadow) emits -1.0 (bearish), and a long-legged/standard Doji emits 0.0. The default detection-flag behaviour (+1.0/0.0) is unchanged, so existing callers are unaffected. The other 14 candlestick patterns already emit the uniform +1 bull / -1 bear / 0 none convention; document that explicitly with a "Signed +-1 encoding" section on each so the whole family is a consistent drop-in ML feature. * feat(bindings): expose Doji signed mode in python, node, wasm Hand-write the Doji binding in all three language bindings (instead of the shared candle-pattern macro) so it accepts an opt-in `signed` flag and exposes an `is_signed`/`isSigned` accessor: - Python: `Doji(signed=False)` keyword argument - Node: `new Doji(signed?)` optional constructor argument (index.d.ts/.js regenerated via napi build) - WASM: `new Doji(signed?)` optional constructor argument The default construction is unchanged, so existing callers keep the direction-less +1/0 detection flag. * test(bindings,fuzz): cover Doji signed dragonfly/gravestone encoding - python: dragonfly(+1)/gravestone(-1)/neutral(0) and default-flag cases in test_known_values - node: equivalent signed/default assertions in indicators.test.js - fuzz: drive a signed Doji alongside the default in indicator_update_candle * docs: document signed candlestick convention and Doji signed mode README gains a candlestick sign-convention note; CHANGELOG records the new opt-in Doji signed dragonfly/gravestone encoding under [Unreleased].
This commit is contained in:
@@ -6167,7 +6167,8 @@ impl WasmOpeningRange {
|
||||
//
|
||||
// All 15 patterns take Candles (open, high, low, close) and emit a signed f64
|
||||
// signal per bar: +1.0 bullish, -1.0 bearish, 0.0 no pattern. Doji is
|
||||
// direction-less and emits 0/+1 only.
|
||||
// direction-less by default (0/+1); pass `signed = true` to its constructor for
|
||||
// the dragonfly/gravestone signed +-1 encoding.
|
||||
|
||||
macro_rules! wasm_candle_pattern {
|
||||
($wasm:ident, $inner:ty, $js:ident) => {
|
||||
@@ -6234,7 +6235,75 @@ macro_rules! wasm_candle_pattern {
|
||||
};
|
||||
}
|
||||
|
||||
wasm_candle_pattern!(WasmDoji, wc::Doji, Doji);
|
||||
// Doji is the one pattern with an opt-in signed mode, so it is hand-written
|
||||
// rather than generated by `wasm_candle_pattern!`.
|
||||
#[wasm_bindgen(js_name = Doji)]
|
||||
pub struct WasmDoji {
|
||||
inner: wc::Doji,
|
||||
}
|
||||
|
||||
impl Default for WasmDoji {
|
||||
fn default() -> Self {
|
||||
Self::new(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = Doji)]
|
||||
impl WasmDoji {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(signed: Option<bool>) -> WasmDoji {
|
||||
let inner = if signed.unwrap_or(false) {
|
||||
wc::Doji::new().signed()
|
||||
} else {
|
||||
wc::Doji::new()
|
||||
};
|
||||
Self { inner }
|
||||
}
|
||||
pub fn update(
|
||||
&mut self,
|
||||
open: f64,
|
||||
high: f64,
|
||||
low: f64,
|
||||
close: f64,
|
||||
) -> Result<Option<f64>, JsError> {
|
||||
let c = wc::Candle::new(open, high, low, close, 0.0, 0).map_err(map_err)?;
|
||||
Ok(self.inner.update(c))
|
||||
}
|
||||
pub fn batch(
|
||||
&mut self,
|
||||
open: &[f64],
|
||||
high: &[f64],
|
||||
low: &[f64],
|
||||
close: &[f64],
|
||||
) -> Result<Float64Array, JsError> {
|
||||
let n = open.len();
|
||||
if high.len() != n || low.len() != n || close.len() != n {
|
||||
return Err(JsError::new("open, high, low, close must be equal length"));
|
||||
}
|
||||
let mut out = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
let c = wc::Candle::new(open[i], high[i], low[i], close[i], 0.0, 0).map_err(map_err)?;
|
||||
out.push(self.inner.update(c).unwrap_or(f64::NAN));
|
||||
}
|
||||
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 = isSigned)]
|
||||
pub fn is_signed(&self) -> bool {
|
||||
self.inner.is_signed()
|
||||
}
|
||||
}
|
||||
|
||||
wasm_candle_pattern!(WasmHammer, wc::Hammer, Hammer);
|
||||
wasm_candle_pattern!(WasmInvertedHammer, wc::InvertedHammer, InvertedHammer);
|
||||
wasm_candle_pattern!(WasmHangingMan, wc::HangingMan, HangingMan);
|
||||
|
||||
Reference in New Issue
Block a user