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,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* render_candle_pattern_diagram - 2本足パターンの視覚化SVG生成
|
|
3
|
+
*
|
|
4
|
+
* analyze_candle_patterns で検出されたパターンを初心者向けに視覚化します。
|
|
5
|
+
* オレンジ色のハイライトで「前日」「確定日」を明示し、
|
|
6
|
+
* 5日間のローソク足を並べて表示する構成です。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { failFromError } from '../lib/result.js';
|
|
10
|
+
import { RenderCandlePatternDiagramInputSchema, RenderCandlePatternDiagramOutputSchema } from '../src/schemas.js';
|
|
11
|
+
import type { ToolDefinition } from '../src/tool-definition.js';
|
|
12
|
+
|
|
13
|
+
// ----- 型定義 -----
|
|
14
|
+
interface DiagramCandle {
|
|
15
|
+
date: string;
|
|
16
|
+
open: number;
|
|
17
|
+
high: number;
|
|
18
|
+
low: number;
|
|
19
|
+
close: number;
|
|
20
|
+
type: 'bullish' | 'bearish';
|
|
21
|
+
isPartial?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface DiagramPattern {
|
|
25
|
+
name: string;
|
|
26
|
+
nameEn?: string;
|
|
27
|
+
confirmedDate: string;
|
|
28
|
+
involvedIndices: [number, number];
|
|
29
|
+
direction?: 'bullish' | 'bearish';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ----- 配色定義(bitbank準拠) -----
|
|
33
|
+
const COLORS = {
|
|
34
|
+
dark: {
|
|
35
|
+
background: '#1f2937',
|
|
36
|
+
bullish: '#16a34a',
|
|
37
|
+
bearish: '#ef4444',
|
|
38
|
+
highlight: '#fb923c',
|
|
39
|
+
arrow: '#93c5fd', // 淡いブルー(矢印・包むラベル用)
|
|
40
|
+
grid: '#374151',
|
|
41
|
+
text: '#e5e7eb',
|
|
42
|
+
textMuted: '#9ca3af',
|
|
43
|
+
descBox: '#374151',
|
|
44
|
+
},
|
|
45
|
+
light: {
|
|
46
|
+
background: '#f8fafc',
|
|
47
|
+
bullish: '#16a34a',
|
|
48
|
+
bearish: '#ef4444',
|
|
49
|
+
highlight: '#f97316',
|
|
50
|
+
arrow: '#60a5fa', // 淡いブルー(矢印・包むラベル用)
|
|
51
|
+
grid: '#e2e8f0',
|
|
52
|
+
text: '#1e293b',
|
|
53
|
+
textMuted: '#64748b',
|
|
54
|
+
descBox: '#e2e8f0',
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ----- レイアウト定数 -----
|
|
59
|
+
const LAYOUT = {
|
|
60
|
+
width: 800,
|
|
61
|
+
height: 450,
|
|
62
|
+
plotTop: 60,
|
|
63
|
+
plotBottom: 340,
|
|
64
|
+
plotLeft: 100,
|
|
65
|
+
plotRight: 750,
|
|
66
|
+
candleWidth: 40,
|
|
67
|
+
candleSpacing: 110,
|
|
68
|
+
startX: 140,
|
|
69
|
+
dateLabelY: 365,
|
|
70
|
+
descBoxY: 385,
|
|
71
|
+
descBoxHeight: 50,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// ----- ヘルパー関数 -----
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 価格をフォーマット(万円単位)
|
|
78
|
+
*/
|
|
79
|
+
function formatPriceLabel(price: number): string {
|
|
80
|
+
if (price >= 10000000) {
|
|
81
|
+
return `${(price / 10000).toFixed(0)}万`;
|
|
82
|
+
}
|
|
83
|
+
return price.toLocaleString('ja-JP');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* パターンの説明文を生成(パターン名に応じた固定説明文)
|
|
88
|
+
*/
|
|
89
|
+
function getPatternDescription(pattern: DiagramPattern): string {
|
|
90
|
+
const { name } = pattern;
|
|
91
|
+
|
|
92
|
+
// パターン名に応じた固定の説明文マッピング
|
|
93
|
+
const descriptions: Record<string, string> = {
|
|
94
|
+
陽線包み線: '陽線包み線: 前日の陰線(赤)を翌日の陽線(緑)が完全に包む → 上昇転換のサイン',
|
|
95
|
+
陰線包み線: '陰線包み線: 前日の陽線(緑)を翌日の陰線(赤)が完全に包む → 下落転換のサイン',
|
|
96
|
+
陽線はらみ線: '陽線はらみ線: 前日の大陰線の中に小さな陽線が収まる → 上昇転換の予兆',
|
|
97
|
+
陰線はらみ線: '陰線はらみ線: 前日の大陽線の中に小さな陰線が収まる → 下落転換の予兆',
|
|
98
|
+
毛抜き天井: '毛抜き天井: 高値圏で2日連続同じ高値 → 上昇の限界、下落転換のサイン',
|
|
99
|
+
毛抜き底: '毛抜き底: 安値圏で2日連続同じ安値 → 下落の限界、上昇転換のサイン',
|
|
100
|
+
かぶせ線: 'かぶせ線: 高寄り後に陰線で前日陽線の中心以下 → 上昇一服、調整のサイン',
|
|
101
|
+
切り込み線: '切り込み線: 安寄り後に陽線で前日陰線の中心超え → 下落一服、反発のサイン',
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return descriptions[name] || `${name}パターン`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ----- SVG生成関数 -----
|
|
108
|
+
|
|
109
|
+
export default async function renderCandlePatternDiagram(opts: {
|
|
110
|
+
candles: DiagramCandle[];
|
|
111
|
+
pattern?: DiagramPattern;
|
|
112
|
+
title?: string;
|
|
113
|
+
theme?: 'dark' | 'light';
|
|
114
|
+
}) {
|
|
115
|
+
try {
|
|
116
|
+
// 入力の正規化
|
|
117
|
+
const input = RenderCandlePatternDiagramInputSchema.parse(opts);
|
|
118
|
+
const { candles, pattern, theme = 'dark' } = input;
|
|
119
|
+
const colors = COLORS[theme];
|
|
120
|
+
|
|
121
|
+
// タイトル決定
|
|
122
|
+
const title = input.title || (pattern?.name ? `${pattern.name}パターン` : 'ローソク足チャート');
|
|
123
|
+
|
|
124
|
+
// === Y軸の動的スケーリング ===
|
|
125
|
+
const allPrices = candles.flatMap((c) => [c.high, c.low]);
|
|
126
|
+
const maxPrice = Math.max(...allPrices);
|
|
127
|
+
const minPrice = Math.min(...allPrices);
|
|
128
|
+
const priceRange = maxPrice - minPrice;
|
|
129
|
+
|
|
130
|
+
// 上下に10%の余白(priceRange が 0 の場合は固定余白を使用)
|
|
131
|
+
const padding = priceRange === 0 ? Math.max(Math.abs(maxPrice) * 0.1, 1) : priceRange * 0.1;
|
|
132
|
+
const yMax = maxPrice + padding;
|
|
133
|
+
const yMin = minPrice - padding;
|
|
134
|
+
|
|
135
|
+
const { plotTop, plotBottom } = LAYOUT;
|
|
136
|
+
const plotHeight = plotBottom - plotTop;
|
|
137
|
+
const ySpan = yMax - yMin; // padding により必ず > 0
|
|
138
|
+
|
|
139
|
+
// 価格 → Y座標の変換
|
|
140
|
+
const priceToY = (price: number): number => {
|
|
141
|
+
return plotTop + ((yMax - price) / ySpan) * plotHeight;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// === SVG生成開始 ===
|
|
145
|
+
const parts: string[] = [];
|
|
146
|
+
|
|
147
|
+
// フォント定義
|
|
148
|
+
const fontFamily = `'Noto Sans JP', sans-serif`;
|
|
149
|
+
|
|
150
|
+
// ヘッダー
|
|
151
|
+
parts.push(`<svg width="${LAYOUT.width}" height="${LAYOUT.height}" xmlns="http://www.w3.org/2000/svg">`);
|
|
152
|
+
|
|
153
|
+
// Google Fonts インポート & グローバルスタイル
|
|
154
|
+
parts.push(`<defs>
|
|
155
|
+
<style>
|
|
156
|
+
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap');
|
|
157
|
+
text { font-family: ${fontFamily}; }
|
|
158
|
+
</style>
|
|
159
|
+
</defs>`);
|
|
160
|
+
|
|
161
|
+
parts.push(`<rect width="${LAYOUT.width}" height="${LAYOUT.height}" fill="${colors.background}"/>`);
|
|
162
|
+
|
|
163
|
+
// タイトル
|
|
164
|
+
parts.push(
|
|
165
|
+
`<text x="${LAYOUT.width / 2}" y="35" text-anchor="middle" font-size="18" font-weight="bold" fill="${colors.text}">${escapeXml(title)}</text>`,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// グリッド線(横)
|
|
169
|
+
const gridCount = 5;
|
|
170
|
+
for (let i = 0; i <= gridCount; i++) {
|
|
171
|
+
const y = plotTop + (plotHeight / gridCount) * i;
|
|
172
|
+
parts.push(
|
|
173
|
+
`<line x1="${LAYOUT.plotLeft}" y1="${y}" x2="${LAYOUT.plotRight}" y2="${y}" stroke="${colors.grid}" stroke-width="1" stroke-dasharray="4,4"/>`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Y軸ラベル(価格)
|
|
178
|
+
for (let i = 0; i <= gridCount; i++) {
|
|
179
|
+
const y = plotTop + (plotHeight / gridCount) * i;
|
|
180
|
+
const price = yMax - ((yMax - yMin) / gridCount) * i;
|
|
181
|
+
parts.push(
|
|
182
|
+
`<text x="${LAYOUT.plotLeft - 10}" y="${y + 4}" text-anchor="end" font-size="11" fill="${colors.textMuted}">${formatPriceLabel(price)}</text>`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// === ローソク足の描画 ===
|
|
187
|
+
for (let i = 0; i < candles.length; i++) {
|
|
188
|
+
const candle = candles[i];
|
|
189
|
+
const x = LAYOUT.startX + i * LAYOUT.candleSpacing;
|
|
190
|
+
|
|
191
|
+
// ヒゲ(高値〜安値)
|
|
192
|
+
const highY = priceToY(candle.high);
|
|
193
|
+
const lowY = priceToY(candle.low);
|
|
194
|
+
const wickColor = candle.type === 'bullish' ? colors.bullish : colors.bearish;
|
|
195
|
+
parts.push(`<line x1="${x}" y1="${highY}" x2="${x}" y2="${lowY}" stroke="${wickColor}" stroke-width="2"/>`);
|
|
196
|
+
|
|
197
|
+
// 実体(始値〜終値)
|
|
198
|
+
const openY = priceToY(candle.open);
|
|
199
|
+
const closeY = priceToY(candle.close);
|
|
200
|
+
const bodyTop = Math.min(openY, closeY);
|
|
201
|
+
const bodyHeight = Math.max(Math.abs(openY - closeY), 2); // 最低2pxを保証
|
|
202
|
+
|
|
203
|
+
const fill = candle.type === 'bullish' ? colors.bullish : colors.bearish;
|
|
204
|
+
|
|
205
|
+
// ハイライト判定
|
|
206
|
+
const isHighlighted = pattern?.involvedIndices.includes(i);
|
|
207
|
+
const strokeAttr = isHighlighted ? `stroke="${colors.highlight}" stroke-width="3"` : '';
|
|
208
|
+
|
|
209
|
+
parts.push(
|
|
210
|
+
`<rect x="${x - LAYOUT.candleWidth / 2}" y="${bodyTop}" width="${LAYOUT.candleWidth}" height="${bodyHeight}" fill="${fill}" ${strokeAttr}/>`,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// 日付ラベル
|
|
214
|
+
const dateColor = candle.isPartial ? colors.highlight : colors.text;
|
|
215
|
+
const partialMark = candle.isPartial ? ' ⚠' : '';
|
|
216
|
+
parts.push(
|
|
217
|
+
`<text x="${x}" y="${LAYOUT.dateLabelY}" text-anchor="middle" font-size="12" fill="${dateColor}">${escapeXml(candle.date)}${partialMark}</text>`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// === パターンハイライト ===
|
|
222
|
+
if (pattern) {
|
|
223
|
+
const [prevIndex, confirmedIndex] = pattern.involvedIndices;
|
|
224
|
+
|
|
225
|
+
// 「前日」ラベル
|
|
226
|
+
if (prevIndex >= 0 && prevIndex < candles.length) {
|
|
227
|
+
const prevX = LAYOUT.startX + prevIndex * LAYOUT.candleSpacing;
|
|
228
|
+
const prevY = priceToY(candles[prevIndex].high) - 20;
|
|
229
|
+
parts.push(
|
|
230
|
+
`<text x="${prevX}" y="${prevY}" text-anchor="middle" font-size="16" font-weight="bold" fill="${colors.highlight}">前日</text>`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 「確定日」ラベル
|
|
235
|
+
if (confirmedIndex >= 0 && confirmedIndex < candles.length) {
|
|
236
|
+
const confX = LAYOUT.startX + confirmedIndex * LAYOUT.candleSpacing;
|
|
237
|
+
const confY = priceToY(candles[confirmedIndex].high) - 20;
|
|
238
|
+
parts.push(
|
|
239
|
+
`<text x="${confX}" y="${confY}" text-anchor="middle" font-size="16" font-weight="bold" fill="${colors.highlight}">確定日</text>`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 矢印と「包む」ラベル(包み線の場合)
|
|
244
|
+
// 「包む」の矢印は大きい方(確定日)から小さい方(前日)へ向かう
|
|
245
|
+
// 矢印は両方のローソク足の「間」(内側)に配置
|
|
246
|
+
if (pattern.name.includes('包み線') && prevIndex >= 0 && confirmedIndex >= 0) {
|
|
247
|
+
const prevX = LAYOUT.startX + prevIndex * LAYOUT.candleSpacing;
|
|
248
|
+
const confX = LAYOUT.startX + confirmedIndex * LAYOUT.candleSpacing;
|
|
249
|
+
|
|
250
|
+
// 矢印のY位置: 両方のローソク足の実体の下端の下に配置(重ならないように)
|
|
251
|
+
const prevBodyBottom = Math.max(priceToY(candles[prevIndex].open), priceToY(candles[prevIndex].close));
|
|
252
|
+
const confBodyBottom = Math.max(
|
|
253
|
+
priceToY(candles[confirmedIndex].open),
|
|
254
|
+
priceToY(candles[confirmedIndex].close),
|
|
255
|
+
);
|
|
256
|
+
const maxBodyBottom = Math.max(prevBodyBottom, confBodyBottom);
|
|
257
|
+
const arrowY = maxBodyBottom + 15; // 実体の下端から15px下
|
|
258
|
+
|
|
259
|
+
// 矢印マーカー定義(淡いブルー)
|
|
260
|
+
// orient="auto"で線の方向に回転するため、右向き三角形を定義
|
|
261
|
+
// 線が右→左の場合、180度回転して左向き矢印になる
|
|
262
|
+
parts.push(`<defs>
|
|
263
|
+
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
|
|
264
|
+
<polygon points="0 0, 10 3.5, 0 7" fill="${colors.arrow}"/>
|
|
265
|
+
</marker>
|
|
266
|
+
</defs>`);
|
|
267
|
+
|
|
268
|
+
// 矢印線: 確定日の左端 → 前日の右端(内側を通る短い矢印)
|
|
269
|
+
const halfWidth = LAYOUT.candleWidth / 2; // 20px
|
|
270
|
+
const gap = 10; // 実体からの距離
|
|
271
|
+
const confLeftEdge = confX - halfWidth; // 確定日の実体左端
|
|
272
|
+
const prevRightEdge = prevX + halfWidth; // 前日の実体右端
|
|
273
|
+
const arrowStartX = confLeftEdge - gap; // 確定日の左端からgap分離す
|
|
274
|
+
const arrowEndX = prevRightEdge + gap; // 前日の右端からgap分離す
|
|
275
|
+
parts.push(
|
|
276
|
+
`<line x1="${arrowStartX}" y1="${arrowY}" x2="${arrowEndX}" y2="${arrowY}" stroke="${colors.arrow}" stroke-width="2" marker-end="url(#arrowhead)"/>`,
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
// 「包む」ラベル: 矢印の下に配置(淡いブルー・太字)
|
|
280
|
+
const labelX = (arrowStartX + arrowEndX) / 2;
|
|
281
|
+
parts.push(
|
|
282
|
+
`<text x="${labelX}" y="${arrowY + 30}" text-anchor="middle" font-size="16" font-weight="bold" fill="${colors.arrow}">包む</text>`,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 説明ボックス
|
|
287
|
+
const desc = getPatternDescription(pattern);
|
|
288
|
+
parts.push(
|
|
289
|
+
`<rect x="50" y="${LAYOUT.descBoxY}" width="${LAYOUT.width - 100}" height="${LAYOUT.descBoxHeight}" rx="5" fill="${colors.descBox}"/>`,
|
|
290
|
+
);
|
|
291
|
+
parts.push(
|
|
292
|
+
`<text x="${LAYOUT.width / 2}" y="${LAYOUT.descBoxY + 30}" text-anchor="middle" font-size="14" fill="${colors.text}">${escapeXml(desc)}</text>`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// SVG終了
|
|
297
|
+
parts.push('</svg>');
|
|
298
|
+
|
|
299
|
+
const svg = parts.join('\n');
|
|
300
|
+
|
|
301
|
+
const result = {
|
|
302
|
+
ok: true as const,
|
|
303
|
+
summary: `${title}のSVG図を生成しました(${candles.length}本のローソク足)`,
|
|
304
|
+
data: { svg },
|
|
305
|
+
meta: {
|
|
306
|
+
width: LAYOUT.width,
|
|
307
|
+
height: LAYOUT.height,
|
|
308
|
+
candleCount: candles.length,
|
|
309
|
+
patternName: pattern?.name || null,
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
return RenderCandlePatternDiagramOutputSchema.parse(result);
|
|
314
|
+
} catch (e: unknown) {
|
|
315
|
+
return failFromError(e, {
|
|
316
|
+
schema: RenderCandlePatternDiagramOutputSchema,
|
|
317
|
+
defaultMessage: 'ローソク足パターン図の描画中にエラーが発生しました',
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ----- XMLエスケープ -----
|
|
323
|
+
function escapeXml(str: string): string {
|
|
324
|
+
return str
|
|
325
|
+
.replace(/&/g, '&')
|
|
326
|
+
.replace(/</g, '<')
|
|
327
|
+
.replace(/>/g, '>')
|
|
328
|
+
.replace(/"/g, '"')
|
|
329
|
+
.replace(/'/g, ''');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ── MCP ツール定義(tool-registry から自動収集) ──
|
|
333
|
+
export const toolDef: ToolDefinition = {
|
|
334
|
+
name: 'render_candle_pattern_diagram',
|
|
335
|
+
description: `[Candle Pattern Diagram / Education] ローソク足パターンの教育用構造図(candle pattern diagram / education / visualization)。analyze_candle_patterns の結果を初心者向けに視覚化。
|
|
336
|
+
|
|
337
|
+
ユーザーが「図で見せて」等と明示した場合のみ使用。自発的呼び出し禁止。data.svg を受け取ったら Visualizer(show_widget) の widget_code にそのまま渡してレンダリングすること。
|
|
338
|
+
チャット本文へのSVGコード直接出力は禁止。`,
|
|
339
|
+
inputSchema: RenderCandlePatternDiagramInputSchema,
|
|
340
|
+
handler: async (args: {
|
|
341
|
+
candles: {
|
|
342
|
+
date: string;
|
|
343
|
+
open: number;
|
|
344
|
+
high: number;
|
|
345
|
+
low: number;
|
|
346
|
+
close: number;
|
|
347
|
+
type: 'bullish' | 'bearish';
|
|
348
|
+
isPartial?: boolean;
|
|
349
|
+
}[];
|
|
350
|
+
pattern?: {
|
|
351
|
+
name: string;
|
|
352
|
+
nameEn?: string;
|
|
353
|
+
confirmedDate: string;
|
|
354
|
+
involvedIndices: [number, number];
|
|
355
|
+
direction?: 'bullish' | 'bearish';
|
|
356
|
+
};
|
|
357
|
+
title?: string;
|
|
358
|
+
theme?: 'dark' | 'light';
|
|
359
|
+
}) => {
|
|
360
|
+
const res = await renderCandlePatternDiagram(args);
|
|
361
|
+
if (!res?.ok) return res;
|
|
362
|
+
const data = res.data || {};
|
|
363
|
+
const meta = res.meta || {};
|
|
364
|
+
if (data?.svg) {
|
|
365
|
+
const patternName = meta?.patternName || args?.pattern?.name || 'ローソク足パターン';
|
|
366
|
+
const identifier = `candle-pattern-${patternName.replace(/[^a-z0-9]+/gi, '-')}-${Date.now()}`;
|
|
367
|
+
const title = `${patternName}構造図`;
|
|
368
|
+
const text = [
|
|
369
|
+
'--- Candle Pattern Diagram ---',
|
|
370
|
+
`identifier: ${identifier}`,
|
|
371
|
+
`title: ${title}`,
|
|
372
|
+
'type: image/svg+xml',
|
|
373
|
+
'',
|
|
374
|
+
String(data.svg),
|
|
375
|
+
'',
|
|
376
|
+
'Chart Info:',
|
|
377
|
+
`- Pattern: ${patternName}`,
|
|
378
|
+
`- Size: ${meta?.width || 800}x${meta?.height || 450}px`,
|
|
379
|
+
`- Candles: ${meta?.candleCount || 5}本`,
|
|
380
|
+
].join('\n');
|
|
381
|
+
const enriched = {
|
|
382
|
+
...res,
|
|
383
|
+
artifactHint: { renderHint: 'ARTIFACT_REQUIRED', displayType: 'image/svg+xml', source: 'inline_svg' },
|
|
384
|
+
};
|
|
385
|
+
return { content: [{ type: 'text', text }], structuredContent: enriched };
|
|
386
|
+
}
|
|
387
|
+
return { content: [{ type: 'text', text: res.summary || 'Diagram rendered' }], structuredContent: res };
|
|
388
|
+
},
|
|
389
|
+
};
|