@exagent/agent 0.3.6 → 0.3.8

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.
Files changed (69) hide show
  1. package/dist/chunk-7UGLJO6W.js +6392 -0
  2. package/dist/chunk-EHAOPCTJ.js +6406 -0
  3. package/dist/chunk-FGMXTW5I.js +6540 -0
  4. package/dist/chunk-GYYW4EKM.js +6756 -0
  5. package/dist/chunk-IVA2SCSN.js +6756 -0
  6. package/dist/chunk-JHXCSGPC.js +6352 -0
  7. package/dist/chunk-V6O4UXVN.js +6345 -0
  8. package/dist/chunk-WTECTX2Z.js +6345 -0
  9. package/dist/cli.js +2 -2
  10. package/dist/index.d.ts +24 -2
  11. package/dist/index.js +1 -1
  12. package/package.json +12 -9
  13. package/src/bridge/across.ts +0 -240
  14. package/src/bridge/bridge-manager.ts +0 -87
  15. package/src/bridge/index.ts +0 -9
  16. package/src/bridge/types.ts +0 -77
  17. package/src/chains.ts +0 -105
  18. package/src/cli.ts +0 -250
  19. package/src/config.ts +0 -502
  20. package/src/diagnostics.ts +0 -335
  21. package/src/index.ts +0 -98
  22. package/src/llm/anthropic.ts +0 -63
  23. package/src/llm/base.ts +0 -264
  24. package/src/llm/deepseek.ts +0 -48
  25. package/src/llm/google.ts +0 -63
  26. package/src/llm/groq.ts +0 -48
  27. package/src/llm/index.ts +0 -42
  28. package/src/llm/mistral.ts +0 -48
  29. package/src/llm/ollama.ts +0 -52
  30. package/src/llm/openai.ts +0 -94
  31. package/src/llm/together.ts +0 -48
  32. package/src/llm-providers.ts +0 -8
  33. package/src/logger.ts +0 -137
  34. package/src/paper/executor.ts +0 -201
  35. package/src/paper/index.ts +0 -1
  36. package/src/perp/client.ts +0 -200
  37. package/src/perp/index.ts +0 -12
  38. package/src/perp/msgpack.ts +0 -272
  39. package/src/perp/orders.ts +0 -234
  40. package/src/perp/positions.ts +0 -126
  41. package/src/perp/signer.ts +0 -277
  42. package/src/perp/types.ts +0 -192
  43. package/src/perp/websocket.ts +0 -274
  44. package/src/position-tracker.ts +0 -243
  45. package/src/prediction/client.ts +0 -288
  46. package/src/prediction/index.ts +0 -3
  47. package/src/prediction/order-manager.ts +0 -297
  48. package/src/prediction/types.ts +0 -151
  49. package/src/relay.ts +0 -254
  50. package/src/runtime.ts +0 -1755
  51. package/src/scrub-secrets.ts +0 -39
  52. package/src/setup.ts +0 -392
  53. package/src/signal.ts +0 -212
  54. package/src/spot/aerodrome.ts +0 -158
  55. package/src/spot/client.ts +0 -138
  56. package/src/spot/index.ts +0 -11
  57. package/src/spot/swap-manager.ts +0 -219
  58. package/src/spot/types.ts +0 -203
  59. package/src/spot/uniswap.ts +0 -150
  60. package/src/store.ts +0 -50
  61. package/src/strategy/index.ts +0 -2
  62. package/src/strategy/loader.ts +0 -265
  63. package/src/strategy/templates.ts +0 -74
  64. package/src/trading/index.ts +0 -2
  65. package/src/trading/market.ts +0 -120
  66. package/src/trading/risk.ts +0 -107
  67. package/src/ui.ts +0 -75
  68. package/test/strategy-loader.test.ts +0 -150
  69. 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';
@@ -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
- }
@@ -1,2 +0,0 @@
1
- export { loadStrategy, validateStrategy } from './loader.js';
2
- export { getTemplate, listTemplates } from './templates.js';
@@ -1,265 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { extname, 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 MAX_PROMPT_SIGNALS = 10;
9
- const SUPPORTED_FILE_EXPORTS = new Set(['name', 'description', 'category', 'venues', 'systemPrompt', 'template']);
10
-
11
- const promptSignalSchema = z.object({
12
- symbol: z.string().min(1).max(80),
13
- side: z.enum(['buy', 'sell', 'long', 'short']),
14
- confidence: z.number().min(0).max(1).optional(),
15
- reasoning: z.string().max(1000).optional(),
16
- venue: z.string().min(1).max(80).optional(),
17
- chain: z.string().min(1).max(80).optional(),
18
- size: z.number().positive().optional(),
19
- price: z.number().positive().optional(),
20
- leverage: z.number().positive().max(100).optional(),
21
- orderType: z.enum(['market', 'limit', 'yes', 'no']).optional(),
22
- }).strict();
23
-
24
- const promptSignalArraySchema = z.array(promptSignalSchema).max(MAX_PROMPT_SIGNALS);
25
-
26
- const strategyMetadataSchema = z.object({
27
- name: z.string().min(1).max(120).optional(),
28
- description: z.string().max(1000).optional(),
29
- category: z.string().max(80).optional(),
30
- venues: z.array(z.string().min(1).max(80)).max(20).optional(),
31
- systemPrompt: z.string().min(1).max(12000).optional(),
32
- template: z.string().min(1).max(80).optional(),
33
- }).strict();
34
-
35
- export async function loadStrategy(config: {
36
- file?: string;
37
- code?: string;
38
- template?: string;
39
- prompt?: {
40
- name?: string;
41
- systemPrompt: string;
42
- venues?: string[];
43
- };
44
- }): Promise<StrategyFunction> {
45
- if (config.code?.trim()) {
46
- throw new Error('Raw JavaScript strategy code is disabled. Use a template or prompt-backed strategy instead.');
47
- }
48
-
49
- if (config.file) {
50
- return loadFromFile(config.file);
51
- }
52
-
53
- if (config.prompt) {
54
- return loadFromPrompt(config.prompt);
55
- }
56
-
57
- if (config.template) {
58
- const template = getTemplate(config.template);
59
- if (!template) {
60
- throw new Error(`Unknown strategy template: ${config.template}. Available: momentum, value, arbitrage, hold`);
61
- }
62
- if (!template.systemPrompt.trim()) {
63
- return holdStrategy;
64
- }
65
- return loadFromPrompt({
66
- name: template.name,
67
- systemPrompt: template.systemPrompt,
68
- venues: template.venues,
69
- });
70
- }
71
-
72
- // Default: hold strategy (no trades)
73
- return holdStrategy;
74
- }
75
-
76
- async function loadFromFile(filePath: string): Promise<StrategyFunction> {
77
- const resolved = resolve(filePath);
78
- if (!existsSync(resolved)) {
79
- throw new Error(`Strategy file not found: ${resolved}`);
80
- }
81
-
82
- try {
83
- const source = readFileSync(resolved, 'utf8');
84
- const metadata = extname(resolved) === '.json'
85
- ? strategyMetadataSchema.parse(JSON.parse(source))
86
- : parseStrategyMetadataModule(source, resolved);
87
-
88
- if (metadata.systemPrompt?.trim()) {
89
- return loadFromPrompt({
90
- name: metadata.name,
91
- systemPrompt: metadata.systemPrompt,
92
- venues: metadata.venues,
93
- });
94
- }
95
-
96
- if (metadata.template?.trim()) {
97
- const template = getTemplate(metadata.template);
98
- if (!template) {
99
- throw new Error(`Unknown strategy template: ${metadata.template}. Available: momentum, value, arbitrage, hold`);
100
- }
101
- if (!template.systemPrompt.trim()) {
102
- return holdStrategy;
103
- }
104
- return loadFromPrompt({
105
- name: template.name,
106
- systemPrompt: template.systemPrompt,
107
- venues: metadata.venues?.length ? metadata.venues : template.venues,
108
- });
109
- }
110
-
111
- throw new Error('Strategy file must define a systemPrompt or template. Executable strategy files are disabled.');
112
- } catch (err) {
113
- throw new Error(`Failed to load strategy from ${resolved}: ${(err as Error).message}`);
114
- }
115
- }
116
-
117
- function loadFromPrompt(config: {
118
- name?: string;
119
- systemPrompt: string;
120
- venues?: string[];
121
- }): StrategyFunction {
122
- return async (context: StrategyContext): Promise<TradeSignal[]> => {
123
- const prices = context.market.getPrices();
124
- const positions = context.position.openPositions.map((position) => ({
125
- token: position.token,
126
- quantity: position.quantity,
127
- costBasisPerUnit: position.costBasisPerUnit,
128
- venue: position.venue,
129
- chain: position.chain,
130
- }));
131
-
132
- const allowedVenues = config.venues?.filter(Boolean) ?? [];
133
- const response = await context.llm.chat([
134
- { role: 'system', content: config.systemPrompt },
135
- {
136
- role: 'user',
137
- content: [
138
- `Strategy: ${config.name || 'Prompt Strategy'}`,
139
- `Allowed venues: ${allowedVenues.join(', ') || 'none configured'}`,
140
- `Current prices: ${JSON.stringify(prices)}`,
141
- `Open positions: ${JSON.stringify(positions)}`,
142
- `Risk config: ${JSON.stringify(context.config)}`,
143
- 'Return ONLY a JSON array of trade intents. Do not include executable code. Do not invent fill IDs.',
144
- ].join('\n'),
145
- },
146
- ]);
147
-
148
- // Defense-in-depth: scrub any secrets the LLM might echo back before parsing
149
- const scrubbedContent = scrubSecrets(response.content);
150
-
151
- const jsonPayload = extractJsonArray(scrubbedContent);
152
- if (!jsonPayload) {
153
- context.log('Prompt strategy returned a non-JSON response; no signals emitted.');
154
- return [];
155
- }
156
-
157
- try {
158
- const parsed = promptSignalArraySchema.parse(JSON.parse(jsonPayload));
159
- const signals: TradeSignal[] = [];
160
- for (const signal of parsed) {
161
- const venue = signal.venue || allowedVenues[0];
162
- if (!venue) {
163
- context.log(`Prompt strategy skipped ${signal.symbol}: no allowed venue configured.`);
164
- continue;
165
- }
166
- if (allowedVenues.length > 0 && !allowedVenues.includes(venue)) {
167
- context.log(`Prompt strategy skipped ${signal.symbol}: venue ${venue} is not allowed.`);
168
- continue;
169
- }
170
-
171
- const price = signal.price ?? prices[signal.symbol.toUpperCase()];
172
- if (!price || price <= 0) {
173
- context.log(`Prompt strategy skipped ${signal.symbol}: no usable price in response or market cache.`);
174
- continue;
175
- }
176
-
177
- signals.push({
178
- symbol: signal.symbol,
179
- side: signal.side,
180
- confidence: signal.confidence ?? 0.5,
181
- reasoning: signal.reasoning,
182
- venue,
183
- chain: signal.chain,
184
- size: signal.size ?? 0,
185
- price,
186
- fee: 0,
187
- venueFillId: '',
188
- venueTimestamp: '',
189
- leverage: signal.leverage,
190
- orderType: signal.orderType,
191
- });
192
- }
193
- return signals;
194
- } catch (err) {
195
- context.log(`Prompt strategy parse failed: ${(err as Error).message}`);
196
- return [];
197
- }
198
- };
199
- }
200
-
201
- function extractJsonArray(content: string): string | null {
202
- const trimmed = content.trim();
203
- if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
204
- return trimmed;
205
- }
206
-
207
- const start = trimmed.indexOf('[');
208
- const end = trimmed.lastIndexOf(']');
209
- if (start === -1 || end === -1 || end <= start) {
210
- return null;
211
- }
212
- return trimmed.slice(start, end + 1);
213
- }
214
-
215
- function parseStrategyMetadataModule(source: string, filePath: string): z.infer<typeof strategyMetadataSchema> {
216
- const values: Record<string, unknown> = {};
217
- let statement = '';
218
-
219
- for (const rawLine of source.replace(/\r\n/g, '\n').split('\n')) {
220
- const line = rawLine.trim();
221
- if (!line || line.startsWith('//')) {
222
- continue;
223
- }
224
-
225
- statement = statement ? `${statement}\n${rawLine}` : rawLine;
226
- if (!line.endsWith(';')) {
227
- continue;
228
- }
229
-
230
- const match = statement.match(/^\s*export\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*([\s\S]*);\s*$/);
231
- if (!match) {
232
- throw new Error(`Unsupported statement in ${filePath}. Strategy files may only export JSON constants.`);
233
- }
234
-
235
- const [, name, rawValue] = match;
236
- if (name === 'code') {
237
- throw new Error('Raw JavaScript strategy code is disabled. Replace code with systemPrompt.');
238
- }
239
- if (!SUPPORTED_FILE_EXPORTS.has(name)) {
240
- throw new Error(`Unsupported strategy export "${name}".`);
241
- }
242
-
243
- try {
244
- values[name] = JSON.parse(rawValue.trim());
245
- } catch {
246
- throw new Error(`Strategy export "${name}" must be a JSON literal.`);
247
- }
248
-
249
- statement = '';
250
- }
251
-
252
- if (statement.trim()) {
253
- throw new Error(`Unterminated export in ${filePath}.`);
254
- }
255
-
256
- return strategyMetadataSchema.parse(values);
257
- }
258
-
259
- const holdStrategy: StrategyFunction = async (_context: StrategyContext): Promise<TradeSignal[]> => {
260
- return []; // No trades — hold position
261
- };
262
-
263
- export function validateStrategy(fn: unknown): fn is StrategyFunction {
264
- return typeof fn === 'function';
265
- }