@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.
Files changed (78) 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-ZRAOPQQW.js +6406 -0
  8. package/dist/cli.js +40 -98
  9. package/dist/index.d.ts +24 -2
  10. package/dist/index.js +1 -1
  11. package/package.json +17 -14
  12. package/.turbo/turbo-build.log +0 -17
  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 -244
  19. package/src/config.ts +0 -499
  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 -51
  31. package/src/llm/together.ts +0 -48
  32. package/src/llm-providers.ts +0 -100
  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 -281
  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 -384
  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 -191
  63. package/src/strategy/templates.ts +0 -125
  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-bridge-arb-to-base.mjs +0 -223
  69. package/test-funded-check.mjs +0 -79
  70. package/test-funded-phase19.mjs +0 -933
  71. package/test-hl-deposit-recover.mjs +0 -281
  72. package/test-hl-withdraw.mjs +0 -372
  73. package/test-live-signing.mjs +0 -374
  74. package/test-phase7.mjs +0 -416
  75. package/test-recover-arb.mjs +0 -206
  76. package/test-spot-bridge.mjs +0 -248
  77. package/test-wallet-setup.mjs +0 -126
  78. 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,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
- }