@exagent/agent 0.3.5 → 0.3.7
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-7UGLJO6W.js +6392 -0
- package/dist/chunk-EHAOPCTJ.js +6406 -0
- package/dist/chunk-FGMXTW5I.js +6540 -0
- package/dist/chunk-IVA2SCSN.js +6756 -0
- package/dist/chunk-JHXCSGPC.js +6352 -0
- package/dist/chunk-V6O4UXVN.js +6345 -0
- package/dist/chunk-ZRAOPQQW.js +6406 -0
- package/dist/cli.js +40 -98
- package/dist/index.d.ts +24 -2
- package/dist/index.js +1 -1
- package/package.json +17 -14
- package/.turbo/turbo-build.log +0 -17
- package/src/bridge/across.ts +0 -240
- package/src/bridge/bridge-manager.ts +0 -87
- package/src/bridge/index.ts +0 -9
- package/src/bridge/types.ts +0 -77
- package/src/chains.ts +0 -105
- package/src/cli.ts +0 -244
- package/src/config.ts +0 -499
- package/src/diagnostics.ts +0 -335
- package/src/index.ts +0 -98
- package/src/llm/anthropic.ts +0 -63
- package/src/llm/base.ts +0 -264
- package/src/llm/deepseek.ts +0 -48
- package/src/llm/google.ts +0 -63
- package/src/llm/groq.ts +0 -48
- package/src/llm/index.ts +0 -42
- package/src/llm/mistral.ts +0 -48
- package/src/llm/ollama.ts +0 -52
- package/src/llm/openai.ts +0 -51
- package/src/llm/together.ts +0 -48
- package/src/llm-providers.ts +0 -100
- package/src/logger.ts +0 -137
- package/src/paper/executor.ts +0 -201
- package/src/paper/index.ts +0 -1
- package/src/perp/client.ts +0 -200
- package/src/perp/index.ts +0 -12
- package/src/perp/msgpack.ts +0 -272
- package/src/perp/orders.ts +0 -234
- package/src/perp/positions.ts +0 -126
- package/src/perp/signer.ts +0 -277
- package/src/perp/types.ts +0 -192
- package/src/perp/websocket.ts +0 -274
- package/src/position-tracker.ts +0 -243
- package/src/prediction/client.ts +0 -281
- package/src/prediction/index.ts +0 -3
- package/src/prediction/order-manager.ts +0 -297
- package/src/prediction/types.ts +0 -151
- package/src/relay.ts +0 -254
- package/src/runtime.ts +0 -1755
- package/src/scrub-secrets.ts +0 -39
- package/src/setup.ts +0 -384
- package/src/signal.ts +0 -212
- package/src/spot/aerodrome.ts +0 -158
- package/src/spot/client.ts +0 -138
- package/src/spot/index.ts +0 -11
- package/src/spot/swap-manager.ts +0 -219
- package/src/spot/types.ts +0 -203
- package/src/spot/uniswap.ts +0 -150
- package/src/store.ts +0 -50
- package/src/strategy/index.ts +0 -2
- package/src/strategy/loader.ts +0 -191
- package/src/strategy/templates.ts +0 -125
- package/src/trading/index.ts +0 -2
- package/src/trading/market.ts +0 -120
- package/src/trading/risk.ts +0 -107
- package/src/ui.ts +0 -75
- package/test-bridge-arb-to-base.mjs +0 -223
- package/test-funded-check.mjs +0 -79
- package/test-funded-phase19.mjs +0 -933
- package/test-hl-deposit-recover.mjs +0 -281
- package/test-hl-withdraw.mjs +0 -372
- package/test-live-signing.mjs +0 -374
- package/test-phase7.mjs +0 -416
- package/test-recover-arb.mjs +0 -206
- package/test-spot-bridge.mjs +0 -248
- package/test-wallet-setup.mjs +0 -126
- package/tsconfig.json +0 -8
package/src/spot/types.ts
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
// ─── Spot Config ────────────────────────────────────────────────
|
|
2
|
-
|
|
3
|
-
export interface SpotConfig {
|
|
4
|
-
enabled: boolean;
|
|
5
|
-
chains: string[];
|
|
6
|
-
defaultChain: string;
|
|
7
|
-
maxSlippageBps: number;
|
|
8
|
-
maxSwapValueUSD: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const DEFAULT_SPOT_CONFIG: SpotConfig = {
|
|
12
|
-
enabled: false,
|
|
13
|
-
chains: ['base'],
|
|
14
|
-
defaultChain: 'base',
|
|
15
|
-
maxSlippageBps: 50,
|
|
16
|
-
maxSwapValueUSD: 10_000,
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// ─── Swap Params & Results ──────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
export interface SpotSwapParams {
|
|
22
|
-
tokenIn: `0x${string}`;
|
|
23
|
-
tokenOut: `0x${string}`;
|
|
24
|
-
amountIn: bigint;
|
|
25
|
-
slippageBps: number;
|
|
26
|
-
chain: string;
|
|
27
|
-
dex: 'uniswap' | 'aerodrome';
|
|
28
|
-
recipient?: `0x${string}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface SpotQuoteResult {
|
|
32
|
-
amountOut: bigint;
|
|
33
|
-
feeTier?: number;
|
|
34
|
-
stable?: boolean;
|
|
35
|
-
route: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface SpotSwapResult {
|
|
39
|
-
success: boolean;
|
|
40
|
-
txHash: `0x${string}`;
|
|
41
|
-
amountIn: bigint;
|
|
42
|
-
amountOut: bigint;
|
|
43
|
-
tokenIn: `0x${string}`;
|
|
44
|
-
tokenOut: `0x${string}`;
|
|
45
|
-
effectivePrice: number;
|
|
46
|
-
gasCost: bigint;
|
|
47
|
-
chain: string;
|
|
48
|
-
dex: string;
|
|
49
|
-
error?: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ─── ABI Fragments ──────────────────────────────────────────────
|
|
53
|
-
|
|
54
|
-
export const ERC20_ABI = [
|
|
55
|
-
{
|
|
56
|
-
type: 'function' as const,
|
|
57
|
-
name: 'approve' as const,
|
|
58
|
-
inputs: [
|
|
59
|
-
{ name: 'spender', type: 'address' as const },
|
|
60
|
-
{ name: 'amount', type: 'uint256' as const },
|
|
61
|
-
],
|
|
62
|
-
outputs: [{ type: 'bool' as const }],
|
|
63
|
-
stateMutability: 'nonpayable' as const,
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
type: 'function' as const,
|
|
67
|
-
name: 'allowance' as const,
|
|
68
|
-
inputs: [
|
|
69
|
-
{ name: 'owner', type: 'address' as const },
|
|
70
|
-
{ name: 'spender', type: 'address' as const },
|
|
71
|
-
],
|
|
72
|
-
outputs: [{ type: 'uint256' as const }],
|
|
73
|
-
stateMutability: 'view' as const,
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
type: 'function' as const,
|
|
77
|
-
name: 'balanceOf' as const,
|
|
78
|
-
inputs: [{ name: 'account', type: 'address' as const }],
|
|
79
|
-
outputs: [{ type: 'uint256' as const }],
|
|
80
|
-
stateMutability: 'view' as const,
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
type: 'function' as const,
|
|
84
|
-
name: 'decimals' as const,
|
|
85
|
-
inputs: [],
|
|
86
|
-
outputs: [{ type: 'uint8' as const }],
|
|
87
|
-
stateMutability: 'view' as const,
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
type: 'function' as const,
|
|
91
|
-
name: 'symbol' as const,
|
|
92
|
-
inputs: [],
|
|
93
|
-
outputs: [{ type: 'string' as const }],
|
|
94
|
-
stateMutability: 'view' as const,
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
type: 'function' as const,
|
|
98
|
-
name: 'transfer' as const,
|
|
99
|
-
inputs: [
|
|
100
|
-
{ name: 'to', type: 'address' as const },
|
|
101
|
-
{ name: 'amount', type: 'uint256' as const },
|
|
102
|
-
],
|
|
103
|
-
outputs: [{ type: 'bool' as const }],
|
|
104
|
-
stateMutability: 'nonpayable' as const,
|
|
105
|
-
},
|
|
106
|
-
] as const;
|
|
107
|
-
|
|
108
|
-
export const UNISWAP_QUOTER_V2_ABI = [
|
|
109
|
-
{
|
|
110
|
-
type: 'function' as const,
|
|
111
|
-
name: 'quoteExactInputSingle' as const,
|
|
112
|
-
inputs: [
|
|
113
|
-
{
|
|
114
|
-
name: 'params',
|
|
115
|
-
type: 'tuple' as const,
|
|
116
|
-
components: [
|
|
117
|
-
{ name: 'tokenIn', type: 'address' as const },
|
|
118
|
-
{ name: 'tokenOut', type: 'address' as const },
|
|
119
|
-
{ name: 'amountIn', type: 'uint256' as const },
|
|
120
|
-
{ name: 'fee', type: 'uint24' as const },
|
|
121
|
-
{ name: 'sqrtPriceLimitX96', type: 'uint160' as const },
|
|
122
|
-
],
|
|
123
|
-
},
|
|
124
|
-
],
|
|
125
|
-
outputs: [
|
|
126
|
-
{ name: 'amountOut', type: 'uint256' as const },
|
|
127
|
-
{ name: 'sqrtPriceX96After', type: 'uint160' as const },
|
|
128
|
-
{ name: 'initializedTicksCrossed', type: 'uint32' as const },
|
|
129
|
-
{ name: 'gasEstimate', type: 'uint256' as const },
|
|
130
|
-
],
|
|
131
|
-
stateMutability: 'nonpayable' as const,
|
|
132
|
-
},
|
|
133
|
-
] as const;
|
|
134
|
-
|
|
135
|
-
export const UNISWAP_SWAP_ROUTER_ABI = [
|
|
136
|
-
{
|
|
137
|
-
type: 'function' as const,
|
|
138
|
-
name: 'exactInputSingle' as const,
|
|
139
|
-
inputs: [
|
|
140
|
-
{
|
|
141
|
-
name: 'params',
|
|
142
|
-
type: 'tuple' as const,
|
|
143
|
-
components: [
|
|
144
|
-
{ name: 'tokenIn', type: 'address' as const },
|
|
145
|
-
{ name: 'tokenOut', type: 'address' as const },
|
|
146
|
-
{ name: 'fee', type: 'uint24' as const },
|
|
147
|
-
{ name: 'recipient', type: 'address' as const },
|
|
148
|
-
{ name: 'amountIn', type: 'uint256' as const },
|
|
149
|
-
{ name: 'amountOutMinimum', type: 'uint256' as const },
|
|
150
|
-
{ name: 'sqrtPriceLimitX96', type: 'uint160' as const },
|
|
151
|
-
],
|
|
152
|
-
},
|
|
153
|
-
],
|
|
154
|
-
outputs: [{ name: 'amountOut', type: 'uint256' as const }],
|
|
155
|
-
stateMutability: 'payable' as const,
|
|
156
|
-
},
|
|
157
|
-
] as const;
|
|
158
|
-
|
|
159
|
-
export const AERODROME_ROUTER_ABI = [
|
|
160
|
-
{
|
|
161
|
-
type: 'function' as const,
|
|
162
|
-
name: 'swapExactTokensForTokens' as const,
|
|
163
|
-
inputs: [
|
|
164
|
-
{ name: 'amountIn', type: 'uint256' as const },
|
|
165
|
-
{ name: 'amountOutMin', type: 'uint256' as const },
|
|
166
|
-
{
|
|
167
|
-
name: 'routes',
|
|
168
|
-
type: 'tuple[]' as const,
|
|
169
|
-
components: [
|
|
170
|
-
{ name: 'from', type: 'address' as const },
|
|
171
|
-
{ name: 'to', type: 'address' as const },
|
|
172
|
-
{ name: 'stable', type: 'bool' as const },
|
|
173
|
-
{ name: 'factory', type: 'address' as const },
|
|
174
|
-
],
|
|
175
|
-
},
|
|
176
|
-
{ name: 'to', type: 'address' as const },
|
|
177
|
-
{ name: 'deadline', type: 'uint256' as const },
|
|
178
|
-
],
|
|
179
|
-
outputs: [{ name: 'amounts', type: 'uint256[]' as const }],
|
|
180
|
-
stateMutability: 'nonpayable' as const,
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
type: 'function' as const,
|
|
184
|
-
name: 'getAmountsOut' as const,
|
|
185
|
-
inputs: [
|
|
186
|
-
{ name: 'amountIn', type: 'uint256' as const },
|
|
187
|
-
{
|
|
188
|
-
name: 'routes',
|
|
189
|
-
type: 'tuple[]' as const,
|
|
190
|
-
components: [
|
|
191
|
-
{ name: 'from', type: 'address' as const },
|
|
192
|
-
{ name: 'to', type: 'address' as const },
|
|
193
|
-
{ name: 'stable', type: 'bool' as const },
|
|
194
|
-
{ name: 'factory', type: 'address' as const },
|
|
195
|
-
],
|
|
196
|
-
},
|
|
197
|
-
],
|
|
198
|
-
outputs: [{ name: 'amounts', type: 'uint256[]' as const }],
|
|
199
|
-
stateMutability: 'view' as const,
|
|
200
|
-
},
|
|
201
|
-
] as const;
|
|
202
|
-
|
|
203
|
-
export const AERODROME_DEFAULT_FACTORY: `0x${string}` = '0x420DD381b31aEf6683db6B902084cB0FFECe40Da';
|
package/src/spot/uniswap.ts
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { getChainConfig } from '../chains.js';
|
|
2
|
-
import type { SpotDEXClient } from './client.js';
|
|
3
|
-
import {
|
|
4
|
-
UNISWAP_QUOTER_V2_ABI,
|
|
5
|
-
UNISWAP_SWAP_ROUTER_ABI,
|
|
6
|
-
type SpotSwapParams,
|
|
7
|
-
type SpotQuoteResult,
|
|
8
|
-
type SpotSwapResult,
|
|
9
|
-
} from './types.js';
|
|
10
|
-
|
|
11
|
-
const FEE_TIERS = [500, 3000, 10000] as const; // 0.05%, 0.3%, 1%
|
|
12
|
-
|
|
13
|
-
export class UniswapAdapter {
|
|
14
|
-
private client: SpotDEXClient;
|
|
15
|
-
|
|
16
|
-
constructor(client: SpotDEXClient) {
|
|
17
|
-
this.client = client;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async quote(params: SpotSwapParams): Promise<SpotQuoteResult> {
|
|
21
|
-
const chainConfig = getChainConfig(params.chain);
|
|
22
|
-
if (!chainConfig?.uniswapQuoter) {
|
|
23
|
-
throw new Error(`No Uniswap QuoterV2 on ${params.chain}`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const { publicClient } = this.client.getClients(params.chain);
|
|
27
|
-
let bestAmountOut = 0n;
|
|
28
|
-
let bestFeeTier = 0;
|
|
29
|
-
|
|
30
|
-
for (const fee of FEE_TIERS) {
|
|
31
|
-
try {
|
|
32
|
-
const result = await publicClient.simulateContract({
|
|
33
|
-
address: chainConfig.uniswapQuoter,
|
|
34
|
-
abi: UNISWAP_QUOTER_V2_ABI,
|
|
35
|
-
functionName: 'quoteExactInputSingle',
|
|
36
|
-
args: [
|
|
37
|
-
{
|
|
38
|
-
tokenIn: params.tokenIn,
|
|
39
|
-
tokenOut: params.tokenOut,
|
|
40
|
-
amountIn: params.amountIn,
|
|
41
|
-
fee,
|
|
42
|
-
sqrtPriceLimitX96: 0n,
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const amountOut = result.result[0];
|
|
48
|
-
if (amountOut > bestAmountOut) {
|
|
49
|
-
bestAmountOut = amountOut;
|
|
50
|
-
bestFeeTier = fee;
|
|
51
|
-
}
|
|
52
|
-
} catch {
|
|
53
|
-
// Pool doesn't exist for this fee tier — skip
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (bestAmountOut === 0n) {
|
|
58
|
-
throw new Error(`No Uniswap V3 pool found for ${params.tokenIn}/${params.tokenOut} on ${params.chain}`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const tokenInSymbol = await this.client.getSymbol(params.tokenIn, params.chain);
|
|
62
|
-
const tokenOutSymbol = await this.client.getSymbol(params.tokenOut, params.chain);
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
amountOut: bestAmountOut,
|
|
66
|
-
feeTier: bestFeeTier,
|
|
67
|
-
route: `${tokenInSymbol} → ${tokenOutSymbol} (${bestFeeTier / 10000}%)`,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async swap(params: SpotSwapParams): Promise<SpotSwapResult> {
|
|
72
|
-
const chainConfig = getChainConfig(params.chain);
|
|
73
|
-
if (!chainConfig) {
|
|
74
|
-
throw new Error(`Unknown chain: ${params.chain}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const routerAddress = chainConfig.dexRouters.uniswap;
|
|
78
|
-
if (!routerAddress) {
|
|
79
|
-
throw new Error(`No Uniswap router on ${params.chain}`);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Get quote for fee tier selection and slippage calc
|
|
83
|
-
const quoteResult = await this.quote(params);
|
|
84
|
-
const amountOutMinimum = (quoteResult.amountOut * BigInt(10000 - params.slippageBps)) / 10000n;
|
|
85
|
-
|
|
86
|
-
// Ensure approval
|
|
87
|
-
await this.client.ensureApproval(params.tokenIn, routerAddress, params.amountIn, params.chain);
|
|
88
|
-
|
|
89
|
-
const { publicClient, walletClient } = this.client.getClients(params.chain);
|
|
90
|
-
const recipient = params.recipient ?? this.client.address;
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
const hash = await walletClient.writeContract({
|
|
94
|
-
address: routerAddress,
|
|
95
|
-
abi: UNISWAP_SWAP_ROUTER_ABI,
|
|
96
|
-
functionName: 'exactInputSingle',
|
|
97
|
-
args: [
|
|
98
|
-
{
|
|
99
|
-
tokenIn: params.tokenIn,
|
|
100
|
-
tokenOut: params.tokenOut,
|
|
101
|
-
fee: quoteResult.feeTier!,
|
|
102
|
-
recipient,
|
|
103
|
-
amountIn: params.amountIn,
|
|
104
|
-
amountOutMinimum,
|
|
105
|
-
sqrtPriceLimitX96: 0n,
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
111
|
-
|
|
112
|
-
// Calculate effective price from amounts
|
|
113
|
-
const tokenInDecimals = await this.client.getDecimals(params.tokenIn, params.chain);
|
|
114
|
-
const tokenOutDecimals = await this.client.getDecimals(params.tokenOut, params.chain);
|
|
115
|
-
const amountInFloat = Number(params.amountIn) / 10 ** tokenInDecimals;
|
|
116
|
-
const amountOutFloat = Number(quoteResult.amountOut) / 10 ** tokenOutDecimals;
|
|
117
|
-
const effectivePrice = amountOutFloat / amountInFloat;
|
|
118
|
-
|
|
119
|
-
const gasUsed = receipt.gasUsed ?? 0n;
|
|
120
|
-
const gasPrice = receipt.effectiveGasPrice ?? 0n;
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
success: true,
|
|
124
|
-
txHash: hash,
|
|
125
|
-
amountIn: params.amountIn,
|
|
126
|
-
amountOut: quoteResult.amountOut,
|
|
127
|
-
tokenIn: params.tokenIn,
|
|
128
|
-
tokenOut: params.tokenOut,
|
|
129
|
-
effectivePrice,
|
|
130
|
-
gasCost: gasUsed * gasPrice,
|
|
131
|
-
chain: params.chain,
|
|
132
|
-
dex: 'uniswap',
|
|
133
|
-
};
|
|
134
|
-
} catch (err) {
|
|
135
|
-
return {
|
|
136
|
-
success: false,
|
|
137
|
-
txHash: '0x0' as `0x${string}`,
|
|
138
|
-
amountIn: params.amountIn,
|
|
139
|
-
amountOut: 0n,
|
|
140
|
-
tokenIn: params.tokenIn,
|
|
141
|
-
tokenOut: params.tokenOut,
|
|
142
|
-
effectivePrice: 0,
|
|
143
|
-
gasCost: 0n,
|
|
144
|
-
chain: params.chain,
|
|
145
|
-
dex: 'uniswap',
|
|
146
|
-
error: (err as Error).message,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
package/src/store.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
2
|
-
import { dirname } from 'node:path';
|
|
3
|
-
import type { StrategyStore } from '@exagent/sdk';
|
|
4
|
-
|
|
5
|
-
export class FileStore implements StrategyStore {
|
|
6
|
-
private data: Record<string, unknown> = {};
|
|
7
|
-
private filePath: string;
|
|
8
|
-
|
|
9
|
-
constructor(filePath: string = 'data/strategy-store.json') {
|
|
10
|
-
this.filePath = filePath;
|
|
11
|
-
this.load();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
private load(): void {
|
|
15
|
-
try {
|
|
16
|
-
if (existsSync(this.filePath)) {
|
|
17
|
-
const raw = readFileSync(this.filePath, 'utf-8');
|
|
18
|
-
this.data = JSON.parse(raw);
|
|
19
|
-
}
|
|
20
|
-
} catch {
|
|
21
|
-
this.data = {};
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
private flush(): void {
|
|
26
|
-
const dir = dirname(this.filePath);
|
|
27
|
-
if (!existsSync(dir)) {
|
|
28
|
-
mkdirSync(dir, { recursive: true });
|
|
29
|
-
}
|
|
30
|
-
writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
get<T>(key: string): T | undefined {
|
|
34
|
-
return this.data[key] as T | undefined;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
set<T>(key: string, value: T): void {
|
|
38
|
-
this.data[key] = value;
|
|
39
|
-
this.flush();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
delete(key: string): void {
|
|
43
|
-
delete this.data[key];
|
|
44
|
-
this.flush();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
keys(): string[] {
|
|
48
|
-
return Object.keys(this.data);
|
|
49
|
-
}
|
|
50
|
-
}
|
package/src/strategy/index.ts
DELETED
package/src/strategy/loader.ts
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import type { StrategyFunction, StrategyContext, TradeSignal } from '@exagent/sdk';
|
|
5
|
-
import { getTemplate } from './templates.js';
|
|
6
|
-
import { scrubSecrets } from '../scrub-secrets.js';
|
|
7
|
-
|
|
8
|
-
const promptSignalSchema = z.object({
|
|
9
|
-
symbol: z.string().min(1),
|
|
10
|
-
side: z.enum(['buy', 'sell', 'long', 'short']),
|
|
11
|
-
confidence: z.number().min(0).max(1).optional(),
|
|
12
|
-
reasoning: z.string().optional(),
|
|
13
|
-
venue: z.string().optional(),
|
|
14
|
-
chain: z.string().optional(),
|
|
15
|
-
size: z.number().positive().optional(),
|
|
16
|
-
price: z.number().positive().optional(),
|
|
17
|
-
fee: z.number().min(0).optional(),
|
|
18
|
-
venueFillId: z.string().optional(),
|
|
19
|
-
venueTimestamp: z.string().optional(),
|
|
20
|
-
leverage: z.number().positive().optional(),
|
|
21
|
-
orderType: z.string().optional(),
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const promptSignalArraySchema = z.array(promptSignalSchema);
|
|
25
|
-
|
|
26
|
-
export async function loadStrategy(config: {
|
|
27
|
-
file?: string;
|
|
28
|
-
code?: string;
|
|
29
|
-
template?: string;
|
|
30
|
-
prompt?: {
|
|
31
|
-
name?: string;
|
|
32
|
-
systemPrompt: string;
|
|
33
|
-
venues?: string[];
|
|
34
|
-
};
|
|
35
|
-
}): Promise<StrategyFunction> {
|
|
36
|
-
if (config.file) {
|
|
37
|
-
return loadFromFile(config.file);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (config.code) {
|
|
41
|
-
return loadFromCode(config.code);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (config.prompt) {
|
|
45
|
-
return loadFromPrompt(config.prompt);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (config.template) {
|
|
49
|
-
const template = getTemplate(config.template);
|
|
50
|
-
if (!template) {
|
|
51
|
-
throw new Error(`Unknown strategy template: ${config.template}. Available: momentum, value, arbitrage, hold`);
|
|
52
|
-
}
|
|
53
|
-
return loadFromCode(template.code);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Default: hold strategy (no trades)
|
|
57
|
-
return holdStrategy;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function loadFromFile(filePath: string): Promise<StrategyFunction> {
|
|
61
|
-
const resolved = resolve(filePath);
|
|
62
|
-
if (!existsSync(resolved)) {
|
|
63
|
-
throw new Error(`Strategy file not found: ${resolved}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const mod = await import(resolved);
|
|
68
|
-
const fn = mod.default || mod.strategy;
|
|
69
|
-
|
|
70
|
-
if (typeof fn !== 'function') {
|
|
71
|
-
if (typeof mod.code === 'string' && mod.code.trim()) {
|
|
72
|
-
return loadFromCode(mod.code);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (typeof mod.systemPrompt === 'string' && mod.systemPrompt.trim()) {
|
|
76
|
-
const venues = Array.isArray(mod.venues)
|
|
77
|
-
? mod.venues.filter((venue: unknown): venue is string => typeof venue === 'string')
|
|
78
|
-
: undefined;
|
|
79
|
-
const name = typeof mod.name === 'string' ? mod.name : undefined;
|
|
80
|
-
return loadFromPrompt({
|
|
81
|
-
name,
|
|
82
|
-
systemPrompt: mod.systemPrompt,
|
|
83
|
-
venues,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (typeof mod.template === 'string' && mod.template.trim()) {
|
|
88
|
-
const template = getTemplate(mod.template);
|
|
89
|
-
if (!template) {
|
|
90
|
-
throw new Error(`Unknown strategy template: ${mod.template}. Available: momentum, value, arbitrage, hold`);
|
|
91
|
-
}
|
|
92
|
-
return loadFromCode(template.code);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
throw new Error(`Strategy file must export a default function, 'strategy' function, 'code' string, 'systemPrompt' string, or 'template' string`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return fn as StrategyFunction;
|
|
99
|
-
} catch (err) {
|
|
100
|
-
throw new Error(`Failed to load strategy from ${resolved}: ${(err as Error).message}`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function loadFromCode(code: string): Promise<StrategyFunction> {
|
|
105
|
-
// Templates return a factory function as a string
|
|
106
|
-
// We wrap it in a module and evaluate
|
|
107
|
-
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
|
108
|
-
const fn = new AsyncFunction('context', code) as StrategyFunction;
|
|
109
|
-
return fn;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function loadFromPrompt(config: {
|
|
113
|
-
name?: string;
|
|
114
|
-
systemPrompt: string;
|
|
115
|
-
venues?: string[];
|
|
116
|
-
}): StrategyFunction {
|
|
117
|
-
return async (context: StrategyContext): Promise<TradeSignal[]> => {
|
|
118
|
-
const prices = context.market.getPrices();
|
|
119
|
-
const positions = context.position.openPositions.map((position) => ({
|
|
120
|
-
token: position.token,
|
|
121
|
-
quantity: position.quantity,
|
|
122
|
-
costBasisPerUnit: position.costBasisPerUnit,
|
|
123
|
-
venue: position.venue,
|
|
124
|
-
chain: position.chain,
|
|
125
|
-
}));
|
|
126
|
-
|
|
127
|
-
const response = await context.llm.chat([
|
|
128
|
-
{ role: 'system', content: config.systemPrompt },
|
|
129
|
-
{
|
|
130
|
-
role: 'user',
|
|
131
|
-
content: [
|
|
132
|
-
`Strategy: ${config.name || 'Prompt Strategy'}`,
|
|
133
|
-
`Allowed venues: ${(config.venues || []).join(', ') || 'any'}`,
|
|
134
|
-
`Current prices: ${JSON.stringify(prices)}`,
|
|
135
|
-
`Open positions: ${JSON.stringify(positions)}`,
|
|
136
|
-
`Risk config: ${JSON.stringify(context.config)}`,
|
|
137
|
-
'Return ONLY a JSON array of trade signals.',
|
|
138
|
-
].join('\n'),
|
|
139
|
-
},
|
|
140
|
-
]);
|
|
141
|
-
|
|
142
|
-
// Defense-in-depth: scrub any secrets the LLM might echo back before parsing
|
|
143
|
-
const scrubbedContent = scrubSecrets(response.content);
|
|
144
|
-
|
|
145
|
-
const match = scrubbedContent.match(/\[[\s\S]*\]/);
|
|
146
|
-
if (!match) {
|
|
147
|
-
context.log('Prompt strategy returned a non-JSON response; no signals emitted.');
|
|
148
|
-
return [];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
const parsed = promptSignalArraySchema.parse(JSON.parse(match[0]));
|
|
153
|
-
const signals: TradeSignal[] = [];
|
|
154
|
-
for (const signal of parsed) {
|
|
155
|
-
const price = signal.price ?? prices[signal.symbol.toUpperCase()];
|
|
156
|
-
if (!price || price <= 0) {
|
|
157
|
-
context.log(`Prompt strategy skipped ${signal.symbol}: no usable price in response or market cache.`);
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
signals.push({
|
|
162
|
-
symbol: signal.symbol,
|
|
163
|
-
side: signal.side,
|
|
164
|
-
confidence: signal.confidence ?? 0.5,
|
|
165
|
-
reasoning: signal.reasoning,
|
|
166
|
-
venue: signal.venue || config.venues?.[0] || 'manual',
|
|
167
|
-
chain: signal.chain,
|
|
168
|
-
size: signal.size ?? 1,
|
|
169
|
-
price,
|
|
170
|
-
fee: signal.fee ?? 0,
|
|
171
|
-
venueFillId: signal.venueFillId ?? '',
|
|
172
|
-
venueTimestamp: signal.venueTimestamp ?? new Date().toISOString(),
|
|
173
|
-
leverage: signal.leverage,
|
|
174
|
-
orderType: signal.orderType,
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
return signals;
|
|
178
|
-
} catch (err) {
|
|
179
|
-
context.log(`Prompt strategy parse failed: ${(err as Error).message}`);
|
|
180
|
-
return [];
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const holdStrategy: StrategyFunction = async (_context: StrategyContext): Promise<TradeSignal[]> => {
|
|
186
|
-
return []; // No trades — hold position
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
export function validateStrategy(fn: unknown): fn is StrategyFunction {
|
|
190
|
-
return typeof fn === 'function';
|
|
191
|
-
}
|