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.
- package/LICENSE +21 -0
- package/README.md +388 -0
- package/assets/lightweight-charts.standalone.js +7 -0
- package/bin/bitbank-lab-mcp.js +20 -0
- package/lib/cache.ts +70 -0
- package/lib/candle-utils.ts +48 -0
- package/lib/candle-validate.ts +434 -0
- package/lib/conversions.ts +25 -0
- package/lib/datetime.ts +157 -0
- package/lib/depth-analysis.ts +51 -0
- package/lib/error.ts +15 -0
- package/lib/formatter.ts +296 -0
- package/lib/get-depth.ts +111 -0
- package/lib/http.ts +132 -0
- package/lib/indicator-config.ts +39 -0
- package/lib/indicator_buffer.ts +41 -0
- package/lib/indicators.ts +579 -0
- package/lib/logger.ts +120 -0
- package/lib/ma-snapshot-utils.ts +277 -0
- package/lib/math.ts +89 -0
- package/lib/pattern-diagrams.ts +562 -0
- package/lib/result.ts +104 -0
- package/lib/validate.ts +154 -0
- package/lib/volatility.ts +132 -0
- package/package.json +79 -0
- package/src/env.ts +4 -0
- package/src/handlers/analyzeCandlePatternsHandler.ts +383 -0
- package/src/handlers/analyzeFibonacciHandler.ts +54 -0
- package/src/handlers/analyzeIndicatorsHandler.ts +682 -0
- package/src/handlers/analyzeMarketSignalHandler.ts +272 -0
- package/src/handlers/analyzeMyPortfolioHandler.ts +800 -0
- package/src/handlers/detectPatternsHandler.ts +77 -0
- package/src/handlers/detectPatternsViewsHandler.ts +518 -0
- package/src/handlers/getTickersJpyHandler.ts +145 -0
- package/src/handlers/getVolatilityMetricsHandler.ts +234 -0
- package/src/handlers/portfolio/calc.ts +549 -0
- package/src/handlers/portfolio/fetch.ts +318 -0
- package/src/handlers/portfolio/types.ts +170 -0
- package/src/handlers/renderChartSvgHandler.ts +69 -0
- package/src/handlers/runBacktestHandler.ts +70 -0
- package/src/http.ts +107 -0
- package/src/private/auth.ts +104 -0
- package/src/private/client.ts +298 -0
- package/src/private/config.ts +25 -0
- package/src/private/confirmation.ts +185 -0
- package/src/private/schemas.ts +866 -0
- package/src/prompts.ts +2296 -0
- package/src/resources/app-resources.ts +79 -0
- package/src/schema/analysis.ts +942 -0
- package/src/schema/backtest.ts +100 -0
- package/src/schema/base.ts +88 -0
- package/src/schema/candle-validate.ts +135 -0
- package/src/schema/chart.ts +399 -0
- package/src/schema/index.ts +11 -0
- package/src/schema/indicators.ts +125 -0
- package/src/schema/market-data.ts +298 -0
- package/src/schema/patterns.ts +382 -0
- package/src/schema/types.ts +97 -0
- package/src/schemas.d.ts +37 -0
- package/src/schemas.ts +7 -0
- package/src/server.ts +405 -0
- package/src/tool-definition.ts +44 -0
- package/src/tool-registry.ts +174 -0
- package/src/types/express-shim.d.ts +9 -0
- package/src/types/schemas.generated.d.ts +23 -0
- package/tools/analyze_bb_snapshot.ts +385 -0
- package/tools/analyze_candle_patterns.ts +810 -0
- package/tools/analyze_currency_strength.ts +273 -0
- package/tools/analyze_ema_snapshot.ts +183 -0
- package/tools/analyze_fibonacci.ts +530 -0
- package/tools/analyze_ichimoku_snapshot.ts +606 -0
- package/tools/analyze_indicators.ts +691 -0
- package/tools/analyze_market_signal.ts +665 -0
- package/tools/analyze_mtf_fibonacci.ts +273 -0
- package/tools/analyze_mtf_sma.ts +175 -0
- package/tools/analyze_sma_snapshot.ts +146 -0
- package/tools/analyze_stoch_snapshot.ts +276 -0
- package/tools/analyze_support_resistance.ts +817 -0
- package/tools/analyze_volume_profile.ts +546 -0
- package/tools/chart/ichimoku-cloud.ts +113 -0
- package/tools/chart/render-depth.ts +139 -0
- package/tools/chart/render-sub-panels.ts +208 -0
- package/tools/chart/svg-utils.ts +102 -0
- package/tools/detect_macd_cross.ts +691 -0
- package/tools/detect_patterns.ts +424 -0
- package/tools/detect_whale_events.ts +181 -0
- package/tools/get_candles.ts +487 -0
- package/tools/get_flow_metrics.ts +596 -0
- package/tools/get_orderbook.ts +540 -0
- package/tools/get_ticker.ts +132 -0
- package/tools/get_tickers_jpy.ts +240 -0
- package/tools/get_transactions.ts +209 -0
- package/tools/get_volatility_metrics.ts +302 -0
- package/tools/patterns/aftermath.ts +212 -0
- package/tools/patterns/config.ts +151 -0
- package/tools/patterns/detect_doubles.ts +650 -0
- package/tools/patterns/detect_hs.ts +635 -0
- package/tools/patterns/detect_pennants.ts +373 -0
- package/tools/patterns/detect_triangles.ts +820 -0
- package/tools/patterns/detect_triples.ts +633 -0
- package/tools/patterns/detect_wedges.ts +1072 -0
- package/tools/patterns/helpers.ts +517 -0
- package/tools/patterns/index.ts +40 -0
- package/tools/patterns/regression.ts +153 -0
- package/tools/patterns/smoothing.ts +168 -0
- package/tools/patterns/swing.ts +91 -0
- package/tools/patterns/types.ts +193 -0
- package/tools/prepare_chart_data.ts +294 -0
- package/tools/prepare_depth_data.ts +189 -0
- package/tools/private/analyze_my_portfolio.ts +21 -0
- package/tools/private/cancel_order.ts +127 -0
- package/tools/private/cancel_orders.ts +121 -0
- package/tools/private/create_order.ts +236 -0
- package/tools/private/get_margin_positions.ts +134 -0
- package/tools/private/get_margin_status.ts +155 -0
- package/tools/private/get_margin_trade_history.ts +156 -0
- package/tools/private/get_my_assets.ts +207 -0
- package/tools/private/get_my_deposit_withdrawal.ts +500 -0
- package/tools/private/get_my_orders.ts +157 -0
- package/tools/private/get_my_trade_history.ts +229 -0
- package/tools/private/get_order.ts +95 -0
- package/tools/private/get_orders_info.ts +90 -0
- package/tools/private/preview_cancel_order.ts +172 -0
- package/tools/private/preview_cancel_orders.ts +137 -0
- package/tools/private/preview_order.ts +292 -0
- package/tools/render_candle_pattern_diagram.ts +389 -0
- package/tools/render_chart_svg.ts +799 -0
- package/tools/render_depth_svg.ts +274 -0
- package/tools/trading_process/index.ts +7 -0
- package/tools/trading_process/lib/backtest_engine.ts +252 -0
- package/tools/trading_process/lib/equity.ts +131 -0
- package/tools/trading_process/lib/fetch_candles.ts +181 -0
- package/tools/trading_process/lib/sma.ts +62 -0
- package/tools/trading_process/lib/strategies/bb_breakout.ts +141 -0
- package/tools/trading_process/lib/strategies/index.ts +52 -0
- package/tools/trading_process/lib/strategies/macd_cross.ts +256 -0
- package/tools/trading_process/lib/strategies/rsi.ts +133 -0
- package/tools/trading_process/lib/strategies/sma_cross.ts +214 -0
- package/tools/trading_process/lib/strategies/types.ts +118 -0
- package/tools/trading_process/lib/svg_to_png.ts +64 -0
- package/tools/trading_process/render_backtest_chart_generic.ts +729 -0
- package/tools/trading_process/run_backtest.ts +243 -0
- package/tools/trading_process/types.ts +85 -0
- package/tools/validate_candle_data.ts +260 -0
- package/tsconfig.json +17 -0
- package/ui/cancel-confirm/dist/cancel-confirm.html +99 -0
- package/ui/order-confirm/dist/order-confirm.html +99 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* analyzeCandlePatterns のビュー(表示)ロジック
|
|
3
|
+
* generateSummary / generateContent + 共有型定義
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { z } from 'zod';
|
|
7
|
+
import { formatDateWithDayOfWeek } from '../../lib/datetime.js';
|
|
8
|
+
import { formatPrice as fmtPrice } from '../../lib/formatter.js';
|
|
9
|
+
import type { CandlePatternTypeEnum } from '../schemas.js';
|
|
10
|
+
|
|
11
|
+
// ── 共有型定義 ──
|
|
12
|
+
|
|
13
|
+
export type CandlePatternType = z.infer<typeof CandlePatternTypeEnum>;
|
|
14
|
+
|
|
15
|
+
export interface WindowCandle {
|
|
16
|
+
timestamp: string;
|
|
17
|
+
open: number;
|
|
18
|
+
high: number;
|
|
19
|
+
low: number;
|
|
20
|
+
close: number;
|
|
21
|
+
volume: number;
|
|
22
|
+
is_partial: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface HistoryHorizonStats {
|
|
26
|
+
avg_return: number;
|
|
27
|
+
win_rate: number;
|
|
28
|
+
sample: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface HistoryStats {
|
|
32
|
+
lookback_days: number;
|
|
33
|
+
occurrences: number;
|
|
34
|
+
horizons: Record<string, HistoryHorizonStats>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface LocalContext {
|
|
38
|
+
trend_before: 'up' | 'down' | 'neutral';
|
|
39
|
+
volatility_level: 'low' | 'medium' | 'high';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface DetectedCandlePattern {
|
|
43
|
+
pattern: CandlePatternType;
|
|
44
|
+
pattern_jp: string;
|
|
45
|
+
direction: 'bullish' | 'bearish' | 'neutral';
|
|
46
|
+
strength: number;
|
|
47
|
+
candle_range_index: [number, number];
|
|
48
|
+
uses_partial_candle: boolean;
|
|
49
|
+
status: 'confirmed' | 'forming';
|
|
50
|
+
local_context: LocalContext;
|
|
51
|
+
history_stats: HistoryStats | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Lookup Maps ──
|
|
55
|
+
|
|
56
|
+
const TREND_LABELS: Record<string, string> = { down: '下落傾向', up: '上昇傾向' };
|
|
57
|
+
const DIRECTION_LABELS: Record<string, string> = {
|
|
58
|
+
bullish: '上昇転換のサイン',
|
|
59
|
+
bearish: '下落転換のサイン',
|
|
60
|
+
};
|
|
61
|
+
const DIRECTION_DETAIL_LABELS: Record<string, string> = {
|
|
62
|
+
bullish: '強気(上昇転換シグナル)',
|
|
63
|
+
bearish: '弱気(下落転換シグナル)',
|
|
64
|
+
};
|
|
65
|
+
const TREND_SHORT_LABELS: Record<string, string> = { up: '上昇', down: '下落' };
|
|
66
|
+
|
|
67
|
+
// ── ヘルパー ──
|
|
68
|
+
|
|
69
|
+
function formatPrice(price: number): string {
|
|
70
|
+
return fmtPrice(Math.round(price));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── サマリー生成 ──
|
|
74
|
+
|
|
75
|
+
export function generateSummary(patterns: DetectedCandlePattern[], windowCandles: WindowCandle[]): string {
|
|
76
|
+
if (patterns.length === 0) {
|
|
77
|
+
const trend =
|
|
78
|
+
windowCandles.length >= 3
|
|
79
|
+
? windowCandles[windowCandles.length - 1].close > windowCandles[0].close
|
|
80
|
+
? '上昇'
|
|
81
|
+
: '下落'
|
|
82
|
+
: '横ばい';
|
|
83
|
+
return `直近${windowCandles.length}日間で${trend}傾向ですが、特徴的なローソク足パターンは検出されませんでした。`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const parts: string[] = [];
|
|
87
|
+
|
|
88
|
+
for (const p of patterns) {
|
|
89
|
+
const trendText = TREND_LABELS[p.local_context.trend_before] ?? '横ばい';
|
|
90
|
+
const statusText = p.status === 'forming' ? '形成中(未確定)' : '確定';
|
|
91
|
+
const directionText = DIRECTION_LABELS[p.direction] ?? '方向感の迷いを示すサイン';
|
|
92
|
+
|
|
93
|
+
let statsPart = '';
|
|
94
|
+
if (p.history_stats?.horizons['1']) {
|
|
95
|
+
const h1 = p.history_stats.horizons['1'];
|
|
96
|
+
statsPart = `過去${p.history_stats.lookback_days}日間で同様のパターンが${p.history_stats.occurrences}回出現し、翌日の勝率は${(h1.win_rate * 100).toFixed(0)}%でした。`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
parts.push(
|
|
100
|
+
`${trendText}の中で「${p.pattern_jp}」(${statusText})が検出されました。これは${directionText}とされます。${statsPart}`,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (p.uses_partial_candle) {
|
|
104
|
+
parts.push('⚠️ 本日の日足は未確定のため、終値確定後にパターンが変化する可能性があります。');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return parts.join(' ');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── コンテント生成 ──
|
|
112
|
+
|
|
113
|
+
/** 1本足パターンの詳細行 */
|
|
114
|
+
function format1CandleDetail(p: DetectedCandlePattern, c: WindowCandle, dateStr: string, statusMark: string): string[] {
|
|
115
|
+
const lines: string[] = [];
|
|
116
|
+
const body = c.close - c.open;
|
|
117
|
+
const candleType = body >= 0 ? '陽線' : '陰線';
|
|
118
|
+
|
|
119
|
+
lines.push(` 📍 ${dateStr} に${p.pattern_jp}を検出${statusMark}`);
|
|
120
|
+
lines.push(
|
|
121
|
+
` ${dateStr}: ${candleType} 始値${formatPrice(c.open)} → 終値${formatPrice(c.close)} (実体 ${body >= 0 ? '+' : '-'}${formatPrice(Math.abs(body)).replace('¥', '')}円)`,
|
|
122
|
+
);
|
|
123
|
+
lines.push(
|
|
124
|
+
` 高値${formatPrice(c.high)} 安値${formatPrice(c.low)} (レンジ ${formatPrice(c.high - c.low).replace('¥', '')}円)`,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
if (p.pattern === 'hammer') {
|
|
128
|
+
const lower = Math.min(c.open, c.close) - c.low;
|
|
129
|
+
lines.push(` 判定: 小さい実体 + 長い下ヒゲ(${formatPrice(lower).replace('¥', '')}円)→ 下値の強い買い圧力`);
|
|
130
|
+
} else if (p.pattern === 'shooting_star') {
|
|
131
|
+
const upper = c.high - Math.max(c.open, c.close);
|
|
132
|
+
lines.push(` 判定: 小さい実体 + 長い上ヒゲ(${formatPrice(upper).replace('¥', '')}円)→ 上値の強い売り圧力`);
|
|
133
|
+
} else if (p.pattern === 'doji') {
|
|
134
|
+
const upper = c.high - Math.max(c.open, c.close);
|
|
135
|
+
const lower = Math.min(c.open, c.close) - c.low;
|
|
136
|
+
const variant =
|
|
137
|
+
upper > lower * 1.5
|
|
138
|
+
? 'トウバ型(上ヒゲ優勢)'
|
|
139
|
+
: lower > upper * 1.5
|
|
140
|
+
? 'トンボ型(下ヒゲ優勢)'
|
|
141
|
+
: '通常型(上下均等)';
|
|
142
|
+
lines.push(` 判定: 始値≒終値で売り買い拮抗 → ${variant}`);
|
|
143
|
+
}
|
|
144
|
+
lines.push('');
|
|
145
|
+
return lines;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 2本足パターンの詳細行 */
|
|
149
|
+
function format2CandleDetail(
|
|
150
|
+
p: DetectedCandlePattern,
|
|
151
|
+
c1: WindowCandle,
|
|
152
|
+
c2: WindowCandle,
|
|
153
|
+
statusMark: string,
|
|
154
|
+
): string[] {
|
|
155
|
+
const lines: string[] = [];
|
|
156
|
+
const date1 = formatDateWithDayOfWeek(c1.timestamp);
|
|
157
|
+
const date2 = formatDateWithDayOfWeek(c2.timestamp);
|
|
158
|
+
const body1 = c1.close - c1.open;
|
|
159
|
+
const body2 = c2.close - c2.open;
|
|
160
|
+
const type1 = body1 >= 0 ? '陽線' : '陰線';
|
|
161
|
+
const type2 = body2 >= 0 ? '陽線' : '陰線';
|
|
162
|
+
|
|
163
|
+
lines.push(` 📍 ${date2} に${p.pattern_jp}を検出${statusMark}(${date1}-${date2}で形成)`);
|
|
164
|
+
lines.push(
|
|
165
|
+
` ${date1}(前日): ${type1} 始値${formatPrice(c1.open)} → 終値${formatPrice(c1.close)} (実体 ${body1 >= 0 ? '+' : '-'}${formatPrice(Math.abs(body1)).replace('¥', '')}円)`,
|
|
166
|
+
);
|
|
167
|
+
lines.push(
|
|
168
|
+
` ${date2}(確定日): ${type2} 始値${formatPrice(c2.open)} → 終値${formatPrice(c2.close)} (実体 ${body2 >= 0 ? '+' : '-'}${formatPrice(Math.abs(body2)).replace('¥', '')}円) ← パターン確定`,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
if (p.pattern === 'bullish_engulfing') {
|
|
172
|
+
lines.push(` 判定: 当日の陽線が前日の陰線を完全に包む(始値が前日終値以下、終値が前日始値以上)`);
|
|
173
|
+
} else if (p.pattern === 'bearish_engulfing') {
|
|
174
|
+
lines.push(` 判定: 当日の陰線が前日の陽線を完全に包む(始値が前日終値以上、終値が前日始値以下)`);
|
|
175
|
+
} else if (p.pattern === 'bullish_harami') {
|
|
176
|
+
lines.push(` 判定: 当日のローソク足が前日の大陰線の実体内に収まる`);
|
|
177
|
+
} else if (p.pattern === 'bearish_harami') {
|
|
178
|
+
lines.push(` 判定: 当日のローソク足が前日の大陽線の実体内に収まる`);
|
|
179
|
+
} else if (p.pattern === 'tweezer_top') {
|
|
180
|
+
const highDiff = Math.abs(c1.high - c2.high);
|
|
181
|
+
const matchPct = (1 - highDiff / ((c1.high + c2.high) / 2)) * 100;
|
|
182
|
+
lines.push(
|
|
183
|
+
` 判定: 2日連続で高値がほぼ同じ(誤差${highDiff.toLocaleString('ja-JP')}円, 一致率${matchPct.toFixed(1)}%)`,
|
|
184
|
+
);
|
|
185
|
+
lines.push(` 高値: ${formatPrice(c1.high)} → ${formatPrice(c2.high)}`);
|
|
186
|
+
} else if (p.pattern === 'tweezer_bottom') {
|
|
187
|
+
const lowDiff = Math.abs(c1.low - c2.low);
|
|
188
|
+
const matchPct = (1 - lowDiff / ((c1.low + c2.low) / 2)) * 100;
|
|
189
|
+
lines.push(
|
|
190
|
+
` 判定: 2日連続で安値がほぼ同じ(誤差${lowDiff.toLocaleString('ja-JP')}円, 一致率${matchPct.toFixed(1)}%)`,
|
|
191
|
+
);
|
|
192
|
+
lines.push(` 安値: ${formatPrice(c1.low)} → ${formatPrice(c2.low)}`);
|
|
193
|
+
} else if (p.pattern === 'dark_cloud_cover') {
|
|
194
|
+
const midPoint = (c1.open + c1.close) / 2;
|
|
195
|
+
lines.push(` 判定: 高寄り後に陰線で前日陽線の中心値(${formatPrice(midPoint)})を下回る`);
|
|
196
|
+
lines.push(` ギャップ: ${formatPrice(c2.open)} > 前日終値${formatPrice(c1.close)}`);
|
|
197
|
+
} else if (p.pattern === 'piercing_line') {
|
|
198
|
+
const midPoint = (c1.open + c1.close) / 2;
|
|
199
|
+
lines.push(` 判定: 安寄り後に陽線で前日陰線の中心値(${formatPrice(midPoint)})を上回る`);
|
|
200
|
+
lines.push(` ギャップ: ${formatPrice(c2.open)} < 前日終値${formatPrice(c1.close)}`);
|
|
201
|
+
}
|
|
202
|
+
lines.push('');
|
|
203
|
+
return lines;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** 3本足パターンの詳細行 */
|
|
207
|
+
function format3CandleDetail(
|
|
208
|
+
p: DetectedCandlePattern,
|
|
209
|
+
c1: WindowCandle,
|
|
210
|
+
c2: WindowCandle,
|
|
211
|
+
c3: WindowCandle,
|
|
212
|
+
statusMark: string,
|
|
213
|
+
): string[] {
|
|
214
|
+
const lines: string[] = [];
|
|
215
|
+
const date1 = formatDateWithDayOfWeek(c1.timestamp);
|
|
216
|
+
const date2 = formatDateWithDayOfWeek(c2.timestamp);
|
|
217
|
+
const date3 = formatDateWithDayOfWeek(c3.timestamp);
|
|
218
|
+
|
|
219
|
+
lines.push(` 📍 ${date1}-${date3} に${p.pattern_jp}を検出${statusMark}(3本足パターン)`);
|
|
220
|
+
for (const [label, c, dateStr] of [
|
|
221
|
+
['1本目', c1, date1],
|
|
222
|
+
['2本目', c2, date2],
|
|
223
|
+
['3本目(確定日)', c3, date3],
|
|
224
|
+
] as const) {
|
|
225
|
+
const body = c.close - c.open;
|
|
226
|
+
const ct = body >= 0 ? '陽線' : '陰線';
|
|
227
|
+
lines.push(
|
|
228
|
+
` ${dateStr}(${label}): ${ct} 始値${formatPrice(c.open)} → 終値${formatPrice(c.close)} (実体 ${body >= 0 ? '+' : '-'}${formatPrice(Math.abs(body)).replace('¥', '')}円)`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (p.pattern === 'morning_star') {
|
|
233
|
+
const midPoint = (c1.open + c1.close) / 2;
|
|
234
|
+
lines.push(` 判定: 大陰線→コマ→大陽線が1本目の中心値(${formatPrice(midPoint)})超え → 底打ち反転`);
|
|
235
|
+
} else if (p.pattern === 'evening_star') {
|
|
236
|
+
const midPoint = (c1.open + c1.close) / 2;
|
|
237
|
+
lines.push(` 判定: 大陽線→コマ→大陰線が1本目の中心値(${formatPrice(midPoint)})割れ → 天井反転`);
|
|
238
|
+
} else if (p.pattern === 'three_white_soldiers') {
|
|
239
|
+
lines.push(` 判定: 3本連続陽線で各終値が前日を上回る → 力強い上昇トレンド`);
|
|
240
|
+
} else if (p.pattern === 'three_black_crows') {
|
|
241
|
+
lines.push(` 判定: 3本連続陰線で各終値が前日を下回る → 力強い下落トレンド`);
|
|
242
|
+
}
|
|
243
|
+
lines.push('');
|
|
244
|
+
return lines;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** 過去統計セクション */
|
|
248
|
+
function formatHistoryStats(p: DetectedCandlePattern): string[] {
|
|
249
|
+
const lines: string[] = [];
|
|
250
|
+
if (p.history_stats) {
|
|
251
|
+
const hs = p.history_stats;
|
|
252
|
+
lines.push(` === 過去の実績(直近${hs.lookback_days}日間) ===`);
|
|
253
|
+
lines.push(` ${p.pattern_jp}の出現回数: ${hs.occurrences}回`);
|
|
254
|
+
for (const [horizon, stats] of Object.entries(hs.horizons)) {
|
|
255
|
+
const wins = Math.round(stats.win_rate * stats.sample);
|
|
256
|
+
const losses = stats.sample - wins;
|
|
257
|
+
lines.push(
|
|
258
|
+
` ${horizon}日後: 勝率${(stats.win_rate * 100).toFixed(1)}% (${wins}勝${losses}敗), 平均リターン ${stats.avg_return >= 0 ? '+' : ''}${stats.avg_return.toFixed(2)}%`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
lines.push('');
|
|
262
|
+
} else {
|
|
263
|
+
lines.push(' === 過去の実績 ===');
|
|
264
|
+
lines.push(' 統計データなし(サンプル数不足または期間外)');
|
|
265
|
+
lines.push('');
|
|
266
|
+
}
|
|
267
|
+
return lines;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** パターンの読み方(固定テキスト) */
|
|
271
|
+
const PATTERN_GUIDE = [
|
|
272
|
+
'【パターンの読み方】',
|
|
273
|
+
'〈1本足〉',
|
|
274
|
+
'・ハンマー: 下落局面で長い下ヒゲ→買い圧力が強く、上昇転換のサイン',
|
|
275
|
+
'・シューティングスター: 上昇局面で長い上ヒゲ→売り圧力が強く、下落転換のサイン',
|
|
276
|
+
'・十字線: 始値≒終値で売り買い拮抗→トレンド転換の予兆(前のトレンドの逆方向に注目)',
|
|
277
|
+
'〈2本足〉',
|
|
278
|
+
'・陽線包み線: 下落後に出現すると上昇転換のサイン',
|
|
279
|
+
'・陰線包み線: 上昇後に出現すると下落転換のサイン',
|
|
280
|
+
'・はらみ線: 大きなローソク足の中に小さなローソク足が収まる形で、トレンド転換の予兆',
|
|
281
|
+
'・毛抜き天井: 高値圏で2日連続同じ高値→上昇の限界、下落転換のサイン',
|
|
282
|
+
'・毛抜き底: 安値圏で2日連続同じ安値→下落の限界、上昇転換のサイン',
|
|
283
|
+
'・かぶせ線: 高寄り後に陰線で前日陽線の中心以下→上昇一服、調整のサイン',
|
|
284
|
+
'・切り込み線: 安寄り後に陽線で前日陰線の中心超え→下落一服、反発のサイン',
|
|
285
|
+
'〈3本足〉',
|
|
286
|
+
'・明けの明星: 大陰線→コマ→大陽線で底打ち反転のサイン',
|
|
287
|
+
'・宵の明星: 大陽線→コマ→大陰線で天井反転のサイン',
|
|
288
|
+
'・赤三兵: 3本連続陽線で力強い上昇の開始・継続',
|
|
289
|
+
'・黒三兵: 3本連続陰線で力強い下落の開始・継続',
|
|
290
|
+
'',
|
|
291
|
+
'※勝率50%超でもリスク管理は必須です。統計は参考値であり、将来を保証するものではありません。',
|
|
292
|
+
].join('\n');
|
|
293
|
+
|
|
294
|
+
export function generateContent(
|
|
295
|
+
patterns: DetectedCandlePattern[],
|
|
296
|
+
windowCandles: WindowCandle[],
|
|
297
|
+
): Array<{ type: 'text'; text: string }> {
|
|
298
|
+
const lines: string[] = [];
|
|
299
|
+
|
|
300
|
+
lines.push('【ローソク足パターン分析結果】');
|
|
301
|
+
lines.push('');
|
|
302
|
+
lines.push(
|
|
303
|
+
`分析期間: ${windowCandles[0]?.timestamp?.split('T')[0] || '?'} 〜 ${windowCandles[windowCandles.length - 1]?.timestamp?.split('T')[0] || '?'}`,
|
|
304
|
+
);
|
|
305
|
+
lines.push('');
|
|
306
|
+
|
|
307
|
+
// === 1. ローソク足データ ===
|
|
308
|
+
lines.push(`=== ${windowCandles.length}日間のローソク足 ===`);
|
|
309
|
+
for (const c of windowCandles) {
|
|
310
|
+
const dateStr = formatDateWithDayOfWeek(c.timestamp);
|
|
311
|
+
const change = c.close - c.open;
|
|
312
|
+
const changeSign = change >= 0 ? '+' : '-';
|
|
313
|
+
const candleType = change >= 0 ? '陽線' : '陰線';
|
|
314
|
+
const partialMark = c.is_partial ? ' ⚠未確定' : '';
|
|
315
|
+
lines.push(
|
|
316
|
+
`${dateStr}: 始値${formatPrice(c.open)} 高値${formatPrice(c.high)} 安値${formatPrice(c.low)} 終値${formatPrice(c.close)} [${candleType} ${changeSign}${formatPrice(Math.abs(change)).replace('¥', '')}円]${partialMark}`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
lines.push('');
|
|
320
|
+
|
|
321
|
+
// === 2. パターン検出結果 ===
|
|
322
|
+
if (patterns.length === 0) {
|
|
323
|
+
lines.push('=== 検出パターン ===');
|
|
324
|
+
lines.push('なし');
|
|
325
|
+
lines.push('');
|
|
326
|
+
lines.push('直近の値動きには特徴的なローソク足パターンは見られませんでした。');
|
|
327
|
+
lines.push('');
|
|
328
|
+
} else {
|
|
329
|
+
for (const p of patterns) {
|
|
330
|
+
lines.push(`■ ${p.pattern_jp}(${p.pattern})`);
|
|
331
|
+
const dirLabel = DIRECTION_DETAIL_LABELS[p.direction] ?? '中立(方向性の迷い)';
|
|
332
|
+
lines.push(` 方向性: ${dirLabel}`);
|
|
333
|
+
lines.push(` 状態: ${p.status === 'forming' ? '形成中(終値未確定)' : '確定'}`);
|
|
334
|
+
lines.push(` 強度: ${(p.strength * 100).toFixed(0)}%`);
|
|
335
|
+
lines.push(` 直前トレンド: ${TREND_SHORT_LABELS[p.local_context.trend_before] ?? '中立'}`);
|
|
336
|
+
lines.push('');
|
|
337
|
+
|
|
338
|
+
// === 3. パターン該当箇所の詳細 ===
|
|
339
|
+
const [idxStart, idxEnd] = p.candle_range_index;
|
|
340
|
+
const spanSize = idxEnd - idxStart + 1;
|
|
341
|
+
|
|
342
|
+
if (idxStart >= 0 && idxEnd < windowCandles.length) {
|
|
343
|
+
const statusMark = p.uses_partial_candle ? '(形成中)' : '(確定)';
|
|
344
|
+
lines.push(' === 検出パターンの詳細 ===');
|
|
345
|
+
|
|
346
|
+
if (spanSize === 1) {
|
|
347
|
+
lines.push(
|
|
348
|
+
...format1CandleDetail(
|
|
349
|
+
p,
|
|
350
|
+
windowCandles[idxStart],
|
|
351
|
+
formatDateWithDayOfWeek(windowCandles[idxStart].timestamp),
|
|
352
|
+
statusMark,
|
|
353
|
+
),
|
|
354
|
+
);
|
|
355
|
+
} else if (spanSize === 2) {
|
|
356
|
+
lines.push(...format2CandleDetail(p, windowCandles[idxStart], windowCandles[idxEnd], statusMark));
|
|
357
|
+
} else if (spanSize === 3) {
|
|
358
|
+
lines.push(
|
|
359
|
+
...format3CandleDetail(
|
|
360
|
+
p,
|
|
361
|
+
windowCandles[idxStart],
|
|
362
|
+
windowCandles[idxStart + 1],
|
|
363
|
+
windowCandles[idxEnd],
|
|
364
|
+
statusMark,
|
|
365
|
+
),
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// === 4. 過去統計 ===
|
|
371
|
+
lines.push(...formatHistoryStats(p));
|
|
372
|
+
|
|
373
|
+
if (p.uses_partial_candle) {
|
|
374
|
+
lines.push(' ⚠️ 注意: 本日の日足は未確定です。終値確定後にパターンが変化・消失する可能性があります。');
|
|
375
|
+
lines.push('');
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
lines.push(PATTERN_GUIDE);
|
|
381
|
+
|
|
382
|
+
return [{ type: 'text', text: lines.join('\n') }];
|
|
383
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import { formatPair, timeframeLabel } from '../../lib/formatter.js';
|
|
3
|
+
import analyzeFibonacci from '../../tools/analyze_fibonacci.js';
|
|
4
|
+
import { AnalyzeFibonacciInputSchema, AnalyzeFibonacciOutputSchema } from '../schemas.js';
|
|
5
|
+
import type { ToolDefinition } from '../tool-definition.js';
|
|
6
|
+
|
|
7
|
+
type FibOutput = z.infer<typeof AnalyzeFibonacciOutputSchema>;
|
|
8
|
+
|
|
9
|
+
export const toolDef: ToolDefinition = {
|
|
10
|
+
name: 'analyze_fibonacci',
|
|
11
|
+
description: `[Fibonacci / Retracement / Extension] フィボナッチ分析(fibonacci / retracement / extension / swing high-low)。スイング自動検出→リトレースメント/エクステンション水準を算出。過去の反応実績(反発率・リターン)付き。
|
|
12
|
+
|
|
13
|
+
複数タイムフレーム分析には analyze_mtf_fibonacci を使用。`,
|
|
14
|
+
inputSchema: AnalyzeFibonacciInputSchema,
|
|
15
|
+
handler: async (args: Record<string, unknown>) => {
|
|
16
|
+
const result = await analyzeFibonacci(args);
|
|
17
|
+
const res: FibOutput = AnalyzeFibonacciOutputSchema.parse(result);
|
|
18
|
+
if (!res.ok) return res;
|
|
19
|
+
|
|
20
|
+
const data = res.data;
|
|
21
|
+
const meta = res.meta;
|
|
22
|
+
const pair = String(data.pair || (args as Record<string, unknown>).pair || 'btc_jpy');
|
|
23
|
+
const _tfLabel = timeframeLabel(String(data.timeframe || '1day'));
|
|
24
|
+
const pairLabel = formatPair(pair);
|
|
25
|
+
|
|
26
|
+
// Build text content for LLM
|
|
27
|
+
const contentArr = res.content ?? [];
|
|
28
|
+
const existingText = contentArr.length > 0 ? contentArr[0].text : '';
|
|
29
|
+
|
|
30
|
+
// Return with structuredContent for tool chaining / visualization
|
|
31
|
+
return {
|
|
32
|
+
content: [{ type: 'text', text: existingText }],
|
|
33
|
+
structuredContent: {
|
|
34
|
+
ok: true,
|
|
35
|
+
type: 'fibonacci',
|
|
36
|
+
summary: res.summary ?? `${pairLabel} フィボナッチ分析`,
|
|
37
|
+
data: {
|
|
38
|
+
pair: data.pair,
|
|
39
|
+
timeframe: data.timeframe,
|
|
40
|
+
currentPrice: data.currentPrice,
|
|
41
|
+
trend: data.trend,
|
|
42
|
+
swingHigh: data.swingHigh,
|
|
43
|
+
swingLow: data.swingLow,
|
|
44
|
+
range: data.range,
|
|
45
|
+
levels: data.levels ?? [],
|
|
46
|
+
extensions: data.extensions ?? [],
|
|
47
|
+
position: data.position ?? {},
|
|
48
|
+
levelStats: data.levelStats ?? [],
|
|
49
|
+
},
|
|
50
|
+
meta,
|
|
51
|
+
} as Record<string, unknown>,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|