@stvy/fund-indicators 1.0.0 → 1.0.5

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.
Files changed (52) hide show
  1. package/.github/workflows/publish.yml +73 -0
  2. package/AGENTS.md +322 -0
  3. package/dist/index.cjs +7014 -0
  4. package/dist/index.d.cts +779 -0
  5. package/dist/index.d.cts.map +1 -0
  6. package/dist/index.mjs +6917 -0
  7. package/package.json +15 -32
  8. package/pnpm-workspace.yaml +2 -0
  9. package/src/dca.ts +420 -0
  10. package/src/index.ts +133 -0
  11. package/src/jstat.d.ts +17 -0
  12. package/src/pattern.ts +447 -0
  13. package/src/risk.ts +516 -0
  14. package/src/statistics.ts +428 -0
  15. package/src/technical.ts +738 -0
  16. package/src/types.ts +369 -0
  17. package/test/index.test.ts +355 -0
  18. package/tsconfig.json +20 -0
  19. package/dist/browser/fund-indicators.esm.js +0 -7505
  20. package/dist/browser/fund-indicators.esm.min.js +0 -8
  21. package/dist/browser/fund-indicators.esm.min.js.map +0 -7
  22. package/dist/browser/fund-indicators.js +0 -7517
  23. package/dist/browser/fund-indicators.min.js +0 -8
  24. package/dist/browser/fund-indicators.min.js.map +0 -7
  25. package/dist/dca.d.ts +0 -91
  26. package/dist/dca.d.ts.map +0 -1
  27. package/dist/dca.js +0 -354
  28. package/dist/dca.js.map +0 -1
  29. package/dist/index.d.ts +0 -18
  30. package/dist/index.d.ts.map +0 -1
  31. package/dist/index.js +0 -141
  32. package/dist/index.js.map +0 -1
  33. package/dist/pattern.d.ts +0 -60
  34. package/dist/pattern.d.ts.map +0 -1
  35. package/dist/pattern.js +0 -386
  36. package/dist/pattern.js.map +0 -1
  37. package/dist/risk.d.ts +0 -115
  38. package/dist/risk.d.ts.map +0 -1
  39. package/dist/risk.js +0 -502
  40. package/dist/risk.js.map +0 -1
  41. package/dist/statistics.d.ts +0 -78
  42. package/dist/statistics.d.ts.map +0 -1
  43. package/dist/statistics.js +0 -402
  44. package/dist/statistics.js.map +0 -1
  45. package/dist/technical.d.ts +0 -105
  46. package/dist/technical.d.ts.map +0 -1
  47. package/dist/technical.js +0 -633
  48. package/dist/technical.js.map +0 -1
  49. package/dist/types.d.ts +0 -327
  50. package/dist/types.d.ts.map +0 -1
  51. package/dist/types.js +0 -7
  52. package/dist/types.js.map +0 -1
@@ -0,0 +1,73 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ name: Build & Publish
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: read
13
+ id-token: write
14
+
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v6
18
+
19
+ - name: Setup pnpm
20
+ uses: pnpm/action-setup@v6
21
+
22
+ - name: Setup Node.js (LTS)
23
+ uses: actions/setup-node@v6
24
+ with:
25
+ node-version: lts/*
26
+ registry-url: "https://registry.npmjs.org"
27
+ cache: "pnpm"
28
+
29
+ - name: Install dependencies
30
+ run: pnpm install --frozen-lockfile
31
+
32
+ - name: Run tests
33
+ run: pnpm test
34
+
35
+ - name: TypeScript type check
36
+ run: pnpm exec tsc --noEmit
37
+
38
+ - name: Build (Node + Browser)
39
+ run: pnpm run build
40
+
41
+ - name: Verify build output
42
+ run: |
43
+ test -f dist/index.mjs || (echo "Missing dist/index.mjs" && exit 1)
44
+ test -f dist/index.cjs || (echo "Missing dist/index.cjs" && exit 1)
45
+ test -f dist/index.d.cts || (echo "Missing dist/index.d.cts" && exit 1)
46
+ echo "All build artifacts verified."
47
+
48
+ - name: Update package version from release tag
49
+ uses: BellCubeDev/update-package-version-by-release-tag@v2
50
+
51
+ - name: Verify version matches release tag
52
+ run: |
53
+ PKG_VERSION=$(node -p "require('./package.json').version")
54
+ TAG_VERSION=${GITHUB_REF_NAME#v}
55
+ echo "Package version: $PKG_VERSION"
56
+ echo "Release tag: $TAG_VERSION"
57
+ if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
58
+ echo "::error::package.json version ($PKG_VERSION) does not match release tag ($TAG_VERSION)"
59
+ exit 1
60
+ fi
61
+
62
+ - name: Publish to npm
63
+ run: pnpm publish --access public --no-git-checks
64
+ env:
65
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
66
+
67
+ - name: Summary
68
+ run: |
69
+ echo "## Published to npm" >> $GITHUB_STEP_SUMMARY
70
+ echo "" >> $GITHUB_STEP_SUMMARY
71
+ echo "- Package: \`@stvy/fund-indicators@${GITHUB_REF_NAME#v}\`" >> $GITHUB_STEP_SUMMARY
72
+ echo "- Registry: https://www.npmjs.com/package/@stvy/fund-indicators" >> $GITHUB_STEP_SUMMARY
73
+ echo "- Includes browser bundles ESM" >> $GITHUB_STEP_SUMMARY
package/AGENTS.md ADDED
@@ -0,0 +1,322 @@
1
+ # AGENTS.md — @stvy/fund-indicators
2
+
3
+ ## Project Overview
4
+
5
+ **@stvy/fund-indicators** is a TypeScript library for computing quantitative fund NAV (Net Asset Value) analysis indicators. Unlike stock-oriented TA libraries, this project is purpose-built for **fund NAV series** — daily closing prices with no intraday high/low and no volume data. It covers five domains: technical indicators, risk/performance metrics, statistical features, DCA (Dollar-Cost Averaging) simulation, and chart pattern recognition.
6
+
7
+ - **Package name**: `@stvy/fund-indicators`
8
+ - **License**: MIT
9
+ - **Runtime**: Node.js >= 18
10
+ - **Language**: TypeScript (strict mode, target ES2020)
11
+ - **Main entry**: `dist/index.js` (CommonJS)
12
+ - **Browser entry**: `dist/browser/fund-indicators.min.js` (IIFE) / `dist/browser/fund-indicators.esm.js` (ESM)
13
+ - **Types**: `dist/index.d.ts`
14
+
15
+ ---
16
+
17
+ ## Architecture
18
+
19
+ The source lives in `src/` with 5 feature modules, 1 type-definition file, 1 barrel export, and 1 custom type declaration:
20
+
21
+ ```
22
+ src/
23
+ index.ts — Barrel re-export of all public functions and types
24
+ types.ts — 30+ interfaces and type aliases
25
+ technical.ts — Trend, momentum, oscillator, and channel indicators
26
+ risk.ts — Volatility, drawdown, VaR/CVaR, Sharpe, Alpha/Beta, etc.
27
+ statistics.ts — Skewness, kurtosis, Hurst exponent, GARCH, autocorrelation
28
+ dca.ts — DCA simulation, IRR, smart DCA, take-profit/stop-loss
29
+ pattern.ts — Support/resistance, double bottom/top, gaps, head-and-shoulders
30
+ jstat.d.ts — Custom ambient type declaration for the jstat package
31
+ ```
32
+
33
+ ### Module Breakdown
34
+
35
+ #### `technical.ts` — Technical Indicators
36
+ Moving averages (SMA, EMA, WMA, DEMA, TEMA, KAMA), MACD, RSI, KDJ, Bollinger Bands, Donchian Channel, Keltner Channel, ADX, ATR, CCI, ROC, Momentum, Williams %R, Stochastic RSI, SAR, TRIX, DPO, Mass Index, BIAS (deviation rate), NAV percentile, MA cross-signal detection, MA alignment detection.
37
+
38
+ Uses the `technicalindicators` npm package internally (aliased as `TI_*`). Indicators that need candle input (ADX, CCI, ATR, KDJ, SAR, Williams %R) use the `navToHLC()` helper to set `high = low = close = NAV`.
39
+
40
+ #### `risk.ts` — Risk and Performance Metrics
41
+ NAV-to-returns conversion (`navToReturns`), annualized return (geometric), total return, annualized/downside/rolling volatility, volatility cone, max drawdown (with recovery detection), max drawdown duration, VaR (historical and parametric), CVaR, Sharpe/Sortino/Calmar/Treynor/Omega ratios, win rate, profit/loss ratio, profit factor, consecutive win/loss streaks, Beta, Alpha, tracking error, information ratio, and aggregate `riskMetrics()` / `performanceMetrics()` / `benchmarkMetrics()` summaries.
42
+
43
+ #### `statistics.ts` — Statistical Features
44
+ Full statistical feature set (`statisticalFeatures`, `navStatisticalFeatures`), Hurst exponent via R/S analysis, autocorrelation (ACF), Ljung-Box test, return quantiles, rolling skewness/kurtosis, GARCH(1,1) volatility forecast.
45
+
46
+ #### `dca.ts` — DCA and P&L Analysis
47
+ DCA simulation (`simulateDCA`), IRR via Newton's method with bisection fallback, take-profit/stop-loss signals, trailing stop, safety margin, price position, smart DCA multiplier, tiered buy/sell signals, position P&L calculation.
48
+
49
+ #### `pattern.ts` — Pattern Recognition
50
+ Support/resistance levels (KDE-inspired local-extrema clustering), double bottom (W) / double top (M) detection, gap detection (up/down with fill tracking), trend strength scoring (0-100, multi-factor), head-and-shoulders top/bottom identification.
51
+
52
+ ---
53
+
54
+ ## Key Conventions
55
+
56
+ ### Input: `number[]` (NAV Series)
57
+
58
+ All functions take a `number[]` representing a time-ordered NAV series as their primary input. The `NavSeries` type alias is defined in `types.ts`:
59
+
60
+ ```ts
61
+ export type NavSeries = number[];
62
+ ```
63
+
64
+ ### No Volume, No Intraday High/Low
65
+
66
+ Fund NAV has a single daily value. For candle-based indicators that expect high/low/close, the `navToHLC()` helper in `technical.ts` sets all three to the same value:
67
+
68
+ ```ts
69
+ function navToHLC(nav: NavSeries) {
70
+ return nav.map((v) => ({ high: v, low: v, close: v }));
71
+ }
72
+ ```
73
+
74
+ ### Annualization: 242 Trading Days
75
+
76
+ The constant `TRADING_DAYS_PER_YEAR = 242` in `risk.ts` reflects the A-share (China) market convention. It is used for annualizing volatility, returns, and IRR calculations throughout the library.
77
+
78
+ ### Output Arrays Are Padded with `null`
79
+
80
+ All array-returning indicators produce output arrays whose length matches the input NAV series. Leading positions (where the indicator has not yet "warmed up") are filled with `null`. This is done by the `padLeft` helper in `technical.ts`:
81
+
82
+ ```ts
83
+ function padLeft(arr: (number | undefined)[], totalLen: number): (number | null)[] {
84
+ const padLen = totalLen - arr.length;
85
+ const result: (number | null)[] = new Array(padLen).fill(null);
86
+ for (const v of arr) {
87
+ result.push(v ?? null);
88
+ }
89
+ return result;
90
+ }
91
+ ```
92
+
93
+ ### Returns Calculation
94
+
95
+ Daily returns are computed via `navToReturns()` in `risk.ts`:
96
+
97
+ ```ts
98
+ export function navToReturns(nav: NavSeries): ReturnSeries {
99
+ const returns: ReturnSeries = [];
100
+ for (let i = 1; i < nav.length; i++) {
101
+ returns.push((nav[i] - nav[i - 1]) / nav[i - 1]);
102
+ }
103
+ return returns;
104
+ }
105
+ ```
106
+
107
+ The output length is `nav.length - 1`.
108
+
109
+ ### Types in `types.ts`
110
+
111
+ All public interfaces are defined in `src/types.ts` (30+ types). Key categories:
112
+ - **Basic**: `NavSeries`, `ReturnSeries`, `DateSeries`
113
+ - **Technical**: `MAResult`, `MACDResult`, `BollingerResult`, `ChannelResult`, `RSIResult`, `KDJResult`, `ADXResult`, `SARResult`, `MACrossSignal`, `MAAlignmentResult`
114
+ - **Risk/Performance**: `DrawdownResult`, `RiskMetrics`, `PerformanceMetrics`, `BenchmarkMetrics`
115
+ - **DCA**: `DCAConfig`, `DCAResult`, `TakeProfitStopLossSignal`
116
+ - **Statistics**: `StatisticalFeatures`, `HurstResult`, `AutocorrelationResult`
117
+ - **Pattern**: `SupportResistanceResult`, `DoubleBottomTopResult`, `GapResult`
118
+
119
+ ---
120
+
121
+ ## Build System
122
+
123
+ ### Commands
124
+
125
+ | Command | Description |
126
+ |---|---|
127
+ | `npm run build:node` | `tsc` -- Compiles `src/` to `dist/*.js` + `dist/*.d.ts` (CommonJS, ES2020 target) |
128
+ | `npm run build:browser` | `node scripts/build-browser.mjs` -- Bundles with esbuild to `dist/browser/` (IIFE as `FundIndicators` global + ESM) |
129
+ | `npm run build` | Runs both `build:node` and `build:browser` sequentially |
130
+ | `npm test` | `tsx test/index.test.ts` -- Runs all tests via tsx (no compilation step needed) |
131
+ | `npx tsc --noEmit` | Type-check only, useful during development |
132
+
133
+ ### TypeScript Configuration (`tsconfig.json`)
134
+
135
+ - **target**: ES2020
136
+ - **module**: CommonJS
137
+ - **rootDir**: `./src`
138
+ - **outDir**: `./dist`
139
+ - **strict**: true
140
+ - **declaration**: true (generates `.d.ts` files)
141
+ - **declarationMap**: true
142
+ - **sourceMap**: true
143
+
144
+ ---
145
+
146
+ ## Dependencies
147
+
148
+ ### Runtime Dependencies
149
+
150
+ | Package | Purpose |
151
+ |---|---|
152
+ | `technicalindicators` ^3.1.0 | Classic TA library: SMA, EMA, WMA, MACD, RSI, Stochastic, BollingerBands, ADX, CCI, ROC, WilliamsR, StochasticRSI, ATR, PSAR, VWAP |
153
+ | `simple-statistics` ^7.8.0 | Statistical functions: mean, median, standardDeviation, sampleSkewness, sampleKurtosis, linearRegression, quantileSorted, sampleCovariance, variance, sampleCorrelation |
154
+ | `jstat` ^1.9.6 | Probability distributions: `jStat.normal.inv()` used for parametric VaR. **Has no `@types` package** -- a custom declaration file `src/jstat.d.ts` provides ambient types |
155
+
156
+ ### Dev Dependencies
157
+
158
+ | Package | Purpose |
159
+ |---|---|
160
+ | `typescript` ^5.3.0 | Compiler |
161
+ | `esbuild` ^0.28.0 | Browser bundling (IIFE + ESM) |
162
+ | `tsx` ^4.7.0 | TypeScript execution for running tests directly |
163
+ | `@types/node` ^20.0.0 | Node.js type definitions |
164
+
165
+ ---
166
+
167
+ ## Testing Approach
168
+
169
+ ### Single Test File
170
+
171
+ All tests live in `test/index.test.ts`. There is **no test framework** -- it uses a custom `assert()` function and manual pass/fail counting:
172
+
173
+ ```ts
174
+ let passed = 0;
175
+ let failed = 0;
176
+
177
+ function assert(condition: boolean, message: string) {
178
+ if (condition) {
179
+ passed++;
180
+ console.log(` [PASS] ${message}`);
181
+ } else {
182
+ failed++;
183
+ console.log(` [FAIL] ${message}`);
184
+ }
185
+ }
186
+ ```
187
+
188
+ The process exits with code 1 if any test fails: `process.exit(failed > 0 ? 1 : 0)`.
189
+
190
+ ### Mock NAV Data
191
+
192
+ Tests use synthetic NAV data generated with a **seeded pseudo-random number generator** (linear congruential generator, seed=42) and **Box-Muller transform** for normal distribution:
193
+
194
+ ```ts
195
+ function generateMockNav(days: number = 500, startNav: number = 1.0, seed: number = 42): number[]
196
+ ```
197
+
198
+ Parameters: ~0.03% daily drift (annualized ~7%), ~1.5% daily volatility. A separate benchmark series uses seed=123.
199
+
200
+ ### Test Coverage
201
+
202
+ The test file covers all 5 modules with ~35 assertions organized into sections:
203
+ - Moving averages (SMA, EMA, KAMA, DEMA)
204
+ - MACD, RSI, KDJ, Bollinger Bands
205
+ - Channels (Donchian, Keltner)
206
+ - Other technicals (ADX, CCI, ROC, Momentum, BIAS, TRIX, percentile)
207
+ - MA cross signals and alignment
208
+ - Risk metrics (volatility, drawdown, VaR, CVaR, risk summary)
209
+ - Performance metrics (Sharpe, Sortino, Calmar, Omega, win rate, profit factor)
210
+ - Benchmark comparison (Alpha, Beta, tracking error, information ratio)
211
+ - Statistical features, Hurst exponent, autocorrelation, Ljung-Box, GARCH
212
+ - DCA simulation, take-profit/stop-loss, trailing stop
213
+ - Smart DCA (safety margin, price position, multiplier, tiered signals)
214
+ - Pattern recognition (support/resistance, double bottom/top, gaps, trend strength, head-and-shoulders)
215
+
216
+ ### Running Tests
217
+
218
+ ```bash
219
+ npm test # Run all tests
220
+ npx tsx test/index.test.ts # Equivalent direct invocation
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Adding a New Indicator
226
+
227
+ Follow these steps when adding a new indicator to the library:
228
+
229
+ ### 1. Add the Function to the Appropriate Module
230
+
231
+ Place it in the module that matches its domain:
232
+ - `src/technical.ts` -- Trend, momentum, oscillator, channel indicators
233
+ - `src/risk.ts` -- Risk, performance, and benchmark metrics
234
+ - `src/statistics.ts` -- Statistical features and tests
235
+ - `src/dca.ts` -- DCA simulation and P&L analysis
236
+ - `src/pattern.ts` -- Pattern recognition and detection
237
+
238
+ ### 2. Add an Interface to `types.ts` (If Needed)
239
+
240
+ If the function returns a new result shape, define an interface in `src/types.ts`. Follow the existing naming convention: result interfaces use PascalCase with a descriptive suffix (e.g., `MAResult`, `HurstResult`, `GapResult`).
241
+
242
+ ### 3. Export from `src/index.ts`
243
+
244
+ Add the function to the appropriate `export { ... } from './module'` block in `src/index.ts`. All public API surface goes through this barrel file.
245
+
246
+ ### 4. Add a Test Case in `test/index.test.ts`
247
+
248
+ Add a test section (using `section('...')` for console output grouping) and assertions using the `assert()` helper. Call the new function with the mock NAV data and verify:
249
+ - Output array length matches input (if applicable)
250
+ - Values are in expected ranges
251
+ - Non-null results where expected
252
+
253
+ ### 5. Verify
254
+
255
+ ```bash
256
+ npm test # All tests must pass
257
+ npx tsc --noEmit # Type-check without emitting
258
+ ```
259
+
260
+ ---
261
+
262
+ ## Things to Watch Out For
263
+
264
+ ### `padLeft` Alignment
265
+
266
+ The `padLeft` helper in `technical.ts` left-pads output arrays with `null` to match input length. When chaining indicators or manually combining results, always verify array alignment. Off-by-one errors here silently corrupt downstream calculations.
267
+
268
+ ### KDJ Computation
269
+
270
+ KDJ is computed from `Stochastic` output for K and D, then J is calculated manually as `J = 3K - 2D`. The J array is derived element-wise, and any position where K or D is `null` must also produce `null` for J.
271
+
272
+ ### DEMA and TEMA Nested EMA Offset Tracking
273
+
274
+ DEMA (`2*EMA - EMA(EMA)`) and TEMA (`3*EMA1 - 3*EMA2 + EMA3`) perform nested EMA calculations. Each successive EMA starts later than the previous one, so the code must carefully track array offsets when combining them into a full-length output array. Getting the offset wrong produces misaligned or `NaN` results.
275
+
276
+ ### Hurst Exponent: R/S Analysis
277
+
278
+ The Hurst exponent implementation uses Rescaled Range (R/S) analysis:
279
+ 1. Generate logarithmically-spaced window sizes
280
+ 2. For each window, divide the return series into sub-periods
281
+ 3. Compute the rescaled range `R/S` for each sub-period
282
+ 4. Perform log-log linear regression; the slope is the Hurst exponent
283
+
284
+ Interpretation thresholds: `H > 0.55` = trending, `H < 0.45` = mean-reverting, else random walk. The result is clamped to [0, 1].
285
+
286
+ ### GARCH(1,1): Simplified Moment Estimation
287
+
288
+ The GARCH(1,1) implementation uses **simplified moment estimation** (not maximum likelihood estimation). Parameters are estimated from the autocorrelation of squared returns. This is fast and adequate for approximate volatility forecasting, but not suitable for academic-grade inference.
289
+
290
+ For short series (< 30 observations), it falls back to unconditional variance with default parameters (`alpha=0.1`, `beta=0.8`).
291
+
292
+ ### jstat Has No `@types` Package
293
+
294
+ The `jstat` package does not ship TypeScript types and has no `@types/jstat` on DefinitelyTyped. A custom ambient declaration file at `src/jstat.d.ts` declares the module with the specific functions used:
295
+
296
+ ```ts
297
+ declare module 'jstat' {
298
+ export const jStat: {
299
+ normal: { pdf, cdf, inv };
300
+ chisquare: { cdf, inv };
301
+ studentt: { cdf, inv };
302
+ };
303
+ }
304
+ ```
305
+
306
+ If you need additional jstat functions, extend this declaration file rather than adding inline `any` casts.
307
+
308
+ ### `TRADING_DAYS_PER_YEAR` Is Hardcoded
309
+
310
+ The constant `TRADING_DAYS_PER_YEAR = 242` is defined in `risk.ts` and used for all annualization. It is not configurable via function parameters. If you need a different market convention (e.g., 252 for US markets), you must modify this constant.
311
+
312
+ ### Returns vs. NAV Input
313
+
314
+ Some functions take a NAV series (`number[]`) and internally convert to returns (e.g., `sharpeRatio`, `performanceMetrics`, `statisticalFeatures`). Others take a pre-computed returns series (e.g., `annualizedVolatility`, `calculateVaR`, `autocorrelation`). Check the function signature carefully -- passing the wrong type will produce silently incorrect results rather than a type error, since both are `number[]`.
315
+
316
+ ### IRR Convergence
317
+
318
+ The IRR calculation in `dca.ts` uses Newton's method with a bisection fallback. It clamps the rate to `[-0.99, 10]` to prevent divergence. For unusual cash flow patterns (e.g., very few investments or extreme NAV changes), IRR may return `null` if neither method converges.
319
+
320
+ ### No Test Framework
321
+
322
+ The test suite uses a custom runner, not Jest/Mocha/Vitest. There is no `describe`/`it` nesting, no snapshot testing, and no mocking infrastructure. Tests are simple imperative assertions against deterministic mock data. Adding parallel test execution or coverage tooling requires introducing a proper test framework.