@stvy/fund-indicators 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +79 -0
- package/AGENTS.md +322 -0
- package/dist/index.cjs +1615 -0
- package/dist/index.d.cts +779 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.mjs +1518 -0
- package/package.json +10 -29
- package/pnpm-workspace.yaml +2 -0
- package/src/dca.ts +420 -0
- package/src/index.ts +133 -0
- package/src/jstat.d.ts +17 -0
- package/src/pattern.ts +447 -0
- package/src/risk.ts +516 -0
- package/src/statistics.ts +428 -0
- package/src/technical.ts +738 -0
- package/src/types.ts +369 -0
- package/test/index.test.ts +355 -0
- package/tsconfig.json +20 -0
- package/dist/browser/fund-indicators.esm.js +0 -7505
- package/dist/browser/fund-indicators.esm.min.js +0 -8
- package/dist/browser/fund-indicators.esm.min.js.map +0 -7
- package/dist/browser/fund-indicators.js +0 -7517
- package/dist/browser/fund-indicators.min.js +0 -8
- package/dist/browser/fund-indicators.min.js.map +0 -7
- package/dist/dca.d.ts +0 -91
- package/dist/dca.d.ts.map +0 -1
- package/dist/dca.js +0 -354
- package/dist/dca.js.map +0 -1
- package/dist/index.d.ts +0 -18
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -141
- package/dist/index.js.map +0 -1
- package/dist/pattern.d.ts +0 -60
- package/dist/pattern.d.ts.map +0 -1
- package/dist/pattern.js +0 -386
- package/dist/pattern.js.map +0 -1
- package/dist/risk.d.ts +0 -115
- package/dist/risk.d.ts.map +0 -1
- package/dist/risk.js +0 -502
- package/dist/risk.js.map +0 -1
- package/dist/statistics.d.ts +0 -78
- package/dist/statistics.d.ts.map +0 -1
- package/dist/statistics.js +0 -402
- package/dist/statistics.js.map +0 -1
- package/dist/technical.d.ts +0 -105
- package/dist/technical.d.ts.map +0 -1
- package/dist/technical.js +0 -633
- package/dist/technical.js.map +0 -1
- package/dist/types.d.ts +0 -327
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -7
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 统计特征模块
|
|
3
|
+
* 包含:偏度、峰度、赫斯特指数、自相关、Jarque-Bera 检验等
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as ss from 'simple-statistics';
|
|
7
|
+
import { NavSeries, ReturnSeries, StatisticalFeatures, HurstResult, AutocorrelationResult } from './types';
|
|
8
|
+
import { navToReturns } from './risk';
|
|
9
|
+
|
|
10
|
+
// ============================================================
|
|
11
|
+
// 统计特征汇总
|
|
12
|
+
// ============================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 计算收益率的完整统计特征
|
|
16
|
+
* @param nav 净值序列(自动转换为收益率)
|
|
17
|
+
*/
|
|
18
|
+
export function statisticalFeatures(nav: NavSeries): StatisticalFeatures {
|
|
19
|
+
const returns = navToReturns(nav);
|
|
20
|
+
if (returns.length < 4) {
|
|
21
|
+
return {
|
|
22
|
+
mean: 0, median: 0, stdDev: 0, skewness: 0, kurtosis: 0,
|
|
23
|
+
min: 0, max: 0, range: 0, coefficientOfVariation: 0, jarqueBera: 0,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const mean = ss.mean(returns);
|
|
28
|
+
const median = ss.median(returns);
|
|
29
|
+
const stdDev = ss.standardDeviation(returns);
|
|
30
|
+
const skewness = ss.sampleSkewness(returns);
|
|
31
|
+
const kurtosis = ss.sampleKurtosis(returns);
|
|
32
|
+
const min = ss.min(returns);
|
|
33
|
+
const max = ss.max(returns);
|
|
34
|
+
const range = max - min;
|
|
35
|
+
const coefficientOfVariation = mean !== 0 ? stdDev / Math.abs(mean) : 0;
|
|
36
|
+
|
|
37
|
+
// Jarque-Bera 检验 = n/6 * (S² + K²/4)
|
|
38
|
+
const n = returns.length;
|
|
39
|
+
const jarqueBera = (n / 6) * (skewness * skewness + (kurtosis * kurtosis) / 4);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
mean, median, stdDev, skewness, kurtosis,
|
|
43
|
+
min, max, range, coefficientOfVariation, jarqueBera,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 计算净值序列(而非收益率)的统计特征
|
|
49
|
+
* @param nav 净值序列
|
|
50
|
+
*/
|
|
51
|
+
export function navStatisticalFeatures(nav: NavSeries): StatisticalFeatures {
|
|
52
|
+
if (nav.length < 4) {
|
|
53
|
+
return {
|
|
54
|
+
mean: 0, median: 0, stdDev: 0, skewness: 0, kurtosis: 0,
|
|
55
|
+
min: 0, max: 0, range: 0, coefficientOfVariation: 0, jarqueBera: 0,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const mean = ss.mean(nav);
|
|
60
|
+
const median = ss.median(nav);
|
|
61
|
+
const stdDev = ss.standardDeviation(nav);
|
|
62
|
+
const skewness = ss.sampleSkewness(nav);
|
|
63
|
+
const kurtosis = ss.sampleKurtosis(nav);
|
|
64
|
+
const min = ss.min(nav);
|
|
65
|
+
const max = ss.max(nav);
|
|
66
|
+
const range = max - min;
|
|
67
|
+
const coefficientOfVariation = mean !== 0 ? stdDev / Math.abs(mean) : 0;
|
|
68
|
+
const n = nav.length;
|
|
69
|
+
const jarqueBera = (n / 6) * (skewness * skewness + (kurtosis * kurtosis) / 4);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
mean, median, stdDev, skewness, kurtosis,
|
|
73
|
+
min, max, range, coefficientOfVariation, jarqueBera,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============================================================
|
|
78
|
+
// 赫斯特指数 (R/S Analysis)
|
|
79
|
+
// ============================================================
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 赫斯特指数 - 通过 R/S 分析(重极差分析)计算
|
|
83
|
+
*
|
|
84
|
+
* 结果解读:
|
|
85
|
+
* - H > 0.5:趋势性(persistent),过去涨未来大概率继续涨
|
|
86
|
+
* - H < 0.5:均值回归(anti-persistent),过去涨未来大概率回落
|
|
87
|
+
* - H ≈ 0.5:随机游走,无可预测性
|
|
88
|
+
*
|
|
89
|
+
* @param nav 净值序列
|
|
90
|
+
* @param minWindowSize 最小窗口大小(默认16)
|
|
91
|
+
* @param maxWindowSize 最大窗口大小(默认为序列长度的1/2)
|
|
92
|
+
* @param numPoints 采样点数(默认10)
|
|
93
|
+
*/
|
|
94
|
+
export function hurstExponent(
|
|
95
|
+
nav: NavSeries,
|
|
96
|
+
minWindowSize: number = 16,
|
|
97
|
+
maxWindowSize?: number,
|
|
98
|
+
numPoints: number = 10
|
|
99
|
+
): HurstResult {
|
|
100
|
+
const returns = navToReturns(nav);
|
|
101
|
+
const n = returns.length;
|
|
102
|
+
|
|
103
|
+
if (n < minWindowSize * 2) {
|
|
104
|
+
return {
|
|
105
|
+
hurstExponent: 0.5,
|
|
106
|
+
interpretation: 'random_walk',
|
|
107
|
+
dataPoints: [],
|
|
108
|
+
rSquared: 0,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const maxWin = maxWindowSize ?? Math.floor(n / 2);
|
|
113
|
+
const dataPoints: { logN: number; logRS: number }[] = [];
|
|
114
|
+
|
|
115
|
+
// 生成不同窗口大小(对数等间距)
|
|
116
|
+
const logMin = Math.log(minWindowSize);
|
|
117
|
+
const logMax = Math.log(maxWin);
|
|
118
|
+
|
|
119
|
+
for (let p = 0; p < numPoints; p++) {
|
|
120
|
+
const logWindowSize = logMin + (p / (numPoints - 1)) * (logMax - logMin);
|
|
121
|
+
const windowSize = Math.floor(Math.exp(logWindowSize));
|
|
122
|
+
|
|
123
|
+
if (windowSize < 2 || windowSize > n) continue;
|
|
124
|
+
|
|
125
|
+
// 将数据分为多个大小为 windowSize 的子区间
|
|
126
|
+
const numSubPeriods = Math.floor(n / windowSize);
|
|
127
|
+
if (numSubPeriods < 1) continue;
|
|
128
|
+
|
|
129
|
+
let totalRS = 0;
|
|
130
|
+
let validCount = 0;
|
|
131
|
+
|
|
132
|
+
for (let s = 0; s < numSubPeriods; s++) {
|
|
133
|
+
const start = s * windowSize;
|
|
134
|
+
const subPeriod = returns.slice(start, start + windowSize);
|
|
135
|
+
|
|
136
|
+
const mean = ss.mean(subPeriod);
|
|
137
|
+
const std = ss.standardDeviation(subPeriod);
|
|
138
|
+
|
|
139
|
+
if (std === 0) continue;
|
|
140
|
+
|
|
141
|
+
// 计算累积偏差序列
|
|
142
|
+
const cumDeviations: number[] = [];
|
|
143
|
+
let cumDev = 0;
|
|
144
|
+
for (const r of subPeriod) {
|
|
145
|
+
cumDev += r - mean;
|
|
146
|
+
cumDeviations.push(cumDev);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// R = max(cumDev) - min(cumDev)
|
|
150
|
+
const range = Math.max(...cumDeviations) - Math.min(...cumDeviations);
|
|
151
|
+
// S = 标准差
|
|
152
|
+
const rs = range / std;
|
|
153
|
+
totalRS += rs;
|
|
154
|
+
validCount++;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (validCount > 0) {
|
|
158
|
+
const avgRS = totalRS / validCount;
|
|
159
|
+
dataPoints.push({ logN: Math.log(windowSize), logRS: Math.log(avgRS) });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (dataPoints.length < 3) {
|
|
164
|
+
return {
|
|
165
|
+
hurstExponent: 0.5,
|
|
166
|
+
interpretation: 'random_walk',
|
|
167
|
+
dataPoints,
|
|
168
|
+
rSquared: 0,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 对 log(N) 和 log(R/S) 做线性回归,斜率即为 Hurst 指数
|
|
173
|
+
const regressionData = dataPoints.map((d) => [d.logN, d.logRS] as [number, number]);
|
|
174
|
+
const regression = ss.linearRegression(regressionData);
|
|
175
|
+
const H = regression.m;
|
|
176
|
+
|
|
177
|
+
// 计算 R²
|
|
178
|
+
const predicted = dataPoints.map((d) => regression.m * d.logN + regression.b);
|
|
179
|
+
const actual = dataPoints.map((d) => d.logRS);
|
|
180
|
+
const meanActual = ss.mean(actual);
|
|
181
|
+
const ssTotal = actual.reduce((sum, v) => sum + (v - meanActual) ** 2, 0);
|
|
182
|
+
const ssResidual = actual.reduce((sum, v, i) => sum + (v - predicted[i]) ** 2, 0);
|
|
183
|
+
const rSquared = ssTotal > 0 ? 1 - ssResidual / ssTotal : 0;
|
|
184
|
+
|
|
185
|
+
// 判断结果
|
|
186
|
+
let interpretation: HurstResult['interpretation'];
|
|
187
|
+
if (H > 0.55) interpretation = 'trending';
|
|
188
|
+
else if (H < 0.45) interpretation = 'mean_reverting';
|
|
189
|
+
else interpretation = 'random_walk';
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
hurstExponent: Math.max(0, Math.min(1, H)), // 限制在 [0, 1]
|
|
193
|
+
interpretation,
|
|
194
|
+
dataPoints,
|
|
195
|
+
rSquared,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ============================================================
|
|
200
|
+
// 自相关分析
|
|
201
|
+
// ============================================================
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 计算收益率序列的自相关系数(ACF)
|
|
205
|
+
* @param nav 净值序列
|
|
206
|
+
* @param maxLag 最大滞后阶数(默认20)
|
|
207
|
+
*/
|
|
208
|
+
export function autocorrelation(nav: NavSeries, maxLag: number = 20): AutocorrelationResult {
|
|
209
|
+
const returns = navToReturns(nav);
|
|
210
|
+
const n = returns.length;
|
|
211
|
+
|
|
212
|
+
if (n < maxLag + 2) {
|
|
213
|
+
maxLag = Math.max(1, n - 2);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const mean = ss.mean(returns);
|
|
217
|
+
const variance = returns.reduce((sum, r) => sum + (r - mean) ** 2, 0) / n;
|
|
218
|
+
|
|
219
|
+
const coefficients: number[] = [];
|
|
220
|
+
|
|
221
|
+
for (let lag = 0; lag <= maxLag; lag++) {
|
|
222
|
+
if (variance === 0) {
|
|
223
|
+
coefficients.push(lag === 0 ? 1 : 0);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
let cov = 0;
|
|
227
|
+
for (let i = 0; i < n - lag; i++) {
|
|
228
|
+
cov += (returns[i] - mean) * (returns[i + lag] - mean);
|
|
229
|
+
}
|
|
230
|
+
cov /= n;
|
|
231
|
+
coefficients.push(cov / variance);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 判断趋势持续性(基于 lag=1 的自相关系数)
|
|
235
|
+
let interpretation: AutocorrelationResult['interpretation'];
|
|
236
|
+
const lag1 = coefficients.length > 1 ? coefficients[1] : 0;
|
|
237
|
+
if (lag1 > 0.05) interpretation = 'persistent';
|
|
238
|
+
else if (lag1 < -0.05) interpretation = 'anti_persistent';
|
|
239
|
+
else interpretation = 'no_correlation';
|
|
240
|
+
|
|
241
|
+
return { coefficients, maxLag, interpretation };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Ljung-Box 检验 - 检测自相关的统计显著性
|
|
246
|
+
* @param returns 收益率序列
|
|
247
|
+
* @param maxLag 最大滞后阶数
|
|
248
|
+
* @returns Q 统计量和 p 值近似
|
|
249
|
+
*/
|
|
250
|
+
export function ljungBoxTest(returns: ReturnSeries, maxLag: number = 10): { qStatistic: number; approximatePValue: number } {
|
|
251
|
+
const n = returns.length;
|
|
252
|
+
const mean = ss.mean(returns);
|
|
253
|
+
const variance = returns.reduce((sum, r) => sum + (r - mean) ** 2, 0) / n;
|
|
254
|
+
|
|
255
|
+
if (variance === 0) return { qStatistic: 0, approximatePValue: 1 };
|
|
256
|
+
|
|
257
|
+
// 计算各滞后期的自相关系数
|
|
258
|
+
const acf: number[] = [];
|
|
259
|
+
for (let k = 1; k <= maxLag; k++) {
|
|
260
|
+
let cov = 0;
|
|
261
|
+
for (let i = 0; i < n - k; i++) {
|
|
262
|
+
cov += (returns[i] - mean) * (returns[i + k] - mean);
|
|
263
|
+
}
|
|
264
|
+
cov /= n;
|
|
265
|
+
acf.push(cov / variance);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Q = n(n+2) * Σ(ρk² / (n-k))
|
|
269
|
+
let q = 0;
|
|
270
|
+
for (let k = 0; k < maxLag; k++) {
|
|
271
|
+
q += (acf[k] * acf[k]) / (n - k - 1);
|
|
272
|
+
}
|
|
273
|
+
q *= n * (n + 2);
|
|
274
|
+
|
|
275
|
+
// p-value 近似(卡方分布,自由度=maxLag)
|
|
276
|
+
// 使用简化的正态近似
|
|
277
|
+
const df = maxLag;
|
|
278
|
+
const z = Math.pow(q / df, 1 / 3) - (1 - 2 / (9 * df));
|
|
279
|
+
const se = Math.sqrt(2 / (9 * df));
|
|
280
|
+
const zScore = z / se;
|
|
281
|
+
|
|
282
|
+
// 近似 p-value(使用正态分布近似卡方分布的立方根变换)
|
|
283
|
+
const pValue = 1 - normalCDF(zScore);
|
|
284
|
+
|
|
285
|
+
return { qStatistic: q, approximatePValue: Math.max(0, Math.min(1, pValue)) };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** 标准正态分布 CDF 近似 */
|
|
289
|
+
function normalCDF(x: number): number {
|
|
290
|
+
// Abramowitz and Stegun approximation
|
|
291
|
+
const a1 = 0.254829592;
|
|
292
|
+
const a2 = -0.284496736;
|
|
293
|
+
const a3 = 1.421413741;
|
|
294
|
+
const a4 = -1.453152027;
|
|
295
|
+
const a5 = 1.061405429;
|
|
296
|
+
const p = 0.3275911;
|
|
297
|
+
|
|
298
|
+
const sign = x < 0 ? -1 : 1;
|
|
299
|
+
x = Math.abs(x) / Math.sqrt(2);
|
|
300
|
+
const t = 1.0 / (1.0 + p * x);
|
|
301
|
+
const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
|
|
302
|
+
return 0.5 * (1.0 + sign * y);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ============================================================
|
|
306
|
+
// 收益率分布特征
|
|
307
|
+
// ============================================================
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 收益率分位数分析
|
|
311
|
+
* @param nav 净值序列
|
|
312
|
+
* @param quantiles 要计算的分位数列表
|
|
313
|
+
*/
|
|
314
|
+
export function returnQuantiles(
|
|
315
|
+
nav: NavSeries,
|
|
316
|
+
quantiles: number[] = [0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99]
|
|
317
|
+
): Map<number, number> {
|
|
318
|
+
const returns = navToReturns(nav);
|
|
319
|
+
const sorted = [...returns].sort((a, b) => a - b);
|
|
320
|
+
const result = new Map<number, number>();
|
|
321
|
+
for (const q of quantiles) {
|
|
322
|
+
result.set(q, ss.quantileSorted(sorted, q));
|
|
323
|
+
}
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* 偏度的滚动计算
|
|
329
|
+
* @param returns 收益率序列
|
|
330
|
+
* @param window 滚动窗口
|
|
331
|
+
*/
|
|
332
|
+
export function rollingSkewness(returns: ReturnSeries, window: number = 60): (number | null)[] {
|
|
333
|
+
const result: (number | null)[] = [];
|
|
334
|
+
for (let i = 0; i < returns.length; i++) {
|
|
335
|
+
if (i < window - 1) {
|
|
336
|
+
result.push(null);
|
|
337
|
+
} else {
|
|
338
|
+
const slice = returns.slice(i - window + 1, i + 1);
|
|
339
|
+
result.push(ss.sampleSkewness(slice));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* 峰度的滚动计算
|
|
347
|
+
* @param returns 收益率序列
|
|
348
|
+
* @param window 滚动窗口
|
|
349
|
+
*/
|
|
350
|
+
export function rollingKurtosis(returns: ReturnSeries, window: number = 60): (number | null)[] {
|
|
351
|
+
const result: (number | null)[] = [];
|
|
352
|
+
for (let i = 0; i < returns.length; i++) {
|
|
353
|
+
if (i < window - 1) {
|
|
354
|
+
result.push(null);
|
|
355
|
+
} else {
|
|
356
|
+
const slice = returns.slice(i - window + 1, i + 1);
|
|
357
|
+
result.push(ss.sampleKurtosis(slice));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ============================================================
|
|
364
|
+
// GARCH(1,1) 波动率预测(简化实现)
|
|
365
|
+
// ============================================================
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* GARCH(1,1) 波动率预测 - 简化版
|
|
369
|
+
* 使用矩估计法近似参数,非最大似然估计
|
|
370
|
+
*
|
|
371
|
+
* @param returns 收益率序列
|
|
372
|
+
* @returns 条件方差序列和预测的下一期方差
|
|
373
|
+
*/
|
|
374
|
+
export function garch11(returns: ReturnSeries): {
|
|
375
|
+
conditionalVariance: number[];
|
|
376
|
+
nextPeriodForecast: number;
|
|
377
|
+
omega: number;
|
|
378
|
+
alpha: number;
|
|
379
|
+
beta: number;
|
|
380
|
+
} {
|
|
381
|
+
const n = returns.length;
|
|
382
|
+
if (n < 30) {
|
|
383
|
+
const v = ss.variance(returns);
|
|
384
|
+
return {
|
|
385
|
+
conditionalVariance: new Array(n).fill(v),
|
|
386
|
+
nextPeriodForecast: v,
|
|
387
|
+
omega: v * 0.1,
|
|
388
|
+
alpha: 0.1,
|
|
389
|
+
beta: 0.8,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// 简化参数估计(矩估计法)
|
|
394
|
+
const unconditionalVar = ss.variance(returns);
|
|
395
|
+
|
|
396
|
+
// 用收益率平方的自相关来估计 alpha + beta
|
|
397
|
+
const squaredReturns = returns.map((r) => r * r);
|
|
398
|
+
const meanSquared = ss.mean(squaredReturns);
|
|
399
|
+
|
|
400
|
+
// 估计一阶自相关
|
|
401
|
+
let autocov = 0;
|
|
402
|
+
for (let i = 0; i < n - 1; i++) {
|
|
403
|
+
autocov += (squaredReturns[i] - meanSquared) * (squaredReturns[i + 1] - meanSquared);
|
|
404
|
+
}
|
|
405
|
+
autocov /= n;
|
|
406
|
+
const autoCorr = meanSquared > 0 ? autocov / (ss.variance(squaredReturns)) : 0;
|
|
407
|
+
|
|
408
|
+
// 简化的参数估计
|
|
409
|
+
const alphaBeta = Math.max(0.5, Math.min(0.99, autoCorr + 0.8));
|
|
410
|
+
const alpha = Math.max(0.01, Math.min(0.3, (1 - alphaBeta) * 2));
|
|
411
|
+
const beta = alphaBeta - alpha;
|
|
412
|
+
const omega = unconditionalVar * (1 - alpha - beta);
|
|
413
|
+
|
|
414
|
+
// 递归计算条件方差
|
|
415
|
+
const conditionalVariance: number[] = [unconditionalVar];
|
|
416
|
+
for (let i = 1; i < n; i++) {
|
|
417
|
+
const prevVar = conditionalVariance[i - 1];
|
|
418
|
+
const newVar = omega + alpha * returns[i - 1] * returns[i - 1] + beta * prevVar;
|
|
419
|
+
conditionalVariance.push(Math.max(newVar, 1e-10));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// 预测下一期
|
|
423
|
+
const lastVar = conditionalVariance[n - 1];
|
|
424
|
+
const lastReturn = returns[n - 1];
|
|
425
|
+
const nextPeriodForecast = omega + alpha * lastReturn * lastReturn + beta * lastVar;
|
|
426
|
+
|
|
427
|
+
return { conditionalVariance, nextPeriodForecast, omega, alpha, beta };
|
|
428
|
+
}
|