@exagent/agent 0.1.37 → 0.1.39
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-27O4UUAA.mjs +5891 -0
- package/dist/chunk-2RZBOQG3.mjs +5891 -0
- package/dist/chunk-3AJR5TQF.mjs +6499 -0
- package/dist/chunk-6Y4PBQ2U.mjs +6806 -0
- package/dist/chunk-CAGJYKJQ.mjs +5885 -0
- package/dist/chunk-EPXZ6MEW.mjs +6959 -0
- package/dist/chunk-KQUNPLRE.mjs +6959 -0
- package/dist/chunk-LDTWZMEI.mjs +6818 -0
- package/dist/chunk-Y6U3JW2V.mjs +5867 -0
- package/dist/cli.js +4125 -2029
- package/dist/cli.mjs +1086 -21
- package/dist/index.d.mts +46 -11
- package/dist/index.d.ts +46 -11
- package/dist/index.js +928 -105
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1635,7 +1635,7 @@ async function loadStrategy(strategyPath) {
|
|
|
1635
1635
|
console.log("No custom strategy found, using default (hold) strategy");
|
|
1636
1636
|
return defaultStrategy;
|
|
1637
1637
|
}
|
|
1638
|
-
async function loadTypeScriptModule(
|
|
1638
|
+
async function loadTypeScriptModule(path4) {
|
|
1639
1639
|
try {
|
|
1640
1640
|
const tsxPath = require.resolve("tsx");
|
|
1641
1641
|
const { pathToFileURL } = await import("url");
|
|
@@ -1646,7 +1646,7 @@ async function loadTypeScriptModule(path2) {
|
|
|
1646
1646
|
"--import",
|
|
1647
1647
|
"tsx/esm",
|
|
1648
1648
|
"-e",
|
|
1649
|
-
`import('${pathToFileURL(
|
|
1649
|
+
`import('${pathToFileURL(path4).href}').then(m => console.log(JSON.stringify({ exports: Object.keys(m) }))).catch(e => console.error('ERROR:', e.message))`
|
|
1650
1650
|
],
|
|
1651
1651
|
{
|
|
1652
1652
|
cwd: process.cwd(),
|
|
@@ -1669,7 +1669,7 @@ async function loadTypeScriptModule(path2) {
|
|
|
1669
1669
|
const tsx = await import("tsx/esm/api");
|
|
1670
1670
|
const unregister = tsx.register();
|
|
1671
1671
|
try {
|
|
1672
|
-
const module2 = await import(
|
|
1672
|
+
const module2 = await import(path4);
|
|
1673
1673
|
return module2;
|
|
1674
1674
|
} finally {
|
|
1675
1675
|
unregister();
|
|
@@ -2248,6 +2248,24 @@ function classifyTradeError(message) {
|
|
|
2248
2248
|
userMessage: "DEX aggregator not whitelisted on the router. Contact support."
|
|
2249
2249
|
};
|
|
2250
2250
|
}
|
|
2251
|
+
if (lower.includes("token approval reverted")) {
|
|
2252
|
+
return {
|
|
2253
|
+
category: "approval_failed",
|
|
2254
|
+
userMessage: "Token approval failed \u2014 this token may have non-standard approval behavior or be blocked. Skipping."
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
if (lower.includes("exceeds allowance") || lower.includes("allowance")) {
|
|
2258
|
+
return {
|
|
2259
|
+
category: "allowance",
|
|
2260
|
+
userMessage: "Token allowance insufficient \u2014 approval may not have propagated. Will retry next cycle."
|
|
2261
|
+
};
|
|
2262
|
+
}
|
|
2263
|
+
if (lower.includes("swapfailed")) {
|
|
2264
|
+
return {
|
|
2265
|
+
category: "swap_failed",
|
|
2266
|
+
userMessage: "DEX swap failed \u2014 likely insufficient liquidity for this token. Will retry next cycle."
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2251
2269
|
if (lower.includes("reverted") || lower.includes("execution reverted")) {
|
|
2252
2270
|
return {
|
|
2253
2271
|
category: "reverted",
|
|
@@ -2274,8 +2292,11 @@ var RiskManager = class {
|
|
|
2274
2292
|
lastResetDate = "";
|
|
2275
2293
|
/** Minimum trade value in USD — trades below this are rejected as dust */
|
|
2276
2294
|
minTradeValueUSD;
|
|
2277
|
-
|
|
2295
|
+
/** Risk universe (0-4). Frontier (4) bypasses buy-only guardrails. */
|
|
2296
|
+
riskUniverse;
|
|
2297
|
+
constructor(config, riskUniverse = 0) {
|
|
2278
2298
|
this.config = config;
|
|
2299
|
+
this.riskUniverse = riskUniverse;
|
|
2279
2300
|
this.minTradeValueUSD = config.minTradeValueUSD ?? 1;
|
|
2280
2301
|
}
|
|
2281
2302
|
/**
|
|
@@ -2311,6 +2332,9 @@ var RiskManager = class {
|
|
|
2311
2332
|
if (signal.action === "sell") {
|
|
2312
2333
|
return true;
|
|
2313
2334
|
}
|
|
2335
|
+
if (this.riskUniverse === 4) {
|
|
2336
|
+
return true;
|
|
2337
|
+
}
|
|
2314
2338
|
const signalValue = this.estimateSignalValue(signal, marketData);
|
|
2315
2339
|
const maxPositionValue = marketData.portfolioValue * this.config.maxPositionSizeBps / 1e4;
|
|
2316
2340
|
if (signalValue > maxPositionValue) {
|
|
@@ -2692,7 +2716,7 @@ var VaultManager = class {
|
|
|
2692
2716
|
canCreateVault: false,
|
|
2693
2717
|
cannotCreateReason: "Vault operations disabled (contract address not set)",
|
|
2694
2718
|
requirementsMet: false,
|
|
2695
|
-
requirements: {
|
|
2719
|
+
requirements: { isBypassed: false }
|
|
2696
2720
|
};
|
|
2697
2721
|
}
|
|
2698
2722
|
const vaultAddress = await this.getVaultAddress();
|
|
@@ -2729,16 +2753,10 @@ var VaultManager = class {
|
|
|
2729
2753
|
}
|
|
2730
2754
|
/**
|
|
2731
2755
|
* Get vault creation requirements
|
|
2732
|
-
* Note:
|
|
2756
|
+
* Note: StakingStub always returns hasVaultAccess=true — no gate.
|
|
2733
2757
|
*/
|
|
2734
2758
|
async getRequirements() {
|
|
2735
|
-
|
|
2736
|
-
address: this.addresses.vaultFactory,
|
|
2737
|
-
abi: VAULT_FACTORY_ABI,
|
|
2738
|
-
functionName: "minimumVeEXARequired"
|
|
2739
|
-
});
|
|
2740
|
-
const isBypassed = veXARequired === BigInt(0);
|
|
2741
|
-
return { veXARequired, isBypassed };
|
|
2759
|
+
return { isBypassed: true };
|
|
2742
2760
|
}
|
|
2743
2761
|
/**
|
|
2744
2762
|
* Get the agent's vault address (cached)
|
|
@@ -3126,6 +3144,609 @@ function openBrowser(url) {
|
|
|
3126
3144
|
}
|
|
3127
3145
|
}
|
|
3128
3146
|
|
|
3147
|
+
// src/paper/executor.ts
|
|
3148
|
+
var PaperExecutor = class {
|
|
3149
|
+
config;
|
|
3150
|
+
allowedTokens;
|
|
3151
|
+
prices;
|
|
3152
|
+
slippageBps;
|
|
3153
|
+
feeRateBps;
|
|
3154
|
+
estimatedGasUSD;
|
|
3155
|
+
/** Running totals for the paper session */
|
|
3156
|
+
totalFeesPaid = 0;
|
|
3157
|
+
totalGasPaid = 0;
|
|
3158
|
+
tradeCount = 0;
|
|
3159
|
+
constructor(config, options) {
|
|
3160
|
+
this.config = config;
|
|
3161
|
+
this.slippageBps = options?.slippageBps ?? 50;
|
|
3162
|
+
this.feeRateBps = options?.feeRateBps ?? 20;
|
|
3163
|
+
this.estimatedGasUSD = options?.estimatedGasUSD ?? 0.01;
|
|
3164
|
+
this.prices = {};
|
|
3165
|
+
this.allowedTokens = new Set(
|
|
3166
|
+
(config.allowedTokens || []).map((t) => t.toLowerCase())
|
|
3167
|
+
);
|
|
3168
|
+
}
|
|
3169
|
+
/**
|
|
3170
|
+
* Update live prices before executing trades.
|
|
3171
|
+
* Called by the runtime before each cycle with current market data.
|
|
3172
|
+
*/
|
|
3173
|
+
updatePrices(prices) {
|
|
3174
|
+
this.prices = prices;
|
|
3175
|
+
}
|
|
3176
|
+
/**
|
|
3177
|
+
* Simulate a single trade.
|
|
3178
|
+
* Returns the same shape as TradeExecutor.execute() for drop-in compatibility.
|
|
3179
|
+
*/
|
|
3180
|
+
async execute(signal) {
|
|
3181
|
+
if (signal.action === "hold") {
|
|
3182
|
+
return { success: true };
|
|
3183
|
+
}
|
|
3184
|
+
if (!this.validateSignal(signal)) {
|
|
3185
|
+
return { success: false, error: "Signal failed validation" };
|
|
3186
|
+
}
|
|
3187
|
+
const tokenInPrice = this.prices[signal.tokenIn.toLowerCase()];
|
|
3188
|
+
const tokenOutPrice = this.prices[signal.tokenOut.toLowerCase()];
|
|
3189
|
+
if (!tokenInPrice || !tokenOutPrice) {
|
|
3190
|
+
return {
|
|
3191
|
+
success: false,
|
|
3192
|
+
error: `Missing price data: ${!tokenInPrice ? signal.tokenIn : signal.tokenOut}`
|
|
3193
|
+
};
|
|
3194
|
+
}
|
|
3195
|
+
const fill = this.simulateFill(signal, tokenInPrice, tokenOutPrice);
|
|
3196
|
+
this.tradeCount++;
|
|
3197
|
+
this.totalFeesPaid += fill.feeUSD;
|
|
3198
|
+
this.totalGasPaid += this.estimatedGasUSD;
|
|
3199
|
+
console.log(
|
|
3200
|
+
` [PAPER] ${signal.action.toUpperCase()}: ${signal.tokenIn.slice(0, 10)}... \u2192 ${signal.tokenOut.slice(0, 10)}...`
|
|
3201
|
+
);
|
|
3202
|
+
console.log(
|
|
3203
|
+
` [PAPER] Fill: $${fill.valueInUSD.toFixed(2)} \u2192 $${fill.valueOutUSD.toFixed(2)} (slippage: ${fill.appliedSlippageBps}bps, fee: $${fill.feeUSD.toFixed(4)})`
|
|
3204
|
+
);
|
|
3205
|
+
return {
|
|
3206
|
+
success: true,
|
|
3207
|
+
txHash: `paper-${Date.now()}-${this.tradeCount}`,
|
|
3208
|
+
paperFill: fill
|
|
3209
|
+
};
|
|
3210
|
+
}
|
|
3211
|
+
/**
|
|
3212
|
+
* Simulate multiple trades (same interface as TradeExecutor.executeAll).
|
|
3213
|
+
*/
|
|
3214
|
+
async executeAll(signals) {
|
|
3215
|
+
const results = [];
|
|
3216
|
+
for (const signal of signals) {
|
|
3217
|
+
const result = await this.execute(signal);
|
|
3218
|
+
results.push({ signal, ...result });
|
|
3219
|
+
}
|
|
3220
|
+
return results;
|
|
3221
|
+
}
|
|
3222
|
+
/**
|
|
3223
|
+
* Get session totals for reporting.
|
|
3224
|
+
*/
|
|
3225
|
+
getSessionStats() {
|
|
3226
|
+
return {
|
|
3227
|
+
tradeCount: this.tradeCount,
|
|
3228
|
+
totalFeesPaid: this.totalFeesPaid,
|
|
3229
|
+
totalGasPaid: this.totalGasPaid,
|
|
3230
|
+
slippageBps: this.slippageBps,
|
|
3231
|
+
feeRateBps: this.feeRateBps
|
|
3232
|
+
};
|
|
3233
|
+
}
|
|
3234
|
+
/**
|
|
3235
|
+
* Reset session stats (e.g., when starting a new paper session).
|
|
3236
|
+
*/
|
|
3237
|
+
resetStats() {
|
|
3238
|
+
this.tradeCount = 0;
|
|
3239
|
+
this.totalFeesPaid = 0;
|
|
3240
|
+
this.totalGasPaid = 0;
|
|
3241
|
+
}
|
|
3242
|
+
// --- Private methods ---
|
|
3243
|
+
simulateFill(signal, tokenInPrice, tokenOutPrice) {
|
|
3244
|
+
const decimalsIn = getTokenDecimals(signal.tokenIn);
|
|
3245
|
+
const valueInUSD = Number(signal.amountIn) / Math.pow(10, decimalsIn) * tokenInPrice;
|
|
3246
|
+
const afterFeeUSD = valueInUSD * (1 - this.feeRateBps / 1e4);
|
|
3247
|
+
const feeUSD = valueInUSD - afterFeeUSD;
|
|
3248
|
+
const afterSlippageUSD = afterFeeUSD * (1 - this.slippageBps / 1e4);
|
|
3249
|
+
const decimalsOut = getTokenDecimals(signal.tokenOut);
|
|
3250
|
+
const amountOut = BigInt(Math.floor(afterSlippageUSD / tokenOutPrice * Math.pow(10, decimalsOut)));
|
|
3251
|
+
return {
|
|
3252
|
+
signal,
|
|
3253
|
+
timestamp: Date.now(),
|
|
3254
|
+
valueInUSD,
|
|
3255
|
+
valueOutUSD: afterSlippageUSD,
|
|
3256
|
+
feeUSD,
|
|
3257
|
+
gasUSD: this.estimatedGasUSD,
|
|
3258
|
+
amountOut,
|
|
3259
|
+
fillPriceUSD: tokenOutPrice * (1 + this.slippageBps / 1e4),
|
|
3260
|
+
// worse price due to slippage
|
|
3261
|
+
appliedSlippageBps: this.slippageBps
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
validateSignal(signal) {
|
|
3265
|
+
if (signal.confidence < 0.5) {
|
|
3266
|
+
console.warn(` [PAPER] Signal confidence ${signal.confidence} below threshold (0.5)`);
|
|
3267
|
+
return false;
|
|
3268
|
+
}
|
|
3269
|
+
if (this.allowedTokens.size > 0) {
|
|
3270
|
+
const tokenInAllowed = this.allowedTokens.has(signal.tokenIn.toLowerCase());
|
|
3271
|
+
const tokenOutAllowed = this.allowedTokens.has(signal.tokenOut.toLowerCase());
|
|
3272
|
+
if (!tokenInAllowed || !tokenOutAllowed) {
|
|
3273
|
+
console.warn(` [PAPER] Token not in allowed list \u2014 skipping`);
|
|
3274
|
+
return false;
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
return true;
|
|
3278
|
+
}
|
|
3279
|
+
};
|
|
3280
|
+
|
|
3281
|
+
// src/paper/portfolio.ts
|
|
3282
|
+
var fs = __toESM(require("fs"));
|
|
3283
|
+
var path = __toESM(require("path"));
|
|
3284
|
+
var SimulatedPortfolio = class _SimulatedPortfolio {
|
|
3285
|
+
balances;
|
|
3286
|
+
initialBalances;
|
|
3287
|
+
equityCurve;
|
|
3288
|
+
trades;
|
|
3289
|
+
startedAt;
|
|
3290
|
+
dataDir;
|
|
3291
|
+
constructor(initialBalances, dataDir, options) {
|
|
3292
|
+
this.balances = {};
|
|
3293
|
+
this.initialBalances = {};
|
|
3294
|
+
for (const [addr, amount] of Object.entries(initialBalances)) {
|
|
3295
|
+
const key = addr.toLowerCase();
|
|
3296
|
+
this.balances[key] = amount;
|
|
3297
|
+
this.initialBalances[key] = amount;
|
|
3298
|
+
}
|
|
3299
|
+
this.equityCurve = [];
|
|
3300
|
+
this.trades = [];
|
|
3301
|
+
this.startedAt = options?.startedAt ?? Date.now();
|
|
3302
|
+
this.dataDir = dataDir;
|
|
3303
|
+
if (!fs.existsSync(dataDir)) {
|
|
3304
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
/**
|
|
3308
|
+
* Get the session start timestamp.
|
|
3309
|
+
*/
|
|
3310
|
+
getStartedAt() {
|
|
3311
|
+
return this.startedAt;
|
|
3312
|
+
}
|
|
3313
|
+
/**
|
|
3314
|
+
* Get current balances in the same format as MarketData.balances.
|
|
3315
|
+
* Used by the runtime to build MarketData for strategy calls.
|
|
3316
|
+
*/
|
|
3317
|
+
getBalances() {
|
|
3318
|
+
return { ...this.balances };
|
|
3319
|
+
}
|
|
3320
|
+
/**
|
|
3321
|
+
* Apply a paper trade fill to the portfolio.
|
|
3322
|
+
* Deducts tokenIn, adds tokenOut.
|
|
3323
|
+
*/
|
|
3324
|
+
applyFill(fill) {
|
|
3325
|
+
const tokenIn = fill.signal.tokenIn.toLowerCase();
|
|
3326
|
+
const tokenOut = fill.signal.tokenOut.toLowerCase();
|
|
3327
|
+
const currentIn = this.balances[tokenIn] || BigInt(0);
|
|
3328
|
+
const newIn = currentIn - fill.signal.amountIn;
|
|
3329
|
+
if (newIn < BigInt(0)) {
|
|
3330
|
+
console.warn(` [PAPER] Warning: balance went negative for ${tokenIn} \u2014 clamping to 0`);
|
|
3331
|
+
this.balances[tokenIn] = BigInt(0);
|
|
3332
|
+
} else {
|
|
3333
|
+
this.balances[tokenIn] = newIn;
|
|
3334
|
+
}
|
|
3335
|
+
const currentOut = this.balances[tokenOut] || BigInt(0);
|
|
3336
|
+
this.balances[tokenOut] = currentOut + fill.amountOut;
|
|
3337
|
+
this.trades.push({
|
|
3338
|
+
timestamp: fill.timestamp,
|
|
3339
|
+
action: fill.signal.action,
|
|
3340
|
+
tokenIn,
|
|
3341
|
+
tokenOut,
|
|
3342
|
+
amountIn: fill.signal.amountIn.toString(),
|
|
3343
|
+
amountOut: fill.amountOut.toString(),
|
|
3344
|
+
valueInUSD: fill.valueInUSD,
|
|
3345
|
+
valueOutUSD: fill.valueOutUSD,
|
|
3346
|
+
feeUSD: fill.feeUSD,
|
|
3347
|
+
gasUSD: fill.gasUSD,
|
|
3348
|
+
reasoning: fill.signal.reasoning
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
/**
|
|
3352
|
+
* Record a portfolio value snapshot (called after each cycle).
|
|
3353
|
+
* @param prices - Current token prices
|
|
3354
|
+
* @param timestamp - Optional timestamp override (for backtesting with historical timestamps)
|
|
3355
|
+
*/
|
|
3356
|
+
recordEquityPoint(prices, timestamp) {
|
|
3357
|
+
const value = this.calculateValue(prices);
|
|
3358
|
+
this.equityCurve.push({
|
|
3359
|
+
timestamp: timestamp ?? Date.now(),
|
|
3360
|
+
value
|
|
3361
|
+
});
|
|
3362
|
+
}
|
|
3363
|
+
/**
|
|
3364
|
+
* Calculate current portfolio value in USD.
|
|
3365
|
+
*/
|
|
3366
|
+
calculateValue(prices) {
|
|
3367
|
+
let total = 0;
|
|
3368
|
+
for (const [token, balance] of Object.entries(this.balances)) {
|
|
3369
|
+
if (balance <= BigInt(0)) continue;
|
|
3370
|
+
const price = prices[token.toLowerCase()] || 0;
|
|
3371
|
+
const decimals = getTokenDecimals(token);
|
|
3372
|
+
total += Number(balance) / Math.pow(10, decimals) * price;
|
|
3373
|
+
}
|
|
3374
|
+
return total;
|
|
3375
|
+
}
|
|
3376
|
+
/**
|
|
3377
|
+
* Calculate initial portfolio value for return comparison.
|
|
3378
|
+
*/
|
|
3379
|
+
calculateInitialValue(prices) {
|
|
3380
|
+
let total = 0;
|
|
3381
|
+
for (const [token, balance] of Object.entries(this.initialBalances)) {
|
|
3382
|
+
if (balance <= BigInt(0)) continue;
|
|
3383
|
+
const price = prices[token.toLowerCase()] || 0;
|
|
3384
|
+
const decimals = getTokenDecimals(token);
|
|
3385
|
+
total += Number(balance) / Math.pow(10, decimals) * price;
|
|
3386
|
+
}
|
|
3387
|
+
return total;
|
|
3388
|
+
}
|
|
3389
|
+
/**
|
|
3390
|
+
* Get equity curve data.
|
|
3391
|
+
*/
|
|
3392
|
+
getEquityCurve() {
|
|
3393
|
+
return [...this.equityCurve];
|
|
3394
|
+
}
|
|
3395
|
+
/**
|
|
3396
|
+
* Get all paper trades.
|
|
3397
|
+
*/
|
|
3398
|
+
getTrades() {
|
|
3399
|
+
return [...this.trades];
|
|
3400
|
+
}
|
|
3401
|
+
/**
|
|
3402
|
+
* Get summary for display.
|
|
3403
|
+
*/
|
|
3404
|
+
getSummary(prices) {
|
|
3405
|
+
const currentValue = this.calculateValue(prices);
|
|
3406
|
+
const initialValue = this.calculateInitialValue(prices);
|
|
3407
|
+
const totalReturn = initialValue > 0 ? (currentValue - initialValue) / initialValue * 100 : 0;
|
|
3408
|
+
const totalFees = this.trades.reduce((sum, t) => sum + t.feeUSD, 0);
|
|
3409
|
+
const totalGas = this.trades.reduce((sum, t) => sum + t.gasUSD, 0);
|
|
3410
|
+
return {
|
|
3411
|
+
startedAt: this.startedAt,
|
|
3412
|
+
currentValue,
|
|
3413
|
+
initialValue,
|
|
3414
|
+
totalReturnPct: totalReturn,
|
|
3415
|
+
totalReturnUSD: currentValue - initialValue,
|
|
3416
|
+
tradeCount: this.trades.length,
|
|
3417
|
+
totalFees,
|
|
3418
|
+
totalGas,
|
|
3419
|
+
positions: this.getPositionBreakdown(prices)
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3422
|
+
/**
|
|
3423
|
+
* Persist portfolio state to disk.
|
|
3424
|
+
*/
|
|
3425
|
+
save() {
|
|
3426
|
+
const filePath = path.join(this.dataDir, "paper-portfolio.json");
|
|
3427
|
+
const data = {
|
|
3428
|
+
startedAt: this.startedAt,
|
|
3429
|
+
balances: Object.fromEntries(
|
|
3430
|
+
Object.entries(this.balances).map(([k, v]) => [k, v.toString()])
|
|
3431
|
+
),
|
|
3432
|
+
initialBalances: Object.fromEntries(
|
|
3433
|
+
Object.entries(this.initialBalances).map(([k, v]) => [k, v.toString()])
|
|
3434
|
+
),
|
|
3435
|
+
equityCurve: this.equityCurve,
|
|
3436
|
+
trades: this.trades
|
|
3437
|
+
};
|
|
3438
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
3439
|
+
}
|
|
3440
|
+
/**
|
|
3441
|
+
* Load portfolio state from disk (for resuming a paper session).
|
|
3442
|
+
* Returns null if no saved state exists.
|
|
3443
|
+
*/
|
|
3444
|
+
static load(dataDir) {
|
|
3445
|
+
const filePath = path.join(dataDir, "paper-portfolio.json");
|
|
3446
|
+
if (!fs.existsSync(filePath)) return null;
|
|
3447
|
+
try {
|
|
3448
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
3449
|
+
const initialBalances = {};
|
|
3450
|
+
for (const [k, v] of Object.entries(raw.initialBalances)) {
|
|
3451
|
+
initialBalances[k] = BigInt(v);
|
|
3452
|
+
}
|
|
3453
|
+
const portfolio = new _SimulatedPortfolio(initialBalances, dataDir);
|
|
3454
|
+
portfolio.startedAt = raw.startedAt;
|
|
3455
|
+
portfolio.equityCurve = raw.equityCurve || [];
|
|
3456
|
+
portfolio.trades = raw.trades || [];
|
|
3457
|
+
portfolio.balances = {};
|
|
3458
|
+
for (const [k, v] of Object.entries(raw.balances)) {
|
|
3459
|
+
portfolio.balances[k] = BigInt(v);
|
|
3460
|
+
}
|
|
3461
|
+
return portfolio;
|
|
3462
|
+
} catch {
|
|
3463
|
+
console.warn(" [PAPER] Failed to load saved portfolio state \u2014 starting fresh");
|
|
3464
|
+
return null;
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
/**
|
|
3468
|
+
* Delete saved state (for starting a new paper session).
|
|
3469
|
+
*/
|
|
3470
|
+
static clear(dataDir) {
|
|
3471
|
+
const filePath = path.join(dataDir, "paper-portfolio.json");
|
|
3472
|
+
if (fs.existsSync(filePath)) {
|
|
3473
|
+
fs.unlinkSync(filePath);
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
// --- Private ---
|
|
3477
|
+
getPositionBreakdown(prices) {
|
|
3478
|
+
const positions = [];
|
|
3479
|
+
for (const [token, balance] of Object.entries(this.balances)) {
|
|
3480
|
+
if (balance <= BigInt(0)) continue;
|
|
3481
|
+
const price = prices[token.toLowerCase()] || 0;
|
|
3482
|
+
const decimals = getTokenDecimals(token);
|
|
3483
|
+
const valueUSD = Number(balance) / Math.pow(10, decimals) * price;
|
|
3484
|
+
if (valueUSD >= 0.01) {
|
|
3485
|
+
positions.push({
|
|
3486
|
+
token,
|
|
3487
|
+
balance: (Number(balance) / Math.pow(10, decimals)).toFixed(6),
|
|
3488
|
+
valueUSD
|
|
3489
|
+
});
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
return positions.sort((a, b) => b.valueUSD - a.valueUSD);
|
|
3493
|
+
}
|
|
3494
|
+
};
|
|
3495
|
+
|
|
3496
|
+
// src/paper/results.ts
|
|
3497
|
+
var fs2 = __toESM(require("fs"));
|
|
3498
|
+
var path2 = __toESM(require("path"));
|
|
3499
|
+
|
|
3500
|
+
// src/paper/metrics.ts
|
|
3501
|
+
function calculateMetrics(trades, equityCurve, initialValue, currentValue, startedAt, endedAt) {
|
|
3502
|
+
const durationMs = (endedAt ?? Date.now()) - startedAt;
|
|
3503
|
+
const durationDays = durationMs / (1e3 * 60 * 60 * 24);
|
|
3504
|
+
const totalReturnUSD = currentValue - initialValue;
|
|
3505
|
+
const totalReturnPct = initialValue > 0 ? totalReturnUSD / initialValue * 100 : 0;
|
|
3506
|
+
const actionTrades = trades.filter((t) => t.action !== "hold");
|
|
3507
|
+
let wins = 0;
|
|
3508
|
+
let losses = 0;
|
|
3509
|
+
let grossProfit = 0;
|
|
3510
|
+
let grossLoss = 0;
|
|
3511
|
+
for (const trade of actionTrades) {
|
|
3512
|
+
const pnl = trade.valueOutUSD - trade.valueInUSD;
|
|
3513
|
+
if (pnl >= 0) {
|
|
3514
|
+
wins++;
|
|
3515
|
+
grossProfit += pnl;
|
|
3516
|
+
} else {
|
|
3517
|
+
losses++;
|
|
3518
|
+
grossLoss += Math.abs(pnl);
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
const winRate = actionTrades.length > 0 ? wins / actionTrades.length : 0;
|
|
3522
|
+
const profitFactor = grossLoss > 0 ? grossProfit / grossLoss : null;
|
|
3523
|
+
const { maxDrawdownPct, maxDrawdownUSD } = calculateMaxDrawdown(equityCurve);
|
|
3524
|
+
const { sharpe, sortino } = calculateRiskMetrics(equityCurve);
|
|
3525
|
+
const totalFees = trades.reduce((s, t) => s + t.feeUSD, 0);
|
|
3526
|
+
const totalGas = trades.reduce((s, t) => s + t.gasUSD, 0);
|
|
3527
|
+
const avgTradeValueUSD = actionTrades.length > 0 ? actionTrades.reduce((s, t) => s + t.valueInUSD, 0) / actionTrades.length : 0;
|
|
3528
|
+
const tradesPerDay = durationDays > 0 ? actionTrades.length / durationDays : 0;
|
|
3529
|
+
return {
|
|
3530
|
+
totalReturnPct,
|
|
3531
|
+
totalReturnUSD,
|
|
3532
|
+
winRate,
|
|
3533
|
+
wins,
|
|
3534
|
+
losses,
|
|
3535
|
+
maxDrawdownPct,
|
|
3536
|
+
maxDrawdownUSD,
|
|
3537
|
+
sharpeRatio: sharpe,
|
|
3538
|
+
sortinoRatio: sortino,
|
|
3539
|
+
profitFactor,
|
|
3540
|
+
avgTradeValueUSD,
|
|
3541
|
+
totalFees,
|
|
3542
|
+
totalGas,
|
|
3543
|
+
durationMs,
|
|
3544
|
+
tradesPerDay
|
|
3545
|
+
};
|
|
3546
|
+
}
|
|
3547
|
+
function calculateMaxDrawdown(curve) {
|
|
3548
|
+
if (curve.length < 2) return { maxDrawdownPct: 0, maxDrawdownUSD: 0 };
|
|
3549
|
+
let peak = curve[0].value;
|
|
3550
|
+
let maxDrawdownPct = 0;
|
|
3551
|
+
let maxDrawdownUSD = 0;
|
|
3552
|
+
for (const point of curve) {
|
|
3553
|
+
if (point.value > peak) {
|
|
3554
|
+
peak = point.value;
|
|
3555
|
+
}
|
|
3556
|
+
if (peak > 0) {
|
|
3557
|
+
const drawdownPct = (point.value - peak) / peak * 100;
|
|
3558
|
+
const drawdownUSD = point.value - peak;
|
|
3559
|
+
if (drawdownPct < maxDrawdownPct) {
|
|
3560
|
+
maxDrawdownPct = drawdownPct;
|
|
3561
|
+
maxDrawdownUSD = drawdownUSD;
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
return { maxDrawdownPct, maxDrawdownUSD };
|
|
3566
|
+
}
|
|
3567
|
+
function calculateRiskMetrics(curve) {
|
|
3568
|
+
if (curve.length < 3) return { sharpe: null, sortino: null };
|
|
3569
|
+
const returns = [];
|
|
3570
|
+
for (let i = 1; i < curve.length; i++) {
|
|
3571
|
+
if (curve[i - 1].value > 0) {
|
|
3572
|
+
returns.push((curve[i].value - curve[i - 1].value) / curve[i - 1].value);
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
if (returns.length < 2) return { sharpe: null, sortino: null };
|
|
3576
|
+
const meanReturn = returns.reduce((s, r) => s + r, 0) / returns.length;
|
|
3577
|
+
const variance = returns.reduce((s, r) => s + (r - meanReturn) ** 2, 0) / (returns.length - 1);
|
|
3578
|
+
const stdDev = Math.sqrt(variance);
|
|
3579
|
+
const downsideVariance = returns.reduce((s, r) => {
|
|
3580
|
+
const downside = Math.min(0, r);
|
|
3581
|
+
return s + downside ** 2;
|
|
3582
|
+
}, 0) / (returns.length - 1);
|
|
3583
|
+
const downsideDev = Math.sqrt(downsideVariance);
|
|
3584
|
+
const totalTimeMs = curve[curve.length - 1].timestamp - curve[0].timestamp;
|
|
3585
|
+
const avgPeriodMs = totalTimeMs / (curve.length - 1);
|
|
3586
|
+
const periodsPerYear = avgPeriodMs > 0 ? 365 * 24 * 60 * 60 * 1e3 / avgPeriodMs : 365;
|
|
3587
|
+
const sharpe = stdDev > 0 ? meanReturn / stdDev * Math.sqrt(periodsPerYear) : null;
|
|
3588
|
+
const sortino = downsideDev > 0 ? meanReturn / downsideDev * Math.sqrt(periodsPerYear) : null;
|
|
3589
|
+
return { sharpe, sortino };
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3592
|
+
// src/paper/results.ts
|
|
3593
|
+
function saveSessionResult(dataDir, agentName, llm, portfolio, equityCurve, trades, startedAt, options) {
|
|
3594
|
+
const endedAt = options?.endedAt ?? Date.now();
|
|
3595
|
+
const durationMs = endedAt - startedAt;
|
|
3596
|
+
const prefix = options?.idPrefix ?? "paper";
|
|
3597
|
+
const id = `${prefix}-${startedAt}`;
|
|
3598
|
+
const metrics = calculateMetrics(
|
|
3599
|
+
trades,
|
|
3600
|
+
equityCurve,
|
|
3601
|
+
portfolio.initialValue,
|
|
3602
|
+
portfolio.currentValue,
|
|
3603
|
+
startedAt,
|
|
3604
|
+
endedAt
|
|
3605
|
+
);
|
|
3606
|
+
const result = {
|
|
3607
|
+
id,
|
|
3608
|
+
agentName,
|
|
3609
|
+
startedAt,
|
|
3610
|
+
endedAt,
|
|
3611
|
+
durationMs,
|
|
3612
|
+
llm,
|
|
3613
|
+
initialValue: portfolio.initialValue,
|
|
3614
|
+
finalValue: portfolio.currentValue,
|
|
3615
|
+
metrics,
|
|
3616
|
+
portfolio,
|
|
3617
|
+
equityCurve,
|
|
3618
|
+
trades
|
|
3619
|
+
};
|
|
3620
|
+
const sessionsDir = path2.join(dataDir, "sessions");
|
|
3621
|
+
if (!fs2.existsSync(sessionsDir)) {
|
|
3622
|
+
fs2.mkdirSync(sessionsDir, { recursive: true });
|
|
3623
|
+
}
|
|
3624
|
+
const filePath = path2.join(sessionsDir, `${id}.json`);
|
|
3625
|
+
fs2.writeFileSync(filePath, JSON.stringify(result, null, 2));
|
|
3626
|
+
console.log(` [PAPER] Session saved: ${filePath}`);
|
|
3627
|
+
return result;
|
|
3628
|
+
}
|
|
3629
|
+
function formatSessionReport(result) {
|
|
3630
|
+
const m = result.metrics;
|
|
3631
|
+
const lines = [];
|
|
3632
|
+
const divider = "\u2550".repeat(56);
|
|
3633
|
+
const thinDivider = "\u2500".repeat(56);
|
|
3634
|
+
lines.push("");
|
|
3635
|
+
lines.push(` ${divider}`);
|
|
3636
|
+
lines.push(` PAPER TRADING RESULTS \u2014 ${result.agentName}`);
|
|
3637
|
+
lines.push(` ${divider}`);
|
|
3638
|
+
lines.push("");
|
|
3639
|
+
lines.push(` Session: ${result.id}`);
|
|
3640
|
+
lines.push(` Started: ${new Date(result.startedAt).toLocaleString()}`);
|
|
3641
|
+
lines.push(` Ended: ${new Date(result.endedAt).toLocaleString()}`);
|
|
3642
|
+
lines.push(` Duration: ${formatDuration(result.durationMs)}`);
|
|
3643
|
+
lines.push(` LLM: ${result.llm.provider}/${result.llm.model}`);
|
|
3644
|
+
lines.push("");
|
|
3645
|
+
lines.push(` ${thinDivider}`);
|
|
3646
|
+
lines.push(" PERFORMANCE");
|
|
3647
|
+
lines.push(` ${thinDivider}`);
|
|
3648
|
+
lines.push("");
|
|
3649
|
+
const returnSign = m.totalReturnPct >= 0 ? "+" : "";
|
|
3650
|
+
lines.push(` Total Return: ${returnSign}${m.totalReturnPct.toFixed(2)}% ($${m.totalReturnUSD.toFixed(2)})`);
|
|
3651
|
+
lines.push(` Initial Value: $${result.initialValue.toFixed(2)}`);
|
|
3652
|
+
lines.push(` Final Value: $${result.finalValue.toFixed(2)}`);
|
|
3653
|
+
lines.push("");
|
|
3654
|
+
lines.push(` Max Drawdown: ${m.maxDrawdownPct.toFixed(2)}% ($${m.maxDrawdownUSD.toFixed(2)})`);
|
|
3655
|
+
lines.push(` Sharpe Ratio: ${m.sharpeRatio !== null ? m.sharpeRatio.toFixed(2) : "N/A"}`);
|
|
3656
|
+
lines.push(` Sortino Ratio: ${m.sortinoRatio !== null ? m.sortinoRatio.toFixed(2) : "N/A"}`);
|
|
3657
|
+
lines.push("");
|
|
3658
|
+
lines.push(` ${thinDivider}`);
|
|
3659
|
+
lines.push(" TRADES");
|
|
3660
|
+
lines.push(` ${thinDivider}`);
|
|
3661
|
+
lines.push("");
|
|
3662
|
+
lines.push(` Total Trades: ${result.trades.length}`);
|
|
3663
|
+
lines.push(` Win Rate: ${(m.winRate * 100).toFixed(1)}% (${m.wins}W / ${m.losses}L)`);
|
|
3664
|
+
lines.push(` Profit Factor: ${m.profitFactor !== null ? m.profitFactor.toFixed(2) : "N/A"}`);
|
|
3665
|
+
lines.push(` Avg Trade Size: $${m.avgTradeValueUSD.toFixed(2)}`);
|
|
3666
|
+
lines.push(` Trades/Day: ${m.tradesPerDay.toFixed(1)}`);
|
|
3667
|
+
lines.push("");
|
|
3668
|
+
lines.push(` ${thinDivider}`);
|
|
3669
|
+
lines.push(" COSTS (SIMULATED)");
|
|
3670
|
+
lines.push(` ${thinDivider}`);
|
|
3671
|
+
lines.push("");
|
|
3672
|
+
lines.push(` Total Fees: $${m.totalFees.toFixed(4)}`);
|
|
3673
|
+
lines.push(` Total Gas: $${m.totalGas.toFixed(4)}`);
|
|
3674
|
+
lines.push(` Total Costs: $${(m.totalFees + m.totalGas).toFixed(4)}`);
|
|
3675
|
+
lines.push("");
|
|
3676
|
+
if (result.portfolio.positions.length > 0) {
|
|
3677
|
+
lines.push(` ${thinDivider}`);
|
|
3678
|
+
lines.push(" FINAL POSITIONS");
|
|
3679
|
+
lines.push(` ${thinDivider}`);
|
|
3680
|
+
lines.push("");
|
|
3681
|
+
for (const pos of result.portfolio.positions) {
|
|
3682
|
+
const tokenShort = pos.token.slice(0, 10) + "...";
|
|
3683
|
+
lines.push(` ${tokenShort} ${pos.balance} $${pos.valueUSD.toFixed(2)}`);
|
|
3684
|
+
}
|
|
3685
|
+
lines.push("");
|
|
3686
|
+
}
|
|
3687
|
+
if (result.equityCurve.length >= 3) {
|
|
3688
|
+
lines.push(` ${thinDivider}`);
|
|
3689
|
+
lines.push(" EQUITY CURVE");
|
|
3690
|
+
lines.push(` ${thinDivider}`);
|
|
3691
|
+
lines.push("");
|
|
3692
|
+
lines.push(renderAsciiChart(result.equityCurve));
|
|
3693
|
+
lines.push("");
|
|
3694
|
+
}
|
|
3695
|
+
lines.push(` ${divider}`);
|
|
3696
|
+
lines.push(" DISCLAIMER: Simulated results do not represent actual");
|
|
3697
|
+
lines.push(" trading. Past performance does not guarantee future");
|
|
3698
|
+
lines.push(" results. LLM strategies are non-deterministic. Real");
|
|
3699
|
+
lines.push(" trading may differ due to slippage, liquidity, MEV,");
|
|
3700
|
+
lines.push(" and execution timing.");
|
|
3701
|
+
lines.push(` ${divider}`);
|
|
3702
|
+
lines.push("");
|
|
3703
|
+
return lines.join("\n");
|
|
3704
|
+
}
|
|
3705
|
+
function formatDuration(ms) {
|
|
3706
|
+
const seconds = Math.floor(ms / 1e3);
|
|
3707
|
+
if (seconds < 60) return `${seconds}s`;
|
|
3708
|
+
const minutes = Math.floor(seconds / 60);
|
|
3709
|
+
if (minutes < 60) return `${minutes}m`;
|
|
3710
|
+
const hours = Math.floor(minutes / 60);
|
|
3711
|
+
const mins = minutes % 60;
|
|
3712
|
+
if (hours < 24) return `${hours}h ${mins}m`;
|
|
3713
|
+
const days = Math.floor(hours / 24);
|
|
3714
|
+
return `${days}d ${hours % 24}h`;
|
|
3715
|
+
}
|
|
3716
|
+
function renderAsciiChart(curve) {
|
|
3717
|
+
const width = 48;
|
|
3718
|
+
const height = 8;
|
|
3719
|
+
const step = Math.max(1, Math.floor(curve.length / width));
|
|
3720
|
+
const sampled = curve.filter((_, i) => i % step === 0).slice(0, width);
|
|
3721
|
+
const values = sampled.map((p) => p.value);
|
|
3722
|
+
const min = Math.min(...values);
|
|
3723
|
+
const max = Math.max(...values);
|
|
3724
|
+
const range = max - min || 1;
|
|
3725
|
+
const lines = [];
|
|
3726
|
+
for (let row = height - 1; row >= 0; row--) {
|
|
3727
|
+
const threshold = min + range * row / (height - 1);
|
|
3728
|
+
let line = " \u2502";
|
|
3729
|
+
for (const val of values) {
|
|
3730
|
+
const normalized = (val - min) / range * (height - 1);
|
|
3731
|
+
if (Math.round(normalized) === row) {
|
|
3732
|
+
line += "\u2588";
|
|
3733
|
+
} else if (Math.round(normalized) > row) {
|
|
3734
|
+
line += "\u2591";
|
|
3735
|
+
} else {
|
|
3736
|
+
line += " ";
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
if (row === height - 1) {
|
|
3740
|
+
line += ` $${max.toFixed(0)}`;
|
|
3741
|
+
} else if (row === 0) {
|
|
3742
|
+
line += ` $${min.toFixed(0)}`;
|
|
3743
|
+
}
|
|
3744
|
+
lines.push(line);
|
|
3745
|
+
}
|
|
3746
|
+
lines.push(" \u2514" + "\u2500".repeat(values.length));
|
|
3747
|
+
return lines.join("\n");
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3129
3750
|
// src/perp/client.ts
|
|
3130
3751
|
var HyperliquidClient = class {
|
|
3131
3752
|
apiUrl;
|
|
@@ -4395,6 +5016,11 @@ var AgentRuntime = class {
|
|
|
4395
5016
|
allowedTokens = /* @__PURE__ */ new Set();
|
|
4396
5017
|
strategyContext;
|
|
4397
5018
|
positionTracker;
|
|
5019
|
+
// Paper trading components (null when not in paper mode)
|
|
5020
|
+
paperExecutor = null;
|
|
5021
|
+
paperPortfolio = null;
|
|
5022
|
+
/** Whether agent was started directly in paper mode via CLI */
|
|
5023
|
+
startInPaperMode = false;
|
|
4398
5024
|
// Perp trading components (null if perp not enabled)
|
|
4399
5025
|
perpClient = null;
|
|
4400
5026
|
perpSigner = null;
|
|
@@ -4417,9 +5043,13 @@ var AgentRuntime = class {
|
|
|
4417
5043
|
cachedPerpMarginUsed = 0;
|
|
4418
5044
|
cachedPerpLeverage = 0;
|
|
4419
5045
|
cachedPerpOpenPositions = 0;
|
|
4420
|
-
constructor(config) {
|
|
5046
|
+
constructor(config, options) {
|
|
4421
5047
|
this.config = config;
|
|
5048
|
+
this.startInPaperMode = options?.paperMode ?? false;
|
|
5049
|
+
this._paperInitialBalances = options?.paperBalances;
|
|
4422
5050
|
}
|
|
5051
|
+
/** Initial balances override for paper mode (from CLI --initial-eth/--initial-usdc) */
|
|
5052
|
+
_paperInitialBalances;
|
|
4423
5053
|
/**
|
|
4424
5054
|
* Initialize the agent runtime
|
|
4425
5055
|
*/
|
|
@@ -4451,7 +5081,7 @@ var AgentRuntime = class {
|
|
|
4451
5081
|
};
|
|
4452
5082
|
this.positionTracker = new PositionTracker(store, { maxTradeHistory: 50 });
|
|
4453
5083
|
this.executor = new TradeExecutor(this.client, this.config, () => this.getConfigHash());
|
|
4454
|
-
this.riskManager = new RiskManager(this.config.trading);
|
|
5084
|
+
this.riskManager = new RiskManager(this.config.trading, this.riskUniverse);
|
|
4455
5085
|
this.marketData = new MarketDataService(this.getRpcUrl(), store);
|
|
4456
5086
|
setGlobalResolver(this.marketData.getResolver());
|
|
4457
5087
|
const savedRisk = this.positionTracker.getRiskState();
|
|
@@ -4872,16 +5502,20 @@ var AgentRuntime = class {
|
|
|
4872
5502
|
} catch (error) {
|
|
4873
5503
|
console.warn("Initial balance sync failed (non-fatal):", error instanceof Error ? error.message : error);
|
|
4874
5504
|
}
|
|
5505
|
+
if (this.startInPaperMode) {
|
|
5506
|
+
await this.startPaperTrading(this._paperInitialBalances);
|
|
5507
|
+
}
|
|
4875
5508
|
this.sendRelayStatus();
|
|
4876
5509
|
this.relay.sendMessage(
|
|
4877
5510
|
"system",
|
|
4878
5511
|
"success",
|
|
4879
5512
|
"Agent Connected",
|
|
4880
|
-
`${this.config.name} is online and waiting for commands.`,
|
|
5513
|
+
`${this.config.name} is online${this.mode === "paper" ? " in PAPER TRADING mode" : " and waiting for commands"}.`,
|
|
4881
5514
|
{ wallet: this.client.address }
|
|
4882
5515
|
);
|
|
4883
5516
|
while (this.processAlive) {
|
|
4884
|
-
|
|
5517
|
+
const currentMode = this.mode;
|
|
5518
|
+
if ((currentMode === "trading" || currentMode === "paper") && this.isRunning) {
|
|
4885
5519
|
try {
|
|
4886
5520
|
await this.runCycle();
|
|
4887
5521
|
} catch (error) {
|
|
@@ -4905,8 +5539,13 @@ var AgentRuntime = class {
|
|
|
4905
5539
|
throw new Error("Agent is already running");
|
|
4906
5540
|
}
|
|
4907
5541
|
this.isRunning = true;
|
|
4908
|
-
this.
|
|
4909
|
-
|
|
5542
|
+
if (this.startInPaperMode) {
|
|
5543
|
+
await this.startPaperTrading(this._paperInitialBalances);
|
|
5544
|
+
console.log("Starting paper trading loop...");
|
|
5545
|
+
} else {
|
|
5546
|
+
this.mode = "trading";
|
|
5547
|
+
console.log("Starting trading loop...");
|
|
5548
|
+
}
|
|
4910
5549
|
console.log(`Interval: ${this.config.trading.tradingIntervalMs}ms`);
|
|
4911
5550
|
while (this.isRunning) {
|
|
4912
5551
|
try {
|
|
@@ -4981,7 +5620,7 @@ var AgentRuntime = class {
|
|
|
4981
5620
|
updated = true;
|
|
4982
5621
|
}
|
|
4983
5622
|
if (updated) {
|
|
4984
|
-
this.riskManager = new RiskManager(this.config.trading);
|
|
5623
|
+
this.riskManager = new RiskManager(this.config.trading, this.riskUniverse);
|
|
4985
5624
|
const savedRiskState = this.positionTracker.getRiskState();
|
|
4986
5625
|
if (savedRiskState.lastResetDate) {
|
|
4987
5626
|
this.riskManager.restoreState(savedRiskState);
|
|
@@ -5153,6 +5792,41 @@ var AgentRuntime = class {
|
|
|
5153
5792
|
}
|
|
5154
5793
|
break;
|
|
5155
5794
|
}
|
|
5795
|
+
case "start_paper_trading":
|
|
5796
|
+
if (this.mode === "paper") {
|
|
5797
|
+
this.relay?.sendCommandResult(cmd.id, true, "Already paper trading");
|
|
5798
|
+
return;
|
|
5799
|
+
}
|
|
5800
|
+
try {
|
|
5801
|
+
await this.startPaperTrading();
|
|
5802
|
+
this.relay?.sendCommandResult(cmd.id, true, "Paper trading started");
|
|
5803
|
+
this.relay?.sendMessage(
|
|
5804
|
+
"system",
|
|
5805
|
+
"success",
|
|
5806
|
+
"Paper Trading Started",
|
|
5807
|
+
"Agent is now paper trading with simulated execution. No on-chain transactions will be made. Results are estimates \u2014 real trading may differ due to slippage, liquidity, and execution timing."
|
|
5808
|
+
);
|
|
5809
|
+
} catch (error) {
|
|
5810
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
5811
|
+
this.relay?.sendCommandResult(cmd.id, false, `Failed to start paper trading: ${msg}`);
|
|
5812
|
+
}
|
|
5813
|
+
this.sendRelayStatus();
|
|
5814
|
+
break;
|
|
5815
|
+
case "stop_paper_trading":
|
|
5816
|
+
if (this.mode !== "paper") {
|
|
5817
|
+
this.relay?.sendCommandResult(cmd.id, true, "Not paper trading");
|
|
5818
|
+
return;
|
|
5819
|
+
}
|
|
5820
|
+
this.stopPaperTrading();
|
|
5821
|
+
this.relay?.sendCommandResult(cmd.id, true, "Paper trading stopped");
|
|
5822
|
+
this.relay?.sendMessage(
|
|
5823
|
+
"system",
|
|
5824
|
+
"info",
|
|
5825
|
+
"Paper Trading Stopped",
|
|
5826
|
+
`Paper session ended. ${this.paperPortfolio ? `Trades: ${this.paperPortfolio.getTrades().length}` : ""}`
|
|
5827
|
+
);
|
|
5828
|
+
this.sendRelayStatus();
|
|
5829
|
+
break;
|
|
5156
5830
|
case "refresh_status":
|
|
5157
5831
|
this.sendRelayStatus();
|
|
5158
5832
|
this.relay?.sendCommandResult(cmd.id, true, "Status refreshed");
|
|
@@ -5179,6 +5853,63 @@ var AgentRuntime = class {
|
|
|
5179
5853
|
this.relay?.sendCommandResult(cmd.id, false, message);
|
|
5180
5854
|
}
|
|
5181
5855
|
}
|
|
5856
|
+
// --- Paper Trading ---
|
|
5857
|
+
/**
|
|
5858
|
+
* Initialize and enter paper trading mode.
|
|
5859
|
+
* Snapshots current on-chain balances (or uses provided overrides) as starting state.
|
|
5860
|
+
*/
|
|
5861
|
+
async startPaperTrading(initialBalances) {
|
|
5862
|
+
console.log("");
|
|
5863
|
+
console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
5864
|
+
console.log(" \u2502 PAPER TRADING MODE \u2502");
|
|
5865
|
+
console.log(" \u2502 Simulated execution \u2014 no on-chain trades \u2502");
|
|
5866
|
+
console.log(" \u2502 Results are estimates only. \u2502");
|
|
5867
|
+
console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
5868
|
+
console.log("");
|
|
5869
|
+
const paperDataDir = require("path").join(process.cwd(), "data", "paper");
|
|
5870
|
+
if (initialBalances) {
|
|
5871
|
+
this.paperPortfolio = new SimulatedPortfolio(initialBalances, paperDataDir);
|
|
5872
|
+
console.log(" [PAPER] Using custom initial balances");
|
|
5873
|
+
} else {
|
|
5874
|
+
const existing = SimulatedPortfolio.load(paperDataDir);
|
|
5875
|
+
if (existing) {
|
|
5876
|
+
this.paperPortfolio = existing;
|
|
5877
|
+
console.log(" [PAPER] Resumed existing paper session");
|
|
5878
|
+
} else {
|
|
5879
|
+
const tokens = this.getTokensToTrack();
|
|
5880
|
+
const realData = await this.marketData.fetchMarketData(this.client.address, tokens);
|
|
5881
|
+
this.paperPortfolio = new SimulatedPortfolio(realData.balances, paperDataDir);
|
|
5882
|
+
console.log(` [PAPER] Snapshotted on-chain balances ($${realData.portfolioValue.toFixed(2)})`);
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
this.paperExecutor = new PaperExecutor(this.config);
|
|
5886
|
+
this.mode = "paper";
|
|
5887
|
+
this.isRunning = true;
|
|
5888
|
+
}
|
|
5889
|
+
/**
|
|
5890
|
+
* Exit paper trading mode and return to idle.
|
|
5891
|
+
* Saves the paper portfolio state to disk.
|
|
5892
|
+
*/
|
|
5893
|
+
stopPaperTrading() {
|
|
5894
|
+
if (this.paperPortfolio) {
|
|
5895
|
+
this.paperPortfolio.save();
|
|
5896
|
+
const summary = this.paperPortfolio.getSummary(this.lastPrices);
|
|
5897
|
+
const dataDir = require("path").join(process.cwd(), "data", "paper");
|
|
5898
|
+
const result = saveSessionResult(
|
|
5899
|
+
dataDir,
|
|
5900
|
+
this.config.name,
|
|
5901
|
+
{ provider: this.config.llm.provider, model: this.config.llm.model || "default" },
|
|
5902
|
+
summary,
|
|
5903
|
+
this.paperPortfolio.getEquityCurve(),
|
|
5904
|
+
this.paperPortfolio.getTrades(),
|
|
5905
|
+
summary.startedAt
|
|
5906
|
+
);
|
|
5907
|
+
console.log(formatSessionReport(result));
|
|
5908
|
+
}
|
|
5909
|
+
this.mode = "idle";
|
|
5910
|
+
this.isRunning = false;
|
|
5911
|
+
this.paperExecutor = null;
|
|
5912
|
+
}
|
|
5182
5913
|
/**
|
|
5183
5914
|
* Periodically check if the owner has approved the pending config hash.
|
|
5184
5915
|
* Called from sendRelayStatus at most every 2.5 minutes (timestamp-throttled).
|
|
@@ -5249,6 +5980,29 @@ var AgentRuntime = class {
|
|
|
5249
5980
|
pendingRecords: this.perpRecorder?.pendingRetries ?? 0
|
|
5250
5981
|
} : void 0,
|
|
5251
5982
|
positions: this.positionTracker ? this.positionTracker.getPositionSummary(this.lastPrices) : void 0,
|
|
5983
|
+
paper: this.mode === "paper" && this.paperPortfolio ? (() => {
|
|
5984
|
+
const summary = this.paperPortfolio.getSummary(this.lastPrices);
|
|
5985
|
+
const trades = this.paperPortfolio.getTrades();
|
|
5986
|
+
let wins = 0, losses = 0;
|
|
5987
|
+
for (const t of trades) {
|
|
5988
|
+
if (t.action === "hold") continue;
|
|
5989
|
+
if (t.valueOutUSD >= t.valueInUSD) wins++;
|
|
5990
|
+
else losses++;
|
|
5991
|
+
}
|
|
5992
|
+
const curve = this.paperPortfolio.getEquityCurve();
|
|
5993
|
+
const equityCurve = curve.length > 50 ? curve.slice(-50) : curve;
|
|
5994
|
+
return {
|
|
5995
|
+
active: true,
|
|
5996
|
+
startedAt: summary.startedAt,
|
|
5997
|
+
simulatedValue: summary.currentValue,
|
|
5998
|
+
simulatedPnLPct: summary.totalReturnPct,
|
|
5999
|
+
tradeCount: summary.tradeCount,
|
|
6000
|
+
totalFees: summary.totalFees,
|
|
6001
|
+
wins,
|
|
6002
|
+
losses,
|
|
6003
|
+
equityCurve
|
|
6004
|
+
};
|
|
6005
|
+
})() : void 0,
|
|
5252
6006
|
configHash: this.configHash || void 0,
|
|
5253
6007
|
pendingConfigHash: this.pendingConfigHash
|
|
5254
6008
|
// null preserved by JSON.stringify for clearing
|
|
@@ -5272,23 +6026,33 @@ var AgentRuntime = class {
|
|
|
5272
6026
|
* Run a single trading cycle
|
|
5273
6027
|
*/
|
|
5274
6028
|
async runCycle() {
|
|
6029
|
+
const isPaper = this.mode === "paper";
|
|
6030
|
+
const modeTag = isPaper ? "[PAPER] " : "";
|
|
5275
6031
|
console.log(`
|
|
5276
|
-
--- Trading Cycle: ${(/* @__PURE__ */ new Date()).toISOString()} ---`);
|
|
6032
|
+
--- ${modeTag}Trading Cycle: ${(/* @__PURE__ */ new Date()).toISOString()} ---`);
|
|
5277
6033
|
this.cycleCount++;
|
|
5278
6034
|
this.lastCycleAt = Date.now();
|
|
5279
6035
|
const tokens = this.getTokensToTrack();
|
|
5280
6036
|
const marketData = await this.marketData.fetchMarketData(this.client.address, tokens);
|
|
5281
|
-
|
|
6037
|
+
if (isPaper && this.paperPortfolio) {
|
|
6038
|
+
const simBalances = this.paperPortfolio.getBalances();
|
|
6039
|
+
marketData.balances = simBalances;
|
|
6040
|
+
marketData.portfolioValue = this.paperPortfolio.calculateValue(marketData.prices);
|
|
6041
|
+
this.paperExecutor?.updatePrices(marketData.prices);
|
|
6042
|
+
}
|
|
6043
|
+
console.log(`${modeTag}Portfolio value: $${marketData.portfolioValue.toFixed(2)}`);
|
|
5282
6044
|
this.lastPortfolioValue = marketData.portfolioValue;
|
|
5283
6045
|
this.lastPrices = marketData.prices;
|
|
5284
6046
|
const nativeEthBal = marketData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
5285
6047
|
this.lastEthBalance = (Number(nativeEthBal) / 1e18).toFixed(6);
|
|
5286
6048
|
this.positionTracker.syncBalances(marketData.balances, marketData.prices);
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
6049
|
+
if (!isPaper) {
|
|
6050
|
+
const fundsOk = this.checkFundsLow(marketData);
|
|
6051
|
+
if (!fundsOk) {
|
|
6052
|
+
console.warn("Skipping trading cycle \u2014 ETH balance critically low");
|
|
6053
|
+
this.sendRelayStatus();
|
|
6054
|
+
return;
|
|
6055
|
+
}
|
|
5292
6056
|
}
|
|
5293
6057
|
this.strategyContext.positions = this.positionTracker.getPositions();
|
|
5294
6058
|
this.strategyContext.tradeHistory = this.positionTracker.getTradeHistory(20);
|
|
@@ -5319,74 +6083,133 @@ var AgentRuntime = class {
|
|
|
5319
6083
|
);
|
|
5320
6084
|
}
|
|
5321
6085
|
if (filteredSignals.length > 0) {
|
|
5322
|
-
const vaultStatus = await this.vaultManager?.getVaultStatus();
|
|
5323
|
-
if (vaultStatus?.hasVault && this.vaultManager?.preferVaultTrading) {
|
|
5324
|
-
console.log(`Trading through vault: ${vaultStatus.vaultAddress}`);
|
|
5325
|
-
}
|
|
5326
6086
|
const preTradePortfolioValue = marketData.portfolioValue;
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
6087
|
+
if (isPaper && this.paperExecutor && this.paperPortfolio) {
|
|
6088
|
+
const results = await this.paperExecutor.executeAll(filteredSignals);
|
|
6089
|
+
let totalFeesUSD = 0;
|
|
6090
|
+
for (const result of results) {
|
|
6091
|
+
if (result.success && result.paperFill) {
|
|
6092
|
+
this.paperPortfolio.applyFill(result.paperFill);
|
|
6093
|
+
totalFeesUSD += result.paperFill.feeUSD;
|
|
6094
|
+
this.riskManager.updateFees(result.paperFill.feeUSD);
|
|
6095
|
+
this.relay?.sendMessage(
|
|
6096
|
+
"paper_trade_executed",
|
|
6097
|
+
"success",
|
|
6098
|
+
"[PAPER] Trade Executed",
|
|
6099
|
+
`${result.signal.action.toUpperCase()}: ${result.signal.reasoning || "No reason provided"}`,
|
|
6100
|
+
{
|
|
6101
|
+
action: result.signal.action,
|
|
6102
|
+
txHash: result.txHash,
|
|
6103
|
+
tokenIn: result.signal.tokenIn,
|
|
6104
|
+
tokenOut: result.signal.tokenOut,
|
|
6105
|
+
paper: true,
|
|
6106
|
+
valueInUSD: result.paperFill.valueInUSD,
|
|
6107
|
+
valueOutUSD: result.paperFill.valueOutUSD
|
|
6108
|
+
}
|
|
6109
|
+
);
|
|
6110
|
+
} else {
|
|
6111
|
+
this.relay?.sendMessage(
|
|
6112
|
+
"paper_trade_failed",
|
|
6113
|
+
"error",
|
|
6114
|
+
"[PAPER] Trade Failed",
|
|
6115
|
+
result.error || "Unknown error",
|
|
6116
|
+
{ action: result.signal.action, paper: true }
|
|
6117
|
+
);
|
|
6118
|
+
}
|
|
6119
|
+
const tokenIn = result.signal.tokenIn.toLowerCase();
|
|
6120
|
+
const tokenOut = result.signal.tokenOut.toLowerCase();
|
|
6121
|
+
this.positionTracker.recordTrade({
|
|
6122
|
+
action: result.signal.action,
|
|
6123
|
+
tokenIn,
|
|
6124
|
+
tokenOut,
|
|
6125
|
+
amountIn: result.signal.amountIn,
|
|
6126
|
+
priceIn: marketData.prices[tokenIn] || 0,
|
|
6127
|
+
priceOut: marketData.prices[tokenOut] || 0,
|
|
6128
|
+
txHash: result.txHash,
|
|
6129
|
+
reasoning: result.signal.reasoning,
|
|
6130
|
+
success: result.success
|
|
6131
|
+
});
|
|
5359
6132
|
}
|
|
6133
|
+
const postValue = this.paperPortfolio.calculateValue(marketData.prices);
|
|
6134
|
+
const marketPnL = postValue - preTradePortfolioValue + totalFeesUSD;
|
|
6135
|
+
this.riskManager.updatePnL(marketPnL);
|
|
6136
|
+
this.positionTracker.saveRiskState(this.riskManager.exportState());
|
|
6137
|
+
if (marketPnL !== 0) {
|
|
6138
|
+
console.log(` [PAPER] Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
|
|
6139
|
+
}
|
|
6140
|
+
this.paperPortfolio.recordEquityPoint(marketData.prices);
|
|
6141
|
+
this.positionTracker.syncBalances(this.paperPortfolio.getBalances(), marketData.prices);
|
|
6142
|
+
this.lastPortfolioValue = postValue;
|
|
6143
|
+
this.paperPortfolio.save();
|
|
6144
|
+
} else {
|
|
6145
|
+
const vaultStatus = await this.vaultManager?.getVaultStatus();
|
|
6146
|
+
if (vaultStatus?.hasVault && this.vaultManager?.preferVaultTrading) {
|
|
6147
|
+
console.log(`Trading through vault: ${vaultStatus.vaultAddress}`);
|
|
6148
|
+
}
|
|
6149
|
+
const results = await this.executor.executeAll(filteredSignals);
|
|
6150
|
+
let totalFeesUSD = 0;
|
|
6151
|
+
for (const result of results) {
|
|
6152
|
+
if (result.success) {
|
|
6153
|
+
console.log(`Trade executed: ${result.signal.action} - ${result.txHash}`);
|
|
6154
|
+
const feeCostBps = 20;
|
|
6155
|
+
const signalPrice = marketData.prices[result.signal.tokenIn.toLowerCase()] || 0;
|
|
6156
|
+
const amountUSD = Number(result.signal.amountIn) / Math.pow(10, getTokenDecimals(result.signal.tokenIn)) * signalPrice;
|
|
6157
|
+
const feeCostUSD = amountUSD * feeCostBps / 1e4;
|
|
6158
|
+
totalFeesUSD += feeCostUSD;
|
|
6159
|
+
this.riskManager.updateFees(feeCostUSD);
|
|
6160
|
+
this.relay?.sendMessage(
|
|
6161
|
+
"trade_executed",
|
|
6162
|
+
"success",
|
|
6163
|
+
"Trade Executed",
|
|
6164
|
+
`${result.signal.action.toUpperCase()}: ${result.signal.reasoning || "No reason provided"}`,
|
|
6165
|
+
{
|
|
6166
|
+
action: result.signal.action,
|
|
6167
|
+
txHash: result.txHash,
|
|
6168
|
+
tokenIn: result.signal.tokenIn,
|
|
6169
|
+
tokenOut: result.signal.tokenOut
|
|
6170
|
+
}
|
|
6171
|
+
);
|
|
6172
|
+
} else {
|
|
6173
|
+
console.warn(`Trade failed: ${result.error}`);
|
|
6174
|
+
this.relay?.sendMessage(
|
|
6175
|
+
"trade_failed",
|
|
6176
|
+
"error",
|
|
6177
|
+
"Trade Failed",
|
|
6178
|
+
result.error || "Unknown error",
|
|
6179
|
+
{ action: result.signal.action }
|
|
6180
|
+
);
|
|
6181
|
+
}
|
|
6182
|
+
}
|
|
6183
|
+
for (const result of results) {
|
|
6184
|
+
const tokenIn = result.signal.tokenIn.toLowerCase();
|
|
6185
|
+
const tokenOut = result.signal.tokenOut.toLowerCase();
|
|
6186
|
+
this.positionTracker.recordTrade({
|
|
6187
|
+
action: result.signal.action,
|
|
6188
|
+
tokenIn,
|
|
6189
|
+
tokenOut,
|
|
6190
|
+
amountIn: result.signal.amountIn,
|
|
6191
|
+
priceIn: marketData.prices[tokenIn] || 0,
|
|
6192
|
+
priceOut: marketData.prices[tokenOut] || 0,
|
|
6193
|
+
txHash: result.txHash,
|
|
6194
|
+
reasoning: result.signal.reasoning,
|
|
6195
|
+
success: result.success
|
|
6196
|
+
});
|
|
6197
|
+
}
|
|
6198
|
+
const postTokens = this.getTokensToTrack();
|
|
6199
|
+
const postTradeData = await this.marketData.fetchMarketData(this.client.address, postTokens);
|
|
6200
|
+
const marketPnL = postTradeData.portfolioValue - preTradePortfolioValue + totalFeesUSD;
|
|
6201
|
+
this.riskManager.updatePnL(marketPnL);
|
|
6202
|
+
this.positionTracker.saveRiskState(this.riskManager.exportState());
|
|
6203
|
+
if (marketPnL !== 0) {
|
|
6204
|
+
console.log(`Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
|
|
6205
|
+
}
|
|
6206
|
+
this.positionTracker.syncBalances(postTradeData.balances, postTradeData.prices);
|
|
6207
|
+
this.lastPortfolioValue = postTradeData.portfolioValue;
|
|
6208
|
+
const postNativeEthBal = postTradeData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
6209
|
+
this.lastEthBalance = (Number(postNativeEthBal) / 1e18).toFixed(6);
|
|
5360
6210
|
}
|
|
5361
|
-
for (const result of results) {
|
|
5362
|
-
const tokenIn = result.signal.tokenIn.toLowerCase();
|
|
5363
|
-
const tokenOut = result.signal.tokenOut.toLowerCase();
|
|
5364
|
-
this.positionTracker.recordTrade({
|
|
5365
|
-
action: result.signal.action,
|
|
5366
|
-
tokenIn,
|
|
5367
|
-
tokenOut,
|
|
5368
|
-
amountIn: result.signal.amountIn,
|
|
5369
|
-
priceIn: marketData.prices[tokenIn] || 0,
|
|
5370
|
-
priceOut: marketData.prices[tokenOut] || 0,
|
|
5371
|
-
txHash: result.txHash,
|
|
5372
|
-
reasoning: result.signal.reasoning,
|
|
5373
|
-
success: result.success
|
|
5374
|
-
});
|
|
5375
|
-
}
|
|
5376
|
-
const postTokens = this.getTokensToTrack();
|
|
5377
|
-
const postTradeData = await this.marketData.fetchMarketData(this.client.address, postTokens);
|
|
5378
|
-
const marketPnL = postTradeData.portfolioValue - preTradePortfolioValue + totalFeesUSD;
|
|
5379
|
-
this.riskManager.updatePnL(marketPnL);
|
|
5380
|
-
this.positionTracker.saveRiskState(this.riskManager.exportState());
|
|
5381
|
-
if (marketPnL !== 0) {
|
|
5382
|
-
console.log(`Cycle PnL: $${marketPnL.toFixed(2)} (market), -$${totalFeesUSD.toFixed(2)} (fees)`);
|
|
5383
|
-
}
|
|
5384
|
-
this.positionTracker.syncBalances(postTradeData.balances, postTradeData.prices);
|
|
5385
|
-
this.lastPortfolioValue = postTradeData.portfolioValue;
|
|
5386
|
-
const postNativeEthBal = postTradeData.balances[NATIVE_ETH.toLowerCase()] || BigInt(0);
|
|
5387
|
-
this.lastEthBalance = (Number(postNativeEthBal) / 1e18).toFixed(6);
|
|
5388
6211
|
}
|
|
5389
|
-
if (this.perpConnected && this.perpTradingActive) {
|
|
6212
|
+
if (!isPaper && this.perpConnected && this.perpTradingActive) {
|
|
5390
6213
|
try {
|
|
5391
6214
|
await this.runPerpCycle();
|
|
5392
6215
|
} catch (error) {
|
|
@@ -5701,8 +6524,8 @@ var AgentRuntime = class {
|
|
|
5701
6524
|
|
|
5702
6525
|
// src/secure-env.ts
|
|
5703
6526
|
var crypto = __toESM(require("crypto"));
|
|
5704
|
-
var
|
|
5705
|
-
var
|
|
6527
|
+
var fs3 = __toESM(require("fs"));
|
|
6528
|
+
var path3 = __toESM(require("path"));
|
|
5706
6529
|
var ALGORITHM = "aes-256-gcm";
|
|
5707
6530
|
var PBKDF2_ITERATIONS = 1e5;
|
|
5708
6531
|
var SALT_LENGTH = 32;
|
|
@@ -5769,10 +6592,10 @@ function parseEnvFile(content) {
|
|
|
5769
6592
|
return entries;
|
|
5770
6593
|
}
|
|
5771
6594
|
function encryptEnvFile(envPath, passphrase, deleteOriginal = false) {
|
|
5772
|
-
if (!
|
|
6595
|
+
if (!fs3.existsSync(envPath)) {
|
|
5773
6596
|
throw new Error(`File not found: ${envPath}`);
|
|
5774
6597
|
}
|
|
5775
|
-
const content =
|
|
6598
|
+
const content = fs3.readFileSync(envPath, "utf-8");
|
|
5776
6599
|
const entries = parseEnvFile(content);
|
|
5777
6600
|
if (entries.length === 0) {
|
|
5778
6601
|
throw new Error("No environment variables found in file");
|
|
@@ -5802,9 +6625,9 @@ function encryptEnvFile(envPath, passphrase, deleteOriginal = false) {
|
|
|
5802
6625
|
entries: encryptedEntries
|
|
5803
6626
|
};
|
|
5804
6627
|
const encPath = envPath + ".enc";
|
|
5805
|
-
|
|
6628
|
+
fs3.writeFileSync(encPath, JSON.stringify(encryptedEnv, null, 2), { mode: 384 });
|
|
5806
6629
|
if (deleteOriginal) {
|
|
5807
|
-
|
|
6630
|
+
fs3.unlinkSync(envPath);
|
|
5808
6631
|
}
|
|
5809
6632
|
const sensitiveCount = encryptedEntries.filter((e) => e.encrypted).length;
|
|
5810
6633
|
const plainCount = encryptedEntries.filter((e) => !e.encrypted).length;
|
|
@@ -5814,10 +6637,10 @@ function encryptEnvFile(envPath, passphrase, deleteOriginal = false) {
|
|
|
5814
6637
|
return encPath;
|
|
5815
6638
|
}
|
|
5816
6639
|
function decryptEnvFile(encPath, passphrase) {
|
|
5817
|
-
if (!
|
|
6640
|
+
if (!fs3.existsSync(encPath)) {
|
|
5818
6641
|
throw new Error(`Encrypted env file not found: ${encPath}`);
|
|
5819
6642
|
}
|
|
5820
|
-
const content = JSON.parse(
|
|
6643
|
+
const content = JSON.parse(fs3.readFileSync(encPath, "utf-8"));
|
|
5821
6644
|
if (content.version !== 1) {
|
|
5822
6645
|
throw new Error(`Unsupported encrypted env version: ${content.version}`);
|
|
5823
6646
|
}
|
|
@@ -5843,9 +6666,9 @@ function decryptEnvFile(encPath, passphrase) {
|
|
|
5843
6666
|
return result;
|
|
5844
6667
|
}
|
|
5845
6668
|
function loadSecureEnv(basePath, passphrase) {
|
|
5846
|
-
const encPath =
|
|
5847
|
-
const envPath =
|
|
5848
|
-
if (
|
|
6669
|
+
const encPath = path3.join(basePath, ".env.enc");
|
|
6670
|
+
const envPath = path3.join(basePath, ".env");
|
|
6671
|
+
if (fs3.existsSync(encPath)) {
|
|
5849
6672
|
if (!passphrase) {
|
|
5850
6673
|
passphrase = process.env.EXAGENT_PASSPHRASE;
|
|
5851
6674
|
}
|
|
@@ -5864,8 +6687,8 @@ function loadSecureEnv(basePath, passphrase) {
|
|
|
5864
6687
|
return true;
|
|
5865
6688
|
}
|
|
5866
6689
|
}
|
|
5867
|
-
if (
|
|
5868
|
-
const content =
|
|
6690
|
+
if (fs3.existsSync(envPath)) {
|
|
6691
|
+
const content = fs3.readFileSync(envPath, "utf-8");
|
|
5869
6692
|
const entries = parseEnvFile(content);
|
|
5870
6693
|
const sensitiveKeys = entries.filter(({ key }) => isSensitiveKey(key)).map(({ key }) => key);
|
|
5871
6694
|
if (sensitiveKeys.length > 0) {
|
|
@@ -5884,7 +6707,7 @@ function loadSecureEnv(basePath, passphrase) {
|
|
|
5884
6707
|
}
|
|
5885
6708
|
|
|
5886
6709
|
// src/index.ts
|
|
5887
|
-
var AGENT_VERSION = "0.1.
|
|
6710
|
+
var AGENT_VERSION = "0.1.39";
|
|
5888
6711
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5889
6712
|
0 && (module.exports = {
|
|
5890
6713
|
AGENT_VERSION,
|