Skip to content

Commit 7614afa

Browse files
Restore ewmVol to original RMS behavior; fix XML doc category tags
- ewmVol (Series/Frame): restore original RiskMetrics RMS formula from main, preserving exact backward-compatible behavior for existing consumers - ewmVol: update Obsolete message to redirect to ewmVolRMS (same formula) or ewmVolStdDev (mean-corrected standard deviation) - ewmVol Frame: inline original RMS logic to avoid internal deprecation warning - Fix all [category: ...] style XML doc comments to proper <category>...</category> XML format (ewmVolRMS x2, ewmVol x2) - Rename misleading test name from 'ewmVol on non-returns...' to 'ewmVolStdDev on non-returns...' since it tests ewmVolStdDev Co-authored-by: Copilot <[email protected]>
1 parent 3cf98bc commit 7614afa

2 files changed

Lines changed: 46 additions & 11 deletions

File tree

src/Deedle.Math/Finance.fs

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ type Finance =
4949
/// Computes vol as sqrt(EWM(x²)), which equals ewmMean for strictly positive sequences.
5050
/// Appropriate for returns series that are already mean-centred (e.g. zero-mean returns).
5151
///
52-
/// [category: Exponentially Weighted Moving]
52+
/// <category>Exponentially Weighted Moving</category>
5353
static member ewmVolRMS (x:Series<'R, float>, ?com, ?span, ?halfLife, ?alpha) =
5454
let alpha = StatsInternal.ewDecay(com, span, halfLife, alpha)
5555
let x = x |> Series.dropMissing
@@ -69,7 +69,7 @@ type Finance =
6969
/// Exponentially weighted moving volatility using root mean square (no mean correction)
7070
/// applied to each column of a frame.
7171
///
72-
/// [category: Exponentially Weighted Moving]
72+
/// <category>Exponentially Weighted Moving</category>
7373
static member ewmVolRMS (df:Frame<'R, 'C>, ?com, ?span, ?halfLife, ?alpha) =
7474
let alpha = StatsInternal.ewDecay(com, span, halfLife, alpha)
7575
df
@@ -79,19 +79,54 @@ type Finance =
7979

8080
/// Exponentially weighted moving volatility on series.
8181
///
82-
/// [category: Exponentially Weighted Moving]
83-
[<Obsolete("ewmVol is deprecated. Use ewmVolStdDev for mean-corrected standard deviation or ewmVolRMS for root-mean-square volatility.")>]
82+
/// <category>Exponentially Weighted Moving</category>
83+
[<Obsolete("ewmVol is deprecated. Use ewmVolRMS for the same root-mean-square behaviour, or ewmVolStdDev for mean-corrected standard deviation.")>]
8484
static member ewmVol (x:Series<'R, float>, ?com, ?span, ?halfLife, ?alpha) =
85+
// Return to RiskMetrics: The Evolution of a Standard
86+
// https://www.msci.com/documents/10199/dbb975aa-5dc2-4441-aa2d-ae34ab5f0945
8587
let alpha = StatsInternal.ewDecay(com, span, halfLife, alpha)
86-
Finance.ewmVolStdDev(x, alpha = alpha)
88+
let x = x |> Series.dropMissing
89+
if x.KeyCount < 2 then
90+
x |> Series.mapValues(fun _ -> nan)
91+
else
92+
let init = x |> Stats.stdDev
93+
let data = x.Values |> Array.ofSeq
94+
let res = Array.zeroCreate x.KeyCount
95+
for i in [|0..x.KeyCount-1|] do
96+
if i = 0 then
97+
res.[i] <- init
98+
else
99+
let prev = res.[i-1]
100+
let curr = data.[i]
101+
res.[i] <- Math.Sqrt((1. - alpha) * prev * prev + alpha * curr * curr)
102+
Series(x.Keys, res)
87103

88104
/// Exponentially weighted moving volatility on frame.
89105
///
90-
/// [category: Exponentially Weighted Moving]
91-
[<Obsolete("ewmVol is deprecated. Use ewmVolStdDev for mean-corrected standard deviation or ewmVolRMS for root-mean-square volatility.")>]
106+
/// <category>Exponentially Weighted Moving</category>
107+
[<Obsolete("ewmVol is deprecated. Use ewmVolRMS for the same root-mean-square behaviour, or ewmVolStdDev for mean-corrected standard deviation.")>]
92108
static member ewmVol (df:Frame<'R, 'C>, ?com, ?span, ?halfLife, ?alpha) =
93109
let alpha = StatsInternal.ewDecay(com, span, halfLife, alpha)
94-
Finance.ewmVolStdDev(df, alpha = alpha)
110+
df
111+
|> Frame.getNumericCols
112+
|> Series.mapValues(fun series ->
113+
// Inline original ewmVol series logic to avoid deprecation warning at call site
114+
let x = series |> Series.dropMissing
115+
if x.KeyCount < 2 then
116+
x |> Series.mapValues(fun _ -> nan)
117+
else
118+
let init = x |> Stats.stdDev
119+
let data = x.Values |> Array.ofSeq
120+
let res = Array.zeroCreate x.KeyCount
121+
for i in [|0..x.KeyCount-1|] do
122+
if i = 0 then
123+
res.[i] <- init
124+
else
125+
let prev = res.[i-1]
126+
let curr = data.[i]
127+
res.[i] <- Math.Sqrt((1. - alpha) * prev * prev + alpha * curr * curr)
128+
Series(x.Keys, res))
129+
|> Frame.ofColumns
95130

96131
/// Exponentially weighted moving variance on series
97132
static member ewmVar (x:Series<'R, float>, ?com, ?span, ?halfLife, ?alpha) =

tests/Deedle.Math.Tests/Finance.fs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ let ``Ex-ante vol of equally weighted portfolio using exponentially weighted cov
5151
annualVol1 |> should beWithin (annualVol2 +/- 1e-6)
5252

5353
[<Test>]
54-
let ``ewmVol on non-returns series should return standard deviation not root-mean-square (issue #555)`` () =
55-
// For a monotonically increasing sequence, ewmVol should give std (~5),
56-
// not a value near the mean (~45) as the old incorrect formula produced.
54+
let ``ewmVolStdDev on non-returns series should return standard deviation not root-mean-square (issue #555)`` () =
55+
// For a monotonically increasing sequence, ewmVolStdDev should give std (~5),
56+
// not a value near the mean (~45) as the old incorrect ewmVol formula produced.
5757
let s = Series.ofValues [ for i in 1.0 .. 50.0 -> i ]
5858
let vol = Finance.ewmVolStdDev(s, span = 10.)
5959
let lastVol = vol |> Series.lastValue

0 commit comments

Comments
 (0)