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,298 @@
1
+ import { z } from 'zod';
2
+ import {
3
+ BaseMetaSchema,
4
+ BasePairInputSchema,
5
+ CandleSchema,
6
+ CandleTypeEnum,
7
+ FailResultSchema,
8
+ toolResultSchema,
9
+ } from './base.js';
10
+
11
+ // === Ticker ===
12
+ export const TickerNormalizedSchema = z.object({
13
+ pair: z.string(),
14
+ last: z.number().nullable(),
15
+ buy: z.number().nullable(),
16
+ sell: z.number().nullable(),
17
+ open: z.number().nullable(),
18
+ high: z.number().nullable(),
19
+ low: z.number().nullable(),
20
+ volume: z.number().nullable(),
21
+ timestamp: z.number().nullable(),
22
+ isoTime: z.string().nullable(),
23
+ });
24
+
25
+ export const GetTickerDataSchemaOut = z.object({ raw: z.unknown(), normalized: TickerNormalizedSchema });
26
+ export const GetTickerMetaSchemaOut = BaseMetaSchema;
27
+ export const GetTickerOutputSchema = toolResultSchema(GetTickerDataSchemaOut, GetTickerMetaSchemaOut);
28
+ export const GetTickerInputSchema = BasePairInputSchema;
29
+
30
+ // === Orderbook ===
31
+ export const OrderbookLevelSchema = z.object({ price: z.number(), size: z.number() });
32
+ export const OrderbookLevelWithCumSchema = OrderbookLevelSchema.extend({ cumSize: z.number() });
33
+ export const OrderbookNormalizedSchema = z.object({
34
+ pair: z.string(),
35
+ bestBid: z.number().nullable(),
36
+ bestAsk: z.number().nullable(),
37
+ spread: z.number().nullable(),
38
+ mid: z.number().nullable(),
39
+ bids: z.array(OrderbookLevelWithCumSchema),
40
+ asks: z.array(OrderbookLevelWithCumSchema),
41
+ timestamp: z.number().nullable(),
42
+ isoTime: z.string().nullable(),
43
+ });
44
+ export const GetOrderbookDataSchemaOut = z.object({ raw: z.unknown(), normalized: OrderbookNormalizedSchema });
45
+ export const GetOrderbookMetaSchemaOut = BaseMetaSchema.extend({ topN: z.number(), count: z.number() });
46
+ export const GetOrderbookOutputSchema = toolResultSchema(GetOrderbookDataSchemaOut, GetOrderbookMetaSchemaOut);
47
+
48
+ export const GetOrderbookInputSchema = BasePairInputSchema.extend({
49
+ mode: z.enum(['summary', 'pressure', 'statistics', 'raw']).optional().default('summary'),
50
+ /** summary mode: 上位N層 (1-200) */
51
+ topN: z.number().int().min(1).max(200).optional().default(10),
52
+ /** pressure mode: 帯域幅 (例: [0.001, 0.005, 0.01]) */
53
+ bandsPct: z.array(z.number().positive()).optional().default([0.001, 0.005, 0.01]),
54
+ /** statistics mode: 範囲% (例: [0.5, 1.0, 2.0]) */
55
+ ranges: z.array(z.number().positive()).optional().default([0.5, 1.0, 2.0]),
56
+ /** statistics mode: 価格ゾーン分割数 */
57
+ priceZones: z.number().int().min(2).max(50).optional().default(10),
58
+ });
59
+
60
+ // === Candles ===
61
+ export const KeyPointSchema = z.object({
62
+ index: z.number(),
63
+ date: z.string().nullable(),
64
+ close: z.number(),
65
+ changePct: z.number().nullable().optional(),
66
+ });
67
+
68
+ export const KeyPointsSchema = z.object({
69
+ today: KeyPointSchema.nullable(),
70
+ sevenDaysAgo: KeyPointSchema.nullable(),
71
+ thirtyDaysAgo: KeyPointSchema.nullable(),
72
+ ninetyDaysAgo: KeyPointSchema.nullable(),
73
+ });
74
+
75
+ export const VolumeStatsSchema = z.object({
76
+ recent7DaysAvg: z.number(),
77
+ previous7DaysAvg: z.number(),
78
+ last30DaysAvg: z.number().nullable(),
79
+ changePct: z.number(),
80
+ judgment: z.string(),
81
+ });
82
+
83
+ export const GetCandlesDataSchemaOut = z.object({
84
+ raw: z.unknown(),
85
+ normalized: z.array(CandleSchema),
86
+ keyPoints: KeyPointsSchema.optional(),
87
+ volumeStats: VolumeStatsSchema.nullable().optional(),
88
+ });
89
+ export const GetCandlesMetaSchemaOut = BaseMetaSchema.extend({
90
+ type: CandleTypeEnum,
91
+ count: z.number(),
92
+ });
93
+ export const GetCandlesOutputSchema = toolResultSchema(GetCandlesDataSchemaOut, GetCandlesMetaSchemaOut);
94
+
95
+ export const GetCandlesInputSchema = z.object({
96
+ pair: z.string(),
97
+ type: CandleTypeEnum,
98
+ date: z
99
+ .string()
100
+ .optional()
101
+ .describe(
102
+ "YYYYMMDD format (e.g., 20251022). Fetches the {limit} most recent candles up to and including this date. For '1month' type use YYYY format. If omitted, returns latest candles.",
103
+ ),
104
+ limit: z.number().int().min(1).max(1000).optional().default(200),
105
+ view: z.enum(['full', 'items']).optional().default('full'),
106
+ tz: z
107
+ .string()
108
+ .optional()
109
+ .default('Asia/Tokyo')
110
+ .describe('タイムゾーン(デフォルト: Asia/Tokyo)。各ローソク足に isoTimeLocal フィールドを追加。空文字でUTCのみ'),
111
+ });
112
+
113
+ // === Transactions ===
114
+ export const TransactionItemSchema = z.object({
115
+ price: z.number(),
116
+ amount: z.number(),
117
+ side: z.enum(['buy', 'sell']),
118
+ timestampMs: z.number().int(),
119
+ isoTime: z.string(),
120
+ });
121
+
122
+ export const GetTransactionsDataSchemaOut = z.object({ raw: z.unknown(), normalized: z.array(TransactionItemSchema) });
123
+ export const GetTransactionsMetaSchemaOut = BaseMetaSchema.extend({
124
+ count: z.number().int(),
125
+ source: z.enum(['latest', 'by_date']),
126
+ });
127
+ export const GetTransactionsOutputSchema = toolResultSchema(GetTransactionsDataSchemaOut, GetTransactionsMetaSchemaOut);
128
+
129
+ export const GetTransactionsInputSchema = BasePairInputSchema.extend({
130
+ limit: z.number().int().min(1).max(1000).optional().default(100),
131
+ date: z
132
+ .string()
133
+ .regex(/^\d{8}$/)
134
+ .optional()
135
+ .describe('YYYYMMDD; omit for latest'),
136
+ minAmount: z.number().positive().optional(),
137
+ maxAmount: z.number().positive().optional(),
138
+ minPrice: z.number().positive().optional(),
139
+ maxPrice: z.number().positive().optional(),
140
+ view: z.enum(['summary', 'items']).optional().default('summary'),
141
+ });
142
+
143
+ // === Depth (raw depth for analysis/visualization) ===
144
+ export const DepthLevelTupleSchema = z.tuple([z.string(), z.string()]);
145
+ export const GetDepthDataSchemaOut = z.object({
146
+ asks: z.array(DepthLevelTupleSchema),
147
+ bids: z.array(DepthLevelTupleSchema),
148
+ asks_over: z.string().optional(),
149
+ asks_under: z.string().optional(),
150
+ bids_over: z.string().optional(),
151
+ bids_under: z.string().optional(),
152
+ ask_market: z.string().optional(),
153
+ bid_market: z.string().optional(),
154
+ timestamp: z.number().int(),
155
+ sequenceId: z.number().int().optional(),
156
+ overlays: z
157
+ .object({
158
+ depth_zones: z.array(
159
+ z.object({ low: z.number(), high: z.number(), color: z.string().optional(), label: z.string().optional() }),
160
+ ),
161
+ })
162
+ .optional(),
163
+ });
164
+ export const GetDepthMetaSchemaOut = BaseMetaSchema;
165
+ export const GetDepthOutputSchema = toolResultSchema(GetDepthDataSchemaOut, GetDepthMetaSchemaOut);
166
+
167
+ // === Flow Metrics (derived from recent transactions) ===
168
+ export const FlowBucketSchema = z.object({
169
+ timestampMs: z.number().int(),
170
+ isoTime: z.string(),
171
+ isoTimeJST: z.string().optional(),
172
+ displayTime: z.string().optional(),
173
+ buyVolume: z.number(),
174
+ sellVolume: z.number(),
175
+ totalVolume: z.number(),
176
+ cvd: z.number(),
177
+ zscore: z.number().nullable().optional(),
178
+ spike: z.enum(['notice', 'warning', 'strong']).nullable().optional(),
179
+ });
180
+
181
+ export const GetFlowMetricsDataSchemaOut = z.object({
182
+ source: z.literal('transactions'),
183
+ params: z.object({ bucketMs: z.number().int().min(1000) }),
184
+ aggregates: z.object({
185
+ totalTrades: z.number().int(),
186
+ buyTrades: z.number().int(),
187
+ sellTrades: z.number().int(),
188
+ buyVolume: z.number(),
189
+ sellVolume: z.number(),
190
+ netVolume: z.number(),
191
+ aggressorRatio: z.number().min(0).max(1),
192
+ finalCvd: z.number(),
193
+ }),
194
+ series: z.object({ buckets: z.array(FlowBucketSchema) }),
195
+ });
196
+
197
+ export const GetFlowMetricsMetaSchemaOut = BaseMetaSchema.extend({
198
+ count: z.number().int(),
199
+ bucketMs: z.number().int(),
200
+ timezone: z.string().optional(),
201
+ timezoneOffset: z.string().optional(),
202
+ serverTime: z.string().optional(),
203
+ hours: z.number().optional(),
204
+ mode: z.enum(['time_range']).optional(),
205
+ actualRange: z
206
+ .object({
207
+ start: z.string(),
208
+ end: z.string(),
209
+ durationMinutes: z.number().int(),
210
+ })
211
+ .optional(),
212
+ warning: z.string().optional(),
213
+ });
214
+
215
+ export const GetFlowMetricsOutputSchema = toolResultSchema(GetFlowMetricsDataSchemaOut, GetFlowMetricsMetaSchemaOut);
216
+
217
+ export const GetFlowMetricsInputSchema = BasePairInputSchema.extend({
218
+ limit: z
219
+ .number()
220
+ .int()
221
+ .min(1)
222
+ .max(2000)
223
+ .optional()
224
+ .default(100)
225
+ .describe('取得する約定件数(バケット数ではない)。hours 指定時は無視されます'),
226
+ hours: z
227
+ .number()
228
+ .min(0.1)
229
+ .max(24)
230
+ .optional()
231
+ .describe(
232
+ '指定した時間数分の約定を取得して分析(例: 8 → 直近8時間)。limit より優先。複数日にまたがる場合も自動で取得します',
233
+ ),
234
+ date: z
235
+ .string()
236
+ .regex(/^\d{8}$/)
237
+ .optional()
238
+ .describe('YYYYMMDD; omit for latest'),
239
+ bucketMs: z
240
+ .number()
241
+ .int()
242
+ .min(1000)
243
+ .max(3600_000)
244
+ .optional()
245
+ .default(60_000)
246
+ .describe('バケットの時間幅(ミリ秒)。デフォルト60000=1分間隔'),
247
+ view: z
248
+ .enum(['summary', 'compact', 'buckets', 'full'])
249
+ .optional()
250
+ .default('summary')
251
+ .describe(
252
+ 'summary: 集計値のみ (buckets 省略) / compact: 非ゼロバケットのみ / buckets: 直近 N バケット / full: 全バケット',
253
+ ),
254
+ bucketsN: z.number().int().min(1).max(100).optional().default(10),
255
+ tz: z.string().optional().default('Asia/Tokyo'),
256
+ });
257
+
258
+ // === /tickers_jpy (public REST) ===
259
+ export const TickerJpyItemSchema = z.object({
260
+ pair: z.string(),
261
+ sell: z.string().nullable(),
262
+ buy: z.string().nullable(),
263
+ high: z.string(),
264
+ low: z.string(),
265
+ open: z.string(),
266
+ last: z.string(),
267
+ vol: z.string(),
268
+ timestamp: z.number(),
269
+ // 追加: 24h変化率(%)。open/last から算出
270
+ change24h: z.number().nullable().optional(),
271
+ change24hPct: z.number().nullable().optional(),
272
+ });
273
+ export const GetTickersJpyOutputSchema = z.union([
274
+ z.object({
275
+ ok: z.literal(true),
276
+ summary: z.string(),
277
+ data: z.array(TickerJpyItemSchema),
278
+ meta: z.object({ cache: z.object({ hit: z.boolean(), key: z.string() }).optional(), ts: z.string() }).passthrough(),
279
+ }),
280
+ FailResultSchema,
281
+ ]);
282
+
283
+ // === Market Summary (tickers + volatility snapshot) ===
284
+ export const MarketSummaryItemSchema = z.object({
285
+ pair: z.string(),
286
+ last: z.number().nullable(),
287
+ change24hPct: z.number().nullable().optional(),
288
+ vol24h: z.number().nullable().optional(),
289
+ rv_std_ann: z.number().nullable().optional(),
290
+ vol_bucket: z.enum(['low', 'mid', 'high']).nullable().optional(),
291
+ tags: z.array(z.string()).optional(),
292
+ });
293
+
294
+ export const MarketSummaryRanksSchema = z.object({
295
+ topGainers: z.array(z.object({ pair: z.string(), change24hPct: z.number().nullable() })).optional(),
296
+ topLosers: z.array(z.object({ pair: z.string(), change24hPct: z.number().nullable() })).optional(),
297
+ topVolatility: z.array(z.object({ pair: z.string(), rv_std_ann: z.number().nullable() })).optional(),
298
+ });
@@ -0,0 +1,382 @@
1
+ import { z } from 'zod';
2
+ import { BaseMetaSchema, BasePairInputSchema, CandleTypeEnum, FailResultSchema, toolResultSchema } from './base.js';
3
+
4
+ // === Pattern Detection ===
5
+ export const PatternTypeEnum = z.enum([
6
+ 'double_top',
7
+ 'double_bottom',
8
+ 'triple_top',
9
+ 'triple_bottom',
10
+ 'head_and_shoulders',
11
+ 'inverse_head_and_shoulders',
12
+ // legacy umbrella key (kept for filter-compat)
13
+ 'triangle',
14
+ // new explicit triangle variants
15
+ 'triangle_ascending',
16
+ 'triangle_descending',
17
+ 'triangle_symmetrical',
18
+ // wedge patterns
19
+ 'falling_wedge',
20
+ 'rising_wedge',
21
+ 'pennant',
22
+ 'flag',
23
+ ]);
24
+
25
+ export const DetectPatternsInputSchema = BasePairInputSchema.extend({
26
+ type: CandleTypeEnum.optional().default('1day'),
27
+ limit: z.number().int().min(20).max(365).optional().default(90),
28
+ patterns: z
29
+ .array(PatternTypeEnum)
30
+ .optional()
31
+ .describe(
32
+ [
33
+ 'Patterns to detect. Recommended params (guideline):',
34
+ '- double_top/double_bottom: default (swingDepth=7, tolerancePct=0.04, minBarsBetweenSwings=5)',
35
+ '- triple_top/triple_bottom: tolerancePct≈0.05',
36
+ '- triangle_*: tolerancePct≈0.06',
37
+ '- pennant: swingDepth≈5, minBarsBetweenSwings≈3',
38
+ ].join('\n'),
39
+ ),
40
+ // Heuristics
41
+ swingDepth: z.number().int().min(1).max(10).optional().default(7),
42
+ tolerancePct: z.number().min(0).max(0.1).optional().default(0.04),
43
+ minBarsBetweenSwings: z.number().int().min(1).max(30).optional().default(5),
44
+ view: z.enum(['summary', 'detailed', 'full', 'debug']).optional().default('detailed'),
45
+ // New: relevance filter for "current-involved" long-term patterns
46
+ requireCurrentInPattern: z.boolean().optional().default(false),
47
+ currentRelevanceDays: z.number().int().min(1).max(365).optional().default(7),
48
+
49
+ // Unified pattern lifecycle options
50
+ includeForming: z.boolean().optional().default(false).describe('形成中パターンを含める'),
51
+ includeCompleted: z.boolean().optional().default(true).describe('完成済みパターンを含める'),
52
+ includeInvalid: z.boolean().optional().default(false).describe('無効化済みパターンを含める'),
53
+ });
54
+
55
+ export const DetectedPatternSchema = z.object({
56
+ type: PatternTypeEnum,
57
+ confidence: z.number().min(0).max(1),
58
+ /** 検出に使用した時間足(例: '1day', '4hour', '1week') */
59
+ timeframe: CandleTypeEnum.optional(),
60
+ /** 人間可読な時間足ラベル(例: '日足', '4時間足', '週足') */
61
+ timeframeLabel: z.string().optional(),
62
+ range: z.object({ start: z.string(), end: z.string() }),
63
+ pivots: z.array(z.object({ idx: z.number().int(), price: z.number() })).optional(),
64
+ neckline: z
65
+ .array(z.object({ x: z.number().int().optional(), y: z.number() }))
66
+ .length(2)
67
+ .optional(),
68
+ // Optional: structure diagram (static SVG artifact to help beginners grok the pattern shape)
69
+ structureDiagram: z
70
+ .object({
71
+ svg: z.string(),
72
+ artifact: z.object({ identifier: z.string(), title: z.string() }),
73
+ })
74
+ .optional(),
75
+ // 統合: パターンのステータス(形成中/完成度近し/完成済み/無効化)
76
+ status: z.enum(['forming', 'near_completion', 'completed', 'invalid']).optional(),
77
+ // 形成中パターン用フィールド
78
+ apexDate: z.string().optional(), // アペックス(頂点)到達予定日
79
+ daysToApex: z.number().int().optional(), // アペックスまでの日数
80
+ completionPct: z.number().int().optional(), // 完成度(%)
81
+ // 完成済みパターン用フィールド
82
+ breakoutDate: z.string().optional(), // ブレイクアウト日
83
+ breakoutBarIndex: z.number().int().optional(), // ブレイクアウトしたローソク足のインデックス
84
+ daysSinceBreakout: z.number().int().optional(), // ブレイクアウトからの経過日数
85
+ // ブレイク方向と結果
86
+ breakoutDirection: z.enum(['up', 'down']).optional(), // ブレイク方向
87
+ outcome: z.enum(['success', 'failure']).optional(), // パターン結果(期待通り=success, 逆方向=failure)
88
+ // ターゲット価格(ブレイクアウト後の想定到達価格)
89
+ breakoutTarget: z.number().optional(), // 想定ターゲット価格(円)
90
+ targetMethod: z.enum(['flagpole_projection', 'pattern_height', 'neckline_projection']).optional(), // 計算根拠
91
+ targetReachedPct: z.number().optional(), // ターゲットまでの進捗率(%)
92
+ // 用語正規化ラベル(neckline フィールドが何を指すかをパターン種別ごとに明示)
93
+ trendlineLabel: z.string().optional(),
94
+ // ペナント用: フラッグポール(旗竿)情報
95
+ poleDirection: z.enum(['up', 'down']).optional(), // フラッグポールの方向
96
+ priorTrendDirection: z.enum(['bullish', 'bearish']).optional(), // 先行トレンド方向
97
+ isTrendContinuation: z.boolean().optional(), // ブレイク方向が先行トレンドと一致しているか
98
+ flagpoleHeight: z.number().optional(), // フラッグポールの値幅
99
+ retracementRatio: z.number().optional(), // フラッグポールに対する戻し比率(0.38未満ならペナント的)
100
+ aftermath: z
101
+ .object({
102
+ breakoutDate: z.string().nullable().optional(),
103
+ breakoutConfirmed: z.boolean(),
104
+ priceMove: z
105
+ .object({
106
+ days3: z.object({ return: z.number(), high: z.number(), low: z.number() }).nullable().optional(),
107
+ days7: z.object({ return: z.number(), high: z.number(), low: z.number() }).nullable().optional(),
108
+ days14: z.object({ return: z.number(), high: z.number(), low: z.number() }).nullable().optional(),
109
+ })
110
+ .optional(),
111
+ targetReached: z.boolean(),
112
+ theoreticalTarget: z.number().nullable().optional(),
113
+ outcome: z.string(),
114
+ // New: number of bars (days for 1day, weeks for 1week, etc.) to reach theoretical target (if reached within evaluation window)
115
+ daysToTarget: z.number().int().nullable().optional(),
116
+ })
117
+ .optional(),
118
+ });
119
+
120
+ export const DetectPatternsOutputSchema = z.union([
121
+ z.object({
122
+ ok: z.literal(true),
123
+ summary: z.string(),
124
+ data: z.object({
125
+ patterns: z.array(DetectedPatternSchema),
126
+ overlays: z
127
+ .object({
128
+ ranges: z
129
+ .array(
130
+ z.object({
131
+ start: z.string(),
132
+ end: z.string(),
133
+ color: z.string().optional(),
134
+ label: z.string().optional(),
135
+ }),
136
+ )
137
+ .optional(),
138
+ annotations: z.array(z.object({ isoTime: z.string(), text: z.string() })).optional(),
139
+ })
140
+ .optional(),
141
+ warnings: z
142
+ .array(
143
+ z.object({
144
+ type: z.string(),
145
+ message: z.string(),
146
+ suggestedParams: z.record(z.string(), z.any()).optional(),
147
+ }),
148
+ )
149
+ .optional(),
150
+ statistics: z
151
+ .record(
152
+ z.string(),
153
+ z.object({
154
+ detected: z.number().int(),
155
+ withAftermath: z.number().int(),
156
+ successRate: z.number().nullable(),
157
+ avgReturn7d: z.number().nullable(),
158
+ avgReturn14d: z.number().nullable(),
159
+ medianReturn7d: z.number().nullable(),
160
+ }),
161
+ )
162
+ .optional(),
163
+ }),
164
+ meta: z.object({
165
+ pair: z.string(),
166
+ type: CandleTypeEnum,
167
+ count: z.number().int(),
168
+ visualization_hints: z
169
+ .object({
170
+ preferred_style: z.enum(['candles', 'line']).optional(),
171
+ highlight_patterns: z.array(PatternTypeEnum).optional(),
172
+ })
173
+ .optional(),
174
+ debug: z
175
+ .object({
176
+ swings: z
177
+ .array(
178
+ z.object({
179
+ idx: z.number().int(),
180
+ price: z.number(),
181
+ kind: z.enum(['H', 'L']),
182
+ isoTime: z.string().optional(),
183
+ }),
184
+ )
185
+ .optional(),
186
+ candidates: z
187
+ .array(
188
+ z.object({
189
+ type: PatternTypeEnum,
190
+ accepted: z.boolean(),
191
+ reason: z.string().optional(),
192
+ indices: z.array(z.number().int()).optional(),
193
+ points: z
194
+ .array(
195
+ z.object({
196
+ role: z.string(),
197
+ idx: z.number().int(),
198
+ price: z.number(),
199
+ isoTime: z.string().optional(),
200
+ }),
201
+ )
202
+ .optional(),
203
+ details: z.any().optional(),
204
+ }),
205
+ )
206
+ .optional(),
207
+ })
208
+ .optional(),
209
+ }),
210
+ }),
211
+ FailResultSchema,
212
+ ]);
213
+
214
+ // === Candle Patterns (2-bar patterns: engulfing, harami, etc.) ===
215
+
216
+ export const CandlePatternTypeEnum = z.enum([
217
+ // 2本足パターン (Phase 1-2)
218
+ 'bullish_engulfing',
219
+ 'bearish_engulfing',
220
+ 'bullish_harami',
221
+ 'bearish_harami',
222
+ 'tweezer_top',
223
+ 'tweezer_bottom',
224
+ 'dark_cloud_cover',
225
+ 'piercing_line',
226
+ // 1本足パターン (Phase 3)
227
+ 'hammer',
228
+ 'shooting_star',
229
+ 'doji',
230
+ // 3本足パターン (Phase 3)
231
+ 'morning_star',
232
+ 'evening_star',
233
+ 'three_white_soldiers',
234
+ 'three_black_crows',
235
+ ]);
236
+
237
+ export const AnalyzeCandlePatternsInputSchema = z.object({
238
+ pair: z.string().optional().default('btc_jpy'),
239
+ timeframe: z.literal('1day').optional().default('1day'),
240
+ // as_of: 主要パラメータ名(ISO形式 "2025-11-05" または YYYYMMDD "20251105" を受け付け)
241
+ as_of: z
242
+ .string()
243
+ .optional()
244
+ .describe('Date to analyze (ISO "2025-11-05" or YYYYMMDD "20251105"). If omitted, uses latest data.'),
245
+ // date: 互換性のため残す(as_of が優先)
246
+ date: z
247
+ .string()
248
+ .regex(/^\d{8}$/)
249
+ .optional()
250
+ .describe('DEPRECATED: Use as_of instead. YYYYMMDD format.'),
251
+ window_days: z.number().int().min(3).max(10).optional().default(5),
252
+ focus_last_n: z.number().int().min(2).max(5).optional().default(5),
253
+ patterns: z
254
+ .array(CandlePatternTypeEnum)
255
+ .optional()
256
+ .describe('Patterns to detect. If omitted, all patterns are checked.'),
257
+ history_lookback_days: z.number().int().min(30).max(365).optional().default(180),
258
+ history_horizons: z.array(z.number().int().min(1).max(10)).optional().default([1, 3, 5]),
259
+ allow_partial_patterns: z.boolean().optional().default(true),
260
+ });
261
+
262
+ const HistoryHorizonStatsSchema = z.object({
263
+ avg_return: z.number(),
264
+ win_rate: z.number(),
265
+ sample: z.number().int(),
266
+ });
267
+
268
+ const HistoryStatsSchema = z.object({
269
+ lookback_days: z.number().int(),
270
+ occurrences: z.number().int(),
271
+ horizons: z.record(z.string(), HistoryHorizonStatsSchema),
272
+ });
273
+
274
+ const LocalContextSchema = z.object({
275
+ trend_before: z.enum(['up', 'down', 'neutral']),
276
+ volatility_level: z.enum(['low', 'medium', 'high']),
277
+ });
278
+
279
+ const DetectedCandlePatternSchema = z.object({
280
+ pattern: CandlePatternTypeEnum,
281
+ pattern_jp: z.string(),
282
+ direction: z.enum(['bullish', 'bearish', 'neutral']),
283
+ strength: z.number().min(0).max(1),
284
+ candle_range_index: z.tuple([z.number().int(), z.number().int()]),
285
+ uses_partial_candle: z.boolean(),
286
+ status: z.enum(['confirmed', 'forming']),
287
+ local_context: LocalContextSchema,
288
+ history_stats: HistoryStatsSchema.nullable(),
289
+ });
290
+
291
+ const WindowCandleSchema = z.object({
292
+ timestamp: z.string(),
293
+ open: z.number(),
294
+ high: z.number(),
295
+ low: z.number(),
296
+ close: z.number(),
297
+ volume: z.number(),
298
+ is_partial: z.boolean(),
299
+ });
300
+
301
+ export const AnalyzeCandlePatternsDataSchemaOut = z.object({
302
+ pair: z.string(),
303
+ timeframe: z.string(),
304
+ snapshot_time: z.string(),
305
+ window: z.object({
306
+ from: z.string(),
307
+ to: z.string(),
308
+ candles: z
309
+ .array(WindowCandleSchema)
310
+ .describe(
311
+ 'CRITICAL: Array order is [oldest, ..., newest]. index 0 = most distant, index n-1 = latest (possibly partial).',
312
+ ),
313
+ }),
314
+ recent_patterns: z.array(DetectedCandlePatternSchema),
315
+ summary: z.string(),
316
+ });
317
+
318
+ export const AnalyzeCandlePatternsMetaSchemaOut = BaseMetaSchema.extend({
319
+ timeframe: z.string(),
320
+ as_of: z.string().nullable().describe('Original input value (ISO or YYYYMMDD)'),
321
+ date: z.string().nullable().describe('YYYYMMDD normalized, null for latest'),
322
+ window_days: z.number().int(),
323
+ patterns_checked: z.array(CandlePatternTypeEnum),
324
+ history_lookback_days: z.number().int(),
325
+ history_horizons: z.array(z.number().int()),
326
+ });
327
+
328
+ export const AnalyzeCandlePatternsOutputSchema = z.union([
329
+ z.object({
330
+ ok: z.literal(true),
331
+ summary: z.string(),
332
+ content: z.array(z.object({ type: z.literal('text'), text: z.string() })).optional(),
333
+ data: AnalyzeCandlePatternsDataSchemaOut,
334
+ meta: AnalyzeCandlePatternsMetaSchemaOut,
335
+ }),
336
+ FailResultSchema,
337
+ ]);
338
+
339
+ // === Candle Pattern Diagram (2-bar pattern visualization) ===
340
+
341
+ const DiagramCandleSchema = z.object({
342
+ date: z.string().describe('Display date e.g. "11/6(木)"'),
343
+ open: z.number(),
344
+ high: z.number(),
345
+ low: z.number(),
346
+ close: z.number(),
347
+ type: z.enum(['bullish', 'bearish']),
348
+ isPartial: z.boolean().optional(),
349
+ });
350
+
351
+ const DiagramPatternSchema = z.object({
352
+ name: z.string().describe('Pattern name in Japanese e.g. "陽線包み線"'),
353
+ nameEn: z.string().optional().describe('Pattern name in English e.g. "bullish_engulfing"'),
354
+ confirmedDate: z.string().describe('Confirmed date e.g. "11/9(日)"'),
355
+ involvedIndices: z.tuple([z.number().int().min(0), z.number().int().min(0)]).describe('[prevIndex, confirmedIndex]'),
356
+ direction: z.enum(['bullish', 'bearish']).optional(),
357
+ });
358
+
359
+ export const RenderCandlePatternDiagramInputSchema = z.object({
360
+ candles: z.array(DiagramCandleSchema).min(2).max(10).describe('Candle data array (oldest first)'),
361
+ pattern: DiagramPatternSchema.optional().describe('Pattern to highlight'),
362
+ title: z.string().optional().describe('Chart title (default: pattern name or "ローソク足チャート")'),
363
+ theme: z.enum(['dark', 'light']).optional().default('dark'),
364
+ });
365
+
366
+ export const RenderCandlePatternDiagramDataSchemaOut = z.object({
367
+ svg: z.string().optional(),
368
+ filePath: z.string().optional(),
369
+ url: z.string().optional(),
370
+ });
371
+
372
+ export const RenderCandlePatternDiagramMetaSchemaOut = z.object({
373
+ width: z.number().int(),
374
+ height: z.number().int(),
375
+ candleCount: z.number().int(),
376
+ patternName: z.string().nullable(),
377
+ });
378
+
379
+ export const RenderCandlePatternDiagramOutputSchema = toolResultSchema(
380
+ RenderCandlePatternDiagramDataSchemaOut,
381
+ RenderCandlePatternDiagramMetaSchemaOut,
382
+ );