bitbank-lab-mcp 0.1.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.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +388 -0
  3. package/assets/lightweight-charts.standalone.js +7 -0
  4. package/bin/bitbank-lab-mcp.js +20 -0
  5. package/lib/cache.ts +70 -0
  6. package/lib/candle-utils.ts +48 -0
  7. package/lib/candle-validate.ts +434 -0
  8. package/lib/conversions.ts +25 -0
  9. package/lib/datetime.ts +157 -0
  10. package/lib/depth-analysis.ts +51 -0
  11. package/lib/error.ts +15 -0
  12. package/lib/formatter.ts +296 -0
  13. package/lib/get-depth.ts +111 -0
  14. package/lib/http.ts +132 -0
  15. package/lib/indicator-config.ts +39 -0
  16. package/lib/indicator_buffer.ts +41 -0
  17. package/lib/indicators.ts +579 -0
  18. package/lib/logger.ts +120 -0
  19. package/lib/ma-snapshot-utils.ts +277 -0
  20. package/lib/math.ts +89 -0
  21. package/lib/pattern-diagrams.ts +562 -0
  22. package/lib/result.ts +104 -0
  23. package/lib/validate.ts +154 -0
  24. package/lib/volatility.ts +132 -0
  25. package/package.json +79 -0
  26. package/src/env.ts +4 -0
  27. package/src/handlers/analyzeCandlePatternsHandler.ts +383 -0
  28. package/src/handlers/analyzeFibonacciHandler.ts +54 -0
  29. package/src/handlers/analyzeIndicatorsHandler.ts +682 -0
  30. package/src/handlers/analyzeMarketSignalHandler.ts +272 -0
  31. package/src/handlers/analyzeMyPortfolioHandler.ts +800 -0
  32. package/src/handlers/detectPatternsHandler.ts +77 -0
  33. package/src/handlers/detectPatternsViewsHandler.ts +518 -0
  34. package/src/handlers/getTickersJpyHandler.ts +145 -0
  35. package/src/handlers/getVolatilityMetricsHandler.ts +234 -0
  36. package/src/handlers/portfolio/calc.ts +549 -0
  37. package/src/handlers/portfolio/fetch.ts +318 -0
  38. package/src/handlers/portfolio/types.ts +170 -0
  39. package/src/handlers/renderChartSvgHandler.ts +69 -0
  40. package/src/handlers/runBacktestHandler.ts +70 -0
  41. package/src/http.ts +107 -0
  42. package/src/private/auth.ts +104 -0
  43. package/src/private/client.ts +298 -0
  44. package/src/private/config.ts +25 -0
  45. package/src/private/confirmation.ts +185 -0
  46. package/src/private/schemas.ts +866 -0
  47. package/src/prompts.ts +2296 -0
  48. package/src/resources/app-resources.ts +79 -0
  49. package/src/schema/analysis.ts +942 -0
  50. package/src/schema/backtest.ts +100 -0
  51. package/src/schema/base.ts +88 -0
  52. package/src/schema/candle-validate.ts +135 -0
  53. package/src/schema/chart.ts +399 -0
  54. package/src/schema/index.ts +11 -0
  55. package/src/schema/indicators.ts +125 -0
  56. package/src/schema/market-data.ts +298 -0
  57. package/src/schema/patterns.ts +382 -0
  58. package/src/schema/types.ts +97 -0
  59. package/src/schemas.d.ts +37 -0
  60. package/src/schemas.ts +7 -0
  61. package/src/server.ts +405 -0
  62. package/src/tool-definition.ts +44 -0
  63. package/src/tool-registry.ts +174 -0
  64. package/src/types/express-shim.d.ts +9 -0
  65. package/src/types/schemas.generated.d.ts +23 -0
  66. package/tools/analyze_bb_snapshot.ts +385 -0
  67. package/tools/analyze_candle_patterns.ts +810 -0
  68. package/tools/analyze_currency_strength.ts +273 -0
  69. package/tools/analyze_ema_snapshot.ts +183 -0
  70. package/tools/analyze_fibonacci.ts +530 -0
  71. package/tools/analyze_ichimoku_snapshot.ts +606 -0
  72. package/tools/analyze_indicators.ts +691 -0
  73. package/tools/analyze_market_signal.ts +665 -0
  74. package/tools/analyze_mtf_fibonacci.ts +273 -0
  75. package/tools/analyze_mtf_sma.ts +175 -0
  76. package/tools/analyze_sma_snapshot.ts +146 -0
  77. package/tools/analyze_stoch_snapshot.ts +276 -0
  78. package/tools/analyze_support_resistance.ts +817 -0
  79. package/tools/analyze_volume_profile.ts +546 -0
  80. package/tools/chart/ichimoku-cloud.ts +113 -0
  81. package/tools/chart/render-depth.ts +139 -0
  82. package/tools/chart/render-sub-panels.ts +208 -0
  83. package/tools/chart/svg-utils.ts +102 -0
  84. package/tools/detect_macd_cross.ts +691 -0
  85. package/tools/detect_patterns.ts +424 -0
  86. package/tools/detect_whale_events.ts +181 -0
  87. package/tools/get_candles.ts +487 -0
  88. package/tools/get_flow_metrics.ts +596 -0
  89. package/tools/get_orderbook.ts +540 -0
  90. package/tools/get_ticker.ts +132 -0
  91. package/tools/get_tickers_jpy.ts +240 -0
  92. package/tools/get_transactions.ts +209 -0
  93. package/tools/get_volatility_metrics.ts +302 -0
  94. package/tools/patterns/aftermath.ts +212 -0
  95. package/tools/patterns/config.ts +151 -0
  96. package/tools/patterns/detect_doubles.ts +650 -0
  97. package/tools/patterns/detect_hs.ts +635 -0
  98. package/tools/patterns/detect_pennants.ts +373 -0
  99. package/tools/patterns/detect_triangles.ts +820 -0
  100. package/tools/patterns/detect_triples.ts +633 -0
  101. package/tools/patterns/detect_wedges.ts +1072 -0
  102. package/tools/patterns/helpers.ts +517 -0
  103. package/tools/patterns/index.ts +40 -0
  104. package/tools/patterns/regression.ts +153 -0
  105. package/tools/patterns/smoothing.ts +168 -0
  106. package/tools/patterns/swing.ts +91 -0
  107. package/tools/patterns/types.ts +193 -0
  108. package/tools/prepare_chart_data.ts +294 -0
  109. package/tools/prepare_depth_data.ts +189 -0
  110. package/tools/private/analyze_my_portfolio.ts +21 -0
  111. package/tools/private/cancel_order.ts +127 -0
  112. package/tools/private/cancel_orders.ts +121 -0
  113. package/tools/private/create_order.ts +236 -0
  114. package/tools/private/get_margin_positions.ts +134 -0
  115. package/tools/private/get_margin_status.ts +155 -0
  116. package/tools/private/get_margin_trade_history.ts +156 -0
  117. package/tools/private/get_my_assets.ts +207 -0
  118. package/tools/private/get_my_deposit_withdrawal.ts +500 -0
  119. package/tools/private/get_my_orders.ts +157 -0
  120. package/tools/private/get_my_trade_history.ts +229 -0
  121. package/tools/private/get_order.ts +95 -0
  122. package/tools/private/get_orders_info.ts +90 -0
  123. package/tools/private/preview_cancel_order.ts +172 -0
  124. package/tools/private/preview_cancel_orders.ts +137 -0
  125. package/tools/private/preview_order.ts +292 -0
  126. package/tools/render_candle_pattern_diagram.ts +389 -0
  127. package/tools/render_chart_svg.ts +799 -0
  128. package/tools/render_depth_svg.ts +274 -0
  129. package/tools/trading_process/index.ts +7 -0
  130. package/tools/trading_process/lib/backtest_engine.ts +252 -0
  131. package/tools/trading_process/lib/equity.ts +131 -0
  132. package/tools/trading_process/lib/fetch_candles.ts +181 -0
  133. package/tools/trading_process/lib/sma.ts +62 -0
  134. package/tools/trading_process/lib/strategies/bb_breakout.ts +141 -0
  135. package/tools/trading_process/lib/strategies/index.ts +52 -0
  136. package/tools/trading_process/lib/strategies/macd_cross.ts +256 -0
  137. package/tools/trading_process/lib/strategies/rsi.ts +133 -0
  138. package/tools/trading_process/lib/strategies/sma_cross.ts +214 -0
  139. package/tools/trading_process/lib/strategies/types.ts +118 -0
  140. package/tools/trading_process/lib/svg_to_png.ts +64 -0
  141. package/tools/trading_process/render_backtest_chart_generic.ts +729 -0
  142. package/tools/trading_process/run_backtest.ts +243 -0
  143. package/tools/trading_process/types.ts +85 -0
  144. package/tools/validate_candle_data.ts +260 -0
  145. package/tsconfig.json +17 -0
  146. package/ui/cancel-confirm/dist/cancel-confirm.html +99 -0
  147. package/ui/order-confirm/dist/order-confirm.html +99 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * strategies/rsi.ts - RSI(相対力指数)戦略
3
+ *
4
+ * エントリー: RSI が oversold 以下から上昇(売られすぎから回復)
5
+ * エグジット: RSI が overbought 以上に到達(買われすぎ)
6
+ */
7
+
8
+ import { rsi } from '../../../../lib/indicators.js';
9
+ import type { Candle } from '../../types.js';
10
+ import type { Overlay, ParamValidationResult, Signal, Strategy } from './types.js';
11
+
12
+ /**
13
+ * RSI戦略のデフォルトパラメータ
14
+ */
15
+ const DEFAULT_PARAMS = {
16
+ period: 14,
17
+ overbought: 70,
18
+ oversold: 30,
19
+ };
20
+
21
+ /**
22
+ * RSIを計算(lib/indicators.ts への委譲)
23
+ *
24
+ * @param closes 終値配列(古い順)
25
+ * @param period RSI期間
26
+ * @returns RSI配列(0-100、先頭period個はNaN)
27
+ */
28
+ export function calculateRSI(closes: number[], period: number): number[] {
29
+ return rsi(closes, period);
30
+ }
31
+
32
+ /**
33
+ * パラメータのバリデーション
34
+ */
35
+ export function validateParams(params: Record<string, number>): ParamValidationResult {
36
+ const errors: string[] = [];
37
+ const normalized = { ...DEFAULT_PARAMS, ...params };
38
+
39
+ if (normalized.period < 2) {
40
+ errors.push('period must be at least 2');
41
+ }
42
+ if (normalized.overbought <= normalized.oversold) {
43
+ errors.push('overbought must be greater than oversold');
44
+ }
45
+ if (normalized.oversold < 0 || normalized.oversold > 100) {
46
+ errors.push('oversold must be between 0 and 100');
47
+ }
48
+ if (normalized.overbought < 0 || normalized.overbought > 100) {
49
+ errors.push('overbought must be between 0 and 100');
50
+ }
51
+
52
+ return {
53
+ valid: errors.length === 0,
54
+ errors,
55
+ normalizedParams: normalized,
56
+ };
57
+ }
58
+
59
+ /**
60
+ * RSI戦略
61
+ */
62
+ export const rsiStrategy: Strategy = {
63
+ name: 'RSI',
64
+ type: 'rsi',
65
+ requiredBars: 20,
66
+ defaultParams: DEFAULT_PARAMS,
67
+
68
+ generate(candles: Candle[], params: Record<string, number>): Signal[] {
69
+ const { period, overbought, oversold } = { ...DEFAULT_PARAMS, ...params };
70
+ const closes = candles.map((c) => c.close);
71
+ const rsi = calculateRSI(closes, period);
72
+
73
+ const signals: Signal[] = [];
74
+ const startIdx = period + 1; // RSIが有効 + 前日比較用
75
+
76
+ for (let i = 0; i < candles.length; i++) {
77
+ if (i < startIdx) {
78
+ signals.push({ time: candles[i].time, action: 'hold' });
79
+ continue;
80
+ }
81
+
82
+ const prevRSI = rsi[i - 1];
83
+ const currRSI = rsi[i];
84
+
85
+ if (Number.isNaN(prevRSI) || Number.isNaN(currRSI)) {
86
+ signals.push({ time: candles[i].time, action: 'hold' });
87
+ continue;
88
+ }
89
+
90
+ // エントリー: RSI が oversold 以下から上抜け
91
+ if (prevRSI <= oversold && currRSI > oversold) {
92
+ signals.push({
93
+ time: candles[i].time,
94
+ action: 'buy',
95
+ reason: `RSI crossed above ${oversold} (oversold exit): ${currRSI.toFixed(1)}`,
96
+ });
97
+ }
98
+ // エグジット: RSI が overbought 以上に到達
99
+ else if (currRSI >= overbought) {
100
+ signals.push({
101
+ time: candles[i].time,
102
+ action: 'sell',
103
+ reason: `RSI reached overbought (${overbought}): ${currRSI.toFixed(1)}`,
104
+ });
105
+ }
106
+ // シグナルなし
107
+ else {
108
+ signals.push({ time: candles[i].time, action: 'hold' });
109
+ }
110
+ }
111
+
112
+ return signals;
113
+ },
114
+
115
+ getOverlays(candles: Candle[], params: Record<string, number>): Overlay[] {
116
+ const { period, overbought: _overbought, oversold: _oversold } = { ...DEFAULT_PARAMS, ...params };
117
+ const closes = candles.map((c) => c.close);
118
+ const rsi = calculateRSI(closes, period);
119
+
120
+ // RSIは別のスケールなのでバンドとして表現(実際はサブチャートに表示すべき)
121
+ // ここでは簡易的にRSIの値をオーバーレイとして返す
122
+ return [
123
+ {
124
+ type: 'line',
125
+ name: `RSI(${period})`,
126
+ color: '#a855f7', // purple
127
+ data: rsi, // 注: これは0-100のスケールで、価格チャートには直接描画できない
128
+ },
129
+ ];
130
+ },
131
+ };
132
+
133
+ export default rsiStrategy;
@@ -0,0 +1,214 @@
1
+ /**
2
+ * strategies/sma_cross.ts - SMAクロスオーバー戦略
3
+ *
4
+ * エントリー: 短期SMA > 長期SMA にクロス(ゴールデンクロス)
5
+ * エグジット: 短期SMA < 長期SMA にクロス(デッドクロス)
6
+ *
7
+ * オプションフィルター:
8
+ * - sma_filter_period: SMAトレンドフィルター(例: 200)。価格がSMA上の場合のみ買い
9
+ * - rsi_filter_period: RSIフィルター期間(例: 14)。0で無効
10
+ * - rsi_filter_max: RSI上限(例: 70)。RSIがこの値未満の場合のみ買い
11
+ */
12
+
13
+ import type { Candle } from '../../types.js';
14
+ import { calculateSMA } from '../sma.js';
15
+ import { calculateRSI } from './rsi.js';
16
+ import type { Overlay, ParamValidationResult, Signal, Strategy } from './types.js';
17
+
18
+ /**
19
+ * SMAクロスオーバー戦略のデフォルトパラメータ
20
+ */
21
+ const DEFAULT_PARAMS: Record<string, number> = {
22
+ short: 5,
23
+ long: 20,
24
+ // フィルター(0 = 無効)
25
+ sma_filter_period: 0,
26
+ rsi_filter_period: 0,
27
+ rsi_filter_max: 100, // RSI < この値 の場合のみ買い(100=フィルター無効)
28
+ };
29
+
30
+ /**
31
+ * パラメータのバリデーション
32
+ */
33
+ export function validateParams(params: Record<string, number>): ParamValidationResult {
34
+ const errors: string[] = [];
35
+ const normalized = { ...DEFAULT_PARAMS, ...params };
36
+
37
+ if (normalized.short >= normalized.long) {
38
+ errors.push('short must be less than long');
39
+ }
40
+ if (normalized.short < 2) {
41
+ errors.push('short must be at least 2');
42
+ }
43
+ if (normalized.long < 3) {
44
+ errors.push('long must be at least 3');
45
+ }
46
+ if (normalized.sma_filter_period < 0) {
47
+ errors.push('sma_filter_period must be >= 0');
48
+ }
49
+ if (normalized.rsi_filter_period < 0) {
50
+ errors.push('rsi_filter_period must be >= 0');
51
+ }
52
+ if (normalized.rsi_filter_max < 0 || normalized.rsi_filter_max > 100) {
53
+ errors.push('rsi_filter_max must be 0-100');
54
+ }
55
+
56
+ return {
57
+ valid: errors.length === 0,
58
+ errors,
59
+ normalizedParams: normalized,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * フィルター条件の説明文を生成
65
+ */
66
+ function describeFilters(params: Record<string, number>): string {
67
+ const parts: string[] = [];
68
+ if (params.sma_filter_period > 0) {
69
+ parts.push(`SMA${params.sma_filter_period} trend filter`);
70
+ }
71
+ if (params.rsi_filter_period > 0 && params.rsi_filter_max < 100) {
72
+ parts.push(`RSI(${params.rsi_filter_period})<${params.rsi_filter_max}`);
73
+ }
74
+ return parts.length > 0 ? ` [${parts.join(', ')}]` : '';
75
+ }
76
+
77
+ /**
78
+ * SMAクロスオーバー戦略
79
+ */
80
+ export const smaCrossStrategy: Strategy = {
81
+ name: 'SMA Crossover',
82
+ type: 'sma_cross',
83
+ requiredBars: 30, // 最低でもlong期間 + バッファ
84
+ defaultParams: DEFAULT_PARAMS,
85
+
86
+ generate(candles: Candle[], params: Record<string, number>): Signal[] {
87
+ const p = { ...DEFAULT_PARAMS, ...params };
88
+ const { short: shortPeriod, long: longPeriod } = p;
89
+ const closes = candles.map((c) => c.close);
90
+
91
+ const smaShort = calculateSMA(closes, shortPeriod);
92
+ const smaLong = calculateSMA(closes, longPeriod);
93
+
94
+ // フィルター用の指標を事前計算
95
+ const smaFilter = p.sma_filter_period > 0 ? calculateSMA(closes, p.sma_filter_period) : null;
96
+ const rsi = p.rsi_filter_period > 0 ? calculateRSI(closes, p.rsi_filter_period) : null;
97
+
98
+ const signals: Signal[] = [];
99
+ // SMAフィルターが有効な場合、開始インデックスをその分遅らせる
100
+ const baseStartIdx = longPeriod;
101
+ const startIdx = smaFilter ? Math.max(baseStartIdx, p.sma_filter_period) : baseStartIdx;
102
+
103
+ for (let i = 0; i < candles.length; i++) {
104
+ if (i < startIdx) {
105
+ signals.push({ time: candles[i].time, action: 'hold' });
106
+ continue;
107
+ }
108
+
109
+ const prevShort = smaShort[i - 1];
110
+ const prevLong = smaLong[i - 1];
111
+ const currShort = smaShort[i];
112
+ const currLong = smaLong[i];
113
+
114
+ // NaN チェック
115
+ if (Number.isNaN(prevShort) || Number.isNaN(prevLong) || Number.isNaN(currShort) || Number.isNaN(currLong)) {
116
+ signals.push({ time: candles[i].time, action: 'hold' });
117
+ continue;
118
+ }
119
+
120
+ // ゴールデンクロス: short が long を上抜け
121
+ if (prevShort <= prevLong && currShort > currLong) {
122
+ // フィルター適用(買いシグナルのみにフィルターを適用)
123
+ let filtered = false;
124
+
125
+ // SMAトレンドフィルター: 価格がSMA上の場合のみ
126
+ if (smaFilter && !Number.isNaN(smaFilter[i]) && closes[i] < smaFilter[i]) {
127
+ filtered = true;
128
+ }
129
+
130
+ // RSIフィルター
131
+ if (rsi && !Number.isNaN(rsi[i]) && rsi[i] >= p.rsi_filter_max) {
132
+ filtered = true;
133
+ }
134
+
135
+ if (filtered) {
136
+ signals.push({ time: candles[i].time, action: 'hold' });
137
+ } else {
138
+ const filterDesc = describeFilters(p);
139
+ signals.push({
140
+ time: candles[i].time,
141
+ action: 'buy',
142
+ reason: `Golden Cross: SMA(${shortPeriod}) > SMA(${longPeriod})${filterDesc}`,
143
+ });
144
+ }
145
+ }
146
+ // デッドクロス: short が long を下抜け(エグジットなのでフィルター適用しない)
147
+ else if (prevShort >= prevLong && currShort < currLong) {
148
+ signals.push({
149
+ time: candles[i].time,
150
+ action: 'sell',
151
+ reason: `Dead Cross: SMA(${shortPeriod}) < SMA(${longPeriod})`,
152
+ });
153
+ }
154
+ // シグナルなし
155
+ else {
156
+ signals.push({ time: candles[i].time, action: 'hold' });
157
+ }
158
+ }
159
+
160
+ return signals;
161
+ },
162
+
163
+ getOverlays(candles: Candle[], params: Record<string, number>): Overlay[] {
164
+ const p = { ...DEFAULT_PARAMS, ...params };
165
+ const { short: shortPeriod, long: longPeriod } = p;
166
+ const closes = candles.map((c) => c.close);
167
+
168
+ const smaShort = calculateSMA(closes, shortPeriod);
169
+ const smaLong = calculateSMA(closes, longPeriod);
170
+
171
+ const overlays: Overlay[] = [
172
+ {
173
+ type: 'line' as const,
174
+ name: `SMA(${shortPeriod})`,
175
+ color: '#fbbf24', // yellow(Closeの青と区別)
176
+ data: smaShort,
177
+ },
178
+ {
179
+ type: 'line' as const,
180
+ name: `SMA(${longPeriod})`,
181
+ color: '#ef4444', // red
182
+ data: smaLong,
183
+ },
184
+ ];
185
+
186
+ // SMAフィルターが有効な場合、SMAラインを価格チャートに表示
187
+ if (p.sma_filter_period > 0) {
188
+ const smaFilter = calculateSMA(closes, p.sma_filter_period);
189
+ overlays.push({
190
+ type: 'line' as const,
191
+ name: `SMA${p.sma_filter_period} (filter)`,
192
+ color: '#8b5cf6', // purple
193
+ data: smaFilter,
194
+ panel: 'price' as const,
195
+ });
196
+ }
197
+
198
+ // RSIフィルターが有効な場合、RSIラインをインジケータパネルに表示
199
+ if (p.rsi_filter_period > 0 && p.rsi_filter_max < 100) {
200
+ const rsi = calculateRSI(closes, p.rsi_filter_period);
201
+ overlays.push({
202
+ type: 'line' as const,
203
+ name: `RSI(${p.rsi_filter_period})`,
204
+ color: '#a78bfa',
205
+ data: rsi,
206
+ panel: 'indicator' as const,
207
+ });
208
+ }
209
+
210
+ return overlays;
211
+ },
212
+ };
213
+
214
+ export default smaCrossStrategy;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * strategies/types.ts - 汎用バックテスト戦略の型定義
3
+ */
4
+
5
+ import type { Candle } from '../../types.js';
6
+
7
+ /**
8
+ * トレードシグナル
9
+ */
10
+ export interface Signal {
11
+ /** シグナル発生時刻 */
12
+ time: string;
13
+ /** シグナルの種類 */
14
+ action: 'buy' | 'sell' | 'hold';
15
+ /** シグナルの理由(オプション) */
16
+ reason?: string;
17
+ }
18
+
19
+ /**
20
+ * 戦略タイプ
21
+ */
22
+ export type StrategyType = 'sma_cross' | 'rsi' | 'macd_cross' | 'bb_breakout';
23
+
24
+ /**
25
+ * 戦略設定
26
+ */
27
+ export interface StrategyConfig {
28
+ type: StrategyType;
29
+ params: Record<string, number>;
30
+ }
31
+
32
+ /**
33
+ * オーバーレイデータ(チャート描画用)
34
+ */
35
+ export interface OverlayLine {
36
+ type: 'line';
37
+ name: string;
38
+ color: string;
39
+ data: number[];
40
+ }
41
+
42
+ export interface OverlayBand {
43
+ type: 'band';
44
+ name: string;
45
+ color: string;
46
+ fillColor: string;
47
+ data: { upper: number[]; middle?: number[]; lower: number[] };
48
+ }
49
+
50
+ export interface OverlayMarker {
51
+ type: 'marker';
52
+ name: string;
53
+ color: string;
54
+ data: Array<{ index: number; value: number; label?: string }>;
55
+ }
56
+
57
+ export interface OverlayHistogram {
58
+ type: 'histogram';
59
+ name: string;
60
+ positiveColor: string;
61
+ negativeColor: string;
62
+ data: number[];
63
+ }
64
+
65
+ /**
66
+ * オーバーレイの描画先パネル
67
+ * - 'price': 価格チャート上に描画(デフォルト)
68
+ * - 'indicator': 独立したインジケータサブパネルに描画(MACD, RSI 等)
69
+ */
70
+ export type OverlayPanel = 'price' | 'indicator';
71
+
72
+ export type Overlay =
73
+ | (OverlayLine & { panel?: OverlayPanel })
74
+ | (OverlayBand & { panel?: OverlayPanel })
75
+ | (OverlayMarker & { panel?: OverlayPanel })
76
+ | (OverlayHistogram & { panel?: OverlayPanel });
77
+
78
+ /**
79
+ * 戦略インターフェース
80
+ */
81
+ export interface Strategy {
82
+ /** 戦略名 */
83
+ name: string;
84
+ /** 戦略タイプ */
85
+ type: StrategyType;
86
+ /** 計算に必要な最低バー数 */
87
+ requiredBars: number;
88
+ /** デフォルトパラメータ */
89
+ defaultParams: Record<string, number>;
90
+ /**
91
+ * シグナル生成
92
+ * @param candles ローソク足データ
93
+ * @param params 戦略パラメータ
94
+ * @returns シグナル配列(各バーに対応)
95
+ */
96
+ generate(candles: Candle[], params: Record<string, number>): Signal[];
97
+ /**
98
+ * オーバーレイデータ取得(チャート描画用)
99
+ * @param candles ローソク足データ
100
+ * @param params 戦略パラメータ
101
+ * @returns オーバーレイ配列
102
+ */
103
+ getOverlays(candles: Candle[], params: Record<string, number>): Overlay[];
104
+ }
105
+
106
+ /**
107
+ * 戦略パラメータのバリデーション結果
108
+ */
109
+ export interface ParamValidationResult {
110
+ valid: boolean;
111
+ errors: string[];
112
+ normalizedParams: Record<string, number>;
113
+ }
114
+
115
+ /**
116
+ * 戦略レジストリ型
117
+ */
118
+ export type StrategyRegistry = Map<StrategyType, Strategy>;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * SVG to PNG conversion utility using sharp
3
+ */
4
+
5
+ import { existsSync, mkdirSync } from 'node:fs';
6
+ import { dirname } from 'node:path';
7
+ import sharp from 'sharp';
8
+ import { dayjs } from '../../../lib/datetime.js';
9
+
10
+ /**
11
+ * Convert SVG string to PNG and save to file
12
+ * @param svg - SVG string
13
+ * @param outputPath - Output file path (should end with .png)
14
+ * @param options - Optional settings
15
+ * @returns Promise<string> - The output file path
16
+ */
17
+ export async function svgToPng(
18
+ svg: string,
19
+ outputPath: string,
20
+ options: {
21
+ width?: number;
22
+ height?: number;
23
+ density?: number; // DPI for SVG rendering
24
+ } = {},
25
+ ): Promise<string> {
26
+ const { density = 150 } = options;
27
+
28
+ // Ensure output directory exists
29
+ const dir = dirname(outputPath);
30
+ if (!existsSync(dir)) {
31
+ mkdirSync(dir, { recursive: true });
32
+ }
33
+
34
+ // Convert SVG to PNG using sharp
35
+ const svgBuffer = Buffer.from(svg, 'utf-8');
36
+
37
+ let pipeline = sharp(svgBuffer, { density });
38
+
39
+ // Resize if dimensions specified
40
+ if (options.width || options.height) {
41
+ pipeline = pipeline.resize(options.width, options.height, {
42
+ fit: 'inside',
43
+ withoutEnlargement: true,
44
+ });
45
+ }
46
+
47
+ await pipeline.png().toFile(outputPath);
48
+
49
+ return outputPath;
50
+ }
51
+
52
+ /**
53
+ * Generate a unique filename for backtest chart
54
+ */
55
+ export function generateBacktestChartFilename(
56
+ pair: string,
57
+ timeframe: string,
58
+ strategy: string,
59
+ format: 'png' | 'svg' = 'png',
60
+ ): string {
61
+ const timestamp = dayjs().format('YYYY-MM-DDTHH-mm-ss');
62
+ const safePair = pair.replace('_', '');
63
+ return `backtest_${safePair}_${timeframe}_${strategy}_${timestamp}.${format}`;
64
+ }