You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Rust-core RelativeStrengthIndex in 2.0.0rc1 has two numerical defects that the Cython
implementation does not (the Cython impl being the stated source of truth per nautechsystems#2507):
ma_type is ignored. The constructor always builds Exponential inner moving averages
regardless of the ma_type argument, so Wilder, Simple, and Exponential produce identical
output. The Cython version plumbs ma_type into MovingAverageFactory.create(...).
fromnautilus_trader.indicatorsimportRelativeStrengthIndex, MovingAverageTypedefrun(values, period, ma_type):
ind=RelativeStrengthIndex(period, ma_type)
return [round(ind.update_raw(float(v)) orind.value*100, 4) forvinvalues]
# 1) ma_type ignored — all three lines are identicals= [44.34,44.09,44.15,43.61,44.33,44.83,45.10,45.42,45.84,46.08,45.89,46.03,45.61,46.28,46.28]
formtin (MovingAverageType.Wilder, MovingAverageType.Simple, MovingAverageType.Exponential):
print(mt, run(s, 14, mt)[-4:])
# 2) flat 100 after real losses (up 1..15 then down 14,12,9,5,2)print(run([float(i) foriinrange(1,16)] + [14,12,9,5,2], 14, MovingAverageType.Wilder))
Expected vs actual
Up 1..15 then down 14,12,9,5,2, period 14, ma_type=Wilder (Wilder RSI ×100):
idx
15
16
17
18
19
expected (Wilder)
89.35
72.69
55.86
41.92
34.89
actual (v2)
100.00
100.00
100.00
100.00
100.00
ma_type on the 15-close series, last value ×100:
ma_type
expected
actual (v2)
Wilder
72.23
72.23
Simple
distinct (≠ Wilder)
72.23
Exponential
distinct (≠ Wilder)
72.23
Root cause (crates/indicators/src/momentum/rsi.rs)
Defect 1 — constructor (RelativeStrengthIndex::new): inner MAs are built with the literal MovingAverageType::Exponential instead of the ma_type argument (which is only stored on the
struct and used by Display).
average_gain:MovingAverageFactory::create(MovingAverageType::Exponential, period),// should be ma_type
average_loss:MovingAverageFactory::create(MovingAverageType::Exponential, period),
Defect 2 — update_raw: self.last_value = value is placed after the early return taken when average_loss.value() == 0.0, so last_value is never advanced on zero-loss bars. This is the
exact case Fix last value updating for RSI indicator nautechsystems/nautilus_trader#2703 fixed in Cython (self._last_value = value before the return).
Proposed fix
Pass the resolved ma_type into the factory for both averages:
let ma = ma_type.unwrap_or(MovingAverageType::Exponential);// ...
average_gain:MovingAverageFactory::create(ma, period),
average_loss:MovingAverageFactory::create(ma, period),
(and set self.ma_type = ma from the same resolved value).
Add regression tests: (a) Wilder/Simple/Exponential produce distinct output on a fixed
series; (b) a series that rises then falls returns < rsi_max once losses appear; (c) a Wilder
golden series vs Wilder's published reference values. The existing Rust unit tests only exercise
the default (Exponential) path and monotonic/constant inputs, which is why both defects slipped
the Confirm parity between Python and Rust indicators nautechsystems/nautilus_trader#2713 parity pass.
Filed on this fork per the ToTheMoon Nautilus-v2 migration fork policy (tracked liability; see bebop23/ToTheMoon#83). Intended to be fixed here on a branch off develop and upstreamed as a PR to nautechsystems/nautilus_trader.
Summary
The Rust-core
RelativeStrengthIndexin2.0.0rc1has two numerical defects that the Cythonimplementation does not (the Cython impl being the stated source of truth per nautechsystems#2507):
ma_typeis ignored. The constructor always buildsExponentialinner moving averagesregardless of the
ma_typeargument, soWilder,Simple, andExponentialproduce identicaloutput. The Cython version plumbs
ma_typeintoMovingAverageFactory.create(...).last_valueis not advanced on zero-loss bars, so RSI gets pinned atrsi_max(1.0) andnever recovers — even after real down-moves. This is the same bug reported in Relative Strength Index (RSI) Indicator always equal to 1.0 when inputs greater than initial nautechsystems/nautilus_trader#2673 and fixed
for the Cython indicator in Fix last value updating for RSI indicator nautechsystems/nautilus_trader#2703; the fix was not carried into the Rust port.
Repro (only
nautilus_traderrequired):Expected vs actual
Up
1..15then down14,12,9,5,2, period 14,ma_type=Wilder(Wilder RSI ×100):ma_typeon the 15-close series, last value ×100:Root cause (
crates/indicators/src/momentum/rsi.rs)RelativeStrengthIndex::new): inner MAs are built with the literalMovingAverageType::Exponentialinstead of thema_typeargument (which is only stored on thestruct and used by
Display).update_raw:self.last_value = valueis placed after the earlyreturntaken whenaverage_loss.value() == 0.0, solast_valueis never advanced on zero-loss bars. This is theexact case Fix last value updating for RSI indicator nautechsystems/nautilus_trader#2703 fixed in Cython (
self._last_value = valuebefore the return).Proposed fix
ma_typeinto the factory for both averages:self.ma_type = mafrom the same resolved value).last_valuein the zero-loss branch, mirroring Fix last value updating for RSI indicator nautechsystems/nautilus_trader#2703:Wilder/Simple/Exponentialproduce distinct output on a fixedseries; (b) a series that rises then falls returns <
rsi_maxonce losses appear; (c) a Wildergolden series vs Wilder's published reference values. The existing Rust unit tests only exercise
the default (
Exponential) path and monotonic/constant inputs, which is why both defects slippedthe Confirm parity between Python and Rust indicators nautechsystems/nautilus_trader#2713 parity pass.
Filed on this fork per the ToTheMoon Nautilus-v2 migration fork policy (tracked liability; see bebop23/ToTheMoon#83). Intended to be fixed here on a branch off
developand upstreamed as a PR to nautechsystems/nautilus_trader.