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,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get_margin_trade_history — 信用取引の約定履歴を取得する Private API ツール。
|
|
3
|
+
*
|
|
4
|
+
* bitbank Private API `/v1/user/spot/trade_history` に `type=margin` を指定し、
|
|
5
|
+
* 信用取引の約定(新規建て・決済)のみを取得して返す。
|
|
6
|
+
*
|
|
7
|
+
* 注意: since を信用新規建て後の日時に指定した場合、
|
|
8
|
+
* 対応する決済約定のみが返される可能性があります。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { nowIso, parseIso8601, toIsoMs } from '../../lib/datetime.js';
|
|
12
|
+
import { formatPair, formatPrice } from '../../lib/formatter.js';
|
|
13
|
+
import { fail, ok } from '../../lib/result.js';
|
|
14
|
+
import { getDefaultClient, PrivateApiError } from '../../src/private/client.js';
|
|
15
|
+
import { GetMarginTradeHistoryInputSchema, GetMarginTradeHistoryOutputSchema } from '../../src/private/schemas.js';
|
|
16
|
+
import type { ToolDefinition } from '../../src/tool-definition.js';
|
|
17
|
+
|
|
18
|
+
/** bitbank /v1/user/spot/trade_history のレスポンス型(信用取引フィルタ時) */
|
|
19
|
+
interface RawMarginTrade {
|
|
20
|
+
trade_id: number;
|
|
21
|
+
pair: string;
|
|
22
|
+
order_id: number;
|
|
23
|
+
side: string;
|
|
24
|
+
position_side?: string;
|
|
25
|
+
type: string;
|
|
26
|
+
amount: string;
|
|
27
|
+
price: string;
|
|
28
|
+
maker_taker: string;
|
|
29
|
+
fee_amount_base: string;
|
|
30
|
+
fee_amount_quote: string;
|
|
31
|
+
profit_loss?: string;
|
|
32
|
+
interest?: string;
|
|
33
|
+
executed_at: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default async function getMarginTradeHistory(args: {
|
|
37
|
+
pair?: string;
|
|
38
|
+
count?: number;
|
|
39
|
+
order?: 'asc' | 'desc';
|
|
40
|
+
since?: string;
|
|
41
|
+
end?: string;
|
|
42
|
+
}) {
|
|
43
|
+
const { pair, count = 20, order = 'desc', since, end } = args;
|
|
44
|
+
const client = getDefaultClient();
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const params: Record<string, string> = { type: 'margin' };
|
|
48
|
+
if (pair) params.pair = pair;
|
|
49
|
+
if (count !== 20) params.count = String(count);
|
|
50
|
+
if (order !== 'desc') params.order = order;
|
|
51
|
+
|
|
52
|
+
// ISO8601 → unix ms 変換
|
|
53
|
+
if (since) {
|
|
54
|
+
const parsed = parseIso8601(since);
|
|
55
|
+
if (!parsed) {
|
|
56
|
+
return GetMarginTradeHistoryOutputSchema.parse(
|
|
57
|
+
fail(`since の日時形式が不正です: ${since}`, 'validation_error'),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
params.since = String(parsed.valueOf());
|
|
61
|
+
}
|
|
62
|
+
if (end) {
|
|
63
|
+
const parsed = parseIso8601(end);
|
|
64
|
+
if (!parsed) {
|
|
65
|
+
return GetMarginTradeHistoryOutputSchema.parse(fail(`end の日時形式が不正です: ${end}`, 'validation_error'));
|
|
66
|
+
}
|
|
67
|
+
params.end = String(parsed.valueOf());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const rawData = await client.get<{ trades: RawMarginTrade[] }>('/v1/user/spot/trade_history', params);
|
|
71
|
+
|
|
72
|
+
const timestamp = nowIso();
|
|
73
|
+
|
|
74
|
+
// 約定データの整形
|
|
75
|
+
const trades = rawData.trades.map((t) => ({
|
|
76
|
+
trade_id: t.trade_id,
|
|
77
|
+
pair: t.pair,
|
|
78
|
+
order_id: t.order_id,
|
|
79
|
+
side: t.side,
|
|
80
|
+
position_side: t.position_side,
|
|
81
|
+
type: t.type,
|
|
82
|
+
amount: t.amount,
|
|
83
|
+
price: t.price,
|
|
84
|
+
maker_taker: t.maker_taker,
|
|
85
|
+
fee_amount_base: t.fee_amount_base,
|
|
86
|
+
fee_amount_quote: t.fee_amount_quote,
|
|
87
|
+
profit_loss: t.profit_loss,
|
|
88
|
+
interest: t.interest,
|
|
89
|
+
executed_at: toIsoMs(t.executed_at) ?? String(t.executed_at),
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
// サマリー文字列の生成
|
|
93
|
+
const lines: string[] = [];
|
|
94
|
+
const pairLabel = pair ? formatPair(pair) : '全ペア';
|
|
95
|
+
lines.push(`信用約定履歴: ${pairLabel} ${trades.length}件`);
|
|
96
|
+
|
|
97
|
+
if (trades.length > 0) {
|
|
98
|
+
lines.push('');
|
|
99
|
+
|
|
100
|
+
const displayTrades = order === 'asc' ? trades.slice(-10) : trades.slice(0, 10);
|
|
101
|
+
for (const t of displayTrades) {
|
|
102
|
+
const sideLabel = t.side === 'buy' ? '買' : '売';
|
|
103
|
+
const posLabel = t.position_side === 'long' ? 'ロング' : t.position_side === 'short' ? 'ショート' : '';
|
|
104
|
+
const isJpy = t.pair.includes('jpy');
|
|
105
|
+
const price = isJpy ? formatPrice(Number(t.price)) : t.price;
|
|
106
|
+
const plInfo = t.profit_loss ? ` 損益: ${formatPrice(Number(t.profit_loss))} 円` : '';
|
|
107
|
+
lines.push(
|
|
108
|
+
`[trade: ${t.trade_id} / order: ${t.order_id}] ${t.executed_at} ${formatPair(t.pair)} ${posLabel}${sideLabel} ${t.amount} @ ${price} (${t.maker_taker})${plInfo}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (trades.length > 10) {
|
|
113
|
+
lines.push(`... 他 ${trades.length - 10}件`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 集計
|
|
117
|
+
const longCount = trades.filter((t) => t.position_side === 'long').length;
|
|
118
|
+
const shortCount = trades.filter((t) => t.position_side === 'short').length;
|
|
119
|
+
lines.push('');
|
|
120
|
+
lines.push(`集計: ロング ${longCount}件 / ショート ${shortCount}件`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const summary = lines.join('\n');
|
|
124
|
+
|
|
125
|
+
const data = {
|
|
126
|
+
trades,
|
|
127
|
+
timestamp,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const meta = {
|
|
131
|
+
fetchedAt: timestamp,
|
|
132
|
+
tradeCount: trades.length,
|
|
133
|
+
pair: pair || undefined,
|
|
134
|
+
...(client.lastRateLimit ? { rateLimit: client.lastRateLimit } : {}),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return GetMarginTradeHistoryOutputSchema.parse(ok(summary, data, meta));
|
|
138
|
+
} catch (err) {
|
|
139
|
+
if (err instanceof PrivateApiError) {
|
|
140
|
+
return GetMarginTradeHistoryOutputSchema.parse(fail(err.message, err.errorType));
|
|
141
|
+
}
|
|
142
|
+
return GetMarginTradeHistoryOutputSchema.parse(
|
|
143
|
+
fail(err instanceof Error ? err.message : '信用約定履歴取得中に予期しないエラーが発生しました', 'upstream_error'),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── MCP ツール定義(tool-registry から自動収集) ──
|
|
149
|
+
export const toolDef: ToolDefinition = {
|
|
150
|
+
name: 'get_margin_trade_history',
|
|
151
|
+
description:
|
|
152
|
+
'[Margin Trades / 信用約定履歴] 信用取引の約定履歴(新規建て・決済)を取得。通貨ペア・期間・件数でフィルタ可能。決済時の実現損益・利息を含む。注意: since を信用新規建て後に指定すると決済約定のみが返る場合があります。Private API。',
|
|
153
|
+
inputSchema: GetMarginTradeHistoryInputSchema,
|
|
154
|
+
handler: async (args: { pair?: string; count?: number; order?: 'asc' | 'desc'; since?: string; end?: string }) =>
|
|
155
|
+
getMarginTradeHistory(args),
|
|
156
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get_my_assets — 自分の資産残高を取得する Private API ツール。
|
|
3
|
+
*
|
|
4
|
+
* bitbank Private API `/v1/user/assets` を呼び出し、
|
|
5
|
+
* ticker 連携で円評価額・構成比を自動算出する。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { toNum } from '../../lib/conversions.js';
|
|
9
|
+
import { nowIso } from '../../lib/datetime.js';
|
|
10
|
+
import { formatPrice } from '../../lib/formatter.js';
|
|
11
|
+
import { fail, ok } from '../../lib/result.js';
|
|
12
|
+
import { getDefaultClient, PrivateApiError } from '../../src/private/client.js';
|
|
13
|
+
import { GetMyAssetsInputSchema, GetMyAssetsOutputSchema } from '../../src/private/schemas.js';
|
|
14
|
+
import type { ToolDefinition } from '../../src/tool-definition.js';
|
|
15
|
+
|
|
16
|
+
/** bitbank /v1/user/assets のレスポンス型 */
|
|
17
|
+
interface RawAsset {
|
|
18
|
+
asset: string;
|
|
19
|
+
free_amount: string;
|
|
20
|
+
amount_precision: number;
|
|
21
|
+
onhand_amount: string;
|
|
22
|
+
locked_amount: string;
|
|
23
|
+
withdrawal_fee: { min: string; max: string } | string;
|
|
24
|
+
stop_deposit: boolean;
|
|
25
|
+
stop_withdrawal: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** ticker から取得する最低限の価格情報 */
|
|
29
|
+
interface _TickerPrice {
|
|
30
|
+
pair: string;
|
|
31
|
+
last: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* public API の tickers_jpy から各通貨の最新価格を取得する。
|
|
36
|
+
* 失敗しても get_my_assets 自体は動作する(partial_data_warning)。
|
|
37
|
+
*/
|
|
38
|
+
async function fetchTickerPrices(): Promise<{ prices: Map<string, number>; error?: string }> {
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch('https://public.bitbank.cc/tickers_jpy', {
|
|
41
|
+
signal: AbortSignal.timeout(3000),
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
return { prices: new Map(), error: `ticker HTTP ${res.status}` };
|
|
45
|
+
}
|
|
46
|
+
const json = (await res.json()) as { success?: number; data?: Array<{ pair: string; last: string }> };
|
|
47
|
+
if (json.success !== 1 || !Array.isArray(json.data)) {
|
|
48
|
+
return { prices: new Map(), error: 'ticker レスポンス不正' };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const prices = new Map<string, number>();
|
|
52
|
+
for (const item of json.data) {
|
|
53
|
+
const asset = item.pair.replace('_jpy', '');
|
|
54
|
+
const last = toNum(item.last);
|
|
55
|
+
if (last != null && last > 0) {
|
|
56
|
+
prices.set(asset, last);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { prices };
|
|
60
|
+
} catch (e) {
|
|
61
|
+
return { prices: new Map(), error: e instanceof Error ? e.message : 'ticker 取得失敗' };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default async function getMyAssets(args: { include_jpy_valuation?: boolean }) {
|
|
66
|
+
const { include_jpy_valuation = true } = args;
|
|
67
|
+
const client = getDefaultClient();
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const rawAssets = await client.get<{ assets: RawAsset[] }>('/v1/user/assets');
|
|
71
|
+
const timestamp = nowIso();
|
|
72
|
+
|
|
73
|
+
// ゼロでない資産のみ抽出
|
|
74
|
+
const nonZeroAssets = rawAssets.assets.filter((a) => {
|
|
75
|
+
const amount = toNum(a.onhand_amount);
|
|
76
|
+
return amount != null && amount > 0;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ticker 連携(オプション)
|
|
80
|
+
let tickerError: string | undefined;
|
|
81
|
+
let prices = new Map<string, number>();
|
|
82
|
+
|
|
83
|
+
if (include_jpy_valuation) {
|
|
84
|
+
const tickerResult = await fetchTickerPrices();
|
|
85
|
+
prices = tickerResult.prices;
|
|
86
|
+
tickerError = tickerResult.error;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 資産データの組み立て
|
|
90
|
+
let totalJpyValue = 0;
|
|
91
|
+
const assets = nonZeroAssets.map((a) => {
|
|
92
|
+
const amount = a.onhand_amount;
|
|
93
|
+
const available = a.free_amount;
|
|
94
|
+
const locked = a.locked_amount;
|
|
95
|
+
|
|
96
|
+
let jpyValue: number | undefined;
|
|
97
|
+
if (include_jpy_valuation) {
|
|
98
|
+
if (a.asset === 'jpy') {
|
|
99
|
+
jpyValue = toNum(amount) ?? undefined;
|
|
100
|
+
} else {
|
|
101
|
+
const price = prices.get(a.asset);
|
|
102
|
+
const amountN = toNum(amount);
|
|
103
|
+
if (price && amountN != null) {
|
|
104
|
+
jpyValue = amountN * price;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (jpyValue != null && Number.isFinite(jpyValue)) {
|
|
110
|
+
totalJpyValue += jpyValue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
asset: a.asset,
|
|
115
|
+
amount,
|
|
116
|
+
available_amount: available,
|
|
117
|
+
locked_amount: locked,
|
|
118
|
+
jpy_value: jpyValue != null && Number.isFinite(jpyValue) ? Math.round(jpyValue) : undefined,
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// 構成比の算出
|
|
123
|
+
if (include_jpy_valuation && totalJpyValue > 0) {
|
|
124
|
+
for (const asset of assets) {
|
|
125
|
+
if (asset.jpy_value != null) {
|
|
126
|
+
(asset as { allocation_pct?: number }).allocation_pct =
|
|
127
|
+
Math.round((asset.jpy_value / totalJpyValue) * 10000) / 100;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// JPY 評価額降順でソート
|
|
133
|
+
assets.sort((a, b) => (b.jpy_value ?? 0) - (a.jpy_value ?? 0));
|
|
134
|
+
|
|
135
|
+
// サマリー文字列の生成
|
|
136
|
+
const lines: string[] = [];
|
|
137
|
+
lines.push(`保有資産: ${assets.length}通貨`);
|
|
138
|
+
if (include_jpy_valuation && totalJpyValue > 0) {
|
|
139
|
+
lines.push(`合計評価額: ${formatPrice(totalJpyValue, 'btc_jpy')}`);
|
|
140
|
+
}
|
|
141
|
+
lines.push('');
|
|
142
|
+
|
|
143
|
+
for (const a of assets) {
|
|
144
|
+
const assetUpper = a.asset.toUpperCase();
|
|
145
|
+
let line = `${assetUpper}: ${a.amount}`;
|
|
146
|
+
if (a.jpy_value != null) {
|
|
147
|
+
line += ` (${formatPrice(a.jpy_value, 'btc_jpy')}`;
|
|
148
|
+
const pct = (a as { allocation_pct?: number }).allocation_pct;
|
|
149
|
+
if (pct != null) {
|
|
150
|
+
line += `, ${pct}%`;
|
|
151
|
+
}
|
|
152
|
+
line += ')';
|
|
153
|
+
}
|
|
154
|
+
if ((toNum(a.locked_amount) ?? 0) > 0) {
|
|
155
|
+
line += ` [ロック: ${a.locked_amount}]`;
|
|
156
|
+
}
|
|
157
|
+
lines.push(line);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (tickerError) {
|
|
161
|
+
lines.push('');
|
|
162
|
+
lines.push(`⚠ ticker 連携一部失敗: ${tickerError}(一部通貨の円評価額が欠損している可能性があります)`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const summary = lines.join('\n');
|
|
166
|
+
const hasJpyValuation = include_jpy_valuation && totalJpyValue > 0;
|
|
167
|
+
|
|
168
|
+
const data = {
|
|
169
|
+
assets,
|
|
170
|
+
total_jpy_value: hasJpyValuation ? Math.round(totalJpyValue) : undefined,
|
|
171
|
+
timestamp,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const meta = {
|
|
175
|
+
fetchedAt: timestamp,
|
|
176
|
+
assetCount: assets.length,
|
|
177
|
+
hasJpyValuation,
|
|
178
|
+
...(client.lastRateLimit ? { rateLimit: client.lastRateLimit } : {}),
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const result = ok(summary, data, meta);
|
|
182
|
+
|
|
183
|
+
// ticker 一部失敗の場合は partial_data_warning を付与
|
|
184
|
+
if (tickerError) {
|
|
185
|
+
(result.meta as Record<string, unknown>).warning = 'partial_data_warning';
|
|
186
|
+
(result.meta as Record<string, unknown>).warningDetail = tickerError;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return GetMyAssetsOutputSchema.parse(result);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
if (err instanceof PrivateApiError) {
|
|
192
|
+
return GetMyAssetsOutputSchema.parse(fail(err.message, err.errorType));
|
|
193
|
+
}
|
|
194
|
+
return GetMyAssetsOutputSchema.parse(
|
|
195
|
+
fail(err instanceof Error ? err.message : 'asset 取得中に予期しないエラーが発生しました', 'upstream_error'),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── MCP ツール定義(tool-registry から自動収集) ──
|
|
201
|
+
export const toolDef: ToolDefinition = {
|
|
202
|
+
name: 'get_my_assets',
|
|
203
|
+
description:
|
|
204
|
+
'[My Assets / Balance / Wallet] 自分の保有資産・残高一覧(my assets / balance / wallet / holdings)を取得。全通貨の数量・円評価額・構成比を返す。Private API。',
|
|
205
|
+
inputSchema: GetMyAssetsInputSchema,
|
|
206
|
+
handler: async (args: Record<string, unknown>) => getMyAssets(args as { include_jpy_valuation?: boolean }),
|
|
207
|
+
};
|