lit-forge-mcp 0.3.0 → 0.4.0
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/README.md +16 -6
- package/dist/lib/__tests__/correlation.test.d.ts +1 -0
- package/dist/lib/__tests__/correlation.test.js +53 -0
- package/dist/lib/__tests__/correlation.test.js.map +1 -0
- package/dist/lib/__tests__/quartile.test.d.ts +1 -0
- package/dist/lib/__tests__/quartile.test.js +148 -0
- package/dist/lib/__tests__/quartile.test.js.map +1 -0
- package/dist/lib/__tests__/rankings.test.d.ts +1 -0
- package/dist/lib/__tests__/rankings.test.js +67 -0
- package/dist/lib/__tests__/rankings.test.js.map +1 -0
- package/dist/lib/__tests__/sessions.test.d.ts +1 -0
- package/dist/lib/__tests__/sessions.test.js +47 -0
- package/dist/lib/__tests__/sessions.test.js.map +1 -0
- package/dist/lib/__tests__/sma.test.d.ts +1 -0
- package/dist/lib/__tests__/sma.test.js +26 -0
- package/dist/lib/__tests__/sma.test.js.map +1 -0
- package/dist/lib/__tests__/thermometer.test.d.ts +1 -0
- package/dist/lib/__tests__/thermometer.test.js +120 -0
- package/dist/lib/__tests__/thermometer.test.js.map +1 -0
- package/dist/lib/__tests__/yield-spread.test.d.ts +1 -0
- package/dist/lib/__tests__/yield-spread.test.js +46 -0
- package/dist/lib/__tests__/yield-spread.test.js.map +1 -0
- package/dist/lib/correlation.d.ts +3 -0
- package/dist/lib/correlation.js +77 -0
- package/dist/lib/correlation.js.map +1 -0
- package/dist/lib/fangplus.d.ts +7 -0
- package/dist/lib/fangplus.js +19 -0
- package/dist/lib/fangplus.js.map +1 -0
- package/dist/lib/indicators.d.ts +1 -0
- package/dist/lib/indicators.js +36 -1
- package/dist/lib/indicators.js.map +1 -1
- package/dist/lib/mag7.d.ts +7 -0
- package/dist/lib/mag7.js +13 -0
- package/dist/lib/mag7.js.map +1 -0
- package/dist/lib/market-types.d.ts +22 -6
- package/dist/lib/market-types.js +1 -0
- package/dist/lib/market-types.js.map +1 -1
- package/dist/lib/performance.d.ts +3 -0
- package/dist/lib/performance.js +43 -0
- package/dist/lib/performance.js.map +1 -0
- package/dist/lib/quartile.d.ts +24 -0
- package/dist/lib/quartile.js +128 -0
- package/dist/lib/quartile.js.map +1 -0
- package/dist/lib/rankings.d.ts +7 -0
- package/dist/lib/rankings.js +29 -0
- package/dist/lib/rankings.js.map +1 -0
- package/dist/lib/sectors.d.ts +7 -0
- package/dist/lib/sectors.js +62 -0
- package/dist/lib/sectors.js.map +1 -0
- package/dist/lib/sessions.d.ts +14 -0
- package/dist/lib/sessions.js +50 -0
- package/dist/lib/sessions.js.map +1 -0
- package/dist/lib/sma.d.ts +2 -0
- package/dist/lib/sma.js +33 -0
- package/dist/lib/sma.js.map +1 -0
- package/dist/lib/thermometer.d.ts +13 -0
- package/dist/lib/thermometer.js +118 -0
- package/dist/lib/thermometer.js.map +1 -0
- package/dist/lib/yahoo.d.ts +2 -0
- package/dist/lib/yahoo.js +76 -15
- package/dist/lib/yahoo.js.map +1 -1
- package/dist/lib/yield-spread.d.ts +6 -0
- package/dist/lib/yield-spread.js +20 -0
- package/dist/lib/yield-spread.js.map +1 -0
- package/dist/tools/get-market-sessions.d.ts +2 -0
- package/dist/tools/get-market-sessions.js +27 -0
- package/dist/tools/get-market-sessions.js.map +1 -0
- package/dist/tools/get-market-snapshot.js +6 -4
- package/dist/tools/get-market-snapshot.js.map +1 -1
- package/dist/tools/get-market-thermometer.d.ts +2 -0
- package/dist/tools/get-market-thermometer.js +47 -0
- package/dist/tools/get-market-thermometer.js.map +1 -0
- package/dist/tools/get-performance-ranking.d.ts +2 -0
- package/dist/tools/get-performance-ranking.js +75 -0
- package/dist/tools/get-performance-ranking.js.map +1 -0
- package/dist/tools/get-sector-heatmap.d.ts +2 -0
- package/dist/tools/get-sector-heatmap.js +34 -0
- package/dist/tools/get-sector-heatmap.js.map +1 -0
- package/dist/tools/get-yield-spread.d.ts +2 -0
- package/dist/tools/get-yield-spread.js +41 -0
- package/dist/tools/get-yield-spread.js.map +1 -0
- package/dist/tools/index.js +13 -0
- package/dist/tools/index.js.map +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
Claude Desktop / Claude Code / Cursor など、MCP に対応した任意の AI クライアントで動作します。
|
|
10
10
|
|
|
11
|
-
> **v0.2.0 で「金融・個人投資家特化」にピボット**、**v0.3.0 で「毎朝の市況チェック」系 3
|
|
11
|
+
> **v0.2.0 で「金融・個人投資家特化」にピボット**、**v0.3.0 で「毎朝の市況チェック」系 3 ツールを追加**、**v0.4.0 で銘柄数 9 → 28 に拡大 + 5 つの分析ツール追加**しました。
|
|
12
12
|
|
|
13
|
-
## 提供ツール(
|
|
13
|
+
## 提供ツール(12 種)
|
|
14
14
|
|
|
15
15
|
### 個人資産形成プランナー(純関数、外部 API 不要)
|
|
16
16
|
|
|
@@ -21,17 +21,27 @@ Claude Desktop / Claude Code / Cursor など、MCP に対応した任意の AI
|
|
|
21
21
|
| `calculate_required_monthly` | 目標金額・現在の貯蓄・年利・年数から、達成に必要な毎月の積立額を逆算 |
|
|
22
22
|
| `calculate_compound_interest` | 元本(一括)と月次積立を月次複利で評価する汎用複利計算ツール |
|
|
23
23
|
|
|
24
|
-
###
|
|
24
|
+
### 市況・経済イベント(**HTTP 通信あり**)
|
|
25
25
|
|
|
26
26
|
| ツール名 | 説明 |
|
|
27
27
|
|---|---|
|
|
28
|
-
| `get_market_snapshot` | USD/JPY・EUR/JPY
|
|
28
|
+
| `get_market_snapshot` | USD/JPY・EUR/JPY・GBP/JPY・AUD/JPY・EUR/USD・CHF/JPY・ドル指数・日経平均・TOPIX・NY ダウ・S&P 500・NASDAQ・VIX・NYSE FANG+・SOX・DAX・FTSE・上海総合・ハンセン・KOSPI・SENSEX・米10年/5年金利・金・原油・銅・ビットコイン・イーサリアム の主要 28 指標を一括取得([lit-forge.com/today](https://lit-forge.com/today) と同等) |
|
|
29
29
|
| `get_economic_events_today` | 当日 or 今週の経済イベント(FOMC・日銀金融政策決定会合・米雇用統計・CPI・GDP・中国 PMI 等)を重要度付きで返す。半年分を手動キュレーション |
|
|
30
30
|
| `get_quote` | 任意の Yahoo Finance ティッカー(株・為替・指数・コモディティ・暗号資産)の現在値・前日比を取得。例: `AAPL` / `^DJI` / `BTC-USD` |
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
### 分析ツール(v0.4.0 新規)
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
| ツール名 | 説明 |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `get_market_thermometer` | VIX・S&P 500・米10年金利・ドル指数を合成した 0-100 のリスクオン/オフ・スコア + 過去 30 営業日推移 |
|
|
37
|
+
| `get_performance_ranking` | 28 銘柄を `1d`/`1w`/`1m` のパフォーマンスでソート、上位/下位 N 件を返す |
|
|
38
|
+
| `get_yield_spread` | 米10年-5年イールドスプレッド(プラス=順イールド / マイナス=逆イールド) |
|
|
39
|
+
| `get_market_sessions` | 主要 4 市場(東京・上海・ロンドン・NY)の現在の取引時間ステータス |
|
|
40
|
+
| `get_sector_heatmap` | 米株セクター ETF(SPDR、11 セクター)の前日比一覧 |
|
|
41
|
+
|
|
42
|
+
> ⚠ **HTTP 通信について**: 市況・分析系ツールは **Yahoo Finance API(query1.finance.yahoo.com)** へ HTTPS リクエストを送信します。実行 PC のネットワークから外部に出る通信が発生する点にご留意ください。データは約 1 時間遅れの参考値で、**投資助言ではなく情報集約**として提供しています。
|
|
43
|
+
|
|
44
|
+
Claude / GPT / Cursor との対話の中で「老後資金大丈夫?」「月いくら積み立てれば?」「今日の市況を要約して」「FOMC は今週いつ?」「マーケット温度計は?」「セクターでどこが強い?」を即座に試算・確認できます。
|
|
35
45
|
|
|
36
46
|
## インストール / 設定
|
|
37
47
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { correlationMatrix, pearson, tailReturns, } from "../correlation.js";
|
|
3
|
+
describe("tailReturns", () => {
|
|
4
|
+
it("末尾 31 件から 30 件のリターンを返す", () => {
|
|
5
|
+
const closes = Array.from({ length: 50 }, (_, i) => 100 + i);
|
|
6
|
+
const r = tailReturns(closes, 30);
|
|
7
|
+
expect(r.length).toBe(30);
|
|
8
|
+
});
|
|
9
|
+
it("データ不足は空配列", () => {
|
|
10
|
+
expect(tailReturns([100], 30)).toEqual([]);
|
|
11
|
+
expect(tailReturns([], 30)).toEqual([]);
|
|
12
|
+
});
|
|
13
|
+
it("分母が 0(直前の close=0)の点を除外", () => {
|
|
14
|
+
const r = tailReturns([100, 0, 50, 75], 10);
|
|
15
|
+
// 100→0 (-100%)、0→50 は分母 0 で除外、50→75 (+50%) → 2 要素
|
|
16
|
+
expect(r.length).toBe(2);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe("pearson", () => {
|
|
20
|
+
it("完全に相関する系列は 1", () => {
|
|
21
|
+
const a = Array.from({ length: 30 }, (_, i) => i / 100);
|
|
22
|
+
const b = Array.from({ length: 30 }, (_, i) => i / 200);
|
|
23
|
+
expect(pearson(a, b)).toBeCloseTo(1, 5);
|
|
24
|
+
});
|
|
25
|
+
it("完全に逆相関する系列は -1", () => {
|
|
26
|
+
const a = Array.from({ length: 30 }, (_, i) => i / 100);
|
|
27
|
+
const b = Array.from({ length: 30 }, (_, i) => -i / 100);
|
|
28
|
+
expect(pearson(a, b)).toBeCloseTo(-1, 5);
|
|
29
|
+
});
|
|
30
|
+
it("点数不足は null", () => {
|
|
31
|
+
expect(pearson([1, 2], [3, 4])).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
it("片方が定数(std=0)は null", () => {
|
|
34
|
+
const a = Array.from({ length: 30 }, () => 0.01);
|
|
35
|
+
const b = Array.from({ length: 30 }, (_, i) => i / 100);
|
|
36
|
+
expect(pearson(a, b)).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("correlationMatrix", () => {
|
|
40
|
+
it("対角線は 1(データ十分時)", () => {
|
|
41
|
+
const closes = Array.from({ length: 50 }, (_, i) => 100 + i);
|
|
42
|
+
const m = correlationMatrix(["A", "B"], () => closes);
|
|
43
|
+
expect(m[0][0]).toBe(1);
|
|
44
|
+
expect(m[1][1]).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
it("対称行列", () => {
|
|
47
|
+
const a = Array.from({ length: 50 }, (_, i) => 100 + i);
|
|
48
|
+
const b = Array.from({ length: 50 }, (_, i) => 200 - i);
|
|
49
|
+
const m = correlationMatrix(["A", "B"], (s) => (s === "A" ? a : b));
|
|
50
|
+
expect(m[0][1]).toBeCloseTo(m[1][0], 5);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=correlation.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correlation.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/correlation.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { detectLevelEvents, detectMilestone, detectOutlier, percentileLabel, pricePercentile, } from "../quartile.js";
|
|
3
|
+
function arithmetic(len, start, step) {
|
|
4
|
+
return Array.from({ length: len }, (_, i) => start + step * i);
|
|
5
|
+
}
|
|
6
|
+
describe("pricePercentile", () => {
|
|
7
|
+
it("単調増加配列の末尾は最高値(100)", () => {
|
|
8
|
+
const closes = arithmetic(252, 100, 0.1);
|
|
9
|
+
expect(pricePercentile(closes)).toBe(100);
|
|
10
|
+
});
|
|
11
|
+
it("単調減少配列の末尾は最安値(0)", () => {
|
|
12
|
+
const closes = arithmetic(252, 200, -0.1);
|
|
13
|
+
expect(pricePercentile(closes)).toBe(0);
|
|
14
|
+
});
|
|
15
|
+
it("中央値付近", () => {
|
|
16
|
+
// 0..100 の値、末尾を 50 にすると過半数が末尾以下なので約 50%
|
|
17
|
+
const closes = Array.from({ length: 250 }, (_, i) => i);
|
|
18
|
+
closes.push(50);
|
|
19
|
+
const p = pricePercentile(closes);
|
|
20
|
+
// 50 以下の数 = 51 (0..50 を含む) / 250 ≒ 20.4%
|
|
21
|
+
expect(p).toBeCloseTo(20.4, 1);
|
|
22
|
+
});
|
|
23
|
+
it("点数不足(< 30)は null", () => {
|
|
24
|
+
expect(pricePercentile([1, 2, 3])).toBeNull();
|
|
25
|
+
expect(pricePercentile([])).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe("detectOutlier", () => {
|
|
29
|
+
it("等差で増加する穏やかな系列に対して 5% の動きは 1σ を大きく超える", () => {
|
|
30
|
+
// closes = [100, 100.1, 100.2, ..., 125]; daily ret ~ 0.1%, std ≈ 0
|
|
31
|
+
const closes = Array.from({ length: 252 }, (_, i) => 100 + i * 0.1);
|
|
32
|
+
const r = detectOutlier(closes, 5);
|
|
33
|
+
expect(r).not.toBeNull();
|
|
34
|
+
expect(r.exceeds1Sigma).toBe(true);
|
|
35
|
+
expect(r.sigma).toBeGreaterThan(1);
|
|
36
|
+
});
|
|
37
|
+
it("典型的な日次変動の範囲内なら 1σ を超えない", () => {
|
|
38
|
+
// 0.5% 標準偏差程度の系列を作る
|
|
39
|
+
const closes = [100];
|
|
40
|
+
for (let i = 1; i < 252; i++) {
|
|
41
|
+
// 単純な疑似乱数
|
|
42
|
+
const r = Math.sin(i * 1.7) * 0.5;
|
|
43
|
+
closes.push(closes[i - 1] * (1 + r / 100));
|
|
44
|
+
}
|
|
45
|
+
const result = detectOutlier(closes, 0.1);
|
|
46
|
+
expect(result).not.toBeNull();
|
|
47
|
+
expect(result.exceeds1Sigma).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
it("点数不足は null", () => {
|
|
50
|
+
expect(detectOutlier([1, 2, 3], 1)).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
it("全て同値(std=0)は null", () => {
|
|
53
|
+
const closes = Array.from({ length: 252 }, () => 100);
|
|
54
|
+
expect(detectOutlier(closes, 0)).toBeNull();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("detectLevelEvents", () => {
|
|
58
|
+
it("52 週新高値: 当日高値 >= 52w 高値で発火", () => {
|
|
59
|
+
const events = detectLevelEvents({
|
|
60
|
+
dayHigh: 5550,
|
|
61
|
+
fiftyTwoWeekHigh: 5500,
|
|
62
|
+
});
|
|
63
|
+
expect(events.find((e) => e.type === "52w-new-high")).toBeDefined();
|
|
64
|
+
});
|
|
65
|
+
it("52 週新安値: 当日安値 <= 52w 安値で発火", () => {
|
|
66
|
+
const events = detectLevelEvents({
|
|
67
|
+
dayLow: 4400,
|
|
68
|
+
fiftyTwoWeekLow: 4500,
|
|
69
|
+
});
|
|
70
|
+
expect(events.find((e) => e.type === "52w-new-low")).toBeDefined();
|
|
71
|
+
});
|
|
72
|
+
it("レベル系イベントは複数同時に出る", () => {
|
|
73
|
+
const events = detectLevelEvents({
|
|
74
|
+
dayHigh: 5550,
|
|
75
|
+
dayLow: 5520,
|
|
76
|
+
fiftyTwoWeekHigh: 5500,
|
|
77
|
+
fiftyTwoWeekLow: 4500,
|
|
78
|
+
});
|
|
79
|
+
expect(events.length).toBeGreaterThanOrEqual(1);
|
|
80
|
+
});
|
|
81
|
+
it("200 日線上抜け: 直近終値 > SMA200、前日 <= SMA200(-1)", () => {
|
|
82
|
+
// 200日 SMA を上抜けるパターン:前 200 日は 100 で平均、最終日に 200 へジャンプ
|
|
83
|
+
const closes = Array.from({ length: 200 }, () => 100);
|
|
84
|
+
closes.push(99); // prev: under
|
|
85
|
+
closes.push(200); // last: above
|
|
86
|
+
const events = detectLevelEvents({ closes1y: closes });
|
|
87
|
+
expect(events.find((e) => e.type === "ma200-cross-up")).toBeDefined();
|
|
88
|
+
});
|
|
89
|
+
it("200 日線下抜け", () => {
|
|
90
|
+
const closes = Array.from({ length: 200 }, () => 100);
|
|
91
|
+
closes.push(101); // prev: above
|
|
92
|
+
closes.push(50); // last: below
|
|
93
|
+
const events = detectLevelEvents({ closes1y: closes });
|
|
94
|
+
expect(events.find((e) => e.type === "ma200-cross-down")).toBeDefined();
|
|
95
|
+
});
|
|
96
|
+
it("データ不足では何も出ない", () => {
|
|
97
|
+
expect(detectLevelEvents({})).toEqual([]);
|
|
98
|
+
expect(detectLevelEvents({ closes1y: [1, 2, 3] })).toEqual([]);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe("detectMilestone", () => {
|
|
102
|
+
it("当日値が直近 30 日の最大なら「30日ぶり高値」", () => {
|
|
103
|
+
const closes = Array.from({ length: 50 }, (_, i) => 100 + i * 0.1);
|
|
104
|
+
closes.push(200); // 末尾を大幅高
|
|
105
|
+
const m = detectMilestone(closes);
|
|
106
|
+
expect(m).not.toBeNull();
|
|
107
|
+
expect(m.type).toBe("n-day-high");
|
|
108
|
+
// 50 件しかないので 30 日ぶり判定が出る
|
|
109
|
+
expect(m.label).toContain("日ぶり高値");
|
|
110
|
+
});
|
|
111
|
+
it("当日値が 1 年(252 日)の最大なら「1年ぶり高値」", () => {
|
|
112
|
+
const closes = Array.from({ length: 300 }, (_, i) => 100 + i * 0.01);
|
|
113
|
+
closes.push(500);
|
|
114
|
+
const m = detectMilestone(closes);
|
|
115
|
+
expect(m?.label).toContain("1年ぶり高値");
|
|
116
|
+
});
|
|
117
|
+
it("どのウィンドウの高安にも届かなければ null", () => {
|
|
118
|
+
// sin 波で 100±5 の範囲に振動する系列。最後を 100(中央値)にする。
|
|
119
|
+
const closes = Array.from({ length: 300 }, (_, i) => 100 + Math.sin(i / 5) * 5);
|
|
120
|
+
closes.push(100);
|
|
121
|
+
expect(detectMilestone(closes)).toBeNull();
|
|
122
|
+
});
|
|
123
|
+
it("点数不足は null", () => {
|
|
124
|
+
expect(detectMilestone([1, 2, 3])).toBeNull();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
describe("percentileLabel", () => {
|
|
128
|
+
it("90% 以上は「上位X%水準」", () => {
|
|
129
|
+
expect(percentileLabel(95)).toContain("上位5%水準");
|
|
130
|
+
expect(percentileLabel(100)).toContain("上位0%水準");
|
|
131
|
+
});
|
|
132
|
+
it("70-90% は「上位X%」", () => {
|
|
133
|
+
expect(percentileLabel(80)).toContain("上位20%");
|
|
134
|
+
});
|
|
135
|
+
it("30-70% は「中央値付近」", () => {
|
|
136
|
+
expect(percentileLabel(50)).toContain("中央値付近");
|
|
137
|
+
});
|
|
138
|
+
it("10-30% は「下位X%」", () => {
|
|
139
|
+
expect(percentileLabel(20)).toContain("下位20%");
|
|
140
|
+
});
|
|
141
|
+
it("10% 未満は「下位X%水準」", () => {
|
|
142
|
+
expect(percentileLabel(5)).toContain("下位5%水準");
|
|
143
|
+
});
|
|
144
|
+
it("null は空文字", () => {
|
|
145
|
+
expect(percentileLabel(null)).toBe("");
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
//# sourceMappingURL=quartile.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quartile.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/quartile.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,eAAe,EACf,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAExB,SAAS,UAAU,CAAC,GAAW,EAAE,KAAa,EAAE,IAAY;IAC1D,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC5B,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACzC,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAC1B,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACf,wCAAwC;QACxC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QACxD,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChB,MAAM,CAAC,GAAG,eAAe,CAAC,MAAM,CAAE,CAAC;QACnC,yCAAyC;QACzC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAC1B,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC9C,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACzC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,oEAAoE;QACpE,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;QACpE,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACnC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QACzB,MAAM,CAAC,CAAE,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,CAAC,CAAE,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,oBAAoB;QACpB,MAAM,MAAM,GAAa,CAAC,GAAG,CAAC,CAAC;QAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7B,UAAU;YACV,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;YAClC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,MAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE;QACpB,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,MAAM,GAAG,iBAAiB,CAAC;YAC/B,OAAO,EAAE,IAAI;YACb,gBAAgB,EAAE,IAAI;SACvB,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,cAAc,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,MAAM,GAAG,iBAAiB,CAAC;YAC/B,MAAM,EAAE,IAAI;YACZ,eAAe,EAAE,IAAI;SACtB,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAC1B,MAAM,MAAM,GAAG,iBAAiB,CAAC;YAC/B,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,IAAI;YACZ,gBAAgB,EAAE,IAAI;YACtB,eAAe,EAAE,IAAI;SACtB,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,qDAAqD;QACrD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc;QAC/B,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc;QAChC,MAAM,MAAM,GAAG,iBAAiB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QACvD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;QACnB,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc;QAChC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc;QAC/B,MAAM,MAAM,GAAG,iBAAiB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QACvD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,kBAAkB,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;QACtB,MAAM,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC1C,MAAM,CAAC,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;QACnE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;QAC3B,MAAM,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QACzB,MAAM,CAAC,CAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACnC,yBAAyB;QACzB,MAAM,CAAC,CAAE,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QACrE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjB,MAAM,CAAC,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,2CAA2C;QAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAChF,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjB,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE;QACpB,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;QACzB,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gBAAgB,EAAE,GAAG,EAAE;QACxB,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;QACzB,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gBAAgB,EAAE,GAAG,EAAE;QACxB,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;QACzB,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE;QACnB,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { rankByPerformance } from "../rankings.js";
|
|
3
|
+
function ind(symbol, name) {
|
|
4
|
+
return {
|
|
5
|
+
symbol,
|
|
6
|
+
displayName: name,
|
|
7
|
+
category: "equity",
|
|
8
|
+
unit: "",
|
|
9
|
+
decimals: 2,
|
|
10
|
+
changeStyle: "ratio",
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function q(symbol, changePercent, perf) {
|
|
14
|
+
return {
|
|
15
|
+
symbol,
|
|
16
|
+
price: 100,
|
|
17
|
+
previousClose: 100,
|
|
18
|
+
change: 0,
|
|
19
|
+
changePercent,
|
|
20
|
+
changeBp: 0,
|
|
21
|
+
fetchedAt: "2026-05-01T00:00:00.000Z",
|
|
22
|
+
sparkline: [100],
|
|
23
|
+
performance: perf
|
|
24
|
+
? { d7: perf.d7 ?? null, d30: perf.d30 ?? null, d365: null }
|
|
25
|
+
: undefined,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
describe("rankByPerformance", () => {
|
|
29
|
+
const indicators = [ind("A", "AAA"), ind("B", "BBB"), ind("C", "CCC")];
|
|
30
|
+
const quotes = {
|
|
31
|
+
A: q("A", 2.0, { d7: 5, d30: 10 }),
|
|
32
|
+
B: q("B", -1.0, { d7: -3, d30: 8 }),
|
|
33
|
+
C: q("C", 0.5, { d7: 7, d30: 2 }),
|
|
34
|
+
};
|
|
35
|
+
it("1d 期間で changePercent 降順", () => {
|
|
36
|
+
const r = rankByPerformance(indicators, quotes, "1d");
|
|
37
|
+
expect(r.map((x) => x.indicator.symbol)).toEqual(["A", "C", "B"]);
|
|
38
|
+
});
|
|
39
|
+
it("1w 期間で d7 降順", () => {
|
|
40
|
+
const r = rankByPerformance(indicators, quotes, "1w");
|
|
41
|
+
expect(r.map((x) => x.indicator.symbol)).toEqual(["C", "A", "B"]);
|
|
42
|
+
});
|
|
43
|
+
it("1m 期間で d30 降順", () => {
|
|
44
|
+
const r = rankByPerformance(indicators, quotes, "1m");
|
|
45
|
+
expect(r.map((x) => x.indicator.symbol)).toEqual(["A", "B", "C"]);
|
|
46
|
+
});
|
|
47
|
+
it("performance 無しは 1w/1m から除外される", () => {
|
|
48
|
+
const noPerf = {
|
|
49
|
+
A: q("A", 2.0),
|
|
50
|
+
B: q("B", -1.0, { d7: 3 }),
|
|
51
|
+
};
|
|
52
|
+
const r = rankByPerformance(indicators, noPerf, "1w");
|
|
53
|
+
// A は performance なし、B のみ含まれる、C は quote 自体無い
|
|
54
|
+
expect(r.length).toBe(1);
|
|
55
|
+
expect(r[0].indicator.symbol).toBe("B");
|
|
56
|
+
});
|
|
57
|
+
it("error の quote は除外", () => {
|
|
58
|
+
const errored = {
|
|
59
|
+
A: { error: "fail" },
|
|
60
|
+
B: q("B", 5),
|
|
61
|
+
};
|
|
62
|
+
const r = rankByPerformance(indicators, errored, "1d");
|
|
63
|
+
expect(r.length).toBe(1);
|
|
64
|
+
expect(r[0].indicator.symbol).toBe("B");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=rankings.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rankings.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/rankings.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { getSessionStatuses } from "../sessions.js";
|
|
3
|
+
// JST 時刻指定用ヘルパ:JST H 時の UTC 換算時刻を作る
|
|
4
|
+
function atJst(hourJst, dow = "mon") {
|
|
5
|
+
// 2026-05-04 (月) 00:00 JST = 2026-05-03 15:00 UTC
|
|
6
|
+
const baseDates = {
|
|
7
|
+
mon: "2026-05-04T00:00:00+09:00",
|
|
8
|
+
tue: "2026-05-05T00:00:00+09:00",
|
|
9
|
+
fri: "2026-05-08T00:00:00+09:00",
|
|
10
|
+
sat: "2026-05-09T00:00:00+09:00",
|
|
11
|
+
sun: "2026-05-10T00:00:00+09:00",
|
|
12
|
+
};
|
|
13
|
+
const base = new Date(baseDates[dow]).getTime();
|
|
14
|
+
return new Date(base + hourJst * 3600 * 1000);
|
|
15
|
+
}
|
|
16
|
+
describe("getSessionStatuses", () => {
|
|
17
|
+
it("月曜 12:00 JST:東京と上海はオープン、他はクローズ", () => {
|
|
18
|
+
const out = getSessionStatuses(atJst(12, "mon"));
|
|
19
|
+
expect(out.find((s) => s.session.id === "tokyo")?.state).toBe("open");
|
|
20
|
+
expect(out.find((s) => s.session.id === "shanghai")?.state).toBe("open");
|
|
21
|
+
expect(out.find((s) => s.session.id === "london")?.state).toBe("closed");
|
|
22
|
+
expect(out.find((s) => s.session.id === "ny")?.state).toBe("closed");
|
|
23
|
+
});
|
|
24
|
+
it("月曜 23:00 JST:NY とロンドンがオープン", () => {
|
|
25
|
+
const out = getSessionStatuses(atJst(23, "mon"));
|
|
26
|
+
expect(out.find((s) => s.session.id === "ny")?.state).toBe("open");
|
|
27
|
+
expect(out.find((s) => s.session.id === "london")?.state).toBe("open");
|
|
28
|
+
expect(out.find((s) => s.session.id === "tokyo")?.state).toBe("closed");
|
|
29
|
+
});
|
|
30
|
+
it("月曜 8:50 JST:東京は pre-open(10 分後にオープン)", () => {
|
|
31
|
+
const out = getSessionStatuses(atJst(8 + 50 / 60, "mon"));
|
|
32
|
+
const tokyo = out.find((s) => s.session.id === "tokyo");
|
|
33
|
+
expect(tokyo?.state).toBe("pre-open");
|
|
34
|
+
expect(tokyo?.hoursUntilOpen).toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
it("土曜は全市場 closed", () => {
|
|
37
|
+
const out = getSessionStatuses(atJst(10, "sat"));
|
|
38
|
+
for (const s of out)
|
|
39
|
+
expect(s.state).toBe("closed");
|
|
40
|
+
});
|
|
41
|
+
it("日曜は全市場 closed", () => {
|
|
42
|
+
const out = getSessionStatuses(atJst(15, "sun"));
|
|
43
|
+
for (const s of out)
|
|
44
|
+
expect(s.state).toBe("closed");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=sessions.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/sessions.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { lastSMA, rollingSMA } from "../sma.js";
|
|
3
|
+
describe("rollingSMA", () => {
|
|
4
|
+
it("先頭 N-1 個は null、N 番目以降は移動平均", () => {
|
|
5
|
+
const r = rollingSMA([1, 2, 3, 4, 5], 3);
|
|
6
|
+
expect(r).toEqual([null, null, 2, 3, 4]);
|
|
7
|
+
});
|
|
8
|
+
it("window=1 は元の配列と同じ", () => {
|
|
9
|
+
expect(rollingSMA([10, 20, 30], 1)).toEqual([10, 20, 30]);
|
|
10
|
+
});
|
|
11
|
+
it("空配列は空", () => {
|
|
12
|
+
expect(rollingSMA([], 3)).toEqual([]);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
describe("lastSMA", () => {
|
|
16
|
+
it("末尾 N 個の平均を返す", () => {
|
|
17
|
+
expect(lastSMA([1, 2, 3, 4, 5], 3)).toBeCloseTo(4, 5); // (3+4+5)/3
|
|
18
|
+
});
|
|
19
|
+
it("点数不足は null", () => {
|
|
20
|
+
expect(lastSMA([1, 2], 3)).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
it("window=length は全平均", () => {
|
|
23
|
+
expect(lastSMA([10, 20, 30], 3)).toBe(20);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=sma.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sma.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/sma.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEhD,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACzC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC3B,MAAM,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACf,MAAM,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE;IACvB,EAAE,CAAC,cAAc,EAAE,GAAG,EAAE;QACtB,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE;QACpB,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC5B,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { computeThermometer, computeThermometerHistory, } from "../thermometer.js";
|
|
3
|
+
function q(symbol, price, changePercent = 0, changeBp = 0) {
|
|
4
|
+
return {
|
|
5
|
+
symbol,
|
|
6
|
+
price,
|
|
7
|
+
previousClose: price - (price * changePercent) / 100,
|
|
8
|
+
change: (price * changePercent) / 100,
|
|
9
|
+
changePercent,
|
|
10
|
+
changeBp,
|
|
11
|
+
fetchedAt: "2026-05-01T00:00:00.000Z",
|
|
12
|
+
sparkline: [price],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function snap(quotes) {
|
|
16
|
+
return { fetchedAt: "2026-05-01T00:00:00.000Z", quotes };
|
|
17
|
+
}
|
|
18
|
+
describe("computeThermometer", () => {
|
|
19
|
+
it("典型的なリスクオン構成 → score >= 70、level=risk-on", () => {
|
|
20
|
+
const s = snap({
|
|
21
|
+
"^VIX": q("^VIX", 12, -5),
|
|
22
|
+
"^GSPC": q("^GSPC", 5500, 1.2),
|
|
23
|
+
"^TNX": q("^TNX", 4.5, 0, 8),
|
|
24
|
+
"DX-Y.NYB": q("DX-Y.NYB", 105, -0.4),
|
|
25
|
+
});
|
|
26
|
+
const r = computeThermometer(s);
|
|
27
|
+
expect(r.score).toBeGreaterThanOrEqual(70);
|
|
28
|
+
expect(r.level).toBe("risk-on");
|
|
29
|
+
});
|
|
30
|
+
it("典型的なリスクオフ構成 → score <= 30、level=risk-off", () => {
|
|
31
|
+
const s = snap({
|
|
32
|
+
"^VIX": q("^VIX", 28, 8),
|
|
33
|
+
"^GSPC": q("^GSPC", 5500, -1.4),
|
|
34
|
+
"^TNX": q("^TNX", 4.5, 0, -8),
|
|
35
|
+
"DX-Y.NYB": q("DX-Y.NYB", 105, 0.4),
|
|
36
|
+
});
|
|
37
|
+
const r = computeThermometer(s);
|
|
38
|
+
expect(r.score).toBeLessThanOrEqual(30);
|
|
39
|
+
expect(r.level).toBe("risk-off");
|
|
40
|
+
});
|
|
41
|
+
it("中立 → 30 < score < 70、level=neutral", () => {
|
|
42
|
+
const s = snap({
|
|
43
|
+
"^VIX": q("^VIX", 20, 0),
|
|
44
|
+
"^GSPC": q("^GSPC", 5500, 0),
|
|
45
|
+
"^TNX": q("^TNX", 4.5, 0, 0),
|
|
46
|
+
"DX-Y.NYB": q("DX-Y.NYB", 105, 0),
|
|
47
|
+
});
|
|
48
|
+
const r = computeThermometer(s);
|
|
49
|
+
expect(r.score).toBeCloseTo(50, 0);
|
|
50
|
+
expect(r.level).toBe("neutral");
|
|
51
|
+
});
|
|
52
|
+
it("一部欠損でも平均を返す(部分縮退)", () => {
|
|
53
|
+
const s = snap({
|
|
54
|
+
"^VIX": q("^VIX", 15, 0),
|
|
55
|
+
"^GSPC": q("^GSPC", 5500, 0.5),
|
|
56
|
+
});
|
|
57
|
+
const r = computeThermometer(s);
|
|
58
|
+
expect(r).not.toBeNull();
|
|
59
|
+
expect(r.components).toHaveLength(4);
|
|
60
|
+
// 取得できた 2 成分のみ平均
|
|
61
|
+
const valid = r.components.filter((c) => c.value !== null);
|
|
62
|
+
expect(valid.length).toBe(2);
|
|
63
|
+
});
|
|
64
|
+
it("全成分欠損なら null", () => {
|
|
65
|
+
expect(computeThermometer(snap({}))).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
it("clamp: 極端値(VIX=5)でも 0..100 に収まる", () => {
|
|
68
|
+
const s = snap({ "^VIX": q("^VIX", 5, 0) });
|
|
69
|
+
const r = computeThermometer(s);
|
|
70
|
+
expect(r.components[0].value).toBeLessThanOrEqual(100);
|
|
71
|
+
expect(r.components[0].value).toBeGreaterThanOrEqual(0);
|
|
72
|
+
});
|
|
73
|
+
it("clamp: 極端値(VIX=80)でも 0..100 に収まる", () => {
|
|
74
|
+
const s = snap({ "^VIX": q("^VIX", 80, 0) });
|
|
75
|
+
const r = computeThermometer(s);
|
|
76
|
+
expect(r.components[0].value).toBeLessThanOrEqual(100);
|
|
77
|
+
expect(r.components[0].value).toBeGreaterThanOrEqual(0);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe("computeThermometerHistory", () => {
|
|
81
|
+
function withCloses(symbol, closes) {
|
|
82
|
+
return { ...q(symbol, closes[closes.length - 1] ?? 0, 0, 0), closes1y: closes };
|
|
83
|
+
}
|
|
84
|
+
it("VIX のみの履歴でもスコアを返す", () => {
|
|
85
|
+
const s = {
|
|
86
|
+
fetchedAt: "2026-05-01T00:00:00.000Z",
|
|
87
|
+
quotes: {
|
|
88
|
+
"^VIX": withCloses("^VIX", [20, 18, 16, 14, 12]),
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
const hist = computeThermometerHistory(s, 30);
|
|
92
|
+
// i=1..4 の 4 点
|
|
93
|
+
expect(hist.length).toBeGreaterThan(0);
|
|
94
|
+
// VIX が下がるにつれてスコアは上がる(リスクオン化)
|
|
95
|
+
expect(hist[hist.length - 1] > hist[0]).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
it("4 成分すべて揃ってもスコア化できる", () => {
|
|
98
|
+
const len = 50;
|
|
99
|
+
const s = {
|
|
100
|
+
fetchedAt: "2026-05-01T00:00:00.000Z",
|
|
101
|
+
quotes: {
|
|
102
|
+
"^VIX": withCloses("^VIX", Array.from({ length: len }, () => 20)),
|
|
103
|
+
"^GSPC": withCloses("^GSPC", Array.from({ length: len }, (_, i) => 5500 + i * 5)),
|
|
104
|
+
"^TNX": withCloses("^TNX", Array.from({ length: len }, (_, i) => 4.5 + i * 0.001)),
|
|
105
|
+
"DX-Y.NYB": withCloses("DX-Y.NYB", Array.from({ length: len }, () => 105)),
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
const hist = computeThermometerHistory(s, 30);
|
|
109
|
+
expect(hist.length).toBe(30);
|
|
110
|
+
for (const v of hist) {
|
|
111
|
+
expect(v).toBeGreaterThanOrEqual(0);
|
|
112
|
+
expect(v).toBeLessThanOrEqual(100);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
it("データ無しなら空配列", () => {
|
|
116
|
+
const s = { fetchedAt: "2026-05-01T00:00:00.000Z", quotes: {} };
|
|
117
|
+
expect(computeThermometerHistory(s, 30)).toEqual([]);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
//# sourceMappingURL=thermometer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thermometer.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/thermometer.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { computeYieldSpread } from "../yield-spread.js";
|
|
3
|
+
function q(symbol, price, change = 0) {
|
|
4
|
+
return {
|
|
5
|
+
symbol,
|
|
6
|
+
price,
|
|
7
|
+
previousClose: price - change,
|
|
8
|
+
change,
|
|
9
|
+
changePercent: 0,
|
|
10
|
+
changeBp: change * 100,
|
|
11
|
+
fetchedAt: "",
|
|
12
|
+
sparkline: [],
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
describe("computeYieldSpread", () => {
|
|
16
|
+
it("^TNX - ^FVX を返す", () => {
|
|
17
|
+
const snap = {
|
|
18
|
+
fetchedAt: "",
|
|
19
|
+
quotes: {
|
|
20
|
+
"^TNX": q("^TNX", 4.5, 0.01),
|
|
21
|
+
"^FVX": q("^FVX", 4.2, 0.005),
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
const r = computeYieldSpread(snap);
|
|
25
|
+
expect(r.spread).toBeCloseTo(0.3, 5);
|
|
26
|
+
expect(r.spreadChangeBp).toBeCloseTo(0.5, 2);
|
|
27
|
+
});
|
|
28
|
+
it("片方欠損は null", () => {
|
|
29
|
+
const snap = {
|
|
30
|
+
fetchedAt: "",
|
|
31
|
+
quotes: { "^TNX": q("^TNX", 4.5) },
|
|
32
|
+
};
|
|
33
|
+
expect(computeYieldSpread(snap)).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
it("error は null", () => {
|
|
36
|
+
const snap = {
|
|
37
|
+
fetchedAt: "",
|
|
38
|
+
quotes: {
|
|
39
|
+
"^TNX": q("^TNX", 4.5),
|
|
40
|
+
"^FVX": { error: "fail" },
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
expect(computeYieldSpread(snap)).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
//# sourceMappingURL=yield-spread.test.js.map
|