mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-19 11:08:05 +00:00
Merge branch 'dev' into main
This commit is contained in:
@@ -19,7 +19,7 @@ Oscillators fluctuate above and below a centerline or within bounded ranges. Use
|
||||
| [DEM](dem/Dem.md) | DeMarker Oscillator | Bounded 0-1 oscillator comparing sequential highs and lows. |
|
||||
| [DOSC](dosc/Dosc.md) | Derivative Oscillator | Double-smoothed RSI minus signal line. Momentum acceleration. |
|
||||
| [DPO](dpo/Dpo.md) | Detrended Price Oscillator | Removes trend via displaced SMA. Reveals cycles. |
|
||||
| [DYMOI](dymoi/Dymoi.md) | Dynamic Momentum Index | RSI with volatility-adaptive period. Shorter in volatile markets. |
|
||||
| [DYMI](dymi/Dymi.md) | Dynamic Momentum Index | RSI with volatility-adaptive period. Shorter in volatile markets. |
|
||||
| [ER](er/Er.md) | Efficiency Ratio | Measures directional efficiency. Net movement / total path length. |
|
||||
| [ERI](eri/Eri.md) | Elder Ray Index | Separates bull and bear power relative to EMA. |
|
||||
| [FI](fi/Fi.md) | Force Index | Combines price change, direction, and volume to measure buying/selling power. |
|
||||
|
||||
@@ -5,7 +5,7 @@ using TradingPlatform.BusinessLayer;
|
||||
namespace QuanTAlib;
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class DymoiIndicator : Indicator, IWatchlistIndicator
|
||||
public sealed class DymiIndicator : Indicator, IWatchlistIndicator
|
||||
{
|
||||
[InputParameter("Base RSI Period", sortIndex: 1, 2, 500, 1, 0)]
|
||||
public int BasePeriod { get; set; } = 14;
|
||||
@@ -28,33 +28,33 @@ public sealed class DymoiIndicator : Indicator, IWatchlistIndicator
|
||||
[InputParameter("Show cold values", sortIndex: 21)]
|
||||
public bool ShowColdValues { get; set; } = true;
|
||||
|
||||
private Dymoi _dymoi = null!;
|
||||
private Dymi _dymi = null!;
|
||||
private readonly LineSeries _series;
|
||||
|
||||
public static int MinHistoryDepths => 0;
|
||||
int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths;
|
||||
|
||||
public override string ShortName =>
|
||||
$"DYMOI ({BasePeriod},{ShortPeriod},{LongPeriod},{MinPeriod},{MaxPeriod})";
|
||||
$"DYMI ({BasePeriod},{ShortPeriod},{LongPeriod},{MinPeriod},{MaxPeriod})";
|
||||
|
||||
public override string SourceCodeLink =>
|
||||
"https://github.com/mihakralj/QuanTAlib/blob/main/lib/oscillators/dymoi/Dymoi.Quantower.cs";
|
||||
"https://github.com/mihakralj/QuanTAlib/blob/main/lib/oscillators/dymi/Dymi.Quantower.cs";
|
||||
|
||||
public DymoiIndicator()
|
||||
public DymiIndicator()
|
||||
{
|
||||
OnBackGround = true;
|
||||
SeparateWindow = true;
|
||||
Name = "DYMOI - Dynamic Momentum Index";
|
||||
Name = "DYMI - Dynamic Momentum Index";
|
||||
Description = "Volatility-adaptive RSI by Chande & Kroll: period shortens in volatile markets, lengthens in quiet ones.";
|
||||
|
||||
_series = new LineSeries("DYMOI", Color.Yellow, 2, LineStyle.Solid);
|
||||
_series = new LineSeries("DYMI", Color.Yellow, 2, LineStyle.Solid);
|
||||
AddLineSeries(_series);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected override void OnInit()
|
||||
{
|
||||
_dymoi = new Dymoi(BasePeriod, ShortPeriod, LongPeriod, MinPeriod, MaxPeriod);
|
||||
_dymi = new Dymi(BasePeriod, ShortPeriod, LongPeriod, MinPeriod, MaxPeriod);
|
||||
base.OnInit();
|
||||
}
|
||||
|
||||
@@ -66,9 +66,9 @@ public sealed class DymoiIndicator : Indicator, IWatchlistIndicator
|
||||
double price = priceSelector(item);
|
||||
|
||||
TValue input = new(item.TimeLeft, price);
|
||||
TValue result = _dymoi.Update(input, args.IsNewBar());
|
||||
TValue result = _dymi.Update(input, args.IsNewBar());
|
||||
|
||||
if (!_dymoi.IsHot && !ShowColdValues)
|
||||
if (!_dymi.IsHot && !ShowColdValues)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using System.Runtime.InteropServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// DYMOI: Dynamic Momentum Index
|
||||
/// DYMI: Dynamic Momentum Index
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Volatility-adaptive RSI by Tushar Chande and Stanley Kroll (1994).
|
||||
@@ -18,10 +18,10 @@ namespace QuanTAlib;
|
||||
///
|
||||
/// References:
|
||||
/// Chande, T. & Kroll, S. (1994). The New Technical Trader.
|
||||
/// PineScript reference: dymoi.pine
|
||||
/// PineScript reference: dymi.pine
|
||||
/// </remarks>
|
||||
[SkipLocalsInit]
|
||||
public sealed class Dymoi : AbstractBase
|
||||
public sealed class Dymi : AbstractBase
|
||||
{
|
||||
private readonly int _basePeriod;
|
||||
private readonly int _shortPeriod;
|
||||
@@ -57,14 +57,14 @@ public sealed class Dymoi : AbstractBase
|
||||
private State _s, _ps;
|
||||
|
||||
/// <summary>
|
||||
/// Creates DYMOI with specified parameters.
|
||||
/// Creates DYMI with specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="basePeriod">Base RSI period (must be >= 2)</param>
|
||||
/// <param name="shortPeriod">Short StdDev window (must be >= 2)</param>
|
||||
/// <param name="longPeriod">Long StdDev window (must be >= 2 and > shortPeriod)</param>
|
||||
/// <param name="minPeriod">Minimum dynamic period (must be >= 2)</param>
|
||||
/// <param name="maxPeriod">Maximum dynamic period (must be >= minPeriod)</param>
|
||||
public Dymoi(int basePeriod = 14, int shortPeriod = 5, int longPeriod = 10,
|
||||
public Dymi(int basePeriod = 14, int shortPeriod = 5, int longPeriod = 10,
|
||||
int minPeriod = 3, int maxPeriod = 30)
|
||||
{
|
||||
if (basePeriod < 2)
|
||||
@@ -106,14 +106,14 @@ public sealed class Dymoi : AbstractBase
|
||||
_s = new State(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1.0, true, double.NaN, double.NaN);
|
||||
_ps = _s;
|
||||
|
||||
Name = $"Dymoi({basePeriod},{shortPeriod},{longPeriod},{minPeriod},{maxPeriod})";
|
||||
Name = $"Dymi({basePeriod},{shortPeriod},{longPeriod},{minPeriod},{maxPeriod})";
|
||||
WarmupPeriod = longPeriod + maxPeriod;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates DYMOI with event-based source chaining.
|
||||
/// Creates DYMI with event-based source chaining.
|
||||
/// </summary>
|
||||
public Dymoi(ITValuePublisher source, int basePeriod = 14, int shortPeriod = 5,
|
||||
public Dymi(ITValuePublisher source, int basePeriod = 14, int shortPeriod = 5,
|
||||
int longPeriod = 10, int minPeriod = 3, int maxPeriod = 30)
|
||||
: this(basePeriod, shortPeriod, longPeriod, minPeriod, maxPeriod)
|
||||
{
|
||||
@@ -234,7 +234,7 @@ public sealed class Dymoi : AbstractBase
|
||||
}
|
||||
|
||||
// ── Stage 3: Wilder RMA RSI with adaptive alpha ──
|
||||
double dymoi = 50.0;
|
||||
double dymi = 50.0;
|
||||
if (!double.IsNaN(s.PrevClose))
|
||||
{
|
||||
double alpha = 1.0 / dynPeriod;
|
||||
@@ -253,7 +253,7 @@ public sealed class Dymoi : AbstractBase
|
||||
double aG = s.AvgGain * c;
|
||||
double aL = s.AvgLoss * c;
|
||||
double total = aG + aL;
|
||||
dymoi = total != 0.0 ? 100.0 * aG / total : 50.0;
|
||||
dymi = total != 0.0 ? 100.0 * aG / total : 50.0;
|
||||
if (s.E <= 1e-10)
|
||||
{
|
||||
s.Warmup = false;
|
||||
@@ -262,15 +262,15 @@ public sealed class Dymoi : AbstractBase
|
||||
else
|
||||
{
|
||||
double total = s.AvgGain + s.AvgLoss;
|
||||
dymoi = total != 0.0 ? 100.0 * s.AvgGain / total : 50.0;
|
||||
dymi = total != 0.0 ? 100.0 * s.AvgGain / total : 50.0;
|
||||
}
|
||||
}
|
||||
|
||||
s.PrevClose = value;
|
||||
_s = s;
|
||||
|
||||
dymoi = Math.Max(0.0, Math.Min(100.0, dymoi));
|
||||
Last = new TValue(input.Time, dymoi);
|
||||
dymi = Math.Max(0.0, Math.Min(100.0, dymi));
|
||||
Last = new TValue(input.Time, dymi);
|
||||
PubEvent(Last, isNew);
|
||||
return Last;
|
||||
}
|
||||
@@ -322,8 +322,8 @@ public sealed class Dymoi : AbstractBase
|
||||
public static TSeries Batch(TSeries source, int basePeriod = 14, int shortPeriod = 5,
|
||||
int longPeriod = 10, int minPeriod = 3, int maxPeriod = 30)
|
||||
{
|
||||
var dymoi = new Dymoi(basePeriod, shortPeriod, longPeriod, minPeriod, maxPeriod);
|
||||
return dymoi.Update(source);
|
||||
var dymi = new Dymi(basePeriod, shortPeriod, longPeriod, minPeriod, maxPeriod);
|
||||
return dymi.Update(source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -480,7 +480,7 @@ public sealed class Dymoi : AbstractBase
|
||||
}
|
||||
|
||||
// Wilder RMA RSI
|
||||
double dymoi = 50.0;
|
||||
double dymi = 50.0;
|
||||
if (!double.IsNaN(prevClose))
|
||||
{
|
||||
double alpha = 1.0 / dynPeriod;
|
||||
@@ -499,7 +499,7 @@ public sealed class Dymoi : AbstractBase
|
||||
double aG = avgGain * c;
|
||||
double aL = avgLoss * c;
|
||||
double total = aG + aL;
|
||||
dymoi = total != 0.0 ? 100.0 * aG / total : 50.0;
|
||||
dymi = total != 0.0 ? 100.0 * aG / total : 50.0;
|
||||
if (e <= 1e-10)
|
||||
{
|
||||
warmup = false;
|
||||
@@ -508,12 +508,12 @@ public sealed class Dymoi : AbstractBase
|
||||
else
|
||||
{
|
||||
double total = avgGain + avgLoss;
|
||||
dymoi = total != 0.0 ? 100.0 * avgGain / total : 50.0;
|
||||
dymi = total != 0.0 ? 100.0 * avgGain / total : 50.0;
|
||||
}
|
||||
}
|
||||
|
||||
prevClose = val;
|
||||
output[i] = Math.Max(0.0, Math.Min(100.0, dymoi));
|
||||
output[i] = Math.Max(0.0, Math.Min(100.0, dymi));
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -1,4 +1,4 @@
|
||||
# DYMOI: Dynamic Momentum Index
|
||||
# DYMI: Dynamic Momentum Index
|
||||
|
||||
> *The market is not a fixed-frequency oscillator. Why would you analyze it with one?*
|
||||
|
||||
@@ -7,20 +7,20 @@
|
||||
| **Category** | Oscillator |
|
||||
| **Inputs** | Source (close) |
|
||||
| **Parameters** | `basePeriod` (default 14), `shortPeriod` (default 5), `longPeriod` (default 10), `minPeriod` (default 3), `maxPeriod` (default 30) |
|
||||
| **Outputs** | Single series (Dymoi) |
|
||||
| **Outputs** | Single series (Dymi) |
|
||||
| **Output range** | Varies (see docs) |
|
||||
| **Warmup** | 1 bar |
|
||||
| **PineScript** | [dymoi.pine](dymoi.pine) |
|
||||
| **PineScript** | [dymi.pine](dymi.pine) |
|
||||
|
||||
- DYMOI is a volatility-adaptive RSI: when recent price swings are large relative to longer-term swings, the RSI period shortens and the indicator be...
|
||||
- DYMI is a volatility-adaptive RSI: when recent price swings are large relative to longer-term swings, the RSI period shortens and the indicator be...
|
||||
- **Similar:** [RSI](../../momentum/rsi/Rsi.md), [Stoch](../stoch/Stoch.md) | **Complementary:** ATR | **Trading note:** Dynamic Momentum Index; RSI with variable lookback based on volatility. Faster in calm, slower in volatile markets.
|
||||
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
|
||||
|
||||
DYMOI is a volatility-adaptive RSI: when recent price swings are large relative to longer-term swings, the RSI period shortens and the indicator becomes more responsive; when price action tightens, the period extends and the output smooths. The result is an oscillator that self-adjusts its sensitivity to the market's current state, avoiding both the lag of long fixed-period RSIs in trending regimes and the noise of short-period RSIs in ranging ones.
|
||||
DYMI is a volatility-adaptive RSI: when recent price swings are large relative to longer-term swings, the RSI period shortens and the indicator becomes more responsive; when price action tightens, the period extends and the output smooths. The result is an oscillator that self-adjusts its sensitivity to the market's current state, avoiding both the lag of long fixed-period RSIs in trending regimes and the noise of short-period RSIs in ranging ones.
|
||||
|
||||
## Historical Context
|
||||
|
||||
Tushar Chande and Stanley Kroll introduced DYMOI in *The New Technical Trader* (1994) as a practical answer to a genuine problem: the standard RSI's fixed period is a blunt instrument. A 14-bar RSI responds identically whether the market has been oscillating ±5% per day or ±0.2%. Chande and Kroll observed that a shorter period in high-volatility environments catches reversals earlier; a longer period in quiet conditions eliminates whipsaws.
|
||||
Tushar Chande and Stanley Kroll introduced DYMI in *The New Technical Trader* (1994) as a practical answer to a genuine problem: the standard RSI's fixed period is a blunt instrument. A 14-bar RSI responds identically whether the market has been oscillating ±5% per day or ±0.2%. Chande and Kroll observed that a shorter period in high-volatility environments catches reversals earlier; a longer period in quiet conditions eliminates whipsaws.
|
||||
|
||||
The mechanism they chose was straightforward: compute the ratio of short-term to long-term price standard deviation. When this ratio exceeds 1, the market is more volatile than its recent baseline — shorten the period. When the ratio is below 1, lengthen it. The result gets clamped to a configurable `[minPeriod, maxPeriod]` range, and a standard Wilder RSI runs on the resulting dynamic period.
|
||||
|
||||
@@ -32,7 +32,7 @@ The indicator has no widely adopted C# open-source implementation, which is why
|
||||
| :--- | :--- | :---: | :---: |
|
||||
| RSI (Wilder) | None — fixed period | 0–100 | period+1 |
|
||||
| CRSI (Connors) | Three-component composite, no period adaptation | 0–100 | rankPeriod+rsiPeriod |
|
||||
| DYMOI (Chande/Kroll) | Dual StdDev ratio drives period selection | 0–100 | longPeriod+maxPeriod |
|
||||
| DYMI (Chande/Kroll) | Dual StdDev ratio drives period selection | 0–100 | longPeriod+maxPeriod |
|
||||
| LRSI (Ehlers Laguerre) | Cycle-adaptive Laguerre filter stages | 0–1 | 4 |
|
||||
|
||||
## Architecture & Physics
|
||||
@@ -115,7 +115,7 @@ $$n_t = \operatorname{clamp}\!\left(\left\lfloor \frac{n_{\text{base}}}{V_t} + 0
|
||||
|
||||
$$\overline{G}_t = \alpha_t \cdot G_t + (1 - \alpha_t) \cdot \overline{G}_{t-1}$$
|
||||
|
||||
$$\text{DYMOI}_t = 100 \cdot \frac{\overline{G}_t}{\overline{G}_t + \overline{L}_t}$$
|
||||
$$\text{DYMI}_t = 100 \cdot \frac{\overline{G}_t}{\overline{G}_t + \overline{L}_t}$$
|
||||
|
||||
### Degenerate Cases
|
||||
|
||||
@@ -131,7 +131,7 @@ $$\text{DYMOI}_t = 100 \cdot \frac{\overline{G}_t}{\overline{G}_t + \overline{L}
|
||||
|
||||
### Operation Count (Streaming Mode)
|
||||
|
||||
DYMOI computes a dynamic momentum oscillator using an EMA-smoothed velocity + acceleration blend.
|
||||
DYMI computes a dynamic momentum oscillator using an EMA-smoothed velocity + acceleration blend.
|
||||
|
||||
| Operation | Count | Cost (cycles) | Subtotal |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
@@ -181,7 +181,7 @@ SIMD is not applicable to the streaming `Update` path because the period changes
|
||||
|
||||
## Validation
|
||||
|
||||
No external C# library (Skender, TA-Lib, Tulip, Ooples) implements DYMOI. Validation is self-consistency only.
|
||||
No external C# library (Skender, TA-Lib, Tulip, Ooples) implements DYMI. Validation is self-consistency only.
|
||||
|
||||
| Test | Method | Tolerance | Result |
|
||||
| :--- | :--- | :---: | :--- |
|
||||
@@ -193,7 +193,7 @@ No external C# library (Skender, TA-Lib, Tulip, Ooples) implements DYMOI. Valida
|
||||
| Fixed period identity | minPeriod=maxPeriod=basePeriod | 1e-9 | Pass |
|
||||
| Determinism | Two identical GBM seeds | 1e-10 | Pass |
|
||||
|
||||
**Mathematical identity test:** When `minPeriod == maxPeriod == basePeriod`, the dynamic period is always fixed at `basePeriod` regardless of the volatility ratio. Under this constraint, DYMOI produces output numerically identical to `Rsi(basePeriod)` (verified at tolerance 1e-9).
|
||||
**Mathematical identity test:** When `minPeriod == maxPeriod == basePeriod`, the dynamic period is always fixed at `basePeriod` regardless of the volatility ratio. Under this constraint, DYMI produces output numerically identical to `Rsi(basePeriod)` (verified at tolerance 1e-9).
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
@@ -201,9 +201,9 @@ No external C# library (Skender, TA-Lib, Tulip, Ooples) implements DYMOI. Valida
|
||||
|
||||
2. **Zero-variance series (flat price)**: When `σ_long = 0`, the ratio is undefined; the implementation defaults to `V = 1` → `n_dyn = n_base`. This is correct — a flat series should produce neutral RSI(=50) at the base period rate, not a degenerate output.
|
||||
|
||||
3. **Warmup period misinterpretation**: `WarmupPeriod = longPeriod + maxPeriod`. The dominant warmup is the Wilder RMA, which takes `maxPeriod` bars to settle after the long StdDev window fills. Using DYMOI output before `IsHot = true` will produce compensated but less accurate values.
|
||||
3. **Warmup period misinterpretation**: `WarmupPeriod = longPeriod + maxPeriod`. The dominant warmup is the Wilder RMA, which takes `maxPeriod` bars to settle after the long StdDev window fills. Using DYMI output before `IsHot = true` will produce compensated but less accurate values.
|
||||
|
||||
4. **Period clamp masking pathology**: If `minPeriod` and `maxPeriod` are very close (e.g., both 14), the adaptive behavior is effectively disabled and DYMOI degenerates to standard RSI. This is a valid use case but should be intentional.
|
||||
4. **Period clamp masking pathology**: If `minPeriod` and `maxPeriod` are very close (e.g., both 14), the adaptive behavior is effectively disabled and DYMI degenerates to standard RSI. This is a valid use case but should be intentional.
|
||||
|
||||
5. **Floating-point drift in running sums**: The O(1) variance formula $E[x^2] - E[x]^2$ is numerically unstable for large values or large windows — specifically, catastrophic cancellation can occur. For price data in the range [0.01, 100000] and periods ≤ 100, drift is negligible in practice. For exotic inputs, a periodic full-recalculation reset (every N steps) would be appropriate; the current implementation does not perform this.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Dynamic Momentum Index (DYMOI)", "DYMOI", overlay=false)
|
||||
indicator("Dynamic Momentum Index (DYMI)", "DYMI", overlay=false)
|
||||
|
||||
//@description Dynamic Momentum Index by Tushar Chande and Stanley Kroll (1994).
|
||||
// A three-stage pipeline that produces a volatility-adaptive RSI:
|
||||
@@ -89,11 +89,11 @@ rsi_wilder(series float source, series int dynPeriod) =>
|
||||
//@param longPeriod Long StdDev window (default 10)
|
||||
//@param minPeriod Minimum dynamic period (default 3)
|
||||
//@param maxPeriod Maximum dynamic period (default 30)
|
||||
//@returns DYMOI value in [0, 100]
|
||||
//@returns DYMI value in [0, 100]
|
||||
//@optimized Uses circular buffers for O(1) StdDev; adaptive Wilder RMA for RSI
|
||||
dymoi(series float source, simple int basePeriod, simple int shortPeriod, simple int longPeriod, simple int minPeriod, simple int maxPeriod) =>
|
||||
dymi(series float source, simple int basePeriod, simple int shortPeriod, simple int longPeriod, simple int minPeriod, simple int maxPeriod) =>
|
||||
if basePeriod < 2 or shortPeriod < 2 or longPeriod <= shortPeriod or minPeriod < 2 or maxPeriod < minPeriod
|
||||
runtime.error("Invalid DYMOI parameters")
|
||||
runtime.error("Invalid DYMI parameters")
|
||||
|
||||
// Stage 1: dual StdDev volatility ratio
|
||||
float sdShort = stddev_circ(source, shortPeriod)
|
||||
@@ -118,9 +118,9 @@ i_minPeriod = input.int(3, "Min Period", minval=2, maxval=500)
|
||||
i_maxPeriod = input.int(30, "Max Period", minval=2, maxval=500)
|
||||
i_source = input.source(close, "Source")
|
||||
|
||||
dymoi_val = dymoi(i_source, i_basePeriod, i_shortPeriod, i_longPeriod, i_minPeriod, i_maxPeriod)
|
||||
dymi_val = dymi(i_source, i_basePeriod, i_shortPeriod, i_longPeriod, i_minPeriod, i_maxPeriod)
|
||||
|
||||
plot(dymoi_val, "DYMOI", color=color.yellow, linewidth=2)
|
||||
plot(dymi_val, "DYMI", color=color.yellow, linewidth=2)
|
||||
hline(70, "Overbought", color=color.gray, linestyle=hline.style_dotted)
|
||||
hline(50, "Midline", color=color.gray, linestyle=hline.style_dotted)
|
||||
hline(30, "Oversold", color=color.gray, linestyle=hline.style_dotted)
|
||||
+22
-22
@@ -3,12 +3,12 @@ using QuanTAlib;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public sealed class DymoiIndicatorTests
|
||||
public sealed class DymiIndicatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void DymoiIndicator_Constructor_SetsDefaults()
|
||||
public void DymiIndicator_Constructor_SetsDefaults()
|
||||
{
|
||||
var indicator = new DymoiIndicator();
|
||||
var indicator = new DymiIndicator();
|
||||
|
||||
Assert.Equal(14, indicator.BasePeriod);
|
||||
Assert.Equal(5, indicator.ShortPeriod);
|
||||
@@ -17,25 +17,25 @@ public sealed class DymoiIndicatorTests
|
||||
Assert.Equal(30, indicator.MaxPeriod);
|
||||
Assert.Equal(SourceType.Close, indicator.Source);
|
||||
Assert.True(indicator.ShowColdValues);
|
||||
Assert.Equal("DYMOI - Dynamic Momentum Index", indicator.Name);
|
||||
Assert.Equal("DYMI - Dynamic Momentum Index", indicator.Name);
|
||||
Assert.True(indicator.SeparateWindow);
|
||||
Assert.True(indicator.OnBackGround);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DymoiIndicator_MinHistoryDepths_EqualsZero()
|
||||
public void DymiIndicator_MinHistoryDepths_EqualsZero()
|
||||
{
|
||||
var indicator = new DymoiIndicator();
|
||||
var indicator = new DymiIndicator();
|
||||
|
||||
Assert.Equal(0, DymoiIndicator.MinHistoryDepths);
|
||||
Assert.Equal(0, DymiIndicator.MinHistoryDepths);
|
||||
IWatchlistIndicator watchlistIndicator = indicator;
|
||||
Assert.Equal(0, watchlistIndicator.MinHistoryDepths);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DymoiIndicator_ShortName_IncludesParameters()
|
||||
public void DymiIndicator_ShortName_IncludesParameters()
|
||||
{
|
||||
var indicator = new DymoiIndicator
|
||||
var indicator = new DymiIndicator
|
||||
{
|
||||
BasePeriod = 10,
|
||||
ShortPeriod = 4,
|
||||
@@ -45,7 +45,7 @@ public sealed class DymoiIndicatorTests
|
||||
};
|
||||
indicator.Initialize();
|
||||
|
||||
Assert.Contains("DYMOI", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("DYMI", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("10", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("4", indicator.ShortName, StringComparison.Ordinal);
|
||||
Assert.Contains("8", indicator.ShortName, StringComparison.Ordinal);
|
||||
@@ -53,18 +53,18 @@ public sealed class DymoiIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DymoiIndicator_SourceCodeLink_IsValid()
|
||||
public void DymiIndicator_SourceCodeLink_IsValid()
|
||||
{
|
||||
var indicator = new DymoiIndicator();
|
||||
var indicator = new DymiIndicator();
|
||||
|
||||
Assert.Contains("github.com", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Dymoi.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
Assert.Contains("Dymi.Quantower.cs", indicator.SourceCodeLink, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DymoiIndicator_Initialize_CreatesLineSeries()
|
||||
public void DymiIndicator_Initialize_CreatesLineSeries()
|
||||
{
|
||||
var indicator = new DymoiIndicator
|
||||
var indicator = new DymiIndicator
|
||||
{
|
||||
BasePeriod = 14,
|
||||
ShortPeriod = 5,
|
||||
@@ -79,9 +79,9 @@ public sealed class DymoiIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DymoiIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
public void DymiIndicator_ProcessUpdate_HistoricalBar_ComputesValue()
|
||||
{
|
||||
var indicator = new DymoiIndicator
|
||||
var indicator = new DymiIndicator
|
||||
{
|
||||
BasePeriod = 14,
|
||||
ShortPeriod = 5,
|
||||
@@ -103,13 +103,13 @@ public sealed class DymoiIndicatorTests
|
||||
|
||||
double value = indicator.LinesSeries[0].GetValue(0);
|
||||
Assert.True(double.IsFinite(value));
|
||||
Assert.True(value >= 0.0 && value <= 100.0, $"DYMOI={value} out of [0,100]");
|
||||
Assert.True(value >= 0.0 && value <= 100.0, $"DYMI={value} out of [0,100]");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DymoiIndicator_ProcessUpdate_NewBar_ComputesValue()
|
||||
public void DymiIndicator_ProcessUpdate_NewBar_ComputesValue()
|
||||
{
|
||||
var indicator = new DymoiIndicator
|
||||
var indicator = new DymiIndicator
|
||||
{
|
||||
BasePeriod = 14,
|
||||
ShortPeriod = 5,
|
||||
@@ -135,11 +135,11 @@ public sealed class DymoiIndicatorTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DymoiIndicator_DifferentSourceTypes_ComputeWithoutError()
|
||||
public void DymiIndicator_DifferentSourceTypes_ComputeWithoutError()
|
||||
{
|
||||
foreach (var sourceType in new[] { SourceType.Close, SourceType.Open, SourceType.High, SourceType.Low })
|
||||
{
|
||||
var indicator = new DymoiIndicator
|
||||
var indicator = new DymiIndicator
|
||||
{
|
||||
BasePeriod = 14,
|
||||
ShortPeriod = 5,
|
||||
+42
-42
@@ -2,7 +2,7 @@ using Xunit;
|
||||
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
public sealed class DymoiTests
|
||||
public sealed class DymiTests
|
||||
{
|
||||
private const double Tolerance = 1e-10;
|
||||
|
||||
@@ -11,62 +11,62 @@ public sealed class DymoiTests
|
||||
[Fact]
|
||||
public void Constructor_BasePeriodOne_ThrowsArgumentException()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Dymoi(basePeriod: 1));
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Dymi(basePeriod: 1));
|
||||
Assert.Equal("basePeriod", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ShortPeriodOne_ThrowsArgumentException()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Dymoi(shortPeriod: 1));
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Dymi(shortPeriod: 1));
|
||||
Assert.Equal("shortPeriod", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_LongPeriodEqualShortPeriod_ThrowsArgumentException()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Dymoi(shortPeriod: 5, longPeriod: 5));
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Dymi(shortPeriod: 5, longPeriod: 5));
|
||||
Assert.Equal("longPeriod", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_LongPeriodLessThanShortPeriod_ThrowsArgumentException()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Dymoi(shortPeriod: 10, longPeriod: 5));
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Dymi(shortPeriod: 10, longPeriod: 5));
|
||||
Assert.Equal("longPeriod", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_MinPeriodOne_ThrowsArgumentException()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Dymoi(minPeriod: 1));
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Dymi(minPeriod: 1));
|
||||
Assert.Equal("minPeriod", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_MaxPeriodLessThanMinPeriod_ThrowsArgumentException()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Dymoi(minPeriod: 10, maxPeriod: 5));
|
||||
var ex = Assert.Throws<ArgumentException>(() => new Dymi(minPeriod: 10, maxPeriod: 5));
|
||||
Assert.Equal("maxPeriod", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ValidDefaults_SetsProperties()
|
||||
{
|
||||
var d = new Dymoi();
|
||||
var d = new Dymi();
|
||||
Assert.Equal(14, d.BasePeriod);
|
||||
Assert.Equal(5, d.ShortPeriod);
|
||||
Assert.Equal(10, d.LongPeriod);
|
||||
Assert.Equal(3, d.MinPeriod);
|
||||
Assert.Equal(30, d.MaxPeriod);
|
||||
Assert.Equal("Dymoi(14,5,10,3,30)", d.Name);
|
||||
Assert.Equal("Dymi(14,5,10,3,30)", d.Name);
|
||||
Assert.False(d.IsHot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_CustomPeriods_SetsProperties()
|
||||
{
|
||||
var d = new Dymoi(basePeriod: 10, shortPeriod: 3, longPeriod: 7, minPeriod: 2, maxPeriod: 20);
|
||||
var d = new Dymi(basePeriod: 10, shortPeriod: 3, longPeriod: 7, minPeriod: 2, maxPeriod: 20);
|
||||
Assert.Equal(10, d.BasePeriod);
|
||||
Assert.Equal(3, d.ShortPeriod);
|
||||
Assert.Equal(7, d.LongPeriod);
|
||||
@@ -79,7 +79,7 @@ public sealed class DymoiTests
|
||||
{
|
||||
var src = new double[] { 1, 2, 3 };
|
||||
var out1 = new double[4];
|
||||
var ex = Assert.Throws<ArgumentException>(() => Dymoi.Batch(src, out1));
|
||||
var ex = Assert.Throws<ArgumentException>(() => Dymi.Batch(src, out1));
|
||||
Assert.Equal("output", ex.ParamName);
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ public sealed class DymoiTests
|
||||
{
|
||||
var src = new double[] { 1, 2, 3 };
|
||||
var out1 = new double[3];
|
||||
var ex = Assert.Throws<ArgumentException>(() => Dymoi.Batch(src, out1, basePeriod: 1));
|
||||
var ex = Assert.Throws<ArgumentException>(() => Dymi.Batch(src, out1, basePeriod: 1));
|
||||
Assert.Equal("basePeriod", ex.ParamName);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public sealed class DymoiTests
|
||||
{
|
||||
var src = new double[] { 1, 2, 3 };
|
||||
var out1 = new double[3];
|
||||
var ex = Assert.Throws<ArgumentException>(() => Dymoi.Batch(src, out1, shortPeriod: 5, longPeriod: 5));
|
||||
var ex = Assert.Throws<ArgumentException>(() => Dymi.Batch(src, out1, shortPeriod: 5, longPeriod: 5));
|
||||
Assert.Equal("longPeriod", ex.ParamName);
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public sealed class DymoiTests
|
||||
{
|
||||
var src = new double[] { 1, 2, 3 };
|
||||
var out1 = new double[3];
|
||||
var ex = Assert.Throws<ArgumentException>(() => Dymoi.Batch(src, out1, minPeriod: 5, maxPeriod: 3));
|
||||
var ex = Assert.Throws<ArgumentException>(() => Dymi.Batch(src, out1, minPeriod: 5, maxPeriod: 3));
|
||||
Assert.Equal("maxPeriod", ex.ParamName);
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ public sealed class DymoiTests
|
||||
[Fact]
|
||||
public void Update_ReturnsTValue()
|
||||
{
|
||||
var d = new Dymoi();
|
||||
var d = new Dymi();
|
||||
var result = d.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
Assert.IsType<TValue>(result);
|
||||
}
|
||||
@@ -123,29 +123,29 @@ public sealed class DymoiTests
|
||||
[Fact]
|
||||
public void Update_OutputInRange0To100()
|
||||
{
|
||||
var d = new Dymoi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var d = new Dymi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.3, seed: 42);
|
||||
var bars = gbm.Fetch(300, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
foreach (var bar in bars.Close)
|
||||
{
|
||||
double v = d.Update(bar).Value;
|
||||
Assert.True(v >= 0.0 && v <= 100.0, $"DYMOI={v} out of [0,100]");
|
||||
Assert.True(v >= 0.0 && v <= 100.0, $"DYMI={v} out of [0,100]");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_NameIsAccessible()
|
||||
{
|
||||
var d = new Dymoi(14, 5, 10, 3, 30);
|
||||
var d = new Dymi(14, 5, 10, 3, 30);
|
||||
_ = d.Update(new TValue(DateTime.UtcNow, 100.0));
|
||||
Assert.Equal("Dymoi(14,5,10,3,30)", d.Name);
|
||||
Assert.Equal("Dymi(14,5,10,3,30)", d.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_LastIsAccessible()
|
||||
{
|
||||
var d = new Dymoi();
|
||||
var d = new Dymi();
|
||||
var t = new TValue(DateTime.UtcNow, 100.0);
|
||||
var result = d.Update(t);
|
||||
Assert.Equal(result, d.Last);
|
||||
@@ -156,7 +156,7 @@ public sealed class DymoiTests
|
||||
[Fact]
|
||||
public void Update_IsNewTrue_AdvancesState()
|
||||
{
|
||||
var d = new Dymoi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var d = new Dymi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var t = DateTime.UtcNow;
|
||||
d.Update(new TValue(t, 100.0), isNew: true);
|
||||
var v1 = d.Last;
|
||||
@@ -169,7 +169,7 @@ public sealed class DymoiTests
|
||||
[Fact]
|
||||
public void Update_IsNewFalse_RollsBack()
|
||||
{
|
||||
var d = new Dymoi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var d = new Dymi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
double[] prices = [100, 102, 104, 103, 105, 107, 106, 108, 110, 109, 111, 113];
|
||||
var t = DateTime.UtcNow;
|
||||
for (int i = 0; i < prices.Length; i++)
|
||||
@@ -191,7 +191,7 @@ public sealed class DymoiTests
|
||||
[Fact]
|
||||
public void Update_IterativeCorrections_Restore()
|
||||
{
|
||||
var d = new Dymoi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var d = new Dymi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
double[] prices = [100, 102, 98, 105, 103, 107, 101, 108, 100, 109, 102, 110];
|
||||
var t = DateTime.UtcNow;
|
||||
for (int i = 0; i < prices.Length; i++)
|
||||
@@ -214,7 +214,7 @@ public sealed class DymoiTests
|
||||
[Fact]
|
||||
public void Update_Reset_ClearsState()
|
||||
{
|
||||
var d = new Dymoi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var d = new Dymi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.01, sigma: 0.2, seed: 7);
|
||||
var bars = gbm.Fetch(50, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
@@ -234,7 +234,7 @@ public sealed class DymoiTests
|
||||
public void IsHot_FlipsAfterWarmup()
|
||||
{
|
||||
// Use small periods to make warmup manageable
|
||||
var d = new Dymoi(basePeriod: 5, shortPeriod: 3, longPeriod: 5, minPeriod: 2, maxPeriod: 10);
|
||||
var d = new Dymi(basePeriod: 5, shortPeriod: 3, longPeriod: 5, minPeriod: 2, maxPeriod: 10);
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.01, sigma: 0.3, seed: 11);
|
||||
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
@@ -249,13 +249,13 @@ public sealed class DymoiTests
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(everHot, "DYMOI should become hot within 200 bars");
|
||||
Assert.True(everHot, "DYMI should become hot within 200 bars");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WarmupPeriod_IsLongPeriodPlusMaxPeriod()
|
||||
{
|
||||
var d = new Dymoi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var d = new Dymi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
Assert.Equal(40, d.WarmupPeriod); // longPeriod(10) + maxPeriod(30)
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ public sealed class DymoiTests
|
||||
[Fact]
|
||||
public void Update_NaN_UsesLastValid()
|
||||
{
|
||||
var d = new Dymoi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var d = new Dymi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var t = DateTime.UtcNow;
|
||||
|
||||
// Feed valid values first
|
||||
@@ -281,7 +281,7 @@ public sealed class DymoiTests
|
||||
[Fact]
|
||||
public void Update_Infinity_UsesLastValid()
|
||||
{
|
||||
var d = new Dymoi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var d = new Dymi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var t = DateTime.UtcNow;
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
@@ -296,7 +296,7 @@ public sealed class DymoiTests
|
||||
[Fact]
|
||||
public void Update_BatchNaN_AllFinite()
|
||||
{
|
||||
var d = new Dymoi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var d = new Dymi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var t = DateTime.UtcNow;
|
||||
|
||||
// Mix NaN into sequence
|
||||
@@ -319,7 +319,7 @@ public sealed class DymoiTests
|
||||
TSeries source = bars.Close;
|
||||
|
||||
// Streaming
|
||||
var streaming = new Dymoi(14, 5, 10, 3, 30);
|
||||
var streaming = new Dymi(14, 5, 10, 3, 30);
|
||||
var streamVals = new double[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
@@ -327,7 +327,7 @@ public sealed class DymoiTests
|
||||
}
|
||||
|
||||
// Batch TSeries
|
||||
TSeries batchTs = Dymoi.Batch(source, 14, 5, 10, 3, 30);
|
||||
TSeries batchTs = Dymi.Batch(source, 14, 5, 10, 3, 30);
|
||||
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
@@ -342,10 +342,10 @@ public sealed class DymoiTests
|
||||
var bars = gbm.Fetch(300, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
TSeries source = bars.Close;
|
||||
|
||||
TSeries batchTs = Dymoi.Batch(source, 14, 5, 10, 3, 30);
|
||||
TSeries batchTs = Dymi.Batch(source, 14, 5, 10, 3, 30);
|
||||
|
||||
var spanOut = new double[source.Count];
|
||||
Dymoi.Batch(source.Values, spanOut, 14, 5, 10, 3, 30);
|
||||
Dymi.Batch(source.Values, spanOut, 14, 5, 10, 3, 30);
|
||||
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
@@ -361,7 +361,7 @@ public sealed class DymoiTests
|
||||
TSeries source = bars.Close;
|
||||
|
||||
// Streaming
|
||||
var streaming = new Dymoi(14, 5, 10, 3, 30);
|
||||
var streaming = new Dymi(14, 5, 10, 3, 30);
|
||||
var streamVals = new double[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
@@ -370,12 +370,12 @@ public sealed class DymoiTests
|
||||
|
||||
// Event-based
|
||||
var eventTs = new TSeries();
|
||||
var eventDymoi = new Dymoi(eventTs, 14, 5, 10, 3, 30);
|
||||
var eventDymi = new Dymi(eventTs, 14, 5, 10, 3, 30);
|
||||
var eventVals = new double[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
eventTs.Add(source[i]);
|
||||
eventVals[i] = eventDymoi.Last.Value;
|
||||
eventVals[i] = eventDymi.Last.Value;
|
||||
}
|
||||
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
@@ -391,7 +391,7 @@ public sealed class DymoiTests
|
||||
{
|
||||
var src = Array.Empty<double>();
|
||||
var out1 = Array.Empty<double>();
|
||||
Dymoi.Batch(src, out1);
|
||||
Dymi.Batch(src, out1);
|
||||
Assert.Empty(out1);
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ public sealed class DymoiTests
|
||||
var out1 = new double[n];
|
||||
|
||||
// Should not throw StackOverflowException — uses ArrayPool for large buffers
|
||||
Dymoi.Batch(src, out1);
|
||||
Dymi.Batch(src, out1);
|
||||
|
||||
bool anyFinite = false;
|
||||
for (int i = 0; i < n; i++)
|
||||
@@ -428,7 +428,7 @@ public sealed class DymoiTests
|
||||
var src = bars.Close.Values;
|
||||
var out1 = new double[src.Length];
|
||||
|
||||
Dymoi.Batch(src, out1);
|
||||
Dymi.Batch(src, out1);
|
||||
|
||||
for (int i = 0; i < src.Length; i++)
|
||||
{
|
||||
@@ -442,7 +442,7 @@ public sealed class DymoiTests
|
||||
public void Chainability_PubFires()
|
||||
{
|
||||
var source = new TSeries();
|
||||
var d = new Dymoi(source, 14, 5, 10, 3, 30);
|
||||
var d = new Dymi(source, 14, 5, 10, 3, 30);
|
||||
|
||||
int count = 0;
|
||||
d.Pub += (object? _, in TValueEventArgs e) => count++;
|
||||
@@ -460,7 +460,7 @@ public sealed class DymoiTests
|
||||
public void Chainability_EventBasedChaining_Works()
|
||||
{
|
||||
var source = new TSeries();
|
||||
var d = new Dymoi(source, 14, 5, 10, 3, 30);
|
||||
var d = new Dymi(source, 14, 5, 10, 3, 30);
|
||||
var output = new TSeries();
|
||||
d.Pub += (object? _, in TValueEventArgs e) => output.Add(e.Value);
|
||||
|
||||
+26
-26
@@ -6,14 +6,14 @@ using OoplesFinance.StockIndicators.Models;
|
||||
namespace QuanTAlib.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Self-consistency validation for DYMOI.
|
||||
/// No external library implements DYMOI in C# bindings, so validation uses:
|
||||
/// Self-consistency validation for DYMI.
|
||||
/// No external library implements DYMI in C# bindings, so validation uses:
|
||||
/// 1. Mathematical identity: when shortPeriod == longPeriod → V ≈ 1 → dynPeriod ≈ basePeriod → matches standard RSI(basePeriod)
|
||||
/// 2. Batch == streaming == span == eventing consistency
|
||||
/// 3. Output always in [0, 100]
|
||||
/// 4. Period adapts: shorter in high-vol, longer in low-vol
|
||||
/// </summary>
|
||||
public sealed class DymoiValidationTests
|
||||
public sealed class DymiValidationTests
|
||||
{
|
||||
private const double Tolerance = 1e-10;
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class DymoiValidationTests
|
||||
TSeries source = bars.Close;
|
||||
|
||||
// Streaming
|
||||
var streaming = new Dymoi(14, 5, 10, 3, 30);
|
||||
var streaming = new Dymi(14, 5, 10, 3, 30);
|
||||
var streamVals = new double[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
@@ -35,7 +35,7 @@ public sealed class DymoiValidationTests
|
||||
}
|
||||
|
||||
// Batch TSeries
|
||||
TSeries batchTs = Dymoi.Batch(source, 14, 5, 10, 3, 30);
|
||||
TSeries batchTs = Dymi.Batch(source, 14, 5, 10, 3, 30);
|
||||
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
@@ -51,11 +51,11 @@ public sealed class DymoiValidationTests
|
||||
TSeries source = bars.Close;
|
||||
|
||||
// Batch TSeries
|
||||
TSeries batchTs = Dymoi.Batch(source, 14, 5, 10, 3, 30);
|
||||
TSeries batchTs = Dymi.Batch(source, 14, 5, 10, 3, 30);
|
||||
|
||||
// Span batch
|
||||
var spanOut = new double[source.Count];
|
||||
Dymoi.Batch(source.Values, spanOut, 14, 5, 10, 3, 30);
|
||||
Dymi.Batch(source.Values, spanOut, 14, 5, 10, 3, 30);
|
||||
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
@@ -71,7 +71,7 @@ public sealed class DymoiValidationTests
|
||||
TSeries source = bars.Close;
|
||||
|
||||
// Streaming
|
||||
var streaming = new Dymoi(14, 5, 10, 3, 30);
|
||||
var streaming = new Dymi(14, 5, 10, 3, 30);
|
||||
var streamVals = new double[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
@@ -80,12 +80,12 @@ public sealed class DymoiValidationTests
|
||||
|
||||
// Event-based
|
||||
var eventTs = new TSeries();
|
||||
var eventDymoi = new Dymoi(eventTs, 14, 5, 10, 3, 30);
|
||||
var eventDymi = new Dymi(eventTs, 14, 5, 10, 3, 30);
|
||||
var eventVals = new double[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
eventTs.Add(source[i]);
|
||||
eventVals[i] = eventDymoi.Last.Value;
|
||||
eventVals[i] = eventDymi.Last.Value;
|
||||
}
|
||||
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
@@ -102,11 +102,11 @@ public sealed class DymoiValidationTests
|
||||
var gbm = new GBM(startPrice: 50.0, mu: 0.05, sigma: 0.8, seed: 3004);
|
||||
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var d = new Dymoi(14, 5, 10, 3, 30);
|
||||
var d = new Dymi(14, 5, 10, 3, 30);
|
||||
foreach (var bar in bars.Close)
|
||||
{
|
||||
double v = d.Update(bar).Value;
|
||||
Assert.True(v >= 0.0 && v <= 100.0, $"DYMOI={v} at high vol");
|
||||
Assert.True(v >= 0.0 && v <= 100.0, $"DYMI={v} at high vol");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,11 +117,11 @@ public sealed class DymoiValidationTests
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.001, sigma: 0.01, seed: 3005);
|
||||
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var d = new Dymoi(14, 5, 10, 3, 30);
|
||||
var d = new Dymi(14, 5, 10, 3, 30);
|
||||
foreach (var bar in bars.Close)
|
||||
{
|
||||
double v = d.Update(bar).Value;
|
||||
Assert.True(v >= 0.0 && v <= 100.0, $"DYMOI={v} at low vol");
|
||||
Assert.True(v >= 0.0 && v <= 100.0, $"DYMI={v} at low vol");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ public sealed class DymoiValidationTests
|
||||
// For the true identity test: construct a series with constant differences
|
||||
// such that a window of any size yields the same stddev.
|
||||
// A simpler verification: at V=1, dynPeriod = round(basePeriod/1) = basePeriod.
|
||||
// We verify that DYMOI output matches Rsi(basePeriod) on a constant-drift series.
|
||||
// We verify that DYMI output matches Rsi(basePeriod) on a constant-drift series.
|
||||
|
||||
// Construct a series with perfectly constant increments → stddev of close levels
|
||||
// is the same in short and long windows only if windows cover the same prices,
|
||||
@@ -147,19 +147,19 @@ public sealed class DymoiValidationTests
|
||||
// close periods and checking that output is nearly identical to standard RSI.
|
||||
|
||||
// Using longPeriod just 1 more than shortPeriod and monitoring range
|
||||
var d = new Dymoi(basePeriod: 14, shortPeriod: 9, longPeriod: 10, minPeriod: 14, maxPeriod: 14);
|
||||
var d = new Dymi(basePeriod: 14, shortPeriod: 9, longPeriod: 10, minPeriod: 14, maxPeriod: 14);
|
||||
var rsi = new Rsi(14);
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.01, sigma: 0.15, seed: 3006);
|
||||
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
// When minPeriod == maxPeriod == basePeriod, dynPeriod is always fixed at basePeriod
|
||||
// → DYMOI is identical to standard RSI(basePeriod)
|
||||
// → DYMI is identical to standard RSI(basePeriod)
|
||||
foreach (var bar in bars.Close)
|
||||
{
|
||||
double dymoiVal = d.Update(bar).Value;
|
||||
double dymiVal = d.Update(bar).Value;
|
||||
double rsiVal = rsi.Update(bar).Value;
|
||||
// With fixed dynPeriod=14, both should match
|
||||
Assert.Equal(rsiVal, dymoiVal, 1e-9);
|
||||
Assert.Equal(rsiVal, dymiVal, 1e-9);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,8 +170,8 @@ public sealed class DymoiValidationTests
|
||||
{
|
||||
// When short-term vol > long-term vol (V > 1), dynPeriod < basePeriod.
|
||||
// We test this indirectly: high-vol data should produce faster RSI transitions.
|
||||
// In high-vol regime, DYMOI changes more rapidly than fixed-period RSI.
|
||||
var d = new Dymoi(basePeriod: 14, shortPeriod: 3, longPeriod: 20, minPeriod: 3, maxPeriod: 30);
|
||||
// In high-vol regime, DYMI changes more rapidly than fixed-period RSI.
|
||||
var d = new Dymi(basePeriod: 14, shortPeriod: 3, longPeriod: 20, minPeriod: 3, maxPeriod: 30);
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.0, sigma: 0.4, seed: 3007);
|
||||
var bars = gbm.Fetch(200, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
@@ -191,8 +191,8 @@ public sealed class DymoiValidationTests
|
||||
var bars1 = gbm1.Fetch(150, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
var bars2 = gbm2.Fetch(150, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
|
||||
var d1 = new Dymoi(14, 5, 10, 3, 30);
|
||||
var d2 = new Dymoi(14, 5, 10, 3, 30);
|
||||
var d1 = new Dymi(14, 5, 10, 3, 30);
|
||||
var d2 = new Dymi(14, 5, 10, 3, 30);
|
||||
|
||||
for (int i = 0; i < bars1.Close.Count; i++)
|
||||
{
|
||||
@@ -207,7 +207,7 @@ public sealed class DymoiValidationTests
|
||||
{
|
||||
var src = Array.Empty<double>();
|
||||
var out1 = Array.Empty<double>();
|
||||
Dymoi.Batch(src, out1);
|
||||
Dymi.Batch(src, out1);
|
||||
Assert.Empty(out1);
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ public sealed class DymoiValidationTests
|
||||
public void Streaming_ConstantPrice_ProducesStable50()
|
||||
{
|
||||
// When price is constant, gain=0, loss=0 → RSI = 50
|
||||
var d = new Dymoi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var d = new Dymi(basePeriod: 14, shortPeriod: 5, longPeriod: 10, minPeriod: 3, maxPeriod: 30);
|
||||
var t = DateTime.UtcNow;
|
||||
double last = 50.0;
|
||||
for (int i = 0; i < 100; i++)
|
||||
@@ -228,7 +228,7 @@ public sealed class DymoiValidationTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dymoi_MatchesOoples_Structural()
|
||||
public void Dymi_MatchesOoples_Structural()
|
||||
{
|
||||
var gbm = new GBM(startPrice: 100.0, mu: 0.02, sigma: 0.15, seed: 42);
|
||||
var bars = gbm.Fetch(500, DateTime.UtcNow.Ticks, TimeSpan.FromMinutes(1));
|
||||
Reference in New Issue
Block a user