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,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get_my_deposit_withdrawal — 入出金(入庫/出庫)履歴を取得する Private API ツール。
|
|
3
|
+
*
|
|
4
|
+
* bitbank Private API `/v1/user/deposit_history` および `/v1/user/withdrawal_history` を呼び出し、
|
|
5
|
+
* LLM が分析しやすい形に整形して返す。
|
|
6
|
+
*
|
|
7
|
+
* - JPY 入出金: asset=jpy で取得
|
|
8
|
+
* - 暗号資産入出庫: asset 省略または通貨コード指定で取得
|
|
9
|
+
* - 両方を統合して返す(デフォルト動作: 全通貨 + JPY の入出金を統合取得)
|
|
10
|
+
* - ページネーション対応: 100件上限を超えるデータも自動取得(最大10ページ=1000件/チャネル)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { nowIso, parseIso8601, toIsoMs } from '../../lib/datetime.js';
|
|
14
|
+
import { getErrorMessage } from '../../lib/error.js';
|
|
15
|
+
import { formatPrice } from '../../lib/formatter.js';
|
|
16
|
+
import { fail, ok } from '../../lib/result.js';
|
|
17
|
+
import { type BitbankPrivateClient, getDefaultClient, PrivateApiError } from '../../src/private/client.js';
|
|
18
|
+
import { GetMyDepositWithdrawalInputSchema, GetMyDepositWithdrawalOutputSchema } from '../../src/private/schemas.js';
|
|
19
|
+
import type { ToolDefinition } from '../../src/tool-definition.js';
|
|
20
|
+
|
|
21
|
+
// ── API レスポンス型 ──
|
|
22
|
+
|
|
23
|
+
/** 個別 API リクエストの結果をラップ */
|
|
24
|
+
type FetchResult<T> = { ok: true; data: T } | { ok: false; error: string };
|
|
25
|
+
|
|
26
|
+
async function tryGet<T>(
|
|
27
|
+
client: BitbankPrivateClient,
|
|
28
|
+
path: string,
|
|
29
|
+
params?: Record<string, string>,
|
|
30
|
+
): Promise<FetchResult<T>> {
|
|
31
|
+
try {
|
|
32
|
+
const data = await client.get<T>(path, params);
|
|
33
|
+
return { ok: true, data };
|
|
34
|
+
} catch (err) {
|
|
35
|
+
return { ok: false, error: getErrorMessage(err) };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** bitbank /v1/user/deposit_history のレスポンス型 */
|
|
40
|
+
interface RawDeposit {
|
|
41
|
+
uuid: string;
|
|
42
|
+
asset: string;
|
|
43
|
+
network?: string;
|
|
44
|
+
amount: string;
|
|
45
|
+
txid?: string | null;
|
|
46
|
+
status: string;
|
|
47
|
+
found_at: number;
|
|
48
|
+
confirmed_at: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** bitbank /v1/user/withdrawal_history のレスポンス型 */
|
|
52
|
+
interface RawWithdrawal {
|
|
53
|
+
uuid: string;
|
|
54
|
+
asset: string;
|
|
55
|
+
account_uuid?: string;
|
|
56
|
+
amount: string;
|
|
57
|
+
fee?: string;
|
|
58
|
+
label?: string;
|
|
59
|
+
address?: string;
|
|
60
|
+
network?: string;
|
|
61
|
+
txid?: string | null;
|
|
62
|
+
destination_tag?: number | string | null;
|
|
63
|
+
bank_name?: string;
|
|
64
|
+
branch_name?: string;
|
|
65
|
+
account_type?: string;
|
|
66
|
+
account_number?: string;
|
|
67
|
+
account_owner?: string;
|
|
68
|
+
status: string;
|
|
69
|
+
requested_at: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── ページネーション(analyze_my_portfolio と同方式) ──
|
|
73
|
+
|
|
74
|
+
const MAX_PAGES = 10;
|
|
75
|
+
|
|
76
|
+
interface PaginatedDeposits {
|
|
77
|
+
deposits: RawDeposit[];
|
|
78
|
+
complete: boolean;
|
|
79
|
+
error?: string;
|
|
80
|
+
}
|
|
81
|
+
interface PaginatedWithdrawals {
|
|
82
|
+
withdrawals: RawWithdrawal[];
|
|
83
|
+
complete: boolean;
|
|
84
|
+
error?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function paginateDeposits(
|
|
88
|
+
client: BitbankPrivateClient,
|
|
89
|
+
baseParams: Record<string, string>,
|
|
90
|
+
): Promise<PaginatedDeposits> {
|
|
91
|
+
const all: RawDeposit[] = [];
|
|
92
|
+
let since: string | undefined;
|
|
93
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
94
|
+
const params = { ...baseParams, count: '100', ...(since ? { since } : {}) };
|
|
95
|
+
const result = await tryGet<{ deposits: RawDeposit[] }>(client, '/v1/user/deposit_history', params);
|
|
96
|
+
if (!result.ok) {
|
|
97
|
+
return { deposits: all, complete: all.length !== 0, error: result.error };
|
|
98
|
+
}
|
|
99
|
+
const batch = result.data.deposits || [];
|
|
100
|
+
all.push(...batch);
|
|
101
|
+
if (batch.length < 100) {
|
|
102
|
+
return { deposits: all, complete: true };
|
|
103
|
+
}
|
|
104
|
+
const lastTs = batch[batch.length - 1]?.confirmed_at;
|
|
105
|
+
if (!lastTs) break;
|
|
106
|
+
since = String(lastTs + 1);
|
|
107
|
+
}
|
|
108
|
+
return { deposits: all, complete: false };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function paginateWithdrawals(
|
|
112
|
+
client: BitbankPrivateClient,
|
|
113
|
+
baseParams: Record<string, string>,
|
|
114
|
+
): Promise<PaginatedWithdrawals> {
|
|
115
|
+
const all: RawWithdrawal[] = [];
|
|
116
|
+
let since: string | undefined;
|
|
117
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
118
|
+
const params = { ...baseParams, count: '100', ...(since ? { since } : {}) };
|
|
119
|
+
const result = await tryGet<{ withdrawals: RawWithdrawal[] }>(client, '/v1/user/withdrawal_history', params);
|
|
120
|
+
if (!result.ok) {
|
|
121
|
+
return { withdrawals: all, complete: all.length !== 0, error: result.error };
|
|
122
|
+
}
|
|
123
|
+
const batch = result.data.withdrawals || [];
|
|
124
|
+
all.push(...batch);
|
|
125
|
+
if (batch.length < 100) {
|
|
126
|
+
return { withdrawals: all, complete: true };
|
|
127
|
+
}
|
|
128
|
+
const lastTs = batch[batch.length - 1]?.requested_at;
|
|
129
|
+
if (!lastTs) break;
|
|
130
|
+
since = String(lastTs + 1);
|
|
131
|
+
}
|
|
132
|
+
return { withdrawals: all, complete: false };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 単発取得(count <= 100 かつ since/end 指定時) */
|
|
136
|
+
async function singleFetchDeposits(
|
|
137
|
+
client: BitbankPrivateClient,
|
|
138
|
+
params: Record<string, string>,
|
|
139
|
+
): Promise<PaginatedDeposits> {
|
|
140
|
+
const result = await tryGet<{ deposits: RawDeposit[] }>(client, '/v1/user/deposit_history', params);
|
|
141
|
+
if (!result.ok) {
|
|
142
|
+
return { deposits: [], complete: false, error: result.error };
|
|
143
|
+
}
|
|
144
|
+
const batch = result.data.deposits || [];
|
|
145
|
+
const count = Number(params.count) || 100;
|
|
146
|
+
return { deposits: batch, complete: batch.length < count };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function singleFetchWithdrawals(
|
|
150
|
+
client: BitbankPrivateClient,
|
|
151
|
+
params: Record<string, string>,
|
|
152
|
+
): Promise<PaginatedWithdrawals> {
|
|
153
|
+
const result = await tryGet<{ withdrawals: RawWithdrawal[] }>(client, '/v1/user/withdrawal_history', params);
|
|
154
|
+
if (!result.ok) {
|
|
155
|
+
return { withdrawals: [], complete: false, error: result.error };
|
|
156
|
+
}
|
|
157
|
+
const batch = result.data.withdrawals || [];
|
|
158
|
+
const count = Number(params.count) || 100;
|
|
159
|
+
return { withdrawals: batch, complete: batch.length < count };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── メインハンドラ ──
|
|
163
|
+
|
|
164
|
+
export default async function getMyDepositWithdrawal(args: {
|
|
165
|
+
asset?: string;
|
|
166
|
+
type?: 'deposit' | 'withdrawal' | 'all';
|
|
167
|
+
count?: number;
|
|
168
|
+
since?: string;
|
|
169
|
+
end?: string;
|
|
170
|
+
}) {
|
|
171
|
+
const { asset, type = 'all', count = 25, since, end } = args;
|
|
172
|
+
const client = getDefaultClient();
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
// クエリパラメータを組み立て
|
|
176
|
+
const baseParams: Record<string, string> = {};
|
|
177
|
+
|
|
178
|
+
// ISO8601 → unix ms 変換
|
|
179
|
+
if (since) {
|
|
180
|
+
const parsed = parseIso8601(since);
|
|
181
|
+
if (!parsed) {
|
|
182
|
+
return GetMyDepositWithdrawalOutputSchema.parse(
|
|
183
|
+
fail(`since の日時形式が不正です: ${since}`, 'validation_error'),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
baseParams.since = String(parsed.valueOf());
|
|
187
|
+
}
|
|
188
|
+
if (end) {
|
|
189
|
+
const parsed = parseIso8601(end);
|
|
190
|
+
if (!parsed) {
|
|
191
|
+
return GetMyDepositWithdrawalOutputSchema.parse(fail(`end の日時形式が不正です: ${end}`, 'validation_error'));
|
|
192
|
+
}
|
|
193
|
+
baseParams.end = String(parsed.valueOf());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const fetchDeposits = type === 'deposit' || type === 'all';
|
|
197
|
+
const fetchWithdrawals = type === 'withdrawal' || type === 'all';
|
|
198
|
+
|
|
199
|
+
// since/end が指定されている場合: 単発取得(ユーザーが期間を指定した場合は count を尊重)
|
|
200
|
+
// since/end が未指定の場合: ページネーションで全件取得
|
|
201
|
+
const hasSinceEnd = !!since || !!end;
|
|
202
|
+
|
|
203
|
+
let allDeposits: RawDeposit[] = [];
|
|
204
|
+
let allWithdrawals: RawWithdrawal[] = [];
|
|
205
|
+
const warnings: string[] = [];
|
|
206
|
+
let isComplete = true;
|
|
207
|
+
|
|
208
|
+
if (asset) {
|
|
209
|
+
// 特定通貨の場合
|
|
210
|
+
const params: Record<string, string> = { ...baseParams, asset };
|
|
211
|
+
if (hasSinceEnd) {
|
|
212
|
+
params.count = String(count);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const [depResult, wdResult] = await Promise.all([
|
|
216
|
+
fetchDeposits
|
|
217
|
+
? hasSinceEnd
|
|
218
|
+
? singleFetchDeposits(client, params)
|
|
219
|
+
: paginateDeposits(client, { asset })
|
|
220
|
+
: Promise.resolve({ deposits: [] as RawDeposit[], complete: true } as PaginatedDeposits),
|
|
221
|
+
fetchWithdrawals
|
|
222
|
+
? hasSinceEnd
|
|
223
|
+
? singleFetchWithdrawals(client, params)
|
|
224
|
+
: paginateWithdrawals(client, { asset })
|
|
225
|
+
: Promise.resolve({ withdrawals: [] as RawWithdrawal[], complete: true } as PaginatedWithdrawals),
|
|
226
|
+
]);
|
|
227
|
+
|
|
228
|
+
if (depResult.error) warnings.push(`入金/入庫履歴の取得に失敗: ${depResult.error}`);
|
|
229
|
+
if (wdResult.error) warnings.push(`出金/出庫履歴の取得に失敗: ${wdResult.error}`);
|
|
230
|
+
allDeposits = depResult.deposits;
|
|
231
|
+
allWithdrawals = wdResult.withdrawals;
|
|
232
|
+
isComplete = depResult.complete && wdResult.complete;
|
|
233
|
+
} else {
|
|
234
|
+
// 全通貨: 暗号資産 + JPY を並列取得(ページネーション)
|
|
235
|
+
if (hasSinceEnd) {
|
|
236
|
+
// since/end 指定時: 単発取得
|
|
237
|
+
const cryptoParams = { ...baseParams, count: String(count) };
|
|
238
|
+
const jpyParams = { ...baseParams, asset: 'jpy', count: String(count) };
|
|
239
|
+
|
|
240
|
+
const [cryptoDepResult, jpyDepResult, cryptoWdResult, jpyWdResult] = await Promise.all([
|
|
241
|
+
fetchDeposits
|
|
242
|
+
? singleFetchDeposits(client, cryptoParams)
|
|
243
|
+
: Promise.resolve({ deposits: [], complete: true } as PaginatedDeposits),
|
|
244
|
+
fetchDeposits
|
|
245
|
+
? singleFetchDeposits(client, jpyParams)
|
|
246
|
+
: Promise.resolve({ deposits: [], complete: true } as PaginatedDeposits),
|
|
247
|
+
fetchWithdrawals
|
|
248
|
+
? singleFetchWithdrawals(client, cryptoParams)
|
|
249
|
+
: Promise.resolve({ withdrawals: [], complete: true } as PaginatedWithdrawals),
|
|
250
|
+
fetchWithdrawals
|
|
251
|
+
? singleFetchWithdrawals(client, jpyParams)
|
|
252
|
+
: Promise.resolve({ withdrawals: [], complete: true } as PaginatedWithdrawals),
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
collectResults(
|
|
256
|
+
cryptoDepResult,
|
|
257
|
+
jpyDepResult,
|
|
258
|
+
cryptoWdResult,
|
|
259
|
+
jpyWdResult,
|
|
260
|
+
warnings,
|
|
261
|
+
(d) => {
|
|
262
|
+
allDeposits = d;
|
|
263
|
+
},
|
|
264
|
+
(w) => {
|
|
265
|
+
allWithdrawals = w;
|
|
266
|
+
},
|
|
267
|
+
);
|
|
268
|
+
isComplete =
|
|
269
|
+
cryptoDepResult.complete && jpyDepResult.complete && cryptoWdResult.complete && jpyWdResult.complete;
|
|
270
|
+
} else {
|
|
271
|
+
// since/end 未指定: ページネーションで全件取得
|
|
272
|
+
const [cryptoDepResult, jpyDepResult, cryptoWdResult, jpyWdResult] = await Promise.all([
|
|
273
|
+
fetchDeposits
|
|
274
|
+
? paginateDeposits(client, {})
|
|
275
|
+
: Promise.resolve({ deposits: [], complete: true } as PaginatedDeposits),
|
|
276
|
+
fetchDeposits
|
|
277
|
+
? paginateDeposits(client, { asset: 'jpy' })
|
|
278
|
+
: Promise.resolve({ deposits: [], complete: true } as PaginatedDeposits),
|
|
279
|
+
fetchWithdrawals
|
|
280
|
+
? paginateWithdrawals(client, {})
|
|
281
|
+
: Promise.resolve({ withdrawals: [], complete: true } as PaginatedWithdrawals),
|
|
282
|
+
fetchWithdrawals
|
|
283
|
+
? paginateWithdrawals(client, { asset: 'jpy' })
|
|
284
|
+
: Promise.resolve({ withdrawals: [], complete: true } as PaginatedWithdrawals),
|
|
285
|
+
]);
|
|
286
|
+
|
|
287
|
+
collectResults(
|
|
288
|
+
cryptoDepResult,
|
|
289
|
+
jpyDepResult,
|
|
290
|
+
cryptoWdResult,
|
|
291
|
+
jpyWdResult,
|
|
292
|
+
warnings,
|
|
293
|
+
(d) => {
|
|
294
|
+
allDeposits = d;
|
|
295
|
+
},
|
|
296
|
+
(w) => {
|
|
297
|
+
allWithdrawals = w;
|
|
298
|
+
},
|
|
299
|
+
);
|
|
300
|
+
isComplete =
|
|
301
|
+
cryptoDepResult.complete && jpyDepResult.complete && cryptoWdResult.complete && jpyWdResult.complete;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// UUID で重複排除(暗号資産クエリに JPY が含まれるケースに備える)
|
|
306
|
+
allDeposits = deduplicateByUuid(allDeposits);
|
|
307
|
+
allWithdrawals = deduplicateByUuid(allWithdrawals);
|
|
308
|
+
|
|
309
|
+
const timestamp = nowIso();
|
|
310
|
+
|
|
311
|
+
// 入金データの整形
|
|
312
|
+
const deposits = allDeposits.map((d) => ({
|
|
313
|
+
uuid: d.uuid,
|
|
314
|
+
asset: d.asset,
|
|
315
|
+
amount: d.amount,
|
|
316
|
+
network: d.network || undefined,
|
|
317
|
+
txid: d.txid || undefined,
|
|
318
|
+
status: d.status,
|
|
319
|
+
found_at: toIsoMs(d.found_at) ?? undefined,
|
|
320
|
+
confirmed_at: toIsoMs(d.confirmed_at) ?? undefined,
|
|
321
|
+
}));
|
|
322
|
+
|
|
323
|
+
// 出金データの整形
|
|
324
|
+
const withdrawals = allWithdrawals.map((w) => ({
|
|
325
|
+
uuid: w.uuid,
|
|
326
|
+
asset: w.asset,
|
|
327
|
+
amount: w.amount,
|
|
328
|
+
fee: w.fee || undefined,
|
|
329
|
+
network: w.network || undefined,
|
|
330
|
+
txid: w.txid || undefined,
|
|
331
|
+
label: w.label || undefined,
|
|
332
|
+
address: w.address || undefined,
|
|
333
|
+
bank_name: w.bank_name || undefined,
|
|
334
|
+
status: w.status,
|
|
335
|
+
requested_at: toIsoMs(w.requested_at) ?? undefined,
|
|
336
|
+
}));
|
|
337
|
+
|
|
338
|
+
// サマリー文字列の生成
|
|
339
|
+
const lines: string[] = [];
|
|
340
|
+
const assetLabel = asset ? asset.toUpperCase() : '全通貨';
|
|
341
|
+
lines.push(`入出金履歴: ${assetLabel}`);
|
|
342
|
+
lines.push(`取得時刻: ${timestamp}`);
|
|
343
|
+
lines.push(`取得状態: ${isComplete ? 'complete' : 'partial'}`);
|
|
344
|
+
lines.push(`警告有無: ${warnings.length > 0 ? 'yes' : 'no'}`);
|
|
345
|
+
if (warnings.length > 0) {
|
|
346
|
+
lines.push('※ 一部API取得失敗あり(詳細は末尾の警告を参照)');
|
|
347
|
+
}
|
|
348
|
+
if (!isComplete) {
|
|
349
|
+
lines.push('※ 全件ではなく一部のみ取得されています。API件数上限に達した可能性があります');
|
|
350
|
+
}
|
|
351
|
+
lines.push('');
|
|
352
|
+
|
|
353
|
+
// 入金サマリー
|
|
354
|
+
if (deposits.length > 0) {
|
|
355
|
+
lines.push(`入金/入庫: ${deposits.length}件`);
|
|
356
|
+
const jpyDeposits = deposits.filter((d) => d.asset === 'jpy');
|
|
357
|
+
const cryptoDeposits = deposits.filter((d) => d.asset !== 'jpy');
|
|
358
|
+
if (jpyDeposits.length > 0) {
|
|
359
|
+
const totalJpy = jpyDeposits.reduce((sum, d) => sum + Number(d.amount), 0);
|
|
360
|
+
lines.push(` JPY 入金: ${jpyDeposits.length}件 合計 ${formatPrice(Math.round(totalJpy))}`);
|
|
361
|
+
for (const d of jpyDeposits.slice(0, 5)) {
|
|
362
|
+
lines.push(
|
|
363
|
+
` [${d.uuid}] JPY ${formatPrice(Math.round(Number(d.amount)))} (${d.status})${d.found_at ? ` ${d.found_at}` : ''}`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
if (jpyDeposits.length > 5) lines.push(` ... 他 ${jpyDeposits.length - 5}件`);
|
|
367
|
+
}
|
|
368
|
+
if (cryptoDeposits.length > 0) {
|
|
369
|
+
lines.push(` 暗号資産入庫: ${cryptoDeposits.length}件(明細表示は先頭5件のみ)`);
|
|
370
|
+
for (const d of cryptoDeposits.slice(0, 5)) {
|
|
371
|
+
lines.push(
|
|
372
|
+
` [${d.uuid}] ${d.asset.toUpperCase()} ${d.amount} (${d.status})${d.found_at ? ` ${d.found_at}` : ''}`,
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
if (cryptoDeposits.length > 5) lines.push(` ... 他 ${cryptoDeposits.length - 5}件`);
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
lines.push('入金/入庫: 0件');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
lines.push('');
|
|
382
|
+
|
|
383
|
+
// 出金サマリー
|
|
384
|
+
if (withdrawals.length > 0) {
|
|
385
|
+
lines.push(`出金/出庫: ${withdrawals.length}件`);
|
|
386
|
+
const jpyWithdrawals = withdrawals.filter((w) => w.asset === 'jpy');
|
|
387
|
+
const cryptoWithdrawals = withdrawals.filter((w) => w.asset !== 'jpy');
|
|
388
|
+
if (jpyWithdrawals.length > 0) {
|
|
389
|
+
const totalJpy = jpyWithdrawals.reduce((sum, w) => sum + Number(w.amount), 0);
|
|
390
|
+
lines.push(` JPY 出金: ${jpyWithdrawals.length}件 合計 ${formatPrice(Math.round(totalJpy))}`);
|
|
391
|
+
for (const w of jpyWithdrawals.slice(0, 5)) {
|
|
392
|
+
lines.push(
|
|
393
|
+
` [${w.uuid}] JPY ${formatPrice(Math.round(Number(w.amount)))} (${w.status})${w.requested_at ? ` ${w.requested_at}` : ''}`,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
if (jpyWithdrawals.length > 5) lines.push(` ... 他 ${jpyWithdrawals.length - 5}件`);
|
|
397
|
+
}
|
|
398
|
+
if (cryptoWithdrawals.length > 0) {
|
|
399
|
+
lines.push(` 暗号資産出庫: ${cryptoWithdrawals.length}件(明細表示は先頭5件のみ)`);
|
|
400
|
+
for (const w of cryptoWithdrawals.slice(0, 5)) {
|
|
401
|
+
lines.push(
|
|
402
|
+
` [${w.uuid}] ${w.asset.toUpperCase()} ${w.amount} (${w.status})${w.requested_at ? ` ${w.requested_at}` : ''}`,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
if (cryptoWithdrawals.length > 5) lines.push(` ... 他 ${cryptoWithdrawals.length - 5}件`);
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
lines.push('出金/出庫: 0件');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// 警告(partial failure)
|
|
412
|
+
if (warnings.length > 0) {
|
|
413
|
+
lines.push('');
|
|
414
|
+
for (const w of warnings) {
|
|
415
|
+
lines.push(`警告: ${w}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const summary = lines.join('\n');
|
|
420
|
+
|
|
421
|
+
const data = {
|
|
422
|
+
deposits,
|
|
423
|
+
withdrawals,
|
|
424
|
+
timestamp,
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const meta = {
|
|
428
|
+
fetchedAt: timestamp,
|
|
429
|
+
depositCount: deposits.length,
|
|
430
|
+
withdrawalCount: withdrawals.length,
|
|
431
|
+
asset: asset || undefined,
|
|
432
|
+
isComplete,
|
|
433
|
+
hasWarnings: warnings.length > 0,
|
|
434
|
+
warnings,
|
|
435
|
+
...(client.lastRateLimit ? { rateLimit: client.lastRateLimit } : {}),
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
return GetMyDepositWithdrawalOutputSchema.parse(ok(summary, data, meta));
|
|
439
|
+
} catch (err) {
|
|
440
|
+
if (err instanceof PrivateApiError) {
|
|
441
|
+
return GetMyDepositWithdrawalOutputSchema.parse(fail(err.message, err.errorType));
|
|
442
|
+
}
|
|
443
|
+
return GetMyDepositWithdrawalOutputSchema.parse(
|
|
444
|
+
fail(err instanceof Error ? err.message : '入出金履歴取得中に予期しないエラーが発生しました', 'upstream_error'),
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// ── ヘルパー ──
|
|
450
|
+
|
|
451
|
+
/** 4チャネルの結果を集約 */
|
|
452
|
+
function collectResults(
|
|
453
|
+
cryptoDepResult: PaginatedDeposits,
|
|
454
|
+
jpyDepResult: PaginatedDeposits,
|
|
455
|
+
cryptoWdResult: PaginatedWithdrawals,
|
|
456
|
+
jpyWdResult: PaginatedWithdrawals,
|
|
457
|
+
warnings: string[],
|
|
458
|
+
setDeposits: (d: RawDeposit[]) => void,
|
|
459
|
+
setWithdrawals: (w: RawWithdrawal[]) => void,
|
|
460
|
+
) {
|
|
461
|
+
const apiResults = [
|
|
462
|
+
{ error: cryptoDepResult.error, label: '暗号資産入庫履歴' },
|
|
463
|
+
{ error: jpyDepResult.error, label: 'JPY入金履歴' },
|
|
464
|
+
{ error: cryptoWdResult.error, label: '暗号資産出庫履歴' },
|
|
465
|
+
{ error: jpyWdResult.error, label: 'JPY出金履歴' },
|
|
466
|
+
];
|
|
467
|
+
for (const { error, label } of apiResults) {
|
|
468
|
+
if (error) {
|
|
469
|
+
warnings.push(`${label}の取得に失敗: ${error}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
setDeposits([...cryptoDepResult.deposits, ...jpyDepResult.deposits]);
|
|
474
|
+
setWithdrawals([...cryptoWdResult.withdrawals, ...jpyWdResult.withdrawals]);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** UUID で重複排除 */
|
|
478
|
+
function deduplicateByUuid<T extends { uuid: string }>(items: T[]): T[] {
|
|
479
|
+
const seen = new Set<string>();
|
|
480
|
+
return items.filter((item) => {
|
|
481
|
+
if (seen.has(item.uuid)) return false;
|
|
482
|
+
seen.add(item.uuid);
|
|
483
|
+
return true;
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ── MCP ツール定義(tool-registry から自動収集) ──
|
|
488
|
+
export const toolDef: ToolDefinition = {
|
|
489
|
+
name: 'get_my_deposit_withdrawal',
|
|
490
|
+
description:
|
|
491
|
+
'[Deposit / Withdrawal / Transfer History] 入出金・入出庫の履歴(deposit / withdrawal / transfer / funding history)を取得。JPY入出金+暗号資産入出庫に対応。全件取得可能。Private API。',
|
|
492
|
+
inputSchema: GetMyDepositWithdrawalInputSchema,
|
|
493
|
+
handler: async (args: {
|
|
494
|
+
asset?: string;
|
|
495
|
+
type?: 'deposit' | 'withdrawal' | 'all';
|
|
496
|
+
count?: number;
|
|
497
|
+
since?: string;
|
|
498
|
+
end?: string;
|
|
499
|
+
}) => getMyDepositWithdrawal(args),
|
|
500
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get_my_orders — 自分のアクティブな注文一覧を取得する Private API ツール。
|
|
3
|
+
*
|
|
4
|
+
* bitbank Private API `/v1/user/spot/active_orders` を呼び出し、
|
|
5
|
+
* LLM が分析しやすい形に整形して返す。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { nowIso, parseIso8601, toIsoMs } from '../../lib/datetime.js';
|
|
9
|
+
import { formatPair, formatPrice } from '../../lib/formatter.js';
|
|
10
|
+
import { fail, ok } from '../../lib/result.js';
|
|
11
|
+
import { getDefaultClient, PrivateApiError } from '../../src/private/client.js';
|
|
12
|
+
import { GetMyOrdersInputSchema, GetMyOrdersOutputSchema } from '../../src/private/schemas.js';
|
|
13
|
+
import type { ToolDefinition } from '../../src/tool-definition.js';
|
|
14
|
+
|
|
15
|
+
/** bitbank /v1/user/spot/active_orders のレスポンス型 */
|
|
16
|
+
interface RawOrder {
|
|
17
|
+
order_id: number;
|
|
18
|
+
pair: string;
|
|
19
|
+
side: string;
|
|
20
|
+
position_side?: string;
|
|
21
|
+
type: string;
|
|
22
|
+
start_amount?: string;
|
|
23
|
+
remaining_amount?: string;
|
|
24
|
+
executed_amount?: string;
|
|
25
|
+
price?: string;
|
|
26
|
+
post_only?: boolean;
|
|
27
|
+
user_cancelable?: boolean;
|
|
28
|
+
average_price?: string;
|
|
29
|
+
ordered_at: number;
|
|
30
|
+
expire_at?: number;
|
|
31
|
+
triggered_at?: number | string;
|
|
32
|
+
trigger_price?: string;
|
|
33
|
+
status: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default async function getMyOrders(args: { pair?: string; count?: number; since?: string; end?: string }) {
|
|
37
|
+
const { pair, count = 100, since, end } = args;
|
|
38
|
+
const client = getDefaultClient();
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
// クエリパラメータを組み立て
|
|
42
|
+
const params: Record<string, string> = {};
|
|
43
|
+
if (pair) params.pair = pair;
|
|
44
|
+
if (count !== 100) params.count = String(count);
|
|
45
|
+
|
|
46
|
+
// ISO8601 → unix ms 変換(strict parse で不正日時を弾く)
|
|
47
|
+
if (since) {
|
|
48
|
+
const parsed = parseIso8601(since);
|
|
49
|
+
if (!parsed) {
|
|
50
|
+
return GetMyOrdersOutputSchema.parse(fail(`since の日時形式が不正です: ${since}`, 'validation_error'));
|
|
51
|
+
}
|
|
52
|
+
params.since = String(parsed.valueOf());
|
|
53
|
+
}
|
|
54
|
+
if (end) {
|
|
55
|
+
const parsed = parseIso8601(end);
|
|
56
|
+
if (!parsed) {
|
|
57
|
+
return GetMyOrdersOutputSchema.parse(fail(`end の日時形式が不正です: ${end}`, 'validation_error'));
|
|
58
|
+
}
|
|
59
|
+
params.end = String(parsed.valueOf());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const rawData = await client.get<{ orders: RawOrder[] }>(
|
|
63
|
+
'/v1/user/spot/active_orders',
|
|
64
|
+
Object.keys(params).length > 0 ? params : undefined,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const timestamp = nowIso();
|
|
68
|
+
|
|
69
|
+
// bitbank API の active_orders は稀に CANCELED_UNFILLED 等の非アクティブ
|
|
70
|
+
// ステータスを含めて返すケースがあるため、サーバー側でも保険として
|
|
71
|
+
// 「ユーザーから見てまだ生きている」注文のみに絞り込む。
|
|
72
|
+
// - INACTIVE: stop / stop_limit のトリガー前。user_cancelable: true で
|
|
73
|
+
// bitbank アプリでも未約定注文として表示される。
|
|
74
|
+
// - UNFILLED: 通常の指値・成行で未約定。
|
|
75
|
+
// - PARTIALLY_FILLED: 部分約定。残量分はまだ生きている。
|
|
76
|
+
// - TRIGGERED: stop がトリガーされ、後続の指値/成行が処理待ち。
|
|
77
|
+
// FULLY_FILLED や CANCELED_* は終端状態なので除外する。
|
|
78
|
+
const ACTIVE_STATUSES = new Set(['INACTIVE', 'UNFILLED', 'PARTIALLY_FILLED', 'TRIGGERED']);
|
|
79
|
+
|
|
80
|
+
// 注文データの整形
|
|
81
|
+
const orders = rawData.orders
|
|
82
|
+
.filter((o) => ACTIVE_STATUSES.has(o.status))
|
|
83
|
+
.map((o) => ({
|
|
84
|
+
order_id: o.order_id,
|
|
85
|
+
pair: o.pair,
|
|
86
|
+
side: o.side,
|
|
87
|
+
type: o.type,
|
|
88
|
+
start_amount: o.start_amount,
|
|
89
|
+
remaining_amount: o.remaining_amount,
|
|
90
|
+
executed_amount: o.executed_amount,
|
|
91
|
+
price: o.price,
|
|
92
|
+
average_price: o.average_price,
|
|
93
|
+
status: o.status,
|
|
94
|
+
ordered_at: toIsoMs(o.ordered_at) ?? String(o.ordered_at),
|
|
95
|
+
expire_at: o.expire_at ? (toIsoMs(o.expire_at) ?? String(o.expire_at)) : undefined,
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
// サマリー文字列の生成
|
|
99
|
+
const lines: string[] = [];
|
|
100
|
+
const pairLabel = pair ? formatPair(pair) : '全ペア';
|
|
101
|
+
lines.push(`アクティブ注文: ${pairLabel} ${orders.length}件`);
|
|
102
|
+
|
|
103
|
+
if (orders.length > 0) {
|
|
104
|
+
lines.push('');
|
|
105
|
+
|
|
106
|
+
for (const o of orders) {
|
|
107
|
+
const sideLabel = o.side === 'buy' ? '買' : '売';
|
|
108
|
+
const isJpy = o.pair.includes('jpy');
|
|
109
|
+
const price = o.price ? (isJpy ? formatPrice(Number(o.price)) : o.price) : '成行';
|
|
110
|
+
const remaining = o.remaining_amount ?? '?';
|
|
111
|
+
lines.push(
|
|
112
|
+
`[ID: ${o.order_id}] ${formatPair(o.pair)} ${sideLabel}${o.type} ${remaining} @ ${price} [${o.status}] (${o.ordered_at})`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 集計
|
|
117
|
+
const buyCount = orders.filter((o) => o.side === 'buy').length;
|
|
118
|
+
const sellCount = orders.filter((o) => o.side === 'sell').length;
|
|
119
|
+
lines.push('');
|
|
120
|
+
lines.push(`集計: 買 ${buyCount}件 / 売 ${sellCount}件`);
|
|
121
|
+
} else {
|
|
122
|
+
lines.push('アクティブな注文はありません。');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const summary = lines.join('\n');
|
|
126
|
+
|
|
127
|
+
const data = {
|
|
128
|
+
orders,
|
|
129
|
+
timestamp,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const meta = {
|
|
133
|
+
fetchedAt: timestamp,
|
|
134
|
+
orderCount: orders.length,
|
|
135
|
+
pair: pair || undefined,
|
|
136
|
+
...(client.lastRateLimit ? { rateLimit: client.lastRateLimit } : {}),
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return GetMyOrdersOutputSchema.parse(ok(summary, data, meta));
|
|
140
|
+
} catch (err) {
|
|
141
|
+
if (err instanceof PrivateApiError) {
|
|
142
|
+
return GetMyOrdersOutputSchema.parse(fail(err.message, err.errorType));
|
|
143
|
+
}
|
|
144
|
+
return GetMyOrdersOutputSchema.parse(
|
|
145
|
+
fail(err instanceof Error ? err.message : '注文情報取得中に予期しないエラーが発生しました', 'upstream_error'),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── MCP ツール定義(tool-registry から自動収集) ──
|
|
151
|
+
export const toolDef: ToolDefinition = {
|
|
152
|
+
name: 'get_my_orders',
|
|
153
|
+
description:
|
|
154
|
+
'[My Orders / Open Orders / Active Orders] 自分の未約定注文一覧(my orders / open orders / active orders / pending)を取得。通貨ペア・期間でフィルタ可能。Private API。',
|
|
155
|
+
inputSchema: GetMyOrdersInputSchema,
|
|
156
|
+
handler: async (args: { pair?: string; count?: number; since?: string; end?: string }) => getMyOrders(args),
|
|
157
|
+
};
|