bitbank-lab-mcp 0.1.2-rc.0 → 0.1.2

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 (121) hide show
  1. package/README.md +71 -31
  2. package/lib/candle-fetch.ts +223 -0
  3. package/lib/datetime.ts +32 -9
  4. package/lib/error.ts +38 -0
  5. package/lib/fees.ts +179 -0
  6. package/lib/formatter.ts +24 -2
  7. package/lib/get-depth.ts +30 -5
  8. package/lib/indicators.ts +185 -60
  9. package/lib/ma-snapshot-utils.ts +2 -2
  10. package/lib/pairs.ts +382 -0
  11. package/lib/pattern-diagrams.ts +0 -1
  12. package/lib/trigger-price.ts +51 -0
  13. package/lib/warning-propagation.ts +93 -0
  14. package/package.json +5 -7
  15. package/src/handlers/analyzeCandlePatternsHandler.ts +2 -2
  16. package/src/handlers/analyzeFibonacciHandler.ts +1 -2
  17. package/src/handlers/analyzeIndicatorsHandler.ts +44 -50
  18. package/src/handlers/analyzeMarketSignalHandler.ts +36 -4
  19. package/src/handlers/analyzeMyPortfolioHandler.ts +294 -149
  20. package/src/handlers/detectPatternsHandler.ts +40 -8
  21. package/src/handlers/detectPatternsViewsHandler.ts +280 -23
  22. package/src/handlers/getTickersJpyHandler.ts +29 -19
  23. package/src/handlers/getVolatilityMetricsHandler.ts +21 -9
  24. package/src/handlers/portfolio/calc.ts +304 -46
  25. package/src/handlers/portfolio/fetch.ts +185 -39
  26. package/src/handlers/portfolio/types.ts +99 -2
  27. package/src/handlers/renderChartSvgHandler.ts +21 -4
  28. package/src/handlers/runBacktestHandler.ts +8 -2
  29. package/src/lib/bitbank-errors.ts +51 -0
  30. package/src/private/client.ts +65 -15
  31. package/src/private/config.ts +21 -0
  32. package/src/private/elicitation.ts +202 -0
  33. package/src/private/schemas.ts +211 -30
  34. package/src/private/tool-error.ts +61 -0
  35. package/src/prompts/beginner.ts +83 -0
  36. package/src/prompts/index.ts +33 -0
  37. package/src/prompts/intermediate.ts +160 -0
  38. package/src/prompts/reports.ts +100 -0
  39. package/src/prompts/shared.ts +19 -0
  40. package/src/prompts/types.ts +32 -0
  41. package/src/prompts.ts +7 -2296
  42. package/src/schema/analysis.ts +34 -1
  43. package/src/schema/backtest.ts +64 -24
  44. package/src/schema/base.ts +6 -2
  45. package/src/schema/chart.ts +37 -28
  46. package/src/schema/index.ts +1 -0
  47. package/src/schema/indicators.ts +3 -0
  48. package/src/schema/market-data.ts +177 -8
  49. package/src/schema/pairs.ts +12 -0
  50. package/src/schema/patterns.ts +131 -12
  51. package/src/server.ts +20 -76
  52. package/src/tool-registry.ts +4 -0
  53. package/tools/analyze_bb_snapshot.ts +16 -4
  54. package/tools/analyze_candle_patterns.ts +30 -8
  55. package/tools/analyze_ema_snapshot.ts +22 -2
  56. package/tools/analyze_fibonacci.ts +38 -7
  57. package/tools/analyze_ichimoku_snapshot.ts +12 -9
  58. package/tools/analyze_indicators.ts +8 -0
  59. package/tools/analyze_market_signal.ts +69 -52
  60. package/tools/analyze_mtf_fibonacci.ts +34 -4
  61. package/tools/analyze_mtf_sma.ts +34 -4
  62. package/tools/analyze_sma_snapshot.ts +13 -2
  63. package/tools/analyze_stoch_snapshot.ts +24 -4
  64. package/tools/analyze_support_resistance.ts +53 -17
  65. package/tools/detect_macd_cross.ts +54 -47
  66. package/tools/detect_patterns.ts +106 -17
  67. package/tools/get_candles.ts +473 -157
  68. package/tools/get_flow_metrics.ts +26 -6
  69. package/tools/get_orderbook.ts +53 -16
  70. package/tools/get_ticker.ts +0 -1
  71. package/tools/get_transactions.ts +59 -11
  72. package/tools/get_volatility_metrics.ts +43 -23
  73. package/tools/patterns/aftermath.ts +9 -7
  74. package/tools/patterns/detect_doubles.ts +340 -17
  75. package/tools/patterns/detect_hs.ts +492 -24
  76. package/tools/patterns/detect_pennants.ts +476 -170
  77. package/tools/patterns/detect_triangles.ts +30 -276
  78. package/tools/patterns/detect_triples.ts +496 -186
  79. package/tools/patterns/detect_wedges.ts +105 -42
  80. package/tools/patterns/helpers.ts +171 -2
  81. package/tools/patterns/ranking.ts +77 -0
  82. package/tools/patterns/structural.ts +225 -0
  83. package/tools/patterns/types.ts +37 -0
  84. package/tools/prepare_chart_data.ts +27 -3
  85. package/tools/prepare_depth_data.ts +59 -9
  86. package/tools/private/analyze_my_portfolio.ts +15 -2
  87. package/tools/private/cancel_order.ts +11 -17
  88. package/tools/private/cancel_orders.ts +10 -12
  89. package/tools/private/create_order.ts +57 -33
  90. package/tools/private/get_margin_positions.ts +27 -22
  91. package/tools/private/get_margin_status.ts +37 -19
  92. package/tools/private/get_margin_trade_history.ts +119 -13
  93. package/tools/private/get_my_assets.ts +6 -26
  94. package/tools/private/get_my_deposit_withdrawal.ts +11 -6
  95. package/tools/private/get_my_orders.ts +27 -12
  96. package/tools/private/get_my_trade_history.ts +28 -85
  97. package/tools/private/get_order.ts +8 -10
  98. package/tools/private/get_orders_info.ts +17 -11
  99. package/tools/private/preview_cancel_order.ts +65 -83
  100. package/tools/private/preview_cancel_orders.ts +62 -79
  101. package/tools/private/preview_order.ts +165 -112
  102. package/tools/refresh_pairs_cache.ts +78 -0
  103. package/tools/render_candle_pattern_diagram.ts +34 -13
  104. package/tools/render_chart_svg.ts +25 -7
  105. package/tools/trading_process/lib/backtest_engine.ts +157 -51
  106. package/tools/trading_process/lib/equity.ts +43 -2
  107. package/tools/trading_process/lib/fetch_candles.ts +183 -98
  108. package/tools/trading_process/lib/resolve_fee.ts +80 -0
  109. package/tools/trading_process/lib/strategies/bb_breakout.ts +13 -1
  110. package/tools/trading_process/lib/strategies/macd_cross.ts +26 -4
  111. package/tools/trading_process/lib/strategies/rsi.ts +13 -1
  112. package/tools/trading_process/lib/strategies/sma_cross.ts +24 -3
  113. package/tools/trading_process/lib/strategies/types.ts +12 -1
  114. package/tools/trading_process/render_backtest_chart_generic.ts +11 -3
  115. package/tools/trading_process/run_backtest.ts +164 -28
  116. package/tools/trading_process/types.ts +13 -4
  117. package/tsconfig.json +1 -1
  118. package/ui/cancel-confirm/dist/cancel-confirm.html +16 -16
  119. package/ui/order-confirm/dist/order-confirm.html +27 -27
  120. package/src/http.ts +0 -107
  121. package/src/types/express-shim.d.ts +0 -9
package/README.md CHANGED
@@ -1,21 +1,29 @@
1
1
  # bitbank-lab-mcp
2
2
 
3
- [![CI](https://github.com/tjackiet/bitbank-genesis-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/tjackiet/bitbank-genesis-mcp-server/actions/workflows/ci.yml)
3
+ [![CI](https://github.com/tjackiet/bitbank-lab-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/tjackiet/bitbank-lab-mcp/actions/workflows/ci.yml)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
5
  [![npm](https://img.shields.io/npm/v/bitbank-lab-mcp.svg)](https://www.npmjs.com/package/bitbank-lab-mcp)
6
+ [![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/tjackiet/bitbank-lab-mcp?utm_source=oss&utm_medium=github&utm_campaign=tjackiet%2Fbitbank-lab-mcp&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews)](https://coderabbit.ai)
6
7
 
7
8
  > bitbank API のデータを使った暗号資産市場分析を、Claude / Cursor / Codex / Gemini CLI など各種 AI クライアントから簡単に実行できる MCP サーバーです。
8
9
 
9
- ## ⚠️ Disclaimer
10
+ ## はじめにお読みください
10
11
 
11
- MCP サーバーが提供するデータを AI エージェントが受け取り処理した結果は、必ずしも正確性・完全性を保証するものではありません。
12
-
13
- 提供される情報は情報提供のみを目的としており、投資助言・代理業に該当するものではありません。投資に関する判断はご自身の責任で行ってください。
12
+ - 本ツールは**開発段階(ベータ版)**です。利用は**自己責任**でお願いします。
13
+ - ご利用の前に必ず [⚠️ 免責事項](#免責事項) をお読みください。
14
+ - 本リポジトリは **bitbank バグバウンティプログラムの対象範囲外** です。
14
15
 
15
16
  ## 本 MCP サーバーについて
16
17
 
17
18
  bitbank の公開 API から取得した価格・取引データを、指標計算・統合・可視化用データの整形まで行った上で LLM に渡します(必要に応じてサーバー側で SVG 描画も可能)。生データを渡すだけのサーバーとは異なり、各ツールの description に「いつ使うべきか」「他ツールとの使い分け」を明示しているため、LLM が自律的に適切なツールを選択できます。
18
19
 
20
+ 姉妹プロジェクトとして、同じ bitbank API に対する真逆のアプローチを提供する **CLI** ([bitbank-cli-skills](https://github.com/tjackiet/bitbank-cli-skills)) もあります。
21
+
22
+ - **この MCP サーバー** はサーバー側で計算済みの結論を LLM に渡す
23
+ - **CLI** は生データを高速に取得し、LLM 自身に計算させる
24
+
25
+ 指標のパラメータやロジックを完全にカスタマイズしたい場合は CLI 側を、すぐに使えるテクニカル分析・可視化を求める場合は MCP サーバー側を選んでください。
26
+
19
27
  ## 概要
20
28
  bitbank の公開 API から価格・板情報・約定履歴・ローソク足データを取得し、以下の分析を実行できます。
21
29
  → 全ツールの一覧と使い分けは [docs/tools.md](docs/tools.md) を参照。
@@ -46,7 +54,7 @@ bitbank の公開 API から価格・板情報・約定履歴・ローソク足
46
54
  ## クイックスタート
47
55
 
48
56
  ### 前提条件
49
- - **Node.js 18 以上**(22 推奨 — CI で検証済み。[公式サイト](https://nodejs.org/) からインストール。`node -v` で確認できます)
57
+ - **Node.js 22 以上**(24 推奨 — CI で検証済み。[公式サイト](https://nodejs.org/) からインストール。`node -v` で確認できます)
50
58
  - npm(Node.js に同梱されています)
51
59
  - 対応 OS: macOS / Linux / Windows(WSL 含む)
52
60
 
@@ -103,6 +111,12 @@ API キーは [bitbank 設定画面](https://app.bitbank.cc/account/api) で発
103
111
 
104
112
  **IP 制限**: bitbank 側で API キーに IP 制限を設定できる場合は、可能な限り設定を推奨します。詳細: [Private API ガイド](docs/private-api.md)。
105
113
 
114
+ #### オプション環境変数
115
+
116
+ | 変数 | 既定 | 用途 |
117
+ |---|---|---|
118
+ | `BITBANK_TRUST_HOST_APPROVAL` | unset(無効) | `1` を設定すると、elicitation を advertise しないが SEP-1865 iframe をサポートするホスト(Claude Desktop / claude-ai 等)で、preview の確認 UI ボタンから注文/キャンセルを実行できるようにする妥協モード。詳細とセキュリティ前提は [ADR-0007](docs/adr/0007-hitl-confirmation-token-delivery.md) と [Private API ガイド](docs/private-api.md) を参照。**有効化時は `create_order` 等のツールを「常に許可」しないこと**。 |
119
+
106
120
  #### 方式B:`npx` の絶対パスを指定(フォールバック)
107
121
 
108
122
  方式A で「サーバーに接続できません」エラーが出る場合、Claude Desktop から `npx` コマンドが見つけられていない可能性があります。その場合は、`npx` の絶対パスを指定してください。
@@ -135,7 +149,7 @@ which npx
135
149
  }
136
150
  ```
137
151
 
138
- > ⚠️ **nvm/volta ユーザーへの注意**: この方式では Node.js をバージョンアップするたびに `command` のパスの書き換えが必要です(例: `v22.22.2` → `v22.22.3`)。アップデート後に `which npx` を再確認してください。
152
+ > ⚠️ **nvm/volta ユーザーへの注意**: この方式では Node.js をバージョンアップするたびに `command` のパスの書き換えが必要です(例: `v24.0.0` → `v24.1.0`)。アップデート後に `which npx` を再確認してください。
139
153
 
140
154
  #### Windows の場合
141
155
 
@@ -199,7 +213,7 @@ Claude Desktop の UI に表示される名前は `claude_desktop_config.json`
199
213
 
200
214
  ```bash
201
215
  # 1. このリポジトリを marketplace として登録(初回のみ)
202
- /plugin marketplace add tjackiet/bitbank-genesis-mcp-server
216
+ /plugin marketplace add tjackiet/bitbank-lab-mcp
203
217
 
204
218
  # 2. plugin を install
205
219
  /plugin install bitbank-lab-mcp@bitbank-lab
@@ -277,8 +291,8 @@ API キーの有無でサーバーが公開する機能が自動的に切り替
277
291
 
278
292
  | 設定 | ツール数 | プロンプト数 | 使える機能 |
279
293
  |------|---------|------------|-----------|
280
- | キー未設定 | 31(Public のみ) | 9 | 価格取得・テクニカル分析・チャート生成・バックテスト |
281
- | キー設定済み | 31 + 16 = **47** | 9 + 1 = **10** | 上記 + 資産確認・注文・ポートフォリオ分析 |
294
+ | キー未設定 | 32(Public のみ) | 8 | 価格取得・テクニカル分析・チャート生成・バックテスト |
295
+ | キー設定済み | 32 + 16 = **48** | 8 + 1 = **9** | 上記 + 資産確認・注文・ポートフォリオ分析 |
282
296
 
283
297
  キー未設定時、Private ツール・プロンプトは MCP クライアントに一切表示されません(エラーではなく、そもそも登録されません)。公開データの取得・分析だけなら設定不要で、そのまま使えます。
284
298
 
@@ -383,7 +397,7 @@ npx @modelcontextprotocol/inspector -- npx -y bitbank-lab-mcp
383
397
  | ツール実行時にタイムアウト | ネットワーク接続を確認 / [bitbank API の状態](https://status.bitbank.cc/)を確認 |
384
398
  | Private API ツールが表示されない | `BITBANK_API_KEY` と `BITBANK_API_SECRET` の両方が設定されているか確認(→ [docs/private-api.md](docs/private-api.md)) |
385
399
  | ログを確認したい | `env` に `"LOG_LEVEL": "debug"` を追加して再起動 |
386
- | Plugin install で「Marketplace not found」 | `/plugin marketplace add tjackiet/bitbank-genesis-mcp-server` を先に実行してから `/plugin install bitbank-lab-mcp@bitbank-lab` |
400
+ | Plugin install で「Marketplace not found」 | `/plugin marketplace add tjackiet/bitbank-lab-mcp` を先に実行してから `/plugin install bitbank-lab-mcp@bitbank-lab` |
387
401
 
388
402
  ---
389
403
 
@@ -394,14 +408,16 @@ npx @modelcontextprotocol/inspector -- npx -y bitbank-lab-mcp
394
408
  ### セットアップ
395
409
 
396
410
  ```bash
397
- git clone https://github.com/tjackiet/bitbank-genesis-mcp-server.git
398
- cd bitbank-genesis-mcp-server
399
- npm install
411
+ git clone https://github.com/tjackiet/bitbank-lab-mcp.git
412
+ cd bitbank-lab-mcp
413
+ npm ci
400
414
  ```
401
415
 
416
+ `package-lock.json` 通りに依存をインストールするため、ローカル開発でも `npm install` ではなく `npm ci` を推奨します(CI も `npm ci` を使用)。
417
+
402
418
  ビルドステップは不要です(tsx で TypeScript を直接実行します)。
403
419
 
404
- ### STDIO モード(既定 — Claude Desktop / Claude Code 向け)
420
+ ### STDIO モード(Claude Desktop / Claude Code 向け)
405
421
 
406
422
  ローカルの開発版を Claude Desktop から使いたい場合、`claude_desktop_config.json` に絶対パス指定で登録します:
407
423
 
@@ -425,18 +441,6 @@ Inspector で動作確認する場合:
425
441
  npx @modelcontextprotocol/inspector -- tsx src/server.ts
426
442
  ```
427
443
 
428
- ### HTTP モード(Web クライアント・開発検証向け)
429
-
430
- ```bash
431
- # 環境変数を指定して HTTP サーバーを起動
432
- MCP_ENABLE_HTTP=1 PORT=8787 tsx src/server.ts
433
-
434
- # 別ターミナルから Inspector で接続
435
- npx @modelcontextprotocol/inspector http://localhost:8787/mcp
436
- ```
437
-
438
- > HTTP サーバは既定で無効です(STDIO 汚染を避けるため)。Docker での起動方法は [docs/ops.md](docs/ops.md#docker起動開発検証用) を参照してください。
439
-
440
444
  ### Windows でローカル開発する場合
441
445
 
442
446
  `npx` が PATH 解決できない環境では、`node` の絶対パスと `tsx` の CLI を直接指定します:
@@ -447,10 +451,10 @@ npx @modelcontextprotocol/inspector http://localhost:8787/mcp
447
451
  "bitbank-dev": {
448
452
  "command": "node",
449
453
  "args": [
450
- "C:\\Users\\<USERNAME>\\bitbank-genesis-mcp-server\\node_modules\\tsx\\dist\\cli.mjs",
451
- "C:\\Users\\<USERNAME>\\bitbank-genesis-mcp-server\\src\\server.ts"
454
+ "C:\\Users\\<USERNAME>\\bitbank-lab-mcp\\node_modules\\tsx\\dist\\cli.mjs",
455
+ "C:\\Users\\<USERNAME>\\bitbank-lab-mcp\\src\\server.ts"
452
456
  ],
453
- "workingDirectory": "C:\\Users\\<USERNAME>\\bitbank-genesis-mcp-server",
457
+ "workingDirectory": "C:\\Users\\<USERNAME>\\bitbank-lab-mcp",
454
458
  "env": { "LOG_LEVEL": "debug", "NO_COLOR": "1" }
455
459
  }
456
460
  }
@@ -463,4 +467,40 @@ npx @modelcontextprotocol/inspector http://localhost:8787/mcp
463
467
 
464
468
  ## フィードバック・バグ報告
465
469
 
466
- バグ報告や機能要望は [GitHub Issues](https://github.com/tjackiet/bitbank-genesis-mcp-server/issues) からお願いします。Issue テンプレートを用意していますので、用途に合ったものを選択してください。
470
+ バグ報告や機能要望は [GitHub Issues](https://github.com/tjackiet/bitbank-lab-mcp/issues) からお願いします。Issue テンプレートを用意していますので、用途に合ったものを選択してください。
471
+
472
+ ## 免責事項
473
+
474
+ ### 開発段階について
475
+
476
+ 本ツールは開発段階(ベータ版)です。バグ、不具合、誤動作、または不正確な分析結果を含む可能性があります。
477
+
478
+ ### AI エージェントによる処理結果について
479
+
480
+ 本MCPサーバー / 本CLIツール が提供するデータを AIエージェント等が処理・生成した結果について、正確性、完全性、有用性、最新性を保証するものではありません。AI エージェント等による処理の結果、注文種別、価格、数量その他の取引条件が利用者の意図と異なる形で処理または実行される可能性があります。
481
+
482
+ ### 金融商品取引法上の位置づけ
483
+
484
+ 本MCPサーバーは情報提供のみを目的として提供されるものであり、投資助言・代理業、投資勧誘、その他金融商品取引法上の行為を目的とするものではありません。
485
+
486
+ ### 外部サービスへの依拠
487
+
488
+ 本MCPサーバーは外部API、LLM、第三者サービス等に依拠して提供するものであり、これらの仕様変更、停止、不具合等が生じた場合には、本MCPサーバーが正常に動作しない可能性があります。
489
+
490
+ ### 安全対策の補助性
491
+
492
+ 本MCPサーバーに実装されている最終注文確認機能、バリデーションその他の安全対策は、誤操作または誤発注等を防止するための補助機能であり、その完全な防止を保証するものではありません。
493
+
494
+ ### 利用者の責任
495
+
496
+ 利用者は、本MCPサーバーにより提供・生成された情報および注文内容等を自身で十分に確認の上、自己の判断と責任において本MCPサーバーを利用し、投資判断、注文実行および取引を行うものとします。
497
+
498
+ ### 損害の免責
499
+
500
+ 当社は、本MCPサーバーの利用もしくは利用不能、または本MCPサーバーにより提供・生成された情報、AI エージェント等による処理結果もしくは取引操作に基づく投資判断・注文・取引等に関連して生じたいかなる損害についても、当社の故意または重過失による場合を除き、一切責任を負いません。
501
+
502
+ ### APIキー・認証情報の管理
503
+
504
+ APIキーおよび取引に必要なパスワード等は利用者自身の責任において適切に管理してください。チャット欄や公開リポジトリその他第三者が閲覧可能な環境等へ APIキーや取引パスワード等の認証情報等を入力・掲載しないよう十分ご注意ください。
505
+
506
+ 利用者による認証情報等の管理不備、誤入力、漏えい、第三者利用等により生じたいかなる損害についても、当社の故意または重過失による場合を除き、当社は一切責任を負いません。
@@ -0,0 +1,223 @@
1
+ /**
2
+ * /candlestick エンドポイントの chunk fetcher と merge ロジック。
3
+ *
4
+ * - `fetchCandleChunk` は単一 chunk(年単位 YYYY / 日単位 YYYYMMDD)の取得。
5
+ * 通信エラーは throw せず result.error にラップ、success:0 は UpstreamApiError として記録する。
6
+ * - `mergeChunks` は複数 chunk を並列 / バッチ並列で取得し、timestamp 昇順でマージする。
7
+ * 失敗ハンドリング(full / majority / partial)は呼び出し側に委ねる — 集計に必要な情報を返す。
8
+ */
9
+
10
+ import { BITBANK_API_BASE, DEFAULT_RETRIES, fetchJsonWithRateLimit, type RateLimitInfo } from './http.js';
11
+
12
+ /** /candlestick エンドポイントが返す 1 行([open, high, low, close, volume, timestamp]) */
13
+ export type OhlcvRow = [unknown, unknown, unknown, unknown, unknown, unknown];
14
+
15
+ /** chunk fetcher が返す結果 */
16
+ export interface FetchChunkResult {
17
+ rows: OhlcvRow[];
18
+ rateLimit: RateLimitInfo | null;
19
+ error?: unknown;
20
+ }
21
+
22
+ /**
23
+ * chunk fetcher が success:0 を検出したときに記録するエラー。
24
+ * 全チャンク失敗時に network ではなく upstream として明示分類するため
25
+ * instanceof で判定する。
26
+ */
27
+ export class UpstreamApiError extends Error {
28
+ constructor(message: string) {
29
+ super(message);
30
+ this.name = 'UpstreamApiError';
31
+ }
32
+ }
33
+
34
+ /**
35
+ * /candlestick エンドポイントのレスポンス body を OHLCV 行に正規化する。
36
+ * - success !== 1 → UpstreamApiError をエラーとして埋め込んで返す(空 rows)
37
+ * - success === 1 → ohlcv 配列を返す(存在しなければ空)
38
+ *
39
+ * 純粋関数で fetch をモックせず単独でテスト可能。
40
+ */
41
+ export function parseCandleChunk(json: unknown, rateLimit: RateLimitInfo | null): FetchChunkResult {
42
+ const jsonObj = json as {
43
+ success?: number;
44
+ data?: { candlestick?: Array<{ ohlcv?: unknown[] }>; code?: number };
45
+ };
46
+ if (jsonObj?.success !== 1) {
47
+ const code = jsonObj?.data?.code;
48
+ const msg = code != null ? `bitbank API error (code: ${code})` : 'bitbank API error';
49
+ return { rows: [], rateLimit, error: new UpstreamApiError(msg) };
50
+ }
51
+ const cs = jsonObj?.data?.candlestick?.[0];
52
+ const ohlcvs = cs?.ohlcv ?? [];
53
+ return { rows: ohlcvs as OhlcvRow[], rateLimit };
54
+ }
55
+
56
+ export interface FetchCandleChunkOptions {
57
+ /** チャンク取得のタイムアウト (ms) */
58
+ timeoutMs?: number;
59
+ /** リトライ回数(初回 + N 回) */
60
+ retries?: number;
61
+ }
62
+
63
+ /**
64
+ * /candlestick の単一 chunk を取得する。
65
+ *
66
+ * @param pair 例: "btc_jpy"
67
+ * @param type 例: "1day", "1hour"
68
+ * @param key 年単位なら "2024"、日単位なら "20240101"
69
+ *
70
+ * 戻り値の semantics:
71
+ * - 成功: rows に OHLCV 行
72
+ * - success:0: rows=[], error=UpstreamApiError(呼び出し側で全 chunk 失敗時に upstream 分類)
73
+ * - 通信エラー / 例外: rows=[], error=原因となった例外(throw しない)
74
+ */
75
+ export async function fetchCandleChunk(
76
+ pair: string,
77
+ type: string,
78
+ key: string,
79
+ options: FetchCandleChunkOptions = {},
80
+ ): Promise<FetchChunkResult> {
81
+ const url = `${BITBANK_API_BASE}/${pair}/candlestick/${type}/${key}`;
82
+ try {
83
+ const { data: json, rateLimit } = await fetchJsonWithRateLimit(url, {
84
+ timeoutMs: options.timeoutMs,
85
+ retries: options.retries ?? DEFAULT_RETRIES,
86
+ });
87
+ return parseCandleChunk(json, rateLimit);
88
+ } catch (e) {
89
+ return { rows: [], rateLimit: null, error: e };
90
+ }
91
+ }
92
+
93
+ export interface MergeChunksOptions {
94
+ /**
95
+ * 指定時はバッチ並列で取得する(bitbank API レート制限対策)。
96
+ * - concurrency: バッチ内の最大同時リクエスト数
97
+ * - batchDelayMs: バッチ間に挟む遅延(先頭バッチ前は挟まない)
98
+ * 未指定なら全 chunk を `Promise.all` で一括並列実行する。
99
+ */
100
+ batched?: { concurrency: number; batchDelayMs: number };
101
+ }
102
+
103
+ export interface MergedChunkResult {
104
+ /** 全 chunk の OHLCV を timestamp 昇順でマージ・ソートした結果 */
105
+ rows: OhlcvRow[];
106
+ /** keys と同じ順序の per-chunk 結果 */
107
+ results: FetchChunkResult[];
108
+ /** 最後に得た非 null の rateLimit(last-wins) */
109
+ lastRateLimit: RateLimitInfo | null;
110
+ /** 失敗した chunk の key 一覧(keys の元順序を保つ) */
111
+ failedKeys: string[];
112
+ }
113
+
114
+ /**
115
+ * 複数 chunk を fetcher に流して結果をマージする。
116
+ *
117
+ * - 取得失敗(full / majority / partial)の判定は呼び出し側に委ねる。
118
+ * そのため `results` と `failedKeys` を返すが、自前で fail を生成しない。
119
+ * - `rows` は timestamp 昇順でソート済み。
120
+ *
121
+ * @param keys 各 chunk の URL 末尾要素(年/日)
122
+ * @param fetcher key を受け取って FetchChunkResult を返す関数
123
+ */
124
+ export async function mergeChunks(
125
+ keys: string[],
126
+ fetcher: (key: string) => Promise<FetchChunkResult>,
127
+ options: MergeChunksOptions = {},
128
+ ): Promise<MergedChunkResult> {
129
+ let results: FetchChunkResult[];
130
+ if (options.batched) {
131
+ const { concurrency, batchDelayMs } = options.batched;
132
+ results = [];
133
+ for (let i = 0; i < keys.length; i += concurrency) {
134
+ if (i > 0) {
135
+ // バッチ間の遅延(先頭バッチ前は挟まない。bitbank API レート制限対策)
136
+ await new Promise((resolve) => setTimeout(resolve, batchDelayMs));
137
+ }
138
+ const batch = keys.slice(i, i + concurrency);
139
+ const batchResults = await Promise.all(batch.map(fetcher));
140
+ results.push(...batchResults);
141
+ }
142
+ } else {
143
+ results = await Promise.all(keys.map(fetcher));
144
+ }
145
+
146
+ let lastRateLimit: RateLimitInfo | null = null;
147
+ for (const r of results) {
148
+ if (r.rateLimit) lastRateLimit = r.rateLimit;
149
+ }
150
+
151
+ const rows: OhlcvRow[] = [];
152
+ for (const r of results) {
153
+ rows.push(...r.rows);
154
+ }
155
+
156
+ // timestamp (index 5) 昇順でソート。Number 変換失敗時は 0 にフォールバック。
157
+ rows.sort((a, b) => (Number(a[5]) || 0) - (Number(b[5]) || 0));
158
+
159
+ const failedKeys = keys.filter((_, i) => results[i].error != null);
160
+ return { rows, results, lastRateLimit, failedKeys };
161
+ }
162
+
163
+ function isOhlcAllZero(row: OhlcvRow): boolean {
164
+ return Number(row[0]) === 0 && Number(row[1]) === 0 && Number(row[2]) === 0 && Number(row[3]) === 0;
165
+ }
166
+
167
+ /**
168
+ * 同一 ts 内で curr を採用すべきかを判定する。
169
+ * - a) 全 0 OHLC プレースホルダを除外
170
+ * - b) volume が大きい行を優先
171
+ * - c) タイなら後勝ち(配列上で後の行)
172
+ */
173
+ function shouldPreferCurr(prev: OhlcvRow, curr: OhlcvRow): boolean {
174
+ const prevAllZero = isOhlcAllZero(prev);
175
+ const currAllZero = isOhlcAllZero(curr);
176
+ if (prevAllZero && !currAllZero) return true;
177
+ if (!prevAllZero && currAllZero) return false;
178
+ const prevV = Number(prev[4]);
179
+ const currV = Number(curr[4]);
180
+ const prevVN = Number.isFinite(prevV) ? prevV : 0;
181
+ const currVN = Number.isFinite(currV) ? currV : 0;
182
+ if (currVN > prevVN) return true;
183
+ if (currVN < prevVN) return false;
184
+ return true;
185
+ }
186
+
187
+ /**
188
+ * 同一 timestamp の重複行を排除する純関数。
189
+ *
190
+ * /candlestick レスポンスで観測される同一 ts 重複(一方は全 0 OHLC のプレースホルダ)を排除し、
191
+ * インジケーター・パターン検出・バックテストへの副作用を防ぐ。
192
+ *
193
+ * 前提: 入力は timestamp 昇順ソート済み。
194
+ *
195
+ * 採用ルール(上から順に評価):
196
+ * a) OHLC の全てが 0 ではない行を優先(全 0 プレースホルダ行を除外)
197
+ * b) 上記タイで残ったら volume が大きい行を優先
198
+ * c) さらにタイなら後勝ち(配列上で後の行を採用)
199
+ *
200
+ * timestamp が NaN/<=0 の行は dedupe 対象外で素通し(後段の row validation で upstream 分類される)。
201
+ */
202
+ export function dedupeByTimestamp(rows: OhlcvRow[]): OhlcvRow[] {
203
+ if (rows.length === 0) return [];
204
+ const result: OhlcvRow[] = [];
205
+ const tsToIndex = new Map<number, number>();
206
+ for (const row of rows) {
207
+ const ts = Number(row[5]);
208
+ if (!Number.isFinite(ts) || ts <= 0) {
209
+ result.push(row);
210
+ continue;
211
+ }
212
+ const existingIdx = tsToIndex.get(ts);
213
+ if (existingIdx === undefined) {
214
+ tsToIndex.set(ts, result.length);
215
+ result.push(row);
216
+ continue;
217
+ }
218
+ if (shouldPreferCurr(result[existingIdx], row)) {
219
+ result[existingIdx] = row;
220
+ }
221
+ }
222
+ return result;
223
+ }
package/lib/datetime.ts CHANGED
@@ -50,6 +50,38 @@ export function toIsoWithTz(ts: number, tz: string): string | null {
50
50
  }
51
51
  }
52
52
 
53
+ /**
54
+ * タイムスタンプを指定タイムゾーンの暦日 YYYY-MM-DD に変換
55
+ * @param ms ミリ秒タイムスタンプ
56
+ * @param tz タイムゾーン(デフォルト: 'Asia/Tokyo'、空文字も Asia/Tokyo にフォールバック)
57
+ * @returns "YYYY-MM-DD" 形式、ms が null/undefined/NaN または tz が不正なら null
58
+ */
59
+ export function formatDateInTz(ms: number | undefined | null, tz: string = 'Asia/Tokyo'): string | null {
60
+ if (ms == null) return null;
61
+ if (!Number.isFinite(ms)) return null;
62
+ const effectiveTz = typeof tz === 'string' && tz.length > 0 ? tz : 'Asia/Tokyo';
63
+ try {
64
+ const d = dayjs(ms).tz(effectiveTz);
65
+ return d.isValid() ? d.format('YYYY-MM-DD') : null;
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * UTC ISO 文字列または ms から、指定 tz の暦日 YYYY-MM-DD を返す(表示・比較用)。
73
+ * isoTime.split('T')[0] は UTC 暦日になるため、JST 表示には使わない。
74
+ */
75
+ export function calendarDateFromIso(
76
+ isoOrMs: string | number | null | undefined,
77
+ tz: string = 'Asia/Tokyo',
78
+ ): string | null {
79
+ if (isoOrMs == null) return null;
80
+ const ms = typeof isoOrMs === 'number' ? isoOrMs : dayjs(isoOrMs).valueOf();
81
+ if (!Number.isFinite(ms)) return null;
82
+ return formatDateInTz(ms, tz);
83
+ }
84
+
53
85
  /**
54
86
  * タイムスタンプを日本語表示形式に変換
55
87
  * @param ts ミリ秒タイムスタンプ(未指定時は現在時刻)
@@ -75,15 +107,6 @@ export function nowIso(): string {
75
107
  return dayjs().toISOString();
76
108
  }
77
109
 
78
- /**
79
- * 現在時刻を指定タイムゾーンで取得
80
- * @param tz タイムゾーン(デフォルト: 'Asia/Tokyo')
81
- * @returns dayjs インスタンス
82
- */
83
- export function nowTz(tz: string = 'Asia/Tokyo') {
84
- return dayjs().tz(tz);
85
- }
86
-
87
110
  /**
88
111
  * N日前の日付を取得
89
112
  * @param daysAgo 何日前か
package/lib/error.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { ZodError } from 'zod';
2
+ import { PrivateApiError } from '../src/private/client.js';
3
+
1
4
  /**
2
5
  * unknown型のエラーからメッセージを安全に取得する
3
6
  */
@@ -13,3 +16,38 @@ export function getErrorMessage(e: unknown): string {
13
16
  export function isAbortError(e: unknown): boolean {
14
17
  return e instanceof Error && e.name === 'AbortError';
15
18
  }
19
+
20
+ /** ユーザ応答層に渡す安全なエラー表現 */
21
+ export interface PublicError {
22
+ summary: string;
23
+ errorType: string;
24
+ }
25
+
26
+ /**
27
+ * 捕捉した例外をユーザ応答に出してよい形へ正規化する。
28
+ *
29
+ * - `ZodError`: 詳細メッセージはローカルパスや入力断片を含む場合があるため汎用文に置き換え
30
+ * - `PrivateApiError`: bitbank の業務エラー文言は素通し("数量が最低取引量を下回っています" 等)。
31
+ * `instanceof` で判定するため `name`/`errorType` を偽装した一般 Error は素通しされない。
32
+ * - その他: ローカルパスや fs / 内部ロジック由来の message を漏らさないため汎用文に置き換え
33
+ *
34
+ * ログ用途では引き続き `getErrorMessage(err)` を使うこと。
35
+ */
36
+ export function toPublicError(e: unknown): PublicError {
37
+ if (e instanceof ZodError) {
38
+ return {
39
+ summary: '入力形式が不正です。パラメータを確認してください',
40
+ errorType: 'validation_error',
41
+ };
42
+ }
43
+ if (e instanceof PrivateApiError) {
44
+ return {
45
+ summary: e.message,
46
+ errorType: e.errorType,
47
+ };
48
+ }
49
+ return {
50
+ summary: '内部エラーが発生しました。ログを確認してください',
51
+ errorType: 'internal',
52
+ };
53
+ }