@exagent/agent 0.3.6 → 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.
Files changed (68) 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-IVA2SCSN.js +6756 -0
  5. package/dist/chunk-JHXCSGPC.js +6352 -0
  6. package/dist/chunk-V6O4UXVN.js +6345 -0
  7. package/dist/chunk-WTECTX2Z.js +6345 -0
  8. package/dist/cli.js +2 -2
  9. package/dist/index.d.ts +24 -2
  10. package/dist/index.js +1 -1
  11. package/package.json +12 -9
  12. package/src/bridge/across.ts +0 -240
  13. package/src/bridge/bridge-manager.ts +0 -87
  14. package/src/bridge/index.ts +0 -9
  15. package/src/bridge/types.ts +0 -77
  16. package/src/chains.ts +0 -105
  17. package/src/cli.ts +0 -250
  18. package/src/config.ts +0 -502
  19. package/src/diagnostics.ts +0 -335
  20. package/src/index.ts +0 -98
  21. package/src/llm/anthropic.ts +0 -63
  22. package/src/llm/base.ts +0 -264
  23. package/src/llm/deepseek.ts +0 -48
  24. package/src/llm/google.ts +0 -63
  25. package/src/llm/groq.ts +0 -48
  26. package/src/llm/index.ts +0 -42
  27. package/src/llm/mistral.ts +0 -48
  28. package/src/llm/ollama.ts +0 -52
  29. package/src/llm/openai.ts +0 -94
  30. package/src/llm/together.ts +0 -48
  31. package/src/llm-providers.ts +0 -8
  32. package/src/logger.ts +0 -137
  33. package/src/paper/executor.ts +0 -201
  34. package/src/paper/index.ts +0 -1
  35. package/src/perp/client.ts +0 -200
  36. package/src/perp/index.ts +0 -12
  37. package/src/perp/msgpack.ts +0 -272
  38. package/src/perp/orders.ts +0 -234
  39. package/src/perp/positions.ts +0 -126
  40. package/src/perp/signer.ts +0 -277
  41. package/src/perp/types.ts +0 -192
  42. package/src/perp/websocket.ts +0 -274
  43. package/src/position-tracker.ts +0 -243
  44. package/src/prediction/client.ts +0 -288
  45. package/src/prediction/index.ts +0 -3
  46. package/src/prediction/order-manager.ts +0 -297
  47. package/src/prediction/types.ts +0 -151
  48. package/src/relay.ts +0 -254
  49. package/src/runtime.ts +0 -1755
  50. package/src/scrub-secrets.ts +0 -39
  51. package/src/setup.ts +0 -392
  52. package/src/signal.ts +0 -212
  53. package/src/spot/aerodrome.ts +0 -158
  54. package/src/spot/client.ts +0 -138
  55. package/src/spot/index.ts +0 -11
  56. package/src/spot/swap-manager.ts +0 -219
  57. package/src/spot/types.ts +0 -203
  58. package/src/spot/uniswap.ts +0 -150
  59. package/src/store.ts +0 -50
  60. package/src/strategy/index.ts +0 -2
  61. package/src/strategy/loader.ts +0 -265
  62. package/src/strategy/templates.ts +0 -74
  63. package/src/trading/index.ts +0 -2
  64. package/src/trading/market.ts +0 -120
  65. package/src/trading/risk.ts +0 -107
  66. package/src/ui.ts +0 -75
  67. package/test/strategy-loader.test.ts +0 -150
  68. 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
- }