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:
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Signed Doji encoding.** `Doji` gains an opt-in `.signed()` mode
|
||||||
|
(`Doji(signed=True)` in Python, `new Doji(true)` in Node and WASM) that
|
||||||
|
classifies a detected Doji by the position of its body within the bar range —
|
||||||
|
a dragonfly (long lower shadow) emits `+1.0` (bullish), a gravestone (long
|
||||||
|
upper shadow) emits `−1.0` (bearish), and a long-legged / standard Doji emits
|
||||||
|
`0.0` (neutral). The default construction is unchanged — a direction-less
|
||||||
|
`+1.0` / `0.0` detection flag — so existing callers are unaffected. This
|
||||||
|
completes the uniform `+1` bull / `−1` bear / `0` none sign convention across
|
||||||
|
every candlestick pattern, making the family a drop-in machine-learning
|
||||||
|
feature where bullish and bearish instances share a single dimension.
|
||||||
|
|
||||||
## [0.4.1] - 2026-06-01
|
## [0.4.1] - 2026-06-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -159,6 +159,12 @@ warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
|
|||||||
| Market Profile | Value Area (POC / VAH / VAL), Initial Balance, Opening Range |
|
| Market Profile | Value Area (POC / VAH / VAL), Initial Balance, Opening Range |
|
||||||
| Risk / Performance | Sharpe Ratio, Sortino Ratio, Calmar Ratio, Omega Ratio, Max Drawdown, Average Drawdown, Drawdown Duration, Pain Index, Value at Risk, Conditional Value at Risk (CVaR), Profit Factor, Gain/Loss Ratio, Recovery Factor, Kelly Criterion, Treynor Ratio, Information Ratio, Alpha (Jensen) |
|
| Risk / Performance | Sharpe Ratio, Sortino Ratio, Calmar Ratio, Omega Ratio, Max Drawdown, Average Drawdown, Drawdown Duration, Pain Index, Value at Risk, Conditional Value at Risk (CVaR), Profit Factor, Gain/Loss Ratio, Recovery Factor, Kelly Criterion, Treynor Ratio, Information Ratio, Alpha (Jensen) |
|
||||||
|
|
||||||
|
Every candlestick pattern emits a signed per-bar value — `+1.0` bullish,
|
||||||
|
`−1.0` bearish, `0.0` none — so the family drops straight into a feature matrix
|
||||||
|
as one column each. `Doji` is direction-less by default (`+1.0` / `0.0`);
|
||||||
|
construct it in signed mode (`Doji::new().signed()`, `Doji(signed=True)`,
|
||||||
|
`new Doji(true)`) for a dragonfly / gravestone `±1` reading.
|
||||||
|
|
||||||
Adding a new indicator means implementing one trait in Rust; all four bindings
|
Adding a new indicator means implementing one trait in Rust; all four bindings
|
||||||
inherit it automatically.
|
inherit it automatically.
|
||||||
|
|
||||||
|
|||||||
@@ -896,3 +896,19 @@ test('ALMA(3, 0.85, 6) reference value on [10, 20, 30]', () => {
|
|||||||
// simple mean of 20.
|
// simple mean of 20.
|
||||||
assert.ok(out[2] > 20);
|
assert.ok(out[2] > 20);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Doji signed mode encodes dragonfly/gravestone/neutral direction', () => {
|
||||||
|
// Default: direction-less detection flag (+1 doji / 0 otherwise).
|
||||||
|
const flag = new wickra.Doji();
|
||||||
|
assert.equal(flag.isSigned(), false);
|
||||||
|
assert.equal(flag.update(10, 11, 9, 10), 1); // body 0, range 2 -> doji
|
||||||
|
assert.equal(flag.update(10, 12, 10, 12), 0); // body == range -> not a doji
|
||||||
|
|
||||||
|
// Signed: classify a detected doji by its body position within the range.
|
||||||
|
const d = new wickra.Doji(true);
|
||||||
|
assert.equal(d.isSigned(), true);
|
||||||
|
assert.equal(d.update(10, 10.05, 6, 10), 1); // dragonfly -> bullish +1
|
||||||
|
assert.equal(d.update(10, 14, 9.95, 10), -1); // gravestone -> bearish -1
|
||||||
|
assert.equal(d.update(10, 12, 8, 10), 0); // long-legged -> neutral 0
|
||||||
|
assert.equal(d.update(10, 12, 10, 12), 0); // not a doji -> 0
|
||||||
|
});
|
||||||
|
|||||||
Vendored
+2
-1
@@ -2055,12 +2055,13 @@ export declare class OpeningRange {
|
|||||||
}
|
}
|
||||||
export type DojiNode = Doji
|
export type DojiNode = Doji
|
||||||
export declare class Doji {
|
export declare class Doji {
|
||||||
constructor()
|
constructor(signed?: boolean | undefined | null)
|
||||||
update(open: number, high: number, low: number, close: number): number | null
|
update(open: number, high: number, low: number, close: number): number | null
|
||||||
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
|
batch(open: Array<number>, high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
|
||||||
reset(): void
|
reset(): void
|
||||||
isReady(): boolean
|
isReady(): boolean
|
||||||
warmupPeriod(): number
|
warmupPeriod(): number
|
||||||
|
isSigned(): boolean
|
||||||
}
|
}
|
||||||
export type HammerNode = Hammer
|
export type HammerNode = Hammer
|
||||||
export declare class Hammer {
|
export declare class Hammer {
|
||||||
|
|||||||
@@ -8581,7 +8581,8 @@ impl OpeningRangeNode {
|
|||||||
//
|
//
|
||||||
// All 15 patterns take Candles (open, high, low, close) and emit a signed f64
|
// 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
|
// 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! node_candle_pattern {
|
macro_rules! node_candle_pattern {
|
||||||
($node:ident, $inner:ty, $js:literal) => {
|
($node:ident, $inner:ty, $js:literal) => {
|
||||||
@@ -8652,7 +8653,80 @@ macro_rules! node_candle_pattern {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
node_candle_pattern!(DojiNode, wc::Doji, "Doji");
|
// Doji is the one pattern with an opt-in signed mode, so it is hand-written
|
||||||
|
// rather than generated by `node_candle_pattern!`.
|
||||||
|
#[napi(js_name = "Doji")]
|
||||||
|
pub struct DojiNode {
|
||||||
|
inner: wc::Doji,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DojiNode {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
impl DojiNode {
|
||||||
|
#[napi(constructor)]
|
||||||
|
pub fn new(signed: Option<bool>) -> Self {
|
||||||
|
let inner = if signed.unwrap_or(false) {
|
||||||
|
wc::Doji::new().signed()
|
||||||
|
} else {
|
||||||
|
wc::Doji::new()
|
||||||
|
};
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
#[napi]
|
||||||
|
pub fn update(
|
||||||
|
&mut self,
|
||||||
|
open: f64,
|
||||||
|
high: f64,
|
||||||
|
low: f64,
|
||||||
|
close: f64,
|
||||||
|
) -> napi::Result<Option<f64>> {
|
||||||
|
let candle = wc::Candle::new(open, high, low, close, 0.0, 0).map_err(map_err)?;
|
||||||
|
Ok(self.inner.update(candle))
|
||||||
|
}
|
||||||
|
#[napi]
|
||||||
|
pub fn batch(
|
||||||
|
&mut self,
|
||||||
|
open: Vec<f64>,
|
||||||
|
high: Vec<f64>,
|
||||||
|
low: Vec<f64>,
|
||||||
|
close: Vec<f64>,
|
||||||
|
) -> napi::Result<Vec<f64>> {
|
||||||
|
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::with_capacity(open.len());
|
||||||
|
for i in 0..open.len() {
|
||||||
|
let candle =
|
||||||
|
wc::Candle::new(open[i], high[i], low[i], close[i], 0.0, 0).map_err(map_err)?;
|
||||||
|
out.push(self.inner.update(candle).unwrap_or(f64::NAN));
|
||||||
|
}
|
||||||
|
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(js_name = "isSigned")]
|
||||||
|
pub fn is_signed(&self) -> bool {
|
||||||
|
self.inner.is_signed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
node_candle_pattern!(HammerNode, wc::Hammer, "Hammer");
|
node_candle_pattern!(HammerNode, wc::Hammer, "Hammer");
|
||||||
node_candle_pattern!(InvertedHammerNode, wc::InvertedHammer, "InvertedHammer");
|
node_candle_pattern!(InvertedHammerNode, wc::InvertedHammer, "InvertedHammer");
|
||||||
node_candle_pattern!(HangingManNode, wc::HangingMan, "HangingMan");
|
node_candle_pattern!(HangingManNode, wc::HangingMan, "HangingMan");
|
||||||
|
|||||||
@@ -11433,8 +11433,9 @@ impl PyOpeningRange {
|
|||||||
// ============================== Candlestick Patterns ==============================
|
// ============================== Candlestick Patterns ==============================
|
||||||
//
|
//
|
||||||
// All 15 patterns take Candles and emit a signed f64 signal per bar:
|
// All 15 patterns take Candles and emit a signed f64 signal per bar:
|
||||||
// +1.0 bullish, -1.0 bearish, 0.0 no pattern. Doji is direction-less, so it
|
// +1.0 bullish, -1.0 bearish, 0.0 no pattern. Doji is direction-less by
|
||||||
// uses +1.0 / 0.0 only.
|
// default (+1.0 / 0.0); construct it with `signed=True` for the
|
||||||
|
// dragonfly/gravestone signed +-1 encoding.
|
||||||
|
|
||||||
macro_rules! candle_pattern_no_param {
|
macro_rules! candle_pattern_no_param {
|
||||||
($name:ident, $inner:ty, $repr:expr) => {
|
($name:ident, $inner:ty, $repr:expr) => {
|
||||||
@@ -11505,7 +11506,86 @@ macro_rules! candle_pattern_no_param {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
candle_pattern_no_param!(PyDoji, wc::Doji, "Doji");
|
// Doji is the one pattern with an opt-in signed mode, so it is hand-written
|
||||||
|
// rather than generated by `candle_pattern_no_param!`.
|
||||||
|
#[pyclass(name = "Doji", module = "wickra._wickra", skip_from_py_object)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct PyDoji {
|
||||||
|
inner: wc::Doji,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyDoji {
|
||||||
|
#[new]
|
||||||
|
#[pyo3(signature = (signed = false))]
|
||||||
|
fn new(signed: bool) -> Self {
|
||||||
|
let inner = if signed {
|
||||||
|
wc::Doji::new().signed()
|
||||||
|
} else {
|
||||||
|
wc::Doji::new()
|
||||||
|
};
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
fn update(&mut self, candle: &Bound<'_, PyAny>) -> PyResult<Option<f64>> {
|
||||||
|
let c = extract_candle(candle)?;
|
||||||
|
Ok(self.inner.update(c))
|
||||||
|
}
|
||||||
|
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, PyArray1<f64>>> {
|
||||||
|
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",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut out = Vec::with_capacity(o.len());
|
||||||
|
for i in 0..o.len() {
|
||||||
|
let candle = wc::Candle::new(o[i], h[i], l[i], c[i], 0.0, 0).map_err(map_err)?;
|
||||||
|
out.push(self.inner.update(candle).unwrap_or(f64::NAN));
|
||||||
|
}
|
||||||
|
Ok(out.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 is_signed(&self) -> bool {
|
||||||
|
self.inner.is_signed()
|
||||||
|
}
|
||||||
|
fn __repr__(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Doji(signed={})",
|
||||||
|
if self.inner.is_signed() {
|
||||||
|
"True"
|
||||||
|
} else {
|
||||||
|
"False"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
candle_pattern_no_param!(PyHammer, wc::Hammer, "Hammer");
|
candle_pattern_no_param!(PyHammer, wc::Hammer, "Hammer");
|
||||||
candle_pattern_no_param!(PyInvertedHammer, wc::InvertedHammer, "InvertedHammer");
|
candle_pattern_no_param!(PyInvertedHammer, wc::InvertedHammer, "InvertedHammer");
|
||||||
candle_pattern_no_param!(PyHangingMan, wc::HangingMan, "HangingMan");
|
candle_pattern_no_param!(PyHangingMan, wc::HangingMan, "HangingMan");
|
||||||
|
|||||||
@@ -823,3 +823,27 @@ def test_yang_zhang_zero_movement_yields_zero():
|
|||||||
ready = out[~np.isnan(out)]
|
ready = out[~np.isnan(out)]
|
||||||
assert ready.size > 0
|
assert ready.size > 0
|
||||||
np.testing.assert_allclose(ready, 0.0, atol=1e-12)
|
np.testing.assert_allclose(ready, 0.0, atol=1e-12)
|
||||||
|
|
||||||
|
|
||||||
|
def test_doji_default_is_directionless_flag():
|
||||||
|
# Default Doji is a direction-less detection flag: +1 on a doji, 0 else.
|
||||||
|
d = ta.Doji()
|
||||||
|
assert d.is_signed() is False
|
||||||
|
# body 0, range 2 -> doji.
|
||||||
|
assert d.update((10.0, 11.0, 9.0, 10.0, 1.0, 0)) == pytest.approx(1.0)
|
||||||
|
# body 2 == range -> not a doji.
|
||||||
|
assert d.update((10.0, 12.0, 10.0, 12.0, 1.0, 1)) == pytest.approx(0.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_doji_signed_dragonfly_gravestone_neutral():
|
||||||
|
# Signed Doji classifies by body position within the range.
|
||||||
|
d = ta.Doji(signed=True)
|
||||||
|
assert d.is_signed() is True
|
||||||
|
# Dragonfly: body at the top, long lower shadow -> bullish +1.
|
||||||
|
assert d.update((10.0, 10.05, 6.0, 10.0, 1.0, 0)) == pytest.approx(1.0)
|
||||||
|
# Gravestone: body at the bottom, long upper shadow -> bearish -1.
|
||||||
|
assert d.update((10.0, 14.0, 9.95, 10.0, 1.0, 1)) == pytest.approx(-1.0)
|
||||||
|
# Long-legged: body centred, symmetric shadows -> neutral 0.
|
||||||
|
assert d.update((10.0, 12.0, 8.0, 10.0, 1.0, 2)) == pytest.approx(0.0)
|
||||||
|
# A large body is not a doji at all -> 0 regardless of position.
|
||||||
|
assert d.update((10.0, 12.0, 10.0, 12.0, 1.0, 3)) == pytest.approx(0.0)
|
||||||
|
|||||||
@@ -6167,7 +6167,8 @@ impl WasmOpeningRange {
|
|||||||
//
|
//
|
||||||
// All 15 patterns take Candles (open, high, low, close) and emit a signed f64
|
// 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
|
// 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 {
|
macro_rules! wasm_candle_pattern {
|
||||||
($wasm:ident, $inner:ty, $js:ident) => {
|
($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!(WasmHammer, wc::Hammer, Hammer);
|
||||||
wasm_candle_pattern!(WasmInvertedHammer, wc::InvertedHammer, InvertedHammer);
|
wasm_candle_pattern!(WasmInvertedHammer, wc::InvertedHammer, InvertedHammer);
|
||||||
wasm_candle_pattern!(WasmHangingMan, wc::HangingMan, HangingMan);
|
wasm_candle_pattern!(WasmHangingMan, wc::HangingMan, HangingMan);
|
||||||
|
|||||||
@@ -16,22 +16,44 @@ use crate::traits::Indicator;
|
|||||||
/// doji = body <= body_threshold * range
|
/// doji = body <= body_threshold * range
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The output is `+1.0` when a Doji is detected and `0.0` otherwise. Doji is
|
/// # Signed ±1 encoding
|
||||||
/// directionless — no `−1.0` is emitted. Pattern-shape check only — no trend
|
///
|
||||||
/// filter is applied; combine with a trend indicator for actionable signals.
|
/// By default the output is `+1.0` when a Doji is detected and `0.0`
|
||||||
|
/// otherwise — a direction-less detection flag. For a drop-in machine-learning
|
||||||
|
/// feature where every candlestick pattern shares the same sign convention
|
||||||
|
/// (`+1.0` bullish, `−1.0` bearish, `0.0` none), switch the detector into
|
||||||
|
/// signed mode with [`Doji::signed`]. A detected Doji is then classified by
|
||||||
|
/// where its (negligible) body sits within the bar's range:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// pos = (0.5 * (open + close) − low) / (high − low)
|
||||||
|
/// pos > 2/3 -> +1.0 dragonfly (long lower shadow, bullish)
|
||||||
|
/// pos < 1/3 -> −1.0 gravestone (long upper shadow, bearish)
|
||||||
|
/// else -> 0.0 long-legged / standard (neutral)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Pattern-shape check only — no trend filter is applied; combine with a trend
|
||||||
|
/// indicator for actionable signals.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use wickra_core::{Candle, Doji, Indicator};
|
/// use wickra_core::{Candle, Doji, Indicator};
|
||||||
///
|
///
|
||||||
|
/// // Default: direction-less detection flag.
|
||||||
/// let mut indicator = Doji::default();
|
/// let mut indicator = Doji::default();
|
||||||
/// let candle = Candle::new(10.0, 11.0, 9.0, 10.0, 1.0, 0).unwrap();
|
/// let candle = Candle::new(10.0, 11.0, 9.0, 10.0, 1.0, 0).unwrap();
|
||||||
/// assert_eq!(indicator.update(candle), Some(1.0));
|
/// assert_eq!(indicator.update(candle), Some(1.0));
|
||||||
|
///
|
||||||
|
/// // Signed: a dragonfly Doji (body at the top, long lower shadow) is bullish.
|
||||||
|
/// let mut signed = Doji::new().signed();
|
||||||
|
/// let dragonfly = Candle::new(10.0, 10.05, 6.0, 10.0, 1.0, 0).unwrap();
|
||||||
|
/// assert_eq!(signed.update(dragonfly), Some(1.0));
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Doji {
|
pub struct Doji {
|
||||||
body_threshold: f64,
|
body_threshold: f64,
|
||||||
|
signed: bool,
|
||||||
has_emitted: bool,
|
has_emitted: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +68,7 @@ impl Doji {
|
|||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
body_threshold: 0.1,
|
body_threshold: 0.1,
|
||||||
|
signed: false,
|
||||||
has_emitted: false,
|
has_emitted: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,14 +84,32 @@ impl Doji {
|
|||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
body_threshold,
|
body_threshold,
|
||||||
|
signed: false,
|
||||||
has_emitted: false,
|
has_emitted: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Switch to the signed dragonfly / gravestone encoding (consuming builder).
|
||||||
|
///
|
||||||
|
/// In signed mode a detected Doji emits `+1.0` (dragonfly, bullish),
|
||||||
|
/// `−1.0` (gravestone, bearish) or `0.0` (long-legged / neutral) instead of
|
||||||
|
/// the default direction-less `+1.0` detection flag. See the type-level
|
||||||
|
/// docs for the exact classification rule.
|
||||||
|
#[must_use]
|
||||||
|
pub fn signed(mut self) -> Self {
|
||||||
|
self.signed = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Configured body / range threshold.
|
/// Configured body / range threshold.
|
||||||
pub fn body_threshold(&self) -> f64 {
|
pub fn body_threshold(&self) -> f64 {
|
||||||
self.body_threshold
|
self.body_threshold
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether this detector emits the signed dragonfly / gravestone encoding.
|
||||||
|
pub fn is_signed(&self) -> bool {
|
||||||
|
self.signed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Indicator for Doji {
|
impl Indicator for Doji {
|
||||||
@@ -82,11 +123,23 @@ impl Indicator for Doji {
|
|||||||
return Some(0.0);
|
return Some(0.0);
|
||||||
}
|
}
|
||||||
let body = (candle.close - candle.open).abs();
|
let body = (candle.close - candle.open).abs();
|
||||||
Some(if body <= self.body_threshold * range {
|
if body > self.body_threshold * range {
|
||||||
1.0
|
return Some(0.0);
|
||||||
|
}
|
||||||
|
if !self.signed {
|
||||||
|
return Some(1.0);
|
||||||
|
}
|
||||||
|
// Signed mode: classify the Doji by where its (negligible) body sits
|
||||||
|
// within the high–low range.
|
||||||
|
let body_mid = 0.5 * (candle.open + candle.close);
|
||||||
|
let pos = (body_mid - candle.low) / range;
|
||||||
|
if pos > 2.0 / 3.0 {
|
||||||
|
Some(1.0)
|
||||||
|
} else if pos < 1.0 / 3.0 {
|
||||||
|
Some(-1.0)
|
||||||
} else {
|
} else {
|
||||||
0.0
|
Some(0.0)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
@@ -134,6 +187,7 @@ mod tests {
|
|||||||
assert_eq!(d.name(), "Doji");
|
assert_eq!(d.name(), "Doji");
|
||||||
assert_eq!(d.warmup_period(), 1);
|
assert_eq!(d.warmup_period(), 1);
|
||||||
assert!(!d.is_ready());
|
assert!(!d.is_ready());
|
||||||
|
assert!(!d.is_signed());
|
||||||
assert!((d.body_threshold() - 0.1).abs() < 1e-12);
|
assert!((d.body_threshold() - 0.1).abs() < 1e-12);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,4 +236,81 @@ mod tests {
|
|||||||
d.reset();
|
d.reset();
|
||||||
assert!(!d.is_ready());
|
assert!(!d.is_ready());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signed_accessor_and_builder() {
|
||||||
|
let d = Doji::new().signed();
|
||||||
|
assert!(d.is_signed());
|
||||||
|
// The consuming builder composes with `with_threshold`.
|
||||||
|
let t = Doji::with_threshold(0.05).unwrap().signed();
|
||||||
|
assert!(t.is_signed());
|
||||||
|
assert!((t.body_threshold() - 0.05).abs() < 1e-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signed_dragonfly_is_plus_one() {
|
||||||
|
// Body at the top of the range, long lower shadow -> bullish.
|
||||||
|
let mut d = Doji::new().signed();
|
||||||
|
assert_eq!(d.update(c(10.0, 10.05, 6.0, 10.0, 0)), Some(1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signed_gravestone_is_minus_one() {
|
||||||
|
// Body at the bottom of the range, long upper shadow -> bearish.
|
||||||
|
let mut d = Doji::new().signed();
|
||||||
|
assert_eq!(d.update(c(10.0, 14.0, 9.95, 10.0, 0)), Some(-1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signed_long_legged_is_zero() {
|
||||||
|
// Body centred, symmetric shadows -> neutral.
|
||||||
|
let mut d = Doji::new().signed();
|
||||||
|
assert_eq!(d.update(c(10.0, 12.0, 8.0, 10.0, 0)), Some(0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signed_non_doji_is_zero() {
|
||||||
|
// A large body is not a Doji at all -> 0 regardless of position.
|
||||||
|
let mut d = Doji::new().signed();
|
||||||
|
assert_eq!(d.update(c(10.0, 12.0, 10.0, 12.0, 0)), Some(0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signed_zero_range_is_zero() {
|
||||||
|
let mut d = Doji::new().signed();
|
||||||
|
assert_eq!(d.update(c(10.0, 10.0, 10.0, 10.0, 0)), Some(0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signed_batch_equals_streaming() {
|
||||||
|
let candles: Vec<Candle> = (0..40)
|
||||||
|
.map(|i| {
|
||||||
|
let base = 100.0 + i as f64;
|
||||||
|
// Alternate dragonfly / gravestone / centred Doji shapes.
|
||||||
|
match i % 3 {
|
||||||
|
0 => c(base, base + 0.05, base - 4.0, base, i),
|
||||||
|
1 => c(base, base + 4.0, base - 0.05, base, i),
|
||||||
|
_ => c(base, base + 2.0, base - 2.0, base, i),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut a = Doji::new().signed();
|
||||||
|
let mut b = Doji::new().signed();
|
||||||
|
assert_eq!(
|
||||||
|
a.batch(&candles),
|
||||||
|
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signed_survives_reset() {
|
||||||
|
let mut d = Doji::new().signed();
|
||||||
|
d.update(c(10.0, 10.05, 6.0, 10.0, 0));
|
||||||
|
assert!(d.is_ready());
|
||||||
|
d.reset();
|
||||||
|
assert!(!d.is_ready());
|
||||||
|
// `reset` clears only the streaming state, not the signed configuration.
|
||||||
|
assert!(d.is_signed());
|
||||||
|
assert_eq!(d.update(c(10.0, 10.05, 6.0, 10.0, 1)), Some(1.0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ use crate::traits::Indicator;
|
|||||||
/// body exists to engulf. Pattern-shape check only — no trend filter is
|
/// body exists to engulf. Pattern-shape check only — no trend filter is
|
||||||
/// applied; combine with a trend indicator for actionable signals.
|
/// applied; combine with a trend indicator for actionable signals.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// This detector already emits the uniform candlestick sign convention shared
|
||||||
|
/// across the pattern family — `+1.0` bullish, `−1.0` bearish, `0.0` no
|
||||||
|
/// pattern — so it drops straight into a machine-learning feature matrix where
|
||||||
|
/// the bullish and bearish variants of the pattern occupy a single dimension.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ use crate::traits::Indicator;
|
|||||||
/// check only — no trend filter is applied; combine with a trend indicator
|
/// check only — no trend filter is applied; combine with a trend indicator
|
||||||
/// for actionable signals.
|
/// for actionable signals.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// A Hammer is bullish by definition, so under the uniform candlestick sign
|
||||||
|
/// convention (`+1.0` bullish, `−1.0` bearish, `0.0` none) it emits `+1.0`
|
||||||
|
/// when the shape matches and `0.0` otherwise — it never emits `−1.0`. The
|
||||||
|
/// same geometry read at the top of an uptrend is the bearish `HangingMan`,
|
||||||
|
/// which carries the opposite sign.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ use crate::traits::Indicator;
|
|||||||
/// check only — no trend filter is applied; combine with a trend indicator
|
/// check only — no trend filter is applied; combine with a trend indicator
|
||||||
/// for actionable signals.
|
/// for actionable signals.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// A Hanging Man is bearish by definition, so under the uniform candlestick
|
||||||
|
/// sign convention (`+1.0` bullish, `−1.0` bearish, `0.0` none) it emits
|
||||||
|
/// `−1.0` when the shape matches and `0.0` otherwise — it never emits `+1.0`.
|
||||||
|
/// The same geometry read at the bottom of a downtrend is the bullish
|
||||||
|
/// `Hammer`, which carries the opposite sign.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ use crate::traits::Indicator;
|
|||||||
/// no trend filter is applied; combine with a trend indicator for actionable
|
/// no trend filter is applied; combine with a trend indicator for actionable
|
||||||
/// signals.
|
/// signals.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// This detector already emits the uniform candlestick sign convention shared
|
||||||
|
/// across the pattern family — `+1.0` bullish, `−1.0` bearish, `0.0` no
|
||||||
|
/// pattern — so it drops straight into a machine-learning feature matrix where
|
||||||
|
/// the bullish and bearish variants of the pattern occupy a single dimension.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ use crate::traits::Indicator;
|
|||||||
/// check only — no trend filter is applied; combine with a trend indicator
|
/// check only — no trend filter is applied; combine with a trend indicator
|
||||||
/// for actionable signals.
|
/// for actionable signals.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// An Inverted Hammer is bullish by definition, so under the uniform
|
||||||
|
/// candlestick sign convention (`+1.0` bullish, `−1.0` bearish, `0.0` none) it
|
||||||
|
/// emits `+1.0` when the shape matches and `0.0` otherwise — it never emits
|
||||||
|
/// `−1.0`. The same geometry read at the top of an uptrend is the bearish
|
||||||
|
/// `ShootingStar`, which carries the opposite sign.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ use crate::traits::Indicator;
|
|||||||
/// `shadow_tolerance` defaults to `0.05` (5 % of the bar range allowed on each
|
/// `shadow_tolerance` defaults to `0.05` (5 % of the bar range allowed on each
|
||||||
/// side) and must lie in `[0, 1)`.
|
/// side) and must lie in `[0, 1)`.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// This detector already emits the uniform candlestick sign convention shared
|
||||||
|
/// across the pattern family — `+1.0` bullish, `−1.0` bearish, `0.0` no
|
||||||
|
/// pattern — so it drops straight into a machine-learning feature matrix where
|
||||||
|
/// the bullish and bearish variants of the pattern occupy a single dimension.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ use crate::traits::Indicator;
|
|||||||
/// trend filter is applied; combine with a trend indicator for actionable
|
/// trend filter is applied; combine with a trend indicator for actionable
|
||||||
/// signals.
|
/// signals.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// This detector already emits the uniform candlestick sign convention shared
|
||||||
|
/// across the pattern family — `+1.0` bullish, `−1.0` bearish, `0.0` no
|
||||||
|
/// pattern — so it drops straight into a machine-learning feature matrix where
|
||||||
|
/// the bullish and bearish variants of the pattern occupy a single dimension.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ use crate::traits::Indicator;
|
|||||||
/// only — no trend filter is applied; combine with a trend indicator for
|
/// only — no trend filter is applied; combine with a trend indicator for
|
||||||
/// actionable signals.
|
/// actionable signals.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// This detector already emits the uniform candlestick sign convention shared
|
||||||
|
/// across the pattern family — `+1.0` bullish, `−1.0` bearish, `0.0` no
|
||||||
|
/// pattern — so it drops straight into a machine-learning feature matrix where
|
||||||
|
/// the bullish and bearish variants of the pattern occupy a single dimension.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ use crate::traits::Indicator;
|
|||||||
/// check only — no trend filter is applied; combine with a trend indicator
|
/// check only — no trend filter is applied; combine with a trend indicator
|
||||||
/// for actionable signals.
|
/// for actionable signals.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// A Shooting Star is bearish by definition, so under the uniform candlestick
|
||||||
|
/// sign convention (`+1.0` bullish, `−1.0` bearish, `0.0` none) it emits
|
||||||
|
/// `−1.0` when the shape matches and `0.0` otherwise — it never emits `+1.0`.
|
||||||
|
/// The same geometry read at the bottom of a downtrend is the bullish
|
||||||
|
/// `InvertedHammer`, which carries the opposite sign.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -24,6 +24,13 @@ use crate::traits::Indicator;
|
|||||||
///
|
///
|
||||||
/// `body_threshold` defaults to `0.3` and must lie in `(0, 1]`.
|
/// `body_threshold` defaults to `0.3` and must lie in `(0, 1]`.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// This detector already emits the uniform candlestick sign convention shared
|
||||||
|
/// across the pattern family — `+1.0` bullish, `−1.0` bearish, `0.0` no
|
||||||
|
/// pattern — so it drops straight into a machine-learning feature matrix where
|
||||||
|
/// the bullish and bearish variants of the pattern occupy a single dimension.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ use crate::traits::Indicator;
|
|||||||
/// Pattern-shape check only — no trend filter is applied; combine with a trend
|
/// Pattern-shape check only — no trend filter is applied; combine with a trend
|
||||||
/// indicator for actionable signals.
|
/// indicator for actionable signals.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// This detector already emits the uniform candlestick sign convention shared
|
||||||
|
/// across the pattern family — `+1.0` bullish, `−1.0` bearish, `0.0` no
|
||||||
|
/// pattern — so it drops straight into a machine-learning feature matrix where
|
||||||
|
/// the bullish and bearish variants of the pattern occupy a single dimension.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ use crate::traits::Indicator;
|
|||||||
/// Pattern-shape check only — no trend filter is applied; combine with a trend
|
/// Pattern-shape check only — no trend filter is applied; combine with a trend
|
||||||
/// indicator for actionable signals.
|
/// indicator for actionable signals.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// This detector already emits the uniform candlestick sign convention shared
|
||||||
|
/// across the pattern family — `+1.0` bullish, `−1.0` bearish, `0.0` no
|
||||||
|
/// pattern — so it drops straight into a machine-learning feature matrix where
|
||||||
|
/// the bullish and bearish variants of the pattern occupy a single dimension.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ use crate::traits::Indicator;
|
|||||||
/// Pattern-shape check only — no trend filter is applied; combine with a trend
|
/// Pattern-shape check only — no trend filter is applied; combine with a trend
|
||||||
/// indicator for actionable signals.
|
/// indicator for actionable signals.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// This detector already emits the uniform candlestick sign convention shared
|
||||||
|
/// across the pattern family — `+1.0` bullish, `−1.0` bearish, `0.0` no
|
||||||
|
/// pattern — so it drops straight into a machine-learning feature matrix where
|
||||||
|
/// the bullish and bearish variants of the pattern occupy a single dimension.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -22,6 +22,13 @@ use crate::traits::Indicator;
|
|||||||
/// Pattern-shape check only — no trend filter is applied; combine with a trend
|
/// Pattern-shape check only — no trend filter is applied; combine with a trend
|
||||||
/// indicator for actionable signals.
|
/// indicator for actionable signals.
|
||||||
///
|
///
|
||||||
|
/// # Signed ±1 encoding
|
||||||
|
///
|
||||||
|
/// This detector already emits the uniform candlestick sign convention shared
|
||||||
|
/// across the pattern family — `+1.0` bullish, `−1.0` bearish, `0.0` no
|
||||||
|
/// pattern — so it drops straight into a machine-learning feature matrix where
|
||||||
|
/// the bullish and bearish variants of the pattern occupy a single dimension.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|||||||
@@ -295,6 +295,7 @@ fuzz_target!(|data: Vec<f64>| {
|
|||||||
|
|
||||||
// --- Candlestick Patterns (family 14) ---
|
// --- Candlestick Patterns (family 14) ---
|
||||||
drive(Doji::new, &candles);
|
drive(Doji::new, &candles);
|
||||||
|
drive(|| Doji::new().signed(), &candles);
|
||||||
drive(Hammer::new, &candles);
|
drive(Hammer::new, &candles);
|
||||||
drive(InvertedHammer::new, &candles);
|
drive(InvertedHammer::new, &candles);
|
||||||
drive(HangingMan::new, &candles);
|
drive(HangingMan::new, &candles);
|
||||||
|
|||||||
Reference in New Issue
Block a user