@t2000/cli 0.15.3 → 0.16.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/dist/chunk-D4HDZEJT.js +0 -0
- package/dist/{dist-TXXQC3NH.js → dist-VP2F4RTR.js} +118 -13
- package/dist/{dist-TXXQC3NH.js.map → dist-VP2F4RTR.js.map} +1 -1
- package/dist/index.js +391 -17
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -1822,7 +1822,7 @@ function registerMcp(program2) {
|
|
|
1822
1822
|
mcp.command("start", { isDefault: true }).description("Start MCP server (stdio transport)").option("--key <path>", "Key file path").action(async (opts) => {
|
|
1823
1823
|
let mod;
|
|
1824
1824
|
try {
|
|
1825
|
-
mod = await import("./dist-
|
|
1825
|
+
mod = await import("./dist-VP2F4RTR.js");
|
|
1826
1826
|
} catch {
|
|
1827
1827
|
console.error(
|
|
1828
1828
|
"MCP server not installed. Run:\n npm install -g @t2000/mcp"
|
|
@@ -2093,11 +2093,369 @@ function registerInvest(program2) {
|
|
|
2093
2093
|
handleError(error);
|
|
2094
2094
|
}
|
|
2095
2095
|
});
|
|
2096
|
+
const strategyCmd = investCmd.command("strategy").description("Manage investment strategies");
|
|
2097
|
+
strategyCmd.command("list").description("List available strategies").option("--key <path>", "Key file path").action(async (opts) => {
|
|
2098
|
+
try {
|
|
2099
|
+
const pin = await resolvePin();
|
|
2100
|
+
const agent = await T200024.create({ pin, keyPath: opts.key });
|
|
2101
|
+
const all = agent.strategies.getAll();
|
|
2102
|
+
if (isJsonMode()) {
|
|
2103
|
+
printJson(all);
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
printBlank();
|
|
2107
|
+
printHeader("Investment Strategies");
|
|
2108
|
+
printSeparator();
|
|
2109
|
+
for (const [key, def] of Object.entries(all)) {
|
|
2110
|
+
const allocs = Object.entries(def.allocations).map(([a, p]) => `${a} ${p}%`).join(", ");
|
|
2111
|
+
const tag = def.custom ? pc13.dim(" (custom)") : "";
|
|
2112
|
+
printKeyValue(key, `${allocs}${tag}`);
|
|
2113
|
+
printLine(` ${pc13.dim(def.description)}`);
|
|
2114
|
+
}
|
|
2115
|
+
printSeparator();
|
|
2116
|
+
const hasPositions = Object.keys(all).some((k) => agent.portfolio.hasStrategyPositions(k));
|
|
2117
|
+
if (!hasPositions) {
|
|
2118
|
+
printInfo("Buy into a strategy: t2000 invest strategy buy bluechip 100");
|
|
2119
|
+
}
|
|
2120
|
+
printBlank();
|
|
2121
|
+
} catch (error) {
|
|
2122
|
+
handleError(error);
|
|
2123
|
+
}
|
|
2124
|
+
});
|
|
2125
|
+
strategyCmd.command("buy <name> <amount>").description("Buy into a strategy").option("--key <path>", "Key file path").option("--dry-run", "Preview allocation without executing").action(async (name, amount, opts) => {
|
|
2126
|
+
try {
|
|
2127
|
+
const parsed = parseFloat(amount);
|
|
2128
|
+
if (isNaN(parsed) || parsed <= 0) {
|
|
2129
|
+
console.error(pc13.red(" \u2717 Amount must be greater than $0"));
|
|
2130
|
+
process.exitCode = 1;
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
const pin = await resolvePin();
|
|
2134
|
+
const agent = await T200024.create({ pin, keyPath: opts.key });
|
|
2135
|
+
const result = await agent.investStrategy({ strategy: name.toLowerCase(), usdAmount: parsed, dryRun: opts.dryRun });
|
|
2136
|
+
if (isJsonMode()) {
|
|
2137
|
+
printJson(result);
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
printBlank();
|
|
2141
|
+
if (opts.dryRun) {
|
|
2142
|
+
printHeader(`Strategy: ${name} \u2014 Dry Run (${formatUsd15(parsed)})`);
|
|
2143
|
+
printSeparator();
|
|
2144
|
+
for (const buy of result.buys) {
|
|
2145
|
+
printKeyValue(buy.asset, `${formatUsd15(buy.usdAmount)} \u2192 ~${formatAssetAmount(buy.amount, buy.asset)} ${buy.asset} @ ${formatUsd15(buy.price)}`);
|
|
2146
|
+
}
|
|
2147
|
+
printSeparator();
|
|
2148
|
+
printInfo("Run without --dry-run to execute");
|
|
2149
|
+
} else {
|
|
2150
|
+
printSuccess(`Invested ${formatUsd15(parsed)} in ${name} strategy`);
|
|
2151
|
+
printSeparator();
|
|
2152
|
+
for (const buy of result.buys) {
|
|
2153
|
+
printKeyValue(buy.asset, `${formatAssetAmount(buy.amount, buy.asset)} @ ${formatUsd15(buy.price)}`);
|
|
2154
|
+
printLine(` ${pc13.dim(`Tx: ${explorerUrl(buy.tx)}`)}`);
|
|
2155
|
+
}
|
|
2156
|
+
printSeparator();
|
|
2157
|
+
printKeyValue("Total invested", formatUsd15(result.totalInvested));
|
|
2158
|
+
}
|
|
2159
|
+
printBlank();
|
|
2160
|
+
} catch (error) {
|
|
2161
|
+
handleError(error);
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2164
|
+
strategyCmd.command("sell <name>").description("Sell all positions in a strategy").option("--key <path>", "Key file path").action(async (name, opts) => {
|
|
2165
|
+
try {
|
|
2166
|
+
const pin = await resolvePin();
|
|
2167
|
+
const agent = await T200024.create({ pin, keyPath: opts.key });
|
|
2168
|
+
const result = await agent.sellStrategy({ strategy: name.toLowerCase() });
|
|
2169
|
+
if (isJsonMode()) {
|
|
2170
|
+
printJson(result);
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
printBlank();
|
|
2174
|
+
printSuccess(`Sold all ${name} strategy positions`);
|
|
2175
|
+
printSeparator();
|
|
2176
|
+
for (const sell of result.sells) {
|
|
2177
|
+
const pnlColor = sell.realizedPnL >= 0 ? pc13.green : pc13.red;
|
|
2178
|
+
const pnlSign = sell.realizedPnL >= 0 ? "+" : "";
|
|
2179
|
+
printKeyValue(sell.asset, `${formatAssetAmount(sell.amount, sell.asset)} \u2192 ${formatUsd15(sell.usdValue)} ${pnlColor(`${pnlSign}${formatUsd15(sell.realizedPnL)}`)}`);
|
|
2180
|
+
}
|
|
2181
|
+
printSeparator();
|
|
2182
|
+
printKeyValue("Total proceeds", formatUsd15(result.totalProceeds));
|
|
2183
|
+
const rpnlColor = result.realizedPnL >= 0 ? pc13.green : pc13.red;
|
|
2184
|
+
const rpnlSign = result.realizedPnL >= 0 ? "+" : "";
|
|
2185
|
+
printKeyValue("Realized P&L", rpnlColor(`${rpnlSign}${formatUsd15(result.realizedPnL)}`));
|
|
2186
|
+
printBlank();
|
|
2187
|
+
} catch (error) {
|
|
2188
|
+
handleError(error);
|
|
2189
|
+
}
|
|
2190
|
+
});
|
|
2191
|
+
strategyCmd.command("status <name>").description("Show current status and weights of a strategy").option("--key <path>", "Key file path").action(async (name, opts) => {
|
|
2192
|
+
try {
|
|
2193
|
+
const pin = await resolvePin();
|
|
2194
|
+
const agent = await T200024.create({ pin, keyPath: opts.key });
|
|
2195
|
+
const status = await agent.getStrategyStatus(name.toLowerCase());
|
|
2196
|
+
if (isJsonMode()) {
|
|
2197
|
+
printJson(status);
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
printBlank();
|
|
2201
|
+
printHeader(`Strategy: ${status.definition.name}`);
|
|
2202
|
+
printSeparator();
|
|
2203
|
+
if (status.positions.length === 0) {
|
|
2204
|
+
printInfo("No positions yet. Buy in with: t2000 invest strategy buy " + name + " 100");
|
|
2205
|
+
} else {
|
|
2206
|
+
for (const pos of status.positions) {
|
|
2207
|
+
const target = status.definition.allocations[pos.asset] ?? 0;
|
|
2208
|
+
const actual = status.currentWeights[pos.asset] ?? 0;
|
|
2209
|
+
const drift = actual - target;
|
|
2210
|
+
const driftColor = Math.abs(drift) > 3 ? pc13.yellow : pc13.dim;
|
|
2211
|
+
const pnlColor = pos.unrealizedPnL >= 0 ? pc13.green : pc13.red;
|
|
2212
|
+
const pnlSign = pos.unrealizedPnL >= 0 ? "+" : "";
|
|
2213
|
+
printKeyValue(
|
|
2214
|
+
pos.asset,
|
|
2215
|
+
`${formatAssetAmount(pos.totalAmount, pos.asset)} ${formatUsd15(pos.currentValue)} ${pnlColor(`${pnlSign}${formatUsd15(pos.unrealizedPnL)}`)} ${driftColor(`${actual.toFixed(0)}% / ${target}% target`)}`
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
2218
|
+
printSeparator();
|
|
2219
|
+
printKeyValue("Total value", formatUsd15(status.totalValue));
|
|
2220
|
+
}
|
|
2221
|
+
printBlank();
|
|
2222
|
+
} catch (error) {
|
|
2223
|
+
handleError(error);
|
|
2224
|
+
}
|
|
2225
|
+
});
|
|
2226
|
+
strategyCmd.command("rebalance <name>").description("Rebalance a strategy to target weights").option("--key <path>", "Key file path").action(async (name, opts) => {
|
|
2227
|
+
try {
|
|
2228
|
+
const pin = await resolvePin();
|
|
2229
|
+
const agent = await T200024.create({ pin, keyPath: opts.key });
|
|
2230
|
+
const result = await agent.rebalanceStrategy({ strategy: name.toLowerCase() });
|
|
2231
|
+
if (isJsonMode()) {
|
|
2232
|
+
printJson(result);
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
printBlank();
|
|
2236
|
+
if (result.trades.length === 0) {
|
|
2237
|
+
printInfo(`Strategy '${name}' is already balanced (within 3% threshold)`);
|
|
2238
|
+
} else {
|
|
2239
|
+
printSuccess(`Rebalanced ${name} strategy`);
|
|
2240
|
+
printSeparator();
|
|
2241
|
+
for (const t of result.trades) {
|
|
2242
|
+
const action = t.action === "buy" ? pc13.green("BUY") : pc13.red("SELL");
|
|
2243
|
+
printKeyValue(t.asset, `${action} ${formatUsd15(t.usdAmount)} (${formatAssetAmount(t.amount, t.asset)})`);
|
|
2244
|
+
}
|
|
2245
|
+
printSeparator();
|
|
2246
|
+
printInfo("Weights: " + Object.entries(result.afterWeights).map(([a, w]) => `${a} ${w.toFixed(0)}%`).join(", "));
|
|
2247
|
+
}
|
|
2248
|
+
printBlank();
|
|
2249
|
+
} catch (error) {
|
|
2250
|
+
handleError(error);
|
|
2251
|
+
}
|
|
2252
|
+
});
|
|
2253
|
+
strategyCmd.command("create <name>").description("Create a custom strategy").requiredOption("--alloc <pairs...>", "Allocations e.g. SUI:60 BTC:20 ETH:20").option("--description <desc>", "Strategy description").action(async (name, opts) => {
|
|
2254
|
+
try {
|
|
2255
|
+
const allocations = {};
|
|
2256
|
+
for (const pair of opts.alloc) {
|
|
2257
|
+
const [asset, pctStr] = pair.split(":");
|
|
2258
|
+
if (!asset || !pctStr) {
|
|
2259
|
+
console.error(pc13.red(` \u2717 Invalid allocation: '${pair}'. Use ASSET:PCT format (e.g. SUI:60)`));
|
|
2260
|
+
process.exitCode = 1;
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
allocations[asset.toUpperCase()] = parseFloat(pctStr);
|
|
2264
|
+
}
|
|
2265
|
+
const pin = await resolvePin();
|
|
2266
|
+
const agent = await T200024.create({ pin });
|
|
2267
|
+
const definition = agent.strategies.create({ name, allocations, description: opts.description });
|
|
2268
|
+
if (isJsonMode()) {
|
|
2269
|
+
printJson(definition);
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
printBlank();
|
|
2273
|
+
printSuccess(`Created strategy '${name}'`);
|
|
2274
|
+
const allocs = Object.entries(definition.allocations).map(([a, p]) => `${a} ${p}%`).join(", ");
|
|
2275
|
+
printKeyValue("Allocations", allocs);
|
|
2276
|
+
printBlank();
|
|
2277
|
+
} catch (error) {
|
|
2278
|
+
handleError(error);
|
|
2279
|
+
}
|
|
2280
|
+
});
|
|
2281
|
+
strategyCmd.command("delete <name>").description("Delete a custom strategy").option("--key <path>", "Key file path").action(async (name, opts) => {
|
|
2282
|
+
try {
|
|
2283
|
+
const pin = await resolvePin();
|
|
2284
|
+
const agent = await T200024.create({ pin, keyPath: opts.key });
|
|
2285
|
+
if (agent.portfolio.hasStrategyPositions(name.toLowerCase())) {
|
|
2286
|
+
console.error(pc13.red(` \u2717 Strategy '${name}' has open positions. Sell first: t2000 invest strategy sell ${name}`));
|
|
2287
|
+
process.exitCode = 1;
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
agent.strategies.delete(name.toLowerCase());
|
|
2291
|
+
if (isJsonMode()) {
|
|
2292
|
+
printJson({ deleted: name });
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
printBlank();
|
|
2296
|
+
printSuccess(`Deleted strategy '${name}'`);
|
|
2297
|
+
printBlank();
|
|
2298
|
+
} catch (error) {
|
|
2299
|
+
handleError(error);
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2302
|
+
const autoCmd = investCmd.command("auto").description("Dollar-cost averaging (DCA) schedules");
|
|
2303
|
+
autoCmd.command("setup <amount> <frequency> [target]").description("Create a DCA schedule (target = strategy name or asset)").option("--key <path>", "Key file path").option("--day <num>", "Day of week (1-7) or month (1-28)").action(async (amount, frequency, target, opts) => {
|
|
2304
|
+
try {
|
|
2305
|
+
const parsed = parseFloat(amount);
|
|
2306
|
+
if (isNaN(parsed) || parsed < 1) {
|
|
2307
|
+
console.error(pc13.red(" \u2717 Amount must be at least $1"));
|
|
2308
|
+
process.exitCode = 1;
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
if (!["daily", "weekly", "monthly"].includes(frequency)) {
|
|
2312
|
+
console.error(pc13.red(" \u2717 Frequency must be daily, weekly, or monthly"));
|
|
2313
|
+
process.exitCode = 1;
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
const pin = await resolvePin();
|
|
2317
|
+
const agent = await T200024.create({ pin, keyPath: opts.key });
|
|
2318
|
+
const allStrategies = agent.strategies.getAll();
|
|
2319
|
+
const isStrategy = target ? target.toLowerCase() in allStrategies : false;
|
|
2320
|
+
const isAsset = target ? ["SUI", "BTC", "ETH"].includes(target.toUpperCase()) : false;
|
|
2321
|
+
if (target && !isStrategy && !isAsset) {
|
|
2322
|
+
console.error(pc13.red(` \u2717 '${target}' is not a valid strategy or asset`));
|
|
2323
|
+
process.exitCode = 1;
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
const dayNum = opts.day ? parseInt(opts.day, 10) : void 0;
|
|
2327
|
+
const schedule = agent.setupAutoInvest({
|
|
2328
|
+
amount: parsed,
|
|
2329
|
+
frequency,
|
|
2330
|
+
strategy: isStrategy ? target.toLowerCase() : void 0,
|
|
2331
|
+
asset: isAsset ? target.toUpperCase() : void 0,
|
|
2332
|
+
dayOfWeek: frequency === "weekly" ? dayNum : void 0,
|
|
2333
|
+
dayOfMonth: frequency === "monthly" ? dayNum : void 0
|
|
2334
|
+
});
|
|
2335
|
+
if (isJsonMode()) {
|
|
2336
|
+
printJson(schedule);
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
printBlank();
|
|
2340
|
+
const targetLabel = schedule.strategy ?? schedule.asset ?? "unknown";
|
|
2341
|
+
printSuccess(`Auto-invest created: ${formatUsd15(schedule.amount)} ${schedule.frequency} \u2192 ${targetLabel}`);
|
|
2342
|
+
printKeyValue("Schedule ID", schedule.id);
|
|
2343
|
+
printKeyValue("Next run", new Date(schedule.nextRun).toLocaleDateString());
|
|
2344
|
+
printInfo("Run manually: t2000 invest auto run");
|
|
2345
|
+
printBlank();
|
|
2346
|
+
} catch (error) {
|
|
2347
|
+
handleError(error);
|
|
2348
|
+
}
|
|
2349
|
+
});
|
|
2350
|
+
autoCmd.command("status").description("Show all DCA schedules").option("--key <path>", "Key file path").action(async (opts) => {
|
|
2351
|
+
try {
|
|
2352
|
+
const pin = await resolvePin();
|
|
2353
|
+
const agent = await T200024.create({ pin, keyPath: opts.key });
|
|
2354
|
+
const status = agent.getAutoInvestStatus();
|
|
2355
|
+
if (isJsonMode()) {
|
|
2356
|
+
printJson(status);
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
printBlank();
|
|
2360
|
+
if (status.schedules.length === 0) {
|
|
2361
|
+
printInfo("No auto-invest schedules. Set one up: t2000 invest auto setup 50 weekly bluechip");
|
|
2362
|
+
printBlank();
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
printHeader("Auto-Invest Schedules");
|
|
2366
|
+
printSeparator();
|
|
2367
|
+
for (const s of status.schedules) {
|
|
2368
|
+
const target = s.strategy ?? s.asset ?? "?";
|
|
2369
|
+
const statusTag = s.enabled ? pc13.green("active") : pc13.dim("paused");
|
|
2370
|
+
printKeyValue(s.id, `${formatUsd15(s.amount)} ${s.frequency} \u2192 ${target} ${statusTag}`);
|
|
2371
|
+
printLine(` ${pc13.dim(`Next: ${new Date(s.nextRun).toLocaleDateString()} \xB7 Runs: ${s.runCount} \xB7 Total: ${formatUsd15(s.totalInvested)}`)}`);
|
|
2372
|
+
}
|
|
2373
|
+
printSeparator();
|
|
2374
|
+
if (status.pendingRuns.length > 0) {
|
|
2375
|
+
printInfo(`${status.pendingRuns.length} pending run(s). Execute: t2000 invest auto run`);
|
|
2376
|
+
} else {
|
|
2377
|
+
printInfo("All schedules up to date");
|
|
2378
|
+
}
|
|
2379
|
+
printBlank();
|
|
2380
|
+
} catch (error) {
|
|
2381
|
+
handleError(error);
|
|
2382
|
+
}
|
|
2383
|
+
});
|
|
2384
|
+
autoCmd.command("run").description("Execute pending DCA purchases").option("--key <path>", "Key file path").action(async (opts) => {
|
|
2385
|
+
try {
|
|
2386
|
+
const pin = await resolvePin();
|
|
2387
|
+
const agent = await T200024.create({ pin, keyPath: opts.key });
|
|
2388
|
+
const status = agent.getAutoInvestStatus();
|
|
2389
|
+
if (status.pendingRuns.length === 0) {
|
|
2390
|
+
if (isJsonMode()) {
|
|
2391
|
+
printJson({ executed: [], skipped: [] });
|
|
2392
|
+
return;
|
|
2393
|
+
}
|
|
2394
|
+
printBlank();
|
|
2395
|
+
printInfo("No pending auto-invest runs. All schedules are up to date.");
|
|
2396
|
+
printBlank();
|
|
2397
|
+
return;
|
|
2398
|
+
}
|
|
2399
|
+
const result = await agent.runAutoInvest();
|
|
2400
|
+
if (isJsonMode()) {
|
|
2401
|
+
printJson(result);
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
printBlank();
|
|
2405
|
+
if (result.executed.length > 0) {
|
|
2406
|
+
printSuccess(`Executed ${result.executed.length} auto-invest run(s)`);
|
|
2407
|
+
for (const exec of result.executed) {
|
|
2408
|
+
const target = exec.strategy ?? exec.asset ?? "?";
|
|
2409
|
+
printKeyValue(target, formatUsd15(exec.amount));
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
if (result.skipped.length > 0) {
|
|
2413
|
+
for (const skip of result.skipped) {
|
|
2414
|
+
printLine(` ${pc13.yellow("\u26A0")} Skipped ${skip.scheduleId}: ${skip.reason}`);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
printBlank();
|
|
2418
|
+
} catch (error) {
|
|
2419
|
+
handleError(error);
|
|
2420
|
+
}
|
|
2421
|
+
});
|
|
2422
|
+
autoCmd.command("stop <id>").description("Stop an auto-invest schedule").option("--key <path>", "Key file path").action(async (id, opts) => {
|
|
2423
|
+
try {
|
|
2424
|
+
const pin = await resolvePin();
|
|
2425
|
+
const agent = await T200024.create({ pin, keyPath: opts.key });
|
|
2426
|
+
agent.stopAutoInvest(id);
|
|
2427
|
+
if (isJsonMode()) {
|
|
2428
|
+
printJson({ stopped: id });
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
printBlank();
|
|
2432
|
+
printSuccess(`Stopped auto-invest schedule ${id}`);
|
|
2433
|
+
printBlank();
|
|
2434
|
+
} catch (error) {
|
|
2435
|
+
handleError(error);
|
|
2436
|
+
}
|
|
2437
|
+
});
|
|
2096
2438
|
}
|
|
2097
2439
|
|
|
2098
2440
|
// src/commands/portfolio.ts
|
|
2099
2441
|
import pc14 from "picocolors";
|
|
2100
2442
|
import { T2000 as T200025, formatUsd as formatUsd16, formatAssetAmount as formatAssetAmount2 } from "@t2000/sdk";
|
|
2443
|
+
function printPositionLine(pos) {
|
|
2444
|
+
if (pos.currentPrice === 0 && pos.totalAmount > 0) {
|
|
2445
|
+
printKeyValue(
|
|
2446
|
+
pos.asset,
|
|
2447
|
+
`${formatAssetAmount2(pos.totalAmount, pos.asset)} Avg: ${formatUsd16(pos.avgPrice)} Now: ${pc14.yellow("unavailable")}`
|
|
2448
|
+
);
|
|
2449
|
+
} else {
|
|
2450
|
+
const pnlColor = pos.unrealizedPnL >= 0 ? pc14.green : pc14.red;
|
|
2451
|
+
const pnlSign = pos.unrealizedPnL >= 0 ? "+" : "";
|
|
2452
|
+
const yieldSuffix = pos.earning && pos.earningApy ? ` ${pc14.cyan(`${pos.earningApy.toFixed(1)}% APY (${pos.earningProtocol})`)}` : "";
|
|
2453
|
+
printKeyValue(
|
|
2454
|
+
pos.asset,
|
|
2455
|
+
`${formatAssetAmount2(pos.totalAmount, pos.asset)} Avg: ${formatUsd16(pos.avgPrice)} Now: ${formatUsd16(pos.currentPrice)} ${pnlColor(`${pnlSign}${formatUsd16(pos.unrealizedPnL)} (${pnlSign}${pos.unrealizedPnLPct.toFixed(1)}%)`)}${yieldSuffix}`
|
|
2456
|
+
);
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2101
2459
|
function registerPortfolio(program2) {
|
|
2102
2460
|
program2.command("portfolio").description("Show investment portfolio").option("--key <path>", "Key file path").action(async (opts) => {
|
|
2103
2461
|
try {
|
|
@@ -2109,27 +2467,43 @@ function registerPortfolio(program2) {
|
|
|
2109
2467
|
return;
|
|
2110
2468
|
}
|
|
2111
2469
|
printBlank();
|
|
2112
|
-
|
|
2470
|
+
const hasDirectPositions = portfolio.positions.length > 0;
|
|
2471
|
+
const hasStrategyPositions = portfolio.strategyPositions && Object.keys(portfolio.strategyPositions).length > 0;
|
|
2472
|
+
if (!hasDirectPositions && !hasStrategyPositions) {
|
|
2113
2473
|
printInfo("No investments yet. Try: t2000 invest buy 100 SUI");
|
|
2114
2474
|
printBlank();
|
|
2115
2475
|
return;
|
|
2116
2476
|
}
|
|
2117
2477
|
printHeader("Investment Portfolio");
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
const
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
);
|
|
2478
|
+
if (hasStrategyPositions) {
|
|
2479
|
+
for (const [key, positions] of Object.entries(portfolio.strategyPositions)) {
|
|
2480
|
+
let stratLabel = key;
|
|
2481
|
+
try {
|
|
2482
|
+
const def = agent.strategies.get(key);
|
|
2483
|
+
stratLabel = def.name;
|
|
2484
|
+
} catch {
|
|
2485
|
+
}
|
|
2486
|
+
printLine(` ${pc14.bold(pc14.cyan(`\u25B8 ${stratLabel}`))}`);
|
|
2487
|
+
printSeparator();
|
|
2488
|
+
for (const pos of positions) {
|
|
2489
|
+
printPositionLine(pos);
|
|
2490
|
+
}
|
|
2491
|
+
const stratValue = positions.reduce((s, p) => s + p.currentValue, 0);
|
|
2492
|
+
printLine(` ${pc14.dim(`Subtotal: ${formatUsd16(stratValue)}`)}`);
|
|
2493
|
+
printBlank();
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
if (hasDirectPositions) {
|
|
2497
|
+
if (hasStrategyPositions) {
|
|
2498
|
+
printLine(` ${pc14.bold(pc14.cyan("\u25B8 Direct"))}`);
|
|
2499
|
+
}
|
|
2500
|
+
printSeparator();
|
|
2501
|
+
for (const pos of portfolio.positions) {
|
|
2502
|
+
printPositionLine(pos);
|
|
2503
|
+
}
|
|
2504
|
+
if (hasStrategyPositions) {
|
|
2505
|
+
const directValue = portfolio.positions.reduce((s, p) => s + p.currentValue, 0);
|
|
2506
|
+
printLine(` ${pc14.dim(`Subtotal: ${formatUsd16(directValue)}`)}`);
|
|
2133
2507
|
}
|
|
2134
2508
|
}
|
|
2135
2509
|
printSeparator();
|