@exagent/agent 0.1.47 → 0.1.48
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-CVRY5TKR.mjs +10261 -0
- package/dist/chunk-D22X7J5B.mjs +10259 -0
- package/dist/chunk-SS7WXSW4.mjs +10138 -0
- package/dist/chunk-UVIDQKP4.mjs +10232 -0
- package/dist/chunk-V5XCNJWM.mjs +10261 -0
- package/dist/chunk-XRLY7P6A.mjs +10134 -0
- package/dist/chunk-YUBAS5YE.mjs +10195 -0
- package/dist/cli.js +541 -67
- package/dist/cli.mjs +91 -1
- package/dist/index.d.mts +26 -9
- package/dist/index.d.ts +26 -9
- package/dist/index.js +448 -66
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -332,7 +332,7 @@ function getTokenDecimals(address) {
|
|
|
332
332
|
function getTokenSymbol(address) {
|
|
333
333
|
return _globalResolver?.getSymbol(address.toLowerCase());
|
|
334
334
|
}
|
|
335
|
-
var import_viem2, import_chains, NATIVE_ETH, _globalResolver, TOKEN_DECIMALS, TOKEN_TO_COINGECKO, STABLECOIN_IDS, PRICE_STALENESS_MS, MarketDataService;
|
|
335
|
+
var import_viem2, import_chains, NATIVE_ETH, _globalResolver, TOKEN_DECIMALS, TOKEN_TO_COINGECKO, STABLECOIN_IDS, PRICE_STALENESS_MS, HISTORY_STALENESS_MS, MarketDataService;
|
|
336
336
|
var init_market = __esm({
|
|
337
337
|
"src/trading/market.ts"() {
|
|
338
338
|
"use strict";
|
|
@@ -498,6 +498,7 @@ var init_market = __esm({
|
|
|
498
498
|
};
|
|
499
499
|
STABLECOIN_IDS = /* @__PURE__ */ new Set(["usd-coin", "dai", "tether"]);
|
|
500
500
|
PRICE_STALENESS_MS = 6e4;
|
|
501
|
+
HISTORY_STALENESS_MS = 60 * 6e4;
|
|
501
502
|
MarketDataService = class {
|
|
502
503
|
rpcUrl;
|
|
503
504
|
client;
|
|
@@ -510,6 +511,10 @@ var init_market = __esm({
|
|
|
510
511
|
cachedVolume24h = {};
|
|
511
512
|
/** Cached price change data */
|
|
512
513
|
cachedPriceChange24h = {};
|
|
514
|
+
/** Cached 24h hourly price history per token address */
|
|
515
|
+
cachedPriceHistory = {};
|
|
516
|
+
/** Timestamp of last successful price history fetch */
|
|
517
|
+
lastHistoryFetchAt = 0;
|
|
513
518
|
constructor(rpcUrl, store) {
|
|
514
519
|
this.rpcUrl = rpcUrl;
|
|
515
520
|
this.client = (0, import_viem2.createPublicClient)({
|
|
@@ -530,11 +535,10 @@ var init_market = __esm({
|
|
|
530
535
|
const prices = await this.fetchPrices(tokenAddresses);
|
|
531
536
|
const balances = await this.fetchBalances(walletAddress, tokenAddresses);
|
|
532
537
|
const portfolioValue = this.calculatePortfolioValue(balances, prices);
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
+
const [priceHistory, gasPrice] = await Promise.all([
|
|
539
|
+
this.fetchPriceHistory(tokenAddresses).catch(() => void 0),
|
|
540
|
+
this.client.getGasPrice().catch(() => void 0)
|
|
541
|
+
]);
|
|
538
542
|
return {
|
|
539
543
|
timestamp: Date.now(),
|
|
540
544
|
prices,
|
|
@@ -545,7 +549,8 @@ var init_market = __esm({
|
|
|
545
549
|
gasPrice,
|
|
546
550
|
network: {
|
|
547
551
|
chainId: this.client.chain?.id ?? 8453
|
|
548
|
-
}
|
|
552
|
+
},
|
|
553
|
+
priceHistory: priceHistory && Object.keys(priceHistory).length > 0 ? priceHistory : void 0
|
|
549
554
|
};
|
|
550
555
|
}
|
|
551
556
|
/**
|
|
@@ -718,6 +723,58 @@ var init_market = __esm({
|
|
|
718
723
|
}
|
|
719
724
|
return prices;
|
|
720
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* Fetch 24h hourly price history from CoinGecko.
|
|
728
|
+
* Returns cached data if still fresh (< 60 min old).
|
|
729
|
+
* Only fetches for tokens with known CoinGecko IDs.
|
|
730
|
+
*/
|
|
731
|
+
async fetchPriceHistory(tokenAddresses) {
|
|
732
|
+
if (Date.now() - this.lastHistoryFetchAt < HISTORY_STALENESS_MS && Object.keys(this.cachedPriceHistory).length > 0) {
|
|
733
|
+
return { ...this.cachedPriceHistory };
|
|
734
|
+
}
|
|
735
|
+
const history = {};
|
|
736
|
+
const idToAddrs = {};
|
|
737
|
+
for (const addr of tokenAddresses) {
|
|
738
|
+
const cgId = TOKEN_TO_COINGECKO[addr.toLowerCase()];
|
|
739
|
+
if (cgId && !STABLECOIN_IDS.has(cgId)) {
|
|
740
|
+
if (!idToAddrs[cgId]) idToAddrs[cgId] = [];
|
|
741
|
+
idToAddrs[cgId].push(addr.toLowerCase());
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
const ids = Object.keys(idToAddrs);
|
|
745
|
+
for (let i = 0; i < ids.length; i++) {
|
|
746
|
+
const cgId = ids[i];
|
|
747
|
+
try {
|
|
748
|
+
const resp = await fetch(
|
|
749
|
+
`https://api.coingecko.com/api/v3/coins/${cgId}/market_chart?vs_currency=usd&days=1`,
|
|
750
|
+
{ signal: AbortSignal.timeout(5e3) }
|
|
751
|
+
);
|
|
752
|
+
if (!resp.ok) {
|
|
753
|
+
if (resp.status === 429) {
|
|
754
|
+
console.warn("CoinGecko rate limit hit during price history fetch \u2014 using partial data");
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
const data = await resp.json();
|
|
760
|
+
if (data.prices && Array.isArray(data.prices)) {
|
|
761
|
+
const candles = data.prices.map(([ts, price]) => ({ timestamp: ts, price }));
|
|
762
|
+
for (const addr of idToAddrs[cgId]) {
|
|
763
|
+
history[addr] = candles;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
} catch {
|
|
767
|
+
}
|
|
768
|
+
if (i < ids.length - 1) {
|
|
769
|
+
await new Promise((r) => setTimeout(r, 2500));
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (Object.keys(history).length > 0) {
|
|
773
|
+
this.cachedPriceHistory = { ...this.cachedPriceHistory, ...history };
|
|
774
|
+
this.lastHistoryFetchAt = Date.now();
|
|
775
|
+
}
|
|
776
|
+
return { ...this.cachedPriceHistory };
|
|
777
|
+
}
|
|
721
778
|
/**
|
|
722
779
|
* Fetch real on-chain balances: native ETH + ERC-20 tokens.
|
|
723
780
|
* Uses Multicall3 to batch all balanceOf calls into a single RPC request.
|
|
@@ -1643,6 +1700,10 @@ var init_executor = __esm({
|
|
|
1643
1700
|
* Validate a signal against config limits and token restrictions
|
|
1644
1701
|
*/
|
|
1645
1702
|
validateSignal(signal) {
|
|
1703
|
+
if (signal.amountIn <= 0n) {
|
|
1704
|
+
console.warn(`Signal amountIn is ${signal.amountIn} \u2014 skipping (must be positive)`);
|
|
1705
|
+
return false;
|
|
1706
|
+
}
|
|
1646
1707
|
if (signal.confidence < 0.5) {
|
|
1647
1708
|
console.warn(`Signal confidence ${signal.confidence} below threshold (0.5)`);
|
|
1648
1709
|
return false;
|
|
@@ -2587,6 +2648,33 @@ function formatSessionReport(result) {
|
|
|
2587
2648
|
}
|
|
2588
2649
|
lines.push("");
|
|
2589
2650
|
}
|
|
2651
|
+
if (result.trades.length > 0) {
|
|
2652
|
+
lines.push(` ${thinDivider}`);
|
|
2653
|
+
lines.push(" TRADE LOG");
|
|
2654
|
+
lines.push(` ${thinDivider}`);
|
|
2655
|
+
lines.push("");
|
|
2656
|
+
const recentTrades = result.trades.slice(-50);
|
|
2657
|
+
for (const t of recentTrades) {
|
|
2658
|
+
const time = new Date(t.timestamp).toLocaleString(void 0, {
|
|
2659
|
+
month: "short",
|
|
2660
|
+
day: "2-digit",
|
|
2661
|
+
hour: "2-digit",
|
|
2662
|
+
minute: "2-digit"
|
|
2663
|
+
});
|
|
2664
|
+
const tag = t.action === "buy" ? "BUY " : "SELL";
|
|
2665
|
+
const tokenIn = t.tokenIn.slice(0, 6) + "\u2026";
|
|
2666
|
+
const tokenOut = t.tokenOut.slice(0, 6) + "\u2026";
|
|
2667
|
+
const value = `$${t.valueInUSD.toFixed(2)}`;
|
|
2668
|
+
const pnl = t.valueOutUSD - t.valueInUSD - t.feeUSD - t.gasUSD;
|
|
2669
|
+
const pnlStr = pnl >= 0 ? `+$${pnl.toFixed(2)}` : `-$${Math.abs(pnl).toFixed(2)}`;
|
|
2670
|
+
const reason = t.reasoning ? ` "${t.reasoning.slice(0, 60)}"` : "";
|
|
2671
|
+
lines.push(` ${time} ${tag} ${tokenIn}\u2192${tokenOut} ${value} ${pnlStr}${reason}`);
|
|
2672
|
+
}
|
|
2673
|
+
if (result.trades.length > 50) {
|
|
2674
|
+
lines.push(` ... and ${result.trades.length - 50} earlier trades`);
|
|
2675
|
+
}
|
|
2676
|
+
lines.push("");
|
|
2677
|
+
}
|
|
2590
2678
|
if (result.equityCurve.length >= 3) {
|
|
2591
2679
|
lines.push(` ${thinDivider}`);
|
|
2592
2680
|
lines.push(" EQUITY CURVE");
|
|
@@ -3920,7 +4008,7 @@ var STRATEGY_TEMPLATES = [
|
|
|
3920
4008
|
{
|
|
3921
4009
|
id: "momentum",
|
|
3922
4010
|
name: "Momentum Trader",
|
|
3923
|
-
description: "Follows price trends and momentum indicators. Buys assets with strong upward momentum.",
|
|
4011
|
+
description: "Follows price trends and momentum indicators. Buys assets with strong upward momentum, sells when momentum fades.",
|
|
3924
4012
|
riskLevel: "medium",
|
|
3925
4013
|
riskWarnings: [
|
|
3926
4014
|
"Momentum strategies can suffer significant losses during trend reversals",
|
|
@@ -3928,60 +4016,248 @@ var STRATEGY_TEMPLATES = [
|
|
|
3928
4016
|
"Past performance does not guarantee future results",
|
|
3929
4017
|
"This strategy may underperform in sideways markets"
|
|
3930
4018
|
],
|
|
3931
|
-
systemPrompt: `You are an AI trading analyst specializing in momentum trading
|
|
4019
|
+
systemPrompt: `You are an AI trading analyst specializing in momentum trading on Base network.
|
|
3932
4020
|
|
|
3933
|
-
|
|
4021
|
+
Analyze the provided market data and identify momentum-based trading opportunities.
|
|
3934
4022
|
|
|
3935
|
-
|
|
4023
|
+
RULES:
|
|
3936
4024
|
- Only recommend trades when there is clear momentum evidence
|
|
3937
|
-
-
|
|
3938
|
-
-
|
|
3939
|
-
- Be conservative with confidence
|
|
4025
|
+
- Use priceTrends (1h, 4h, 12h, 24h changes) as your primary momentum signal
|
|
4026
|
+
- Fall back to priceChange24h if priceTrends is not available
|
|
4027
|
+
- Be conservative with confidence \u2014 only use > 0.6 for strong signals
|
|
4028
|
+
- Never recommend buying a token you already hold significant amounts of
|
|
4029
|
+
- Always consider selling positions that have lost momentum
|
|
4030
|
+
- Return "hold" signals when uncertain \u2014 doing nothing is often the best trade
|
|
3940
4031
|
|
|
3941
|
-
|
|
3942
|
-
1.
|
|
3943
|
-
2.
|
|
3944
|
-
3.
|
|
4032
|
+
ANALYZE:
|
|
4033
|
+
1. Multi-timeframe momentum: use priceTrends (1h, 4h, 12h, 24h) to confirm trend direction
|
|
4034
|
+
2. Strong momentum = positive across all timeframes. Divergence (e.g., 1h negative, 12h positive) = weakening
|
|
4035
|
+
3. Volume confirmation: prefer tokens with > $10K daily volume
|
|
4036
|
+
4. Portfolio concentration: avoid putting > 20% in any single token
|
|
4037
|
+
5. Exit signals: sell when short-term trend (1h, 4h) turns negative on a held position
|
|
3945
4038
|
|
|
3946
|
-
Respond with JSON
|
|
4039
|
+
Respond with ONLY valid JSON (no markdown, no code fences):
|
|
3947
4040
|
{
|
|
3948
|
-
"analysis": "Brief market analysis",
|
|
4041
|
+
"analysis": "Brief market analysis (1-2 sentences)",
|
|
3949
4042
|
"signals": [
|
|
3950
4043
|
{
|
|
3951
4044
|
"action": "buy" | "sell" | "hold",
|
|
3952
|
-
"
|
|
3953
|
-
"
|
|
3954
|
-
"
|
|
3955
|
-
"
|
|
3956
|
-
"reasoning": "Why this trade"
|
|
4045
|
+
"token": "0x... (token address to buy/sell)",
|
|
4046
|
+
"percentage": 5-25,
|
|
4047
|
+
"confidence": 0.0-1.0,
|
|
4048
|
+
"reasoning": "Why this trade (1 sentence)"
|
|
3957
4049
|
}
|
|
3958
4050
|
]
|
|
3959
|
-
}
|
|
3960
|
-
|
|
4051
|
+
}
|
|
4052
|
+
|
|
4053
|
+
If no good opportunities exist, return: { "analysis": "No clear momentum signals", "signals": [] }`,
|
|
4054
|
+
exampleCode: `import type { StrategyFunction, MarketData, TradeSignal, LLMAdapter, AgentConfig, StrategyContext } from '@exagent/agent';
|
|
4055
|
+
|
|
4056
|
+
// Well-known token addresses on Base
|
|
4057
|
+
const WETH = '0x4200000000000000000000000000000000000006';
|
|
4058
|
+
const USDC = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913';
|
|
4059
|
+
|
|
4060
|
+
// Token decimals lookup (extend as needed)
|
|
4061
|
+
const DECIMALS: Record<string, number> = {
|
|
4062
|
+
[WETH]: 18,
|
|
4063
|
+
[USDC]: 6,
|
|
4064
|
+
};
|
|
4065
|
+
|
|
4066
|
+
/**
|
|
4067
|
+
* Format balances for LLM consumption (human-readable amounts)
|
|
4068
|
+
*/
|
|
4069
|
+
function formatBalances(balances: Record<string, bigint>, prices: Record<string, number>): Record<string, { amount: string; usdValue: string }> {
|
|
4070
|
+
const result: Record<string, { amount: string; usdValue: string }> = {};
|
|
4071
|
+
for (const [token, balance] of Object.entries(balances)) {
|
|
4072
|
+
if (balance === 0n) continue;
|
|
4073
|
+
const decimals = DECIMALS[token.toLowerCase()] ?? 18;
|
|
4074
|
+
const amount = Number(balance) / (10 ** decimals);
|
|
4075
|
+
const price = prices[token.toLowerCase()] ?? 0;
|
|
4076
|
+
result[token] = {
|
|
4077
|
+
amount: amount.toFixed(decimals <= 8 ? decimals : 6),
|
|
4078
|
+
usdValue: (amount * price).toFixed(2),
|
|
4079
|
+
};
|
|
4080
|
+
}
|
|
4081
|
+
return result;
|
|
4082
|
+
}
|
|
4083
|
+
|
|
4084
|
+
/**
|
|
4085
|
+
* Convert LLM percentage signal to a TradeSignal with bigint amountIn
|
|
4086
|
+
*
|
|
4087
|
+
* The LLM says "buy token X with 10% of my ETH" \u2014 this function converts
|
|
4088
|
+
* that percentage into the actual wei amount to trade.
|
|
4089
|
+
*/
|
|
4090
|
+
function toTradeSignal(
|
|
4091
|
+
signal: { action: string; token: string; percentage: number; confidence: number; reasoning?: string },
|
|
4092
|
+
balances: Record<string, bigint>,
|
|
4093
|
+
): TradeSignal | null {
|
|
4094
|
+
const { action, token, percentage, confidence, reasoning } = signal;
|
|
4095
|
+
|
|
4096
|
+
// Skip hold signals and low confidence
|
|
4097
|
+
if (action === 'hold' || confidence < 0.5) return null;
|
|
4098
|
+
|
|
4099
|
+
// Clamp percentage to 1-50 range for safety
|
|
4100
|
+
const pct = Math.max(1, Math.min(50, percentage));
|
|
4101
|
+
|
|
4102
|
+
if (action === 'buy') {
|
|
4103
|
+
// Buying: spend ETH (or USDC) to acquire the target token
|
|
4104
|
+
// Use ETH balance as the source
|
|
4105
|
+
const ethBalance = balances[WETH] ?? 0n;
|
|
4106
|
+
if (ethBalance === 0n) return null;
|
|
4107
|
+
const amountIn = (ethBalance * BigInt(Math.round(pct * 100))) / 10000n;
|
|
4108
|
+
if (amountIn === 0n) return null;
|
|
4109
|
+
return { action: 'buy', tokenIn: WETH, tokenOut: token, amountIn, confidence, reasoning };
|
|
4110
|
+
}
|
|
3961
4111
|
|
|
4112
|
+
if (action === 'sell') {
|
|
4113
|
+
// Selling: sell the target token for ETH
|
|
4114
|
+
const tokenBalance = balances[token.toLowerCase()] ?? balances[token] ?? 0n;
|
|
4115
|
+
if (tokenBalance === 0n) return null;
|
|
4116
|
+
const amountIn = (tokenBalance * BigInt(Math.round(pct * 100))) / 10000n;
|
|
4117
|
+
if (amountIn === 0n) return null;
|
|
4118
|
+
return { action: 'sell', tokenIn: token, tokenOut: WETH, amountIn, confidence, reasoning };
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4121
|
+
return null;
|
|
4122
|
+
}
|
|
4123
|
+
|
|
4124
|
+
/**
|
|
4125
|
+
* Momentum Trading Strategy
|
|
4126
|
+
*
|
|
4127
|
+
* Sends market data to the LLM, parses the response, and converts
|
|
4128
|
+
* percentage-based signals into executable TradeSignal objects.
|
|
4129
|
+
*/
|
|
3962
4130
|
export const generateSignals: StrategyFunction = async (
|
|
3963
4131
|
marketData: MarketData,
|
|
3964
4132
|
llm: LLMAdapter,
|
|
3965
|
-
config: AgentConfig
|
|
4133
|
+
config: AgentConfig,
|
|
4134
|
+
context?: StrategyContext,
|
|
3966
4135
|
): Promise<TradeSignal[]> => {
|
|
4136
|
+
// Summarize price history into compact trend data for the LLM
|
|
4137
|
+
// Instead of sending 24 raw candles per token, compute useful signals:
|
|
4138
|
+
// - 1h, 4h, 12h, 24h price changes (momentum at different timeframes)
|
|
4139
|
+
const priceTrends: Record<string, { '1h': string; '4h': string; '12h': string; '24h': string }> = {};
|
|
4140
|
+
if (marketData.priceHistory) {
|
|
4141
|
+
for (const [addr, candles] of Object.entries(marketData.priceHistory)) {
|
|
4142
|
+
if (candles.length < 2) continue;
|
|
4143
|
+
const latest = candles[candles.length - 1].price;
|
|
4144
|
+
const getChangeAt = (hoursAgo: number): string => {
|
|
4145
|
+
const target = Date.now() - hoursAgo * 3600_000;
|
|
4146
|
+
// Find the candle closest to the target time
|
|
4147
|
+
let closest = candles[0];
|
|
4148
|
+
for (const c of candles) {
|
|
4149
|
+
if (Math.abs(c.timestamp - target) < Math.abs(closest.timestamp - target)) closest = c;
|
|
4150
|
+
}
|
|
4151
|
+
if (closest.price === 0) return '0.0%';
|
|
4152
|
+
const pct = ((latest - closest.price) / closest.price) * 100;
|
|
4153
|
+
return (pct >= 0 ? '+' : '') + pct.toFixed(1) + '%';
|
|
4154
|
+
};
|
|
4155
|
+
priceTrends[addr] = {
|
|
4156
|
+
'1h': getChangeAt(1),
|
|
4157
|
+
'4h': getChangeAt(4),
|
|
4158
|
+
'12h': getChangeAt(12),
|
|
4159
|
+
'24h': getChangeAt(24),
|
|
4160
|
+
};
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
|
|
4164
|
+
// Build the data payload for the LLM
|
|
4165
|
+
const payload: Record<string, unknown> = {
|
|
4166
|
+
prices: marketData.prices,
|
|
4167
|
+
balances: formatBalances(marketData.balances, marketData.prices),
|
|
4168
|
+
portfolioValueUSD: marketData.portfolioValue.toFixed(2),
|
|
4169
|
+
priceChange24h: marketData.priceChange24h ?? {},
|
|
4170
|
+
volume24h: marketData.volume24h ?? {},
|
|
4171
|
+
recentTrades: (context?.tradeHistory ?? []).slice(0, 5).map(t => ({
|
|
4172
|
+
action: t.action,
|
|
4173
|
+
token: t.action === 'buy' ? t.tokenOut : t.tokenIn,
|
|
4174
|
+
reasoning: t.reasoning,
|
|
4175
|
+
timestamp: new Date(t.timestamp).toISOString(),
|
|
4176
|
+
})),
|
|
4177
|
+
};
|
|
4178
|
+
|
|
4179
|
+
// Include price trends if available (more useful than raw candles for LLM)
|
|
4180
|
+
if (Object.keys(priceTrends).length > 0) {
|
|
4181
|
+
payload.priceTrends = priceTrends;
|
|
4182
|
+
}
|
|
4183
|
+
|
|
4184
|
+
// Call the LLM
|
|
3967
4185
|
const response = await llm.chat([
|
|
3968
|
-
{ role: 'system', content: MOMENTUM_SYSTEM_PROMPT },
|
|
3969
|
-
{ role: 'user', content: JSON.stringify(
|
|
3970
|
-
prices: marketData.prices,
|
|
3971
|
-
balances: formatBalances(marketData.balances),
|
|
3972
|
-
portfolioValue: marketData.portfolioValue,
|
|
3973
|
-
})}
|
|
4186
|
+
{ role: 'system', content: config.strategyPrompt ?? MOMENTUM_SYSTEM_PROMPT },
|
|
4187
|
+
{ role: 'user', content: JSON.stringify(payload) },
|
|
3974
4188
|
]);
|
|
3975
4189
|
|
|
3976
|
-
// Parse
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
4190
|
+
// Parse the response \u2014 handle markdown code fences if the LLM wraps its output
|
|
4191
|
+
let content = response.content.trim();
|
|
4192
|
+
if (content.startsWith('\`\`\`')) {
|
|
4193
|
+
content = content.replace(/^\`\`\`(?:json)?\\n?/, '').replace(/\\n?\`\`\`$/, '').trim();
|
|
4194
|
+
}
|
|
4195
|
+
|
|
4196
|
+
let parsed: { signals?: Array<{ action: string; token: string; percentage: number; confidence: number; reasoning?: string }> };
|
|
4197
|
+
try {
|
|
4198
|
+
parsed = JSON.parse(content);
|
|
4199
|
+
} catch (e) {
|
|
4200
|
+
console.error('[strategy] Failed to parse LLM response:', (e as Error).message);
|
|
4201
|
+
console.error('[strategy] Raw response:', content.slice(0, 200));
|
|
4202
|
+
return []; // Safe fallback: no trades
|
|
4203
|
+
}
|
|
4204
|
+
|
|
4205
|
+
if (!parsed.signals || !Array.isArray(parsed.signals)) {
|
|
4206
|
+
return [];
|
|
4207
|
+
}
|
|
4208
|
+
|
|
4209
|
+
// Convert each signal to a TradeSignal with proper bigint amountIn
|
|
4210
|
+
const signals: TradeSignal[] = [];
|
|
4211
|
+
for (const raw of parsed.signals) {
|
|
4212
|
+
const signal = toTradeSignal(raw, marketData.balances);
|
|
4213
|
+
if (signal) signals.push(signal);
|
|
4214
|
+
}
|
|
4215
|
+
|
|
4216
|
+
return signals;
|
|
4217
|
+
};
|
|
4218
|
+
|
|
4219
|
+
// Default system prompt \u2014 used when config.strategyPrompt is not set
|
|
4220
|
+
const MOMENTUM_SYSTEM_PROMPT = \`You are an AI trading analyst specializing in momentum trading on Base network.
|
|
4221
|
+
|
|
4222
|
+
Analyze the provided market data and identify momentum-based trading opportunities.
|
|
4223
|
+
|
|
4224
|
+
RULES:
|
|
4225
|
+
- Only recommend trades when there is clear momentum evidence
|
|
4226
|
+
- Use priceTrends (1h, 4h, 12h, 24h changes) as your primary momentum signal
|
|
4227
|
+
- Fall back to priceChange24h if priceTrends is not available
|
|
4228
|
+
- Be conservative with confidence \u2014 only use > 0.6 for strong signals
|
|
4229
|
+
- Never recommend buying a token you already hold significant amounts of
|
|
4230
|
+
- Always consider selling positions that have lost momentum
|
|
4231
|
+
- Return "hold" signals when uncertain \u2014 doing nothing is often the best trade
|
|
4232
|
+
|
|
4233
|
+
ANALYZE:
|
|
4234
|
+
1. Multi-timeframe momentum: use priceTrends (1h, 4h, 12h, 24h) to confirm trend direction
|
|
4235
|
+
2. Strong momentum = positive across all timeframes. Divergence (e.g., 1h negative, 12h positive) = weakening
|
|
4236
|
+
3. Volume confirmation: prefer tokens with > $10K daily volume
|
|
4237
|
+
4. Portfolio concentration: avoid putting > 20% in any single token
|
|
4238
|
+
5. Exit signals: sell when short-term trend (1h, 4h) turns negative on a held position
|
|
4239
|
+
|
|
4240
|
+
Respond with ONLY valid JSON (no markdown, no code fences):
|
|
4241
|
+
{
|
|
4242
|
+
"analysis": "Brief market analysis (1-2 sentences)",
|
|
4243
|
+
"signals": [
|
|
4244
|
+
{
|
|
4245
|
+
"action": "buy" | "sell" | "hold",
|
|
4246
|
+
"token": "0x... (token address to buy/sell)",
|
|
4247
|
+
"percentage": 5-25,
|
|
4248
|
+
"confidence": 0.0-1.0,
|
|
4249
|
+
"reasoning": "Why this trade (1 sentence)"
|
|
4250
|
+
}
|
|
4251
|
+
]
|
|
4252
|
+
}
|
|
4253
|
+
|
|
4254
|
+
If no good opportunities exist, return: { "analysis": "No clear momentum signals", "signals": [] }\`;
|
|
4255
|
+
`
|
|
3980
4256
|
},
|
|
3981
4257
|
{
|
|
3982
4258
|
id: "value",
|
|
3983
4259
|
name: "Value Investor",
|
|
3984
|
-
description: "Looks for undervalued assets based on fundamentals. Takes long-term positions.",
|
|
4260
|
+
description: "Looks for undervalued assets based on fundamentals. Takes long-term positions with lower turnover.",
|
|
3985
4261
|
riskLevel: "low",
|
|
3986
4262
|
riskWarnings: [
|
|
3987
4263
|
"Value traps can result in prolonged losses",
|
|
@@ -3989,47 +4265,135 @@ export const generateSignals: StrategyFunction = async (
|
|
|
3989
4265
|
"Fundamental analysis may not apply well to all crypto assets",
|
|
3990
4266
|
"Market sentiment can override fundamentals for long periods"
|
|
3991
4267
|
],
|
|
3992
|
-
systemPrompt: `You are an AI trading analyst specializing in value investing.
|
|
4268
|
+
systemPrompt: `You are an AI trading analyst specializing in value investing on Base network.
|
|
3993
4269
|
|
|
3994
4270
|
Your role is to identify undervalued assets with strong fundamentals.
|
|
3995
4271
|
|
|
3996
|
-
|
|
4272
|
+
RULES:
|
|
3997
4273
|
- Focus on long-term value, not short-term price movements
|
|
3998
4274
|
- Only recommend assets with clear value propositions
|
|
3999
4275
|
- Consider protocol revenue, TVL, active users, developer activity
|
|
4000
|
-
- Be very selective
|
|
4276
|
+
- Be very selective \u2014 quality over quantity
|
|
4277
|
+
- Prefer established tokens (AERO, WELL, MORPHO, COMP, CRV) over memecoins
|
|
4278
|
+
- Hold positions for days/weeks, not hours
|
|
4001
4279
|
|
|
4002
|
-
|
|
4003
|
-
1. Protocol fundamentals (revenue, TVL, user growth)
|
|
4004
|
-
2. Token economics (supply schedule, utility)
|
|
4005
|
-
3. Competitive positioning
|
|
4006
|
-
4. Valuation relative to peers
|
|
4007
|
-
|
|
4008
|
-
Respond with JSON in this format:
|
|
4280
|
+
Respond with ONLY valid JSON:
|
|
4009
4281
|
{
|
|
4010
4282
|
"analysis": "Brief fundamental analysis",
|
|
4011
4283
|
"signals": [
|
|
4012
4284
|
{
|
|
4013
4285
|
"action": "buy" | "sell" | "hold",
|
|
4014
|
-
"
|
|
4015
|
-
"
|
|
4016
|
-
"
|
|
4017
|
-
"confidence": 0-1,
|
|
4286
|
+
"token": "0x... (token address)",
|
|
4287
|
+
"percentage": 5-20,
|
|
4288
|
+
"confidence": 0.0-1.0,
|
|
4018
4289
|
"reasoning": "Fundamental thesis"
|
|
4019
4290
|
}
|
|
4020
4291
|
]
|
|
4021
4292
|
}`,
|
|
4022
|
-
exampleCode: `import { StrategyFunction } from '@exagent/agent';
|
|
4293
|
+
exampleCode: `import type { StrategyFunction, MarketData, TradeSignal, LLMAdapter, AgentConfig, StrategyContext } from '@exagent/agent';
|
|
4294
|
+
|
|
4295
|
+
const WETH = '0x4200000000000000000000000000000000000006';
|
|
4296
|
+
const USDC = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913';
|
|
4297
|
+
const DECIMALS: Record<string, number> = { [WETH]: 18, [USDC]: 6 };
|
|
4298
|
+
|
|
4299
|
+
function formatBalances(balances: Record<string, bigint>, prices: Record<string, number>): Record<string, { amount: string; usdValue: string }> {
|
|
4300
|
+
const result: Record<string, { amount: string; usdValue: string }> = {};
|
|
4301
|
+
for (const [token, balance] of Object.entries(balances)) {
|
|
4302
|
+
if (balance === 0n) continue;
|
|
4303
|
+
const decimals = DECIMALS[token.toLowerCase()] ?? 18;
|
|
4304
|
+
const amount = Number(balance) / (10 ** decimals);
|
|
4305
|
+
const price = prices[token.toLowerCase()] ?? 0;
|
|
4306
|
+
result[token] = { amount: amount.toFixed(6), usdValue: (amount * price).toFixed(2) };
|
|
4307
|
+
}
|
|
4308
|
+
return result;
|
|
4309
|
+
}
|
|
4310
|
+
|
|
4311
|
+
function toTradeSignal(
|
|
4312
|
+
signal: { action: string; token: string; percentage: number; confidence: number; reasoning?: string },
|
|
4313
|
+
balances: Record<string, bigint>,
|
|
4314
|
+
): TradeSignal | null {
|
|
4315
|
+
if (signal.action === 'hold' || signal.confidence < 0.5) return null;
|
|
4316
|
+
const pct = Math.max(1, Math.min(50, signal.percentage));
|
|
4317
|
+
if (signal.action === 'buy') {
|
|
4318
|
+
const ethBalance = balances[WETH] ?? 0n;
|
|
4319
|
+
if (ethBalance === 0n) return null;
|
|
4320
|
+
const amountIn = (ethBalance * BigInt(Math.round(pct * 100))) / 10000n;
|
|
4321
|
+
if (amountIn === 0n) return null;
|
|
4322
|
+
return { action: 'buy', tokenIn: WETH, tokenOut: signal.token, amountIn, confidence: signal.confidence, reasoning: signal.reasoning };
|
|
4323
|
+
}
|
|
4324
|
+
if (signal.action === 'sell') {
|
|
4325
|
+
const tokenBalance = balances[signal.token.toLowerCase()] ?? balances[signal.token] ?? 0n;
|
|
4326
|
+
if (tokenBalance === 0n) return null;
|
|
4327
|
+
const amountIn = (tokenBalance * BigInt(Math.round(pct * 100))) / 10000n;
|
|
4328
|
+
if (amountIn === 0n) return null;
|
|
4329
|
+
return { action: 'sell', tokenIn: signal.token, tokenOut: WETH, amountIn, confidence: signal.confidence, reasoning: signal.reasoning };
|
|
4330
|
+
}
|
|
4331
|
+
return null;
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4334
|
+
export const generateSignals: StrategyFunction = async (
|
|
4335
|
+
marketData: MarketData,
|
|
4336
|
+
llm: LLMAdapter,
|
|
4337
|
+
config: AgentConfig,
|
|
4338
|
+
context?: StrategyContext,
|
|
4339
|
+
): Promise<TradeSignal[]> => {
|
|
4340
|
+
const payload = {
|
|
4341
|
+
prices: marketData.prices,
|
|
4342
|
+
balances: formatBalances(marketData.balances, marketData.prices),
|
|
4343
|
+
portfolioValueUSD: marketData.portfolioValue.toFixed(2),
|
|
4344
|
+
priceChange24h: marketData.priceChange24h ?? {},
|
|
4345
|
+
positions: (context?.positions ?? []).map(p => ({
|
|
4346
|
+
token: p.tokenAddress,
|
|
4347
|
+
entryPrice: p.averageEntryPrice,
|
|
4348
|
+
currentAmount: p.currentAmount.toString(),
|
|
4349
|
+
})),
|
|
4350
|
+
};
|
|
4023
4351
|
|
|
4024
|
-
export const generateSignals: StrategyFunction = async (marketData, llm, config) => {
|
|
4025
|
-
// Value strategy runs less frequently
|
|
4026
4352
|
const response = await llm.chat([
|
|
4027
|
-
{ role: 'system', content: VALUE_SYSTEM_PROMPT },
|
|
4028
|
-
{ role: 'user', content: JSON.stringify(
|
|
4353
|
+
{ role: 'system', content: config.strategyPrompt ?? VALUE_SYSTEM_PROMPT },
|
|
4354
|
+
{ role: 'user', content: JSON.stringify(payload) },
|
|
4029
4355
|
]);
|
|
4030
4356
|
|
|
4031
|
-
|
|
4032
|
-
|
|
4357
|
+
let content = response.content.trim();
|
|
4358
|
+
if (content.startsWith('\\\`\\\`\\\`')) {
|
|
4359
|
+
content = content.replace(/^\\\`\\\`\\\`(?:json)?\\n?/, '').replace(/\\n?\\\`\\\`\\\`$/, '').trim();
|
|
4360
|
+
}
|
|
4361
|
+
|
|
4362
|
+
try {
|
|
4363
|
+
const parsed = JSON.parse(content);
|
|
4364
|
+
if (!parsed.signals || !Array.isArray(parsed.signals)) return [];
|
|
4365
|
+
return parsed.signals.map((s: any) => toTradeSignal(s, marketData.balances)).filter(Boolean) as TradeSignal[];
|
|
4366
|
+
} catch (e) {
|
|
4367
|
+
console.error('[strategy] Failed to parse LLM response:', (e as Error).message);
|
|
4368
|
+
return [];
|
|
4369
|
+
}
|
|
4370
|
+
};
|
|
4371
|
+
|
|
4372
|
+
const VALUE_SYSTEM_PROMPT = \`You are an AI trading analyst specializing in value investing on Base network.
|
|
4373
|
+
|
|
4374
|
+
Your role is to identify undervalued assets with strong fundamentals.
|
|
4375
|
+
|
|
4376
|
+
RULES:
|
|
4377
|
+
- Focus on long-term value, not short-term price movements
|
|
4378
|
+
- Only recommend assets with clear value propositions
|
|
4379
|
+
- Be very selective \u2014 quality over quantity
|
|
4380
|
+
- Prefer established tokens over memecoins
|
|
4381
|
+
- Hold positions for days/weeks, not hours
|
|
4382
|
+
|
|
4383
|
+
Respond with ONLY valid JSON:
|
|
4384
|
+
{
|
|
4385
|
+
"analysis": "Brief fundamental analysis",
|
|
4386
|
+
"signals": [
|
|
4387
|
+
{
|
|
4388
|
+
"action": "buy" | "sell" | "hold",
|
|
4389
|
+
"token": "0x... (token address)",
|
|
4390
|
+
"percentage": 5-20,
|
|
4391
|
+
"confidence": 0.0-1.0,
|
|
4392
|
+
"reasoning": "Fundamental thesis"
|
|
4393
|
+
}
|
|
4394
|
+
]
|
|
4395
|
+
}\`;
|
|
4396
|
+
`
|
|
4033
4397
|
},
|
|
4034
4398
|
{
|
|
4035
4399
|
id: "arbitrage",
|
|
@@ -4069,7 +4433,7 @@ Respond with JSON in this format:
|
|
|
4069
4433
|
exampleCode: `// Note: Pure arbitrage requires specialized infrastructure
|
|
4070
4434
|
// This template is for educational purposes
|
|
4071
4435
|
|
|
4072
|
-
import { StrategyFunction } from '@exagent/agent';
|
|
4436
|
+
import type { StrategyFunction } from '@exagent/agent';
|
|
4073
4437
|
|
|
4074
4438
|
export const generateSignals: StrategyFunction = async (marketData, llm, config) => {
|
|
4075
4439
|
// Arbitrage requires real-time price feeds from multiple sources
|
|
@@ -4101,18 +4465,21 @@ Respond with JSON:
|
|
|
4101
4465
|
{
|
|
4102
4466
|
"signals": []
|
|
4103
4467
|
}`,
|
|
4104
|
-
exampleCode: `import { StrategyFunction, MarketData, TradeSignal, LLMAdapter, AgentConfig } from '@exagent/agent';
|
|
4468
|
+
exampleCode: `import type { StrategyFunction, MarketData, TradeSignal, LLMAdapter, AgentConfig, StrategyContext } from '@exagent/agent';
|
|
4469
|
+
|
|
4470
|
+
const WETH = '0x4200000000000000000000000000000000000006';
|
|
4105
4471
|
|
|
4106
4472
|
/**
|
|
4107
4473
|
* Custom Strategy Template
|
|
4108
4474
|
*
|
|
4109
4475
|
* Customize this file with your own trading logic and prompts.
|
|
4110
|
-
* Your prompts are YOUR intellectual property
|
|
4476
|
+
* Your prompts are YOUR intellectual property \u2014 we don't store them.
|
|
4111
4477
|
*/
|
|
4112
4478
|
export const generateSignals: StrategyFunction = async (
|
|
4113
4479
|
marketData: MarketData,
|
|
4114
4480
|
llm: LLMAdapter,
|
|
4115
|
-
config: AgentConfig
|
|
4481
|
+
config: AgentConfig,
|
|
4482
|
+
context?: StrategyContext,
|
|
4116
4483
|
): Promise<TradeSignal[]> => {
|
|
4117
4484
|
// Your custom system prompt (this is your secret sauce)
|
|
4118
4485
|
const systemPrompt = \`
|
|
@@ -4122,13 +4489,26 @@ export const generateSignals: StrategyFunction = async (
|
|
|
4122
4489
|
// Call the LLM with your prompt
|
|
4123
4490
|
const response = await llm.chat([
|
|
4124
4491
|
{ role: 'system', content: systemPrompt },
|
|
4125
|
-
{ role: 'user', content: JSON.stringify(
|
|
4492
|
+
{ role: 'user', content: JSON.stringify({
|
|
4493
|
+
prices: marketData.prices,
|
|
4494
|
+
balances: Object.fromEntries(
|
|
4495
|
+
Object.entries(marketData.balances)
|
|
4496
|
+
.filter(([, v]) => v > 0n)
|
|
4497
|
+
.map(([k, v]) => [k, v.toString()])
|
|
4498
|
+
),
|
|
4499
|
+
portfolioValue: marketData.portfolioValue,
|
|
4500
|
+
})},
|
|
4126
4501
|
]);
|
|
4127
4502
|
|
|
4128
4503
|
// Parse and return signals
|
|
4129
4504
|
// IMPORTANT: Validate LLM output before using
|
|
4505
|
+
let content = response.content.trim();
|
|
4506
|
+
if (content.startsWith('\\\`\\\`\\\`')) {
|
|
4507
|
+
content = content.replace(/^\\\`\\\`\\\`(?:json)?\\n?/, '').replace(/\\n?\\\`\\\`\\\`$/, '').trim();
|
|
4508
|
+
}
|
|
4509
|
+
|
|
4130
4510
|
try {
|
|
4131
|
-
const parsed = JSON.parse(
|
|
4511
|
+
const parsed = JSON.parse(content);
|
|
4132
4512
|
return parsed.signals || [];
|
|
4133
4513
|
} catch (e) {
|
|
4134
4514
|
console.error('Failed to parse LLM response:', e);
|
|
@@ -4137,6 +4517,9 @@ export const generateSignals: StrategyFunction = async (
|
|
|
4137
4517
|
};`
|
|
4138
4518
|
}
|
|
4139
4519
|
];
|
|
4520
|
+
function getStrategyTemplate(id) {
|
|
4521
|
+
return STRATEGY_TEMPLATES.find((t) => t.id === id);
|
|
4522
|
+
}
|
|
4140
4523
|
function getAllStrategyTemplates() {
|
|
4141
4524
|
return STRATEGY_TEMPLATES;
|
|
4142
4525
|
}
|
|
@@ -4284,6 +4667,8 @@ var AgentConfigSchema = import_zod.z.object({
|
|
|
4284
4667
|
perp: PerpConfigSchema,
|
|
4285
4668
|
// Prediction market configuration (Polymarket)
|
|
4286
4669
|
prediction: PredictionConfigSchema,
|
|
4670
|
+
// Custom strategy system prompt (overrides built-in template prompt)
|
|
4671
|
+
strategyPrompt: import_zod.z.string().optional(),
|
|
4287
4672
|
// Allowed tokens (addresses)
|
|
4288
4673
|
allowedTokens: import_zod.z.array(import_zod.z.string()).optional()
|
|
4289
4674
|
});
|
|
@@ -8071,7 +8456,7 @@ function loadSecureEnv(basePath, passphrase) {
|
|
|
8071
8456
|
}
|
|
8072
8457
|
|
|
8073
8458
|
// src/index.ts
|
|
8074
|
-
var AGENT_VERSION = "0.1.
|
|
8459
|
+
var AGENT_VERSION = "0.1.48";
|
|
8075
8460
|
|
|
8076
8461
|
// src/relay.ts
|
|
8077
8462
|
var RelayClient = class {
|
|
@@ -10788,6 +11173,95 @@ ${llmEnvVar}EXAGENT_LLM_MODEL=${config.llm?.model || ""}
|
|
|
10788
11173
|
(0, import_dotenv2.config)({ path: envPath, override: true });
|
|
10789
11174
|
}
|
|
10790
11175
|
}
|
|
11176
|
+
program.command("init").description("Scaffold a new agent project with strategy template and config").option("-t, --template <template>", "Strategy template (momentum, value, arbitrage, custom)", "momentum").option("--force", "Overwrite existing files without prompting").action(async (options) => {
|
|
11177
|
+
try {
|
|
11178
|
+
const templateId = options.template;
|
|
11179
|
+
const template = getStrategyTemplate(templateId);
|
|
11180
|
+
if (!template) {
|
|
11181
|
+
const templates = getAllStrategyTemplates();
|
|
11182
|
+
console.error(`Unknown template: ${templateId}`);
|
|
11183
|
+
console.error(`Available templates: ${templates.map((t) => t.id).join(", ")}`);
|
|
11184
|
+
process.exit(1);
|
|
11185
|
+
}
|
|
11186
|
+
console.log("");
|
|
11187
|
+
console.log("=".repeat(50));
|
|
11188
|
+
console.log(" EXAGENT INIT");
|
|
11189
|
+
console.log("=".repeat(50));
|
|
11190
|
+
console.log("");
|
|
11191
|
+
console.log(` Template: ${template.name}`);
|
|
11192
|
+
console.log(` Risk: ${template.riskLevel}`);
|
|
11193
|
+
console.log("");
|
|
11194
|
+
const cwd = process.cwd();
|
|
11195
|
+
const strategyPath = path7.join(cwd, "strategy.ts");
|
|
11196
|
+
const configPath = path7.join(cwd, "agent-config.json");
|
|
11197
|
+
const existingFiles = [];
|
|
11198
|
+
if (fs6.existsSync(strategyPath)) existingFiles.push("strategy.ts");
|
|
11199
|
+
if (fs6.existsSync(configPath)) existingFiles.push("agent-config.json");
|
|
11200
|
+
if (existingFiles.length > 0 && !options.force) {
|
|
11201
|
+
console.log(` Files already exist: ${existingFiles.join(", ")}`);
|
|
11202
|
+
const answer = await prompt(" Overwrite? (y/n): ");
|
|
11203
|
+
if (answer.toLowerCase() !== "y") {
|
|
11204
|
+
console.log(" Aborted.");
|
|
11205
|
+
process.exit(0);
|
|
11206
|
+
}
|
|
11207
|
+
console.log("");
|
|
11208
|
+
}
|
|
11209
|
+
fs6.writeFileSync(strategyPath, template.exampleCode.trim() + "\n");
|
|
11210
|
+
console.log(" Created: strategy.ts");
|
|
11211
|
+
const defaultConfig = {
|
|
11212
|
+
agentId: 0,
|
|
11213
|
+
name: "my-agent",
|
|
11214
|
+
network: "mainnet",
|
|
11215
|
+
llm: {
|
|
11216
|
+
provider: "openai",
|
|
11217
|
+
model: "gpt-4o",
|
|
11218
|
+
temperature: 0.7,
|
|
11219
|
+
maxTokens: 4096
|
|
11220
|
+
},
|
|
11221
|
+
riskUniverse: "established",
|
|
11222
|
+
trading: {
|
|
11223
|
+
timeHorizon: "swing",
|
|
11224
|
+
maxPositionSizeBps: 1e3,
|
|
11225
|
+
maxDailyLossBps: 500,
|
|
11226
|
+
maxConcurrentPositions: 5,
|
|
11227
|
+
tradingIntervalMs: 6e4,
|
|
11228
|
+
maxSlippageBps: 100,
|
|
11229
|
+
minTradeValueUSD: 1
|
|
11230
|
+
},
|
|
11231
|
+
wallet: {
|
|
11232
|
+
setup: "generate"
|
|
11233
|
+
},
|
|
11234
|
+
relay: {
|
|
11235
|
+
enabled: true,
|
|
11236
|
+
apiUrl: "https://exagent-api.onrender.com"
|
|
11237
|
+
}
|
|
11238
|
+
};
|
|
11239
|
+
if (!fs6.existsSync(configPath) || options.force || existingFiles.includes("agent-config.json")) {
|
|
11240
|
+
fs6.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + "\n");
|
|
11241
|
+
console.log(" Created: agent-config.json");
|
|
11242
|
+
}
|
|
11243
|
+
console.log("");
|
|
11244
|
+
console.log("=".repeat(50));
|
|
11245
|
+
console.log(" NEXT STEPS");
|
|
11246
|
+
console.log("=".repeat(50));
|
|
11247
|
+
console.log("");
|
|
11248
|
+
console.log(" 1. Register your agent at https://exagent.io/deploy");
|
|
11249
|
+
console.log(" 2. Copy your agent ID into agent-config.json");
|
|
11250
|
+
console.log(" 3. Update agent-config.json with your name and LLM provider");
|
|
11251
|
+
console.log(" 4. Run: npx @exagent/agent paper");
|
|
11252
|
+
console.log(" (paper trade first to validate your strategy)");
|
|
11253
|
+
console.log("");
|
|
11254
|
+
console.log(" 5. When ready for real trading:");
|
|
11255
|
+
console.log(" npx @exagent/agent run");
|
|
11256
|
+
console.log("");
|
|
11257
|
+
console.log("=".repeat(50));
|
|
11258
|
+
console.log("");
|
|
11259
|
+
process.exit(0);
|
|
11260
|
+
} catch (error) {
|
|
11261
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
11262
|
+
process.exit(1);
|
|
11263
|
+
}
|
|
11264
|
+
});
|
|
10791
11265
|
program.command("run").description("Start the trading agent").option("-c, --config <path>", "Path to agent-config.json", "agent-config.json").option("-p, --passphrase <passphrase>", "Passphrase to decrypt .env.enc").action(async (options) => {
|
|
10792
11266
|
try {
|
|
10793
11267
|
await checkFirstRunSetup(options.config);
|