feat: add 9 Risk / Performance indicators (B18) (#218)
Adds nine risk/performance metrics to the existing **Risk / Performance** family, all consuming a per-period return series (`f64` in, `f64` out). Indicator count **498 → 507**. ## Indicators Single-param (`new(period)`, macro bindings): - **SterlingRatio** — mean return over average drawdown of the equity curve. - **BurkeRatio** — return over root-sum-squared drawdowns. - **MartinRatio** — Ulcer Performance Index; return over RMS percentage drawdown. - **TailRatio** — 95th percentile over the absolute 5th percentile return. - **KRatio** — Kestner; equity-curve OLS slope over the standard error of that slope. - **CommonSenseRatio** — tail ratio times gain-to-pain. - **GainToPainRatio** — sum of returns over the sum of absolute losses. Multi-param (hand-written Python/Node bindings, variadic WASM macro): - **UpsidePotentialRatio** — `new(period, mar)`; upside mean over downside deviation (Sortino philosophy). - **M2Measure** — `new(period, risk_free, benchmark_stddev)`; Modigliani M², Sharpe rescaled into benchmark return units. ## Touchpoints Core modules + unit tests, `mod.rs`/`lib.rs` wiring, Python/Node/WASM bindings (`index.d.ts`/`index.js` regenerated), fuzz drive lines, Python `SCALAR` registry + Node factories, CHANGELOG, and the indicator counters. ## Verification - `cargo test -p wickra-core --lib` — 4149 passed - `cargo test -p wickra-core --doc` — 457 passed - `cargo clippy --workspace --all-targets --all-features -- -D warnings` — clean - `npm test` (node) — 577 passed - `pytest` (python) — 947 passed
This commit is contained in:
@@ -28,6 +28,15 @@ function num(v) {
|
||||
// --- Scalar indicators: update(value) vs batch(prices) ---
|
||||
|
||||
const scalarFactories = {
|
||||
M2Measure: () => new wickra.M2Measure(20, 0.0, 0.02),
|
||||
UpsidePotentialRatio: () => new wickra.UpsidePotentialRatio(20, 0.0),
|
||||
GainToPainRatio: () => new wickra.GainToPainRatio(12),
|
||||
CommonSenseRatio: () => new wickra.CommonSenseRatio(20),
|
||||
KRatio: () => new wickra.KRatio(30),
|
||||
TailRatio: () => new wickra.TailRatio(20),
|
||||
MartinRatio: () => new wickra.MartinRatio(14),
|
||||
BurkeRatio: () => new wickra.BurkeRatio(12),
|
||||
SterlingRatio: () => new wickra.SterlingRatio(12),
|
||||
AUTOCORRPGRAM: () => new wickra.AUTOCORRPGRAM(10, 48),
|
||||
EVENBETTERSINE: () => new wickra.EVENBETTERSINE(40, 10),
|
||||
BANDPASS: () => new wickra.BANDPASS(20, 0.3),
|
||||
|
||||
Vendored
+81
@@ -1176,6 +1176,87 @@ export declare class UNIVERSALOSC {
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type SterlingRatioNode = SterlingRatio
|
||||
export declare class SterlingRatio {
|
||||
constructor(period: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type BurkeRatioNode = BurkeRatio
|
||||
export declare class BurkeRatio {
|
||||
constructor(period: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type MartinRatioNode = MartinRatio
|
||||
export declare class MartinRatio {
|
||||
constructor(period: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type TailRatioNode = TailRatio
|
||||
export declare class TailRatio {
|
||||
constructor(period: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type KRatioNode = KRatio
|
||||
export declare class KRatio {
|
||||
constructor(period: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type CommonSenseRatioNode = CommonSenseRatio
|
||||
export declare class CommonSenseRatio {
|
||||
constructor(period: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type GainToPainRatioNode = GainToPainRatio
|
||||
export declare class GainToPainRatio {
|
||||
constructor(period: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type UpsidePotentialRatioNode = UpsidePotentialRatio
|
||||
export declare class UpsidePotentialRatio {
|
||||
constructor(period: number, mar: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type M2MeasureNode = M2Measure
|
||||
export declare class M2Measure {
|
||||
constructor(period: number, riskFree: number, benchmarkStddev: number)
|
||||
update(value: number): number | null
|
||||
batch(prices: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type BandpassFilterNode = BANDPASS
|
||||
export declare class BANDPASS {
|
||||
constructor(period: number, bandwidth: number)
|
||||
|
||||
+10
-1
File diff suppressed because one or more lines are too long
@@ -245,9 +245,91 @@ node_scalar_indicator!(
|
||||
"UNIVERSALOSC",
|
||||
wc::UniversalOscillator
|
||||
);
|
||||
node_scalar_indicator!(SterlingRatioNode, "SterlingRatio", wc::SterlingRatio);
|
||||
node_scalar_indicator!(BurkeRatioNode, "BurkeRatio", wc::BurkeRatio);
|
||||
node_scalar_indicator!(MartinRatioNode, "MartinRatio", wc::MartinRatio);
|
||||
node_scalar_indicator!(TailRatioNode, "TailRatio", wc::TailRatio);
|
||||
node_scalar_indicator!(KRatioNode, "KRatio", wc::KRatio);
|
||||
node_scalar_indicator!(
|
||||
CommonSenseRatioNode,
|
||||
"CommonSenseRatio",
|
||||
wc::CommonSenseRatio
|
||||
);
|
||||
node_scalar_indicator!(GainToPainRatioNode, "GainToPainRatio", wc::GainToPainRatio);
|
||||
|
||||
// Multi-arg Ehlers scalars: hand-written (node_scalar_indicator! is single-period).
|
||||
|
||||
#[napi(js_name = "UpsidePotentialRatio")]
|
||||
pub struct UpsidePotentialRatioNode {
|
||||
inner: wc::UpsidePotentialRatio,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl UpsidePotentialRatioNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32, mar: f64) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::UpsidePotentialRatio::new(period as usize, mar).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(&mut self, prices: Vec<f64>) -> Vec<f64> {
|
||||
flatten(self.inner.batch(&prices))
|
||||
}
|
||||
#[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 = "M2Measure")]
|
||||
pub struct M2MeasureNode {
|
||||
inner: wc::M2Measure,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl M2MeasureNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32, risk_free: f64, benchmark_stddev: f64) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::M2Measure::new(period as usize, risk_free, benchmark_stddev)
|
||||
.map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, value: f64) -> Option<f64> {
|
||||
self.inner.update(value)
|
||||
}
|
||||
#[napi]
|
||||
pub fn batch(&mut self, prices: Vec<f64>) -> Vec<f64> {
|
||||
flatten(self.inner.batch(&prices))
|
||||
}
|
||||
#[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 = "BANDPASS")]
|
||||
pub struct BandpassFilterNode {
|
||||
inner: wc::BandpassFilter,
|
||||
|
||||
Reference in New Issue
Block a user