@elizaos/plugin-auto-trader 2.0.0-alpha.1

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.
@@ -0,0 +1,310 @@
1
+ // src/strategies/MomentumBreakoutStrategy.ts
2
+ var MomentumBreakoutStrategy = class {
3
+ id = "momentum-breakout-v1";
4
+ name = "Momentum Breakout Strategy";
5
+ description = "Captures momentum moves in volatile meme coins with volume confirmation";
6
+ // Strategy parameters - adjusted for more realistic trading
7
+ minVolumeRatio = 1.1;
8
+ // Further reduced
9
+ minPriceChange = 2e-3;
10
+ // Reduced to 0.2%
11
+ maxRiskPerTrade = 0.02;
12
+ // Increased to 2% for more trades
13
+ profitTarget = 0.01;
14
+ // Reduced to 1%
15
+ stopLoss = 5e-3;
16
+ // Reduced to 0.5%
17
+ // Position tracking
18
+ activePosition = null;
19
+ // Add debug mode
20
+ debugMode = true;
21
+ // Enable by default for testing
22
+ tradeAttempts = 0;
23
+ hasLoggedStart = false;
24
+ isReady() {
25
+ return true;
26
+ }
27
+ inferTradingPair(portfolioSnapshot) {
28
+ const holdings = Object.keys(portfolioSnapshot.holdings);
29
+ const existingPosition = holdings.find(
30
+ (key) => key !== "USDC" && portfolioSnapshot.holdings[key] > 0
31
+ );
32
+ if (existingPosition) {
33
+ return existingPosition;
34
+ }
35
+ const potentialToken = holdings.find((key) => key !== "USDC");
36
+ if (potentialToken) {
37
+ return potentialToken;
38
+ }
39
+ console.warn("[MomentumBreakout] Could not infer trading pair from portfolio");
40
+ return "UNKNOWN";
41
+ }
42
+ async decide(params) {
43
+ const { marketData, portfolioSnapshot } = params;
44
+ const { priceData, currentPrice } = marketData;
45
+ if (!this.hasLoggedStart && this.debugMode) {
46
+ console.log(`[MomentumBreakout] Strategy started:`, {
47
+ minPriceChange: `${(this.minPriceChange * 100).toFixed(1)}%`,
48
+ minVolumeRatio: this.minVolumeRatio,
49
+ dataPoints: priceData?.length || 0,
50
+ initialPrice: currentPrice
51
+ });
52
+ this.hasLoggedStart = true;
53
+ }
54
+ if (!priceData || priceData.length < 100) {
55
+ if (this.debugMode && this.tradeAttempts === 0) {
56
+ console.log(`[MomentumBreakout] Not enough data: ${priceData?.length || 0} candles`);
57
+ }
58
+ return null;
59
+ }
60
+ const tradingPair = this.inferTradingPair(portfolioSnapshot);
61
+ const indicators = this.calculateMomentumIndicators(priceData);
62
+ const holdings = Object.entries(portfolioSnapshot.holdings);
63
+ const assetHolding = holdings.find(([key, value]) => key !== "USDC" && value > 0);
64
+ const hasPosition = assetHolding && assetHolding[1] > 0;
65
+ const assetSymbol = assetHolding ? assetHolding[0] : null;
66
+ if (hasPosition && this.activePosition && assetSymbol) {
67
+ return this.managePosition(currentPrice, indicators, assetSymbol, portfolioSnapshot);
68
+ }
69
+ const shouldEnter = this.shouldEnter(indicators, currentPrice);
70
+ if (this.debugMode && this.tradeAttempts < 5) {
71
+ const conditions = this.evaluateEntryConditions(indicators);
72
+ console.log(`[MomentumBreakout] Early evaluation #${this.tradeAttempts + 1}:`, {
73
+ shouldEnter,
74
+ price: currentPrice.toFixed(6),
75
+ indicators: {
76
+ priceChange5m: `${(indicators.priceChange5m * 100).toFixed(3)}%`,
77
+ priceChange15m: `${(indicators.priceChange15m * 100).toFixed(3)}%`,
78
+ volumeRatio: indicators.volumeRatio.toFixed(2),
79
+ trend: indicators.trendDirection,
80
+ adx: indicators.adx.toFixed(1)
81
+ },
82
+ conditions: {
83
+ momentum: conditions.hasMomentum,
84
+ volume: conditions.hasVolume,
85
+ trend: conditions.trendAligned,
86
+ notAtResistance: conditions.goodEntry,
87
+ metCount: `${conditions.metConditions}/4`
88
+ }
89
+ });
90
+ }
91
+ if (!hasPosition && shouldEnter) {
92
+ const positionSize = this.calculatePositionSize(portfolioSnapshot.totalValue, currentPrice);
93
+ if (positionSize > 1e-3) {
94
+ this.activePosition = {
95
+ entryPrice: currentPrice,
96
+ entryTime: Date.now(),
97
+ highestPrice: currentPrice,
98
+ pair: tradingPair
99
+ };
100
+ console.log(`[MomentumBreakout] \u{1F3AF} BUY SIGNAL:`, {
101
+ price: currentPrice,
102
+ positionSize: positionSize.toFixed(4),
103
+ totalValue: portfolioSnapshot.totalValue.toFixed(2),
104
+ indicators: {
105
+ priceChange5m: `${(indicators.priceChange5m * 100).toFixed(2)}%`,
106
+ priceChange15m: `${(indicators.priceChange15m * 100).toFixed(2)}%`,
107
+ volumeRatio: indicators.volumeRatio.toFixed(2),
108
+ trend: indicators.trendDirection,
109
+ adx: indicators.adx.toFixed(1)
110
+ }
111
+ });
112
+ return {
113
+ action: "BUY" /* BUY */,
114
+ pair: tradingPair,
115
+ quantity: positionSize,
116
+ orderType: "MARKET" /* MARKET */,
117
+ timestamp: Date.now(),
118
+ reason: `Momentum breakout: ${(indicators.priceChange5m * 100).toFixed(1)}% move on ${indicators.volumeRatio.toFixed(1)}x volume`
119
+ };
120
+ }
121
+ }
122
+ this.tradeAttempts++;
123
+ if (this.debugMode && this.tradeAttempts % 200 === 0) {
124
+ const conditions = this.evaluateEntryConditions(indicators);
125
+ console.log(`[MomentumBreakout] Periodic evaluation #${this.tradeAttempts}:`, {
126
+ shouldEnter: conditions.metConditions >= 2,
127
+ price: currentPrice.toFixed(6),
128
+ conditions: {
129
+ momentum: conditions.hasMomentum,
130
+ volume: conditions.hasVolume,
131
+ trend: conditions.trendAligned,
132
+ notAtResistance: conditions.goodEntry,
133
+ metCount: `${conditions.metConditions}/4`
134
+ }
135
+ });
136
+ }
137
+ return null;
138
+ }
139
+ evaluateEntryConditions(indicators) {
140
+ const hasMomentum = indicators.priceChange5m > this.minPriceChange && indicators.priceChange15m > -0.01;
141
+ const hasVolume = indicators.volumeRatio > this.minVolumeRatio || indicators.volumeRatio > 1 && indicators.volumeTrend === "increasing";
142
+ const trendAligned = indicators.trendDirection === "bullish" && indicators.adx > 15 || // Reduced ADX requirement
143
+ indicators.priceChange5m > this.minPriceChange * 1.5;
144
+ const goodEntry = !indicators.nearResistance || indicators.priceChange5m > 5e-3;
145
+ const conditions = [hasMomentum, hasVolume, trendAligned, goodEntry];
146
+ const metConditions = conditions.filter((c) => c).length;
147
+ return {
148
+ hasMomentum,
149
+ hasVolume,
150
+ trendAligned,
151
+ goodEntry,
152
+ metConditions
153
+ };
154
+ }
155
+ calculateMomentumIndicators(priceData) {
156
+ const currentPrice = priceData[priceData.length - 1].close;
157
+ const price5mAgo = priceData[Math.max(0, priceData.length - 5)]?.close || currentPrice;
158
+ const price15mAgo = priceData[Math.max(0, priceData.length - 15)]?.close || currentPrice;
159
+ const price60mAgo = priceData[Math.max(0, priceData.length - 60)]?.close || currentPrice;
160
+ const priceChange5m = (currentPrice - price5mAgo) / price5mAgo;
161
+ const priceChange15m = (currentPrice - price15mAgo) / price15mAgo;
162
+ const priceChange1h = (currentPrice - price60mAgo) / price60mAgo;
163
+ const recentVolumes = priceData.slice(-20).map((c) => c.volume);
164
+ const avgVolume = recentVolumes.reduce((a, b) => a + b) / recentVolumes.length;
165
+ const currentVolume = priceData[priceData.length - 1].volume;
166
+ const volumeRatio = currentVolume / avgVolume;
167
+ const volumeTrend5 = recentVolumes.slice(-5).reduce((a, b) => a + b) / 5;
168
+ const volumeTrend10 = recentVolumes.slice(-10).reduce((a, b) => a + b) / 10;
169
+ let volumeTrend;
170
+ if (volumeTrend5 > volumeTrend10 * 1.2) {
171
+ volumeTrend = "increasing";
172
+ } else if (volumeTrend5 < volumeTrend10 * 0.8) {
173
+ volumeTrend = "decreasing";
174
+ } else {
175
+ volumeTrend = "stable";
176
+ }
177
+ const atr = this.calculateATR(priceData.slice(-14));
178
+ const atrPercentage = atr / currentPrice;
179
+ const adx = this.calculateADX(priceData.slice(-20));
180
+ const recentHighs = priceData.slice(-20).map((c) => c.high);
181
+ const recentLows = priceData.slice(-20).map((c) => c.low);
182
+ const resistance = Math.max(...recentHighs);
183
+ const support = Math.min(...recentLows);
184
+ const ema9 = this.calculateEMA(
185
+ priceData.slice(-20).map((c) => c.close),
186
+ 9
187
+ );
188
+ const ema21 = this.calculateEMA(
189
+ priceData.slice(-30).map((c) => c.close),
190
+ 21
191
+ );
192
+ let trendDirection;
193
+ if (ema9 > ema21 * 1.01 && priceChange15m > 0) {
194
+ trendDirection = "bullish";
195
+ } else if (ema9 < ema21 * 0.99 && priceChange15m < 0) {
196
+ trendDirection = "bearish";
197
+ } else {
198
+ trendDirection = "neutral";
199
+ }
200
+ return {
201
+ priceChange1h,
202
+ priceChange5m,
203
+ priceChange15m,
204
+ volumeRatio,
205
+ volumeSpike: volumeRatio > 3,
206
+ volumeTrend,
207
+ atr,
208
+ volatilityPercentile: atrPercentage > 0.02 ? 0.8 : 0.5,
209
+ adx,
210
+ trendDirection,
211
+ trendStrength: Math.abs(priceChange15m) * adx / 25,
212
+ resistance,
213
+ support,
214
+ nearResistance: (resistance - currentPrice) / currentPrice < 0.01,
215
+ nearSupport: (currentPrice - support) / currentPrice < 0.01
216
+ };
217
+ }
218
+ shouldEnter(indicators, _currentPrice) {
219
+ const conditions = this.evaluateEntryConditions(indicators);
220
+ return conditions.metConditions >= 2;
221
+ }
222
+ managePosition(currentPrice, indicators, assetSymbol, portfolio) {
223
+ if (!this.activePosition) return null;
224
+ const { entryPrice, highestPrice } = this.activePosition;
225
+ const profitPercent = (currentPrice - entryPrice) / entryPrice;
226
+ const drawdownFromHigh = (highestPrice - currentPrice) / highestPrice;
227
+ if (currentPrice > highestPrice) {
228
+ this.activePosition.highestPrice = currentPrice;
229
+ }
230
+ let shouldExit = false;
231
+ let exitReason = "";
232
+ if (profitPercent >= this.profitTarget) {
233
+ shouldExit = true;
234
+ exitReason = `Profit target reached: +${(profitPercent * 100).toFixed(1)}%`;
235
+ } else if (profitPercent <= -this.stopLoss) {
236
+ shouldExit = true;
237
+ exitReason = `Stop loss triggered: ${(profitPercent * 100).toFixed(1)}%`;
238
+ } else if (profitPercent > 0.015 && drawdownFromHigh > 0.01) {
239
+ shouldExit = true;
240
+ exitReason = `Trailing stop: -${(drawdownFromHigh * 100).toFixed(1)}% from high`;
241
+ } else if (indicators.priceChange5m < -0.01 && indicators.volumeRatio > 2) {
242
+ shouldExit = true;
243
+ exitReason = "Momentum reversal detected";
244
+ }
245
+ if (shouldExit) {
246
+ this.activePosition = null;
247
+ return {
248
+ action: "SELL" /* SELL */,
249
+ pair: `${assetSymbol}/USDC`,
250
+ quantity: portfolio.holdings[assetSymbol],
251
+ orderType: "MARKET" /* MARKET */,
252
+ timestamp: Date.now(),
253
+ reason: exitReason
254
+ };
255
+ }
256
+ return null;
257
+ }
258
+ calculatePositionSize(portfolioValue, currentPrice) {
259
+ const riskAmount = portfolioValue * this.maxRiskPerTrade;
260
+ const positionValue = riskAmount / this.stopLoss;
261
+ const maxPositionValue = portfolioValue * 0.25;
262
+ const actualPositionValue = Math.min(positionValue, maxPositionValue);
263
+ const quantity = actualPositionValue / currentPrice;
264
+ if (this.debugMode) {
265
+ console.log(`[MomentumBreakout] Position sizing:`, {
266
+ portfolioValue: portfolioValue.toFixed(2),
267
+ riskAmount: riskAmount.toFixed(2),
268
+ calculatedPositionValue: positionValue.toFixed(2),
269
+ actualPositionValue: actualPositionValue.toFixed(2),
270
+ currentPrice: currentPrice.toFixed(6),
271
+ quantity: quantity.toFixed(4)
272
+ });
273
+ }
274
+ return quantity;
275
+ }
276
+ calculateATR(candles) {
277
+ if (candles.length < 2) return 0;
278
+ const trueRanges = [];
279
+ for (let i = 1; i < candles.length; i++) {
280
+ const highLow = candles[i].high - candles[i].low;
281
+ const highClose = Math.abs(candles[i].high - candles[i - 1].close);
282
+ const lowClose = Math.abs(candles[i].low - candles[i - 1].close);
283
+ trueRanges.push(Math.max(highLow, highClose, lowClose));
284
+ }
285
+ return trueRanges.reduce((a, b) => a + b) / trueRanges.length;
286
+ }
287
+ calculateEMA(values, period) {
288
+ if (values.length < period) return values[values.length - 1];
289
+ const multiplier = 2 / (period + 1);
290
+ let ema = values.slice(0, period).reduce((a, b) => a + b) / period;
291
+ for (let i = period; i < values.length; i++) {
292
+ ema = (values[i] - ema) * multiplier + ema;
293
+ }
294
+ return ema;
295
+ }
296
+ calculateADX(candles, period = 14) {
297
+ if (candles.length < period + 1) return 0;
298
+ const priceChanges = [];
299
+ for (let i = 1; i < candles.length; i++) {
300
+ priceChanges.push(Math.abs(candles[i].close - candles[i - 1].close) / candles[i - 1].close);
301
+ }
302
+ const avgChange = priceChanges.reduce((a, b) => a + b) / priceChanges.length;
303
+ return Math.min(avgChange * 1e3, 100);
304
+ }
305
+ };
306
+
307
+ export {
308
+ MomentumBreakoutStrategy
309
+ };
310
+ //# sourceMappingURL=chunk-Z6SUPK5O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/strategies/MomentumBreakoutStrategy.ts"],"sourcesContent":["import {\n type AgentState,\n type OHLCV,\n OrderType,\n type PortfolioSnapshot,\n type StrategyContextMarketData,\n type TradeOrder,\n TradeType,\n type TradingStrategy,\n} from \"../types.ts\";\n\ninterface MomentumIndicators {\n // Price momentum\n priceChange1h: number;\n priceChange5m: number;\n priceChange15m: number;\n\n // Volume analysis\n volumeRatio: number;\n volumeSpike: boolean;\n volumeTrend: \"increasing\" | \"decreasing\" | \"stable\";\n\n // Volatility\n atr: number;\n volatilityPercentile: number;\n\n // Trend strength\n adx: number;\n trendDirection: \"bullish\" | \"bearish\" | \"neutral\";\n trendStrength: number;\n\n // Market structure\n resistance: number;\n support: number;\n nearResistance: boolean;\n nearSupport: boolean;\n}\n\nexport class MomentumBreakoutStrategy implements TradingStrategy {\n public readonly id = \"momentum-breakout-v1\";\n public readonly name = \"Momentum Breakout Strategy\";\n public readonly description =\n \"Captures momentum moves in volatile meme coins with volume confirmation\";\n\n // Strategy parameters - adjusted for more realistic trading\n private readonly minVolumeRatio = 1.1; // Further reduced\n private readonly minPriceChange = 0.002; // Reduced to 0.2%\n private readonly maxRiskPerTrade = 0.02; // Increased to 2% for more trades\n private readonly profitTarget = 0.01; // Reduced to 1%\n private readonly stopLoss = 0.005; // Reduced to 0.5%\n\n // Position tracking\n private activePosition: {\n entryPrice: number;\n entryTime: number;\n highestPrice: number;\n pair: string; // Add pair tracking\n } | null = null;\n\n // Add debug mode\n private debugMode = true; // Enable by default for testing\n private tradeAttempts = 0;\n private hasLoggedStart = false;\n\n isReady(): boolean {\n return true;\n }\n\n private inferTradingPair(portfolioSnapshot: PortfolioSnapshot): string {\n // The SimulationService uses token addresses as keys in holdings\n // We need to find the non-USDC key which represents the token being traded\n const holdings = Object.keys(portfolioSnapshot.holdings);\n\n // Look for existing position first\n const existingPosition = holdings.find(\n (key) => key !== \"USDC\" && portfolioSnapshot.holdings[key] > 0,\n );\n\n if (existingPosition) {\n return existingPosition;\n }\n\n // If no position, look for any non-USDC key (even with 0 balance)\n // This happens when we have historical trades but currently flat\n const potentialToken = holdings.find((key) => key !== \"USDC\");\n\n if (potentialToken) {\n return potentialToken;\n }\n\n // If we only have USDC, we're likely at the start of a simulation\n // The token address should be inferred from the context\n // For now, we'll return a placeholder that will be replaced\n console.warn(\"[MomentumBreakout] Could not infer trading pair from portfolio\");\n return \"UNKNOWN\";\n }\n\n async decide(params: {\n marketData: StrategyContextMarketData;\n agentState: AgentState;\n portfolioSnapshot: PortfolioSnapshot;\n agentRuntime?: any;\n }): Promise<TradeOrder | null> {\n const { marketData, portfolioSnapshot } = params;\n const { priceData, currentPrice } = marketData;\n\n // Log initial info once\n if (!this.hasLoggedStart && this.debugMode) {\n console.log(`[MomentumBreakout] Strategy started:`, {\n minPriceChange: `${(this.minPriceChange * 100).toFixed(1)}%`,\n minVolumeRatio: this.minVolumeRatio,\n dataPoints: priceData?.length || 0,\n initialPrice: currentPrice,\n });\n this.hasLoggedStart = true;\n }\n\n // Need at least 100 candles for analysis\n if (!priceData || priceData.length < 100) {\n if (this.debugMode && this.tradeAttempts === 0) {\n console.log(`[MomentumBreakout] Not enough data: ${priceData?.length || 0} candles`);\n }\n return null;\n }\n\n // Infer the trading pair from the portfolio\n const tradingPair = this.inferTradingPair(portfolioSnapshot);\n\n // Calculate momentum indicators\n const indicators = this.calculateMomentumIndicators(priceData);\n\n // Get current position - look for any non-USDC holding\n const holdings = Object.entries(portfolioSnapshot.holdings);\n const assetHolding = holdings.find(([key, value]) => key !== \"USDC\" && value > 0);\n const hasPosition = assetHolding && assetHolding[1] > 0;\n const assetSymbol = assetHolding ? assetHolding[0] : null;\n\n // Position management\n if (hasPosition && this.activePosition && assetSymbol) {\n return this.managePosition(currentPrice, indicators, assetSymbol, portfolioSnapshot);\n }\n\n // Entry logic - look for momentum breakout\n const shouldEnter = this.shouldEnter(indicators, currentPrice);\n\n // Log first few evaluations for debugging\n if (this.debugMode && this.tradeAttempts < 5) {\n const conditions = this.evaluateEntryConditions(indicators);\n console.log(`[MomentumBreakout] Early evaluation #${this.tradeAttempts + 1}:`, {\n shouldEnter,\n price: currentPrice.toFixed(6),\n indicators: {\n priceChange5m: `${(indicators.priceChange5m * 100).toFixed(3)}%`,\n priceChange15m: `${(indicators.priceChange15m * 100).toFixed(3)}%`,\n volumeRatio: indicators.volumeRatio.toFixed(2),\n trend: indicators.trendDirection,\n adx: indicators.adx.toFixed(1),\n },\n conditions: {\n momentum: conditions.hasMomentum,\n volume: conditions.hasVolume,\n trend: conditions.trendAligned,\n notAtResistance: conditions.goodEntry,\n metCount: `${conditions.metConditions}/4`,\n },\n });\n }\n\n if (!hasPosition && shouldEnter) {\n const positionSize = this.calculatePositionSize(portfolioSnapshot.totalValue, currentPrice);\n\n if (positionSize > 0.001) {\n this.activePosition = {\n entryPrice: currentPrice,\n entryTime: Date.now(),\n highestPrice: currentPrice,\n pair: tradingPair,\n };\n\n console.log(`[MomentumBreakout] 🎯 BUY SIGNAL:`, {\n price: currentPrice,\n positionSize: positionSize.toFixed(4),\n totalValue: portfolioSnapshot.totalValue.toFixed(2),\n indicators: {\n priceChange5m: `${(indicators.priceChange5m * 100).toFixed(2)}%`,\n priceChange15m: `${(indicators.priceChange15m * 100).toFixed(2)}%`,\n volumeRatio: indicators.volumeRatio.toFixed(2),\n trend: indicators.trendDirection,\n adx: indicators.adx.toFixed(1),\n },\n });\n\n return {\n action: TradeType.BUY,\n pair: tradingPair,\n quantity: positionSize,\n orderType: OrderType.MARKET,\n timestamp: Date.now(),\n reason: `Momentum breakout: ${(indicators.priceChange5m * 100).toFixed(1)}% move on ${indicators.volumeRatio.toFixed(1)}x volume`,\n };\n }\n }\n\n // Log entry evaluation periodically for debugging\n this.tradeAttempts++;\n if (this.debugMode && this.tradeAttempts % 200 === 0) {\n const conditions = this.evaluateEntryConditions(indicators);\n console.log(`[MomentumBreakout] Periodic evaluation #${this.tradeAttempts}:`, {\n shouldEnter: conditions.metConditions >= 2,\n price: currentPrice.toFixed(6),\n conditions: {\n momentum: conditions.hasMomentum,\n volume: conditions.hasVolume,\n trend: conditions.trendAligned,\n notAtResistance: conditions.goodEntry,\n metCount: `${conditions.metConditions}/4`,\n },\n });\n }\n\n return null;\n }\n\n private evaluateEntryConditions(indicators: MomentumIndicators) {\n const hasMomentum =\n indicators.priceChange5m > this.minPriceChange && indicators.priceChange15m > -0.01; // Allow more negative 15m\n\n const hasVolume =\n indicators.volumeRatio > this.minVolumeRatio ||\n (indicators.volumeRatio > 1.0 && indicators.volumeTrend === \"increasing\");\n\n const trendAligned =\n (indicators.trendDirection === \"bullish\" && indicators.adx > 15) || // Reduced ADX requirement\n indicators.priceChange5m > this.minPriceChange * 1.5; // Lower threshold\n\n const goodEntry = !indicators.nearResistance || indicators.priceChange5m > 0.005; // Allow entry near resistance if strong momentum\n\n const conditions = [hasMomentum, hasVolume, trendAligned, goodEntry];\n const metConditions = conditions.filter((c) => c).length;\n\n return {\n hasMomentum,\n hasVolume,\n trendAligned,\n goodEntry,\n metConditions,\n };\n }\n\n private calculateMomentumIndicators(priceData: OHLCV[]): MomentumIndicators {\n const currentPrice = priceData[priceData.length - 1].close;\n\n // Price momentum over different timeframes\n const price5mAgo = priceData[Math.max(0, priceData.length - 5)]?.close || currentPrice;\n const price15mAgo = priceData[Math.max(0, priceData.length - 15)]?.close || currentPrice;\n const price60mAgo = priceData[Math.max(0, priceData.length - 60)]?.close || currentPrice;\n\n const priceChange5m = (currentPrice - price5mAgo) / price5mAgo;\n const priceChange15m = (currentPrice - price15mAgo) / price15mAgo;\n const priceChange1h = (currentPrice - price60mAgo) / price60mAgo;\n\n // Volume analysis\n const recentVolumes = priceData.slice(-20).map((c) => c.volume);\n const avgVolume = recentVolumes.reduce((a, b) => a + b) / recentVolumes.length;\n const currentVolume = priceData[priceData.length - 1].volume;\n const volumeRatio = currentVolume / avgVolume;\n\n // Volume trend\n const volumeTrend5 = recentVolumes.slice(-5).reduce((a, b) => a + b) / 5;\n const volumeTrend10 = recentVolumes.slice(-10).reduce((a, b) => a + b) / 10;\n let volumeTrend: \"increasing\" | \"decreasing\" | \"stable\";\n\n if (volumeTrend5 > volumeTrend10 * 1.2) {\n volumeTrend = \"increasing\";\n } else if (volumeTrend5 < volumeTrend10 * 0.8) {\n volumeTrend = \"decreasing\";\n } else {\n volumeTrend = \"stable\";\n }\n\n // ATR for volatility\n const atr = this.calculateATR(priceData.slice(-14));\n const atrPercentage = atr / currentPrice;\n\n // ADX for trend strength\n const adx = this.calculateADX(priceData.slice(-20));\n\n // Market structure\n const recentHighs = priceData.slice(-20).map((c) => c.high);\n const recentLows = priceData.slice(-20).map((c) => c.low);\n const resistance = Math.max(...recentHighs);\n const support = Math.min(...recentLows);\n\n // Trend direction\n const ema9 = this.calculateEMA(\n priceData.slice(-20).map((c) => c.close),\n 9,\n );\n const ema21 = this.calculateEMA(\n priceData.slice(-30).map((c) => c.close),\n 21,\n );\n\n let trendDirection: \"bullish\" | \"bearish\" | \"neutral\";\n if (ema9 > ema21 * 1.01 && priceChange15m > 0) {\n trendDirection = \"bullish\";\n } else if (ema9 < ema21 * 0.99 && priceChange15m < 0) {\n trendDirection = \"bearish\";\n } else {\n trendDirection = \"neutral\";\n }\n\n return {\n priceChange1h,\n priceChange5m,\n priceChange15m,\n volumeRatio,\n volumeSpike: volumeRatio > 3,\n volumeTrend,\n atr,\n volatilityPercentile: atrPercentage > 0.02 ? 0.8 : 0.5,\n adx,\n trendDirection,\n trendStrength: (Math.abs(priceChange15m) * adx) / 25,\n resistance,\n support,\n nearResistance: (resistance - currentPrice) / currentPrice < 0.01,\n nearSupport: (currentPrice - support) / currentPrice < 0.01,\n };\n }\n\n private shouldEnter(indicators: MomentumIndicators, _currentPrice: number): boolean {\n const conditions = this.evaluateEntryConditions(indicators);\n return conditions.metConditions >= 2; // Reduced from 3 to 2 out of 4\n }\n\n private managePosition(\n currentPrice: number,\n indicators: MomentumIndicators,\n assetSymbol: string,\n portfolio: PortfolioSnapshot,\n ): TradeOrder | null {\n if (!this.activePosition) return null;\n\n const { entryPrice, highestPrice } = this.activePosition;\n const profitPercent = (currentPrice - entryPrice) / entryPrice;\n const drawdownFromHigh = (highestPrice - currentPrice) / highestPrice;\n\n // Update highest price\n if (currentPrice > highestPrice) {\n this.activePosition.highestPrice = currentPrice;\n }\n\n // Exit conditions\n let shouldExit = false;\n let exitReason = \"\";\n\n // 1. Hit profit target\n if (profitPercent >= this.profitTarget) {\n shouldExit = true;\n exitReason = `Profit target reached: +${(profitPercent * 100).toFixed(1)}%`;\n }\n\n // 2. Stop loss\n else if (profitPercent <= -this.stopLoss) {\n shouldExit = true;\n exitReason = `Stop loss triggered: ${(profitPercent * 100).toFixed(1)}%`;\n }\n\n // 3. Trailing stop (if profit > 1.5%)\n else if (profitPercent > 0.015 && drawdownFromHigh > 0.01) {\n shouldExit = true;\n exitReason = `Trailing stop: -${(drawdownFromHigh * 100).toFixed(1)}% from high`;\n }\n\n // 4. Momentum reversal\n else if (indicators.priceChange5m < -0.01 && indicators.volumeRatio > 2) {\n shouldExit = true;\n exitReason = \"Momentum reversal detected\";\n }\n\n if (shouldExit) {\n this.activePosition = null;\n\n return {\n action: TradeType.SELL,\n pair: `${assetSymbol}/USDC`,\n quantity: portfolio.holdings[assetSymbol],\n orderType: OrderType.MARKET,\n timestamp: Date.now(),\n reason: exitReason,\n };\n }\n\n return null;\n }\n\n private calculatePositionSize(portfolioValue: number, currentPrice: number): number {\n // Risk-based position sizing\n // We want to risk maxRiskPerTrade of our portfolio\n // If we hit our stop loss, we should lose exactly that amount\n\n // Calculate the position value that would result in our max risk if stopped out\n const riskAmount = portfolioValue * this.maxRiskPerTrade;\n const positionValue = riskAmount / this.stopLoss;\n\n // But we can't use more than a reasonable portion of our portfolio\n // Use the lesser of our risk-based size or 25% of portfolio\n const maxPositionValue = portfolioValue * 0.25;\n const actualPositionValue = Math.min(positionValue, maxPositionValue);\n\n const quantity = actualPositionValue / currentPrice;\n\n if (this.debugMode) {\n console.log(`[MomentumBreakout] Position sizing:`, {\n portfolioValue: portfolioValue.toFixed(2),\n riskAmount: riskAmount.toFixed(2),\n calculatedPositionValue: positionValue.toFixed(2),\n actualPositionValue: actualPositionValue.toFixed(2),\n currentPrice: currentPrice.toFixed(6),\n quantity: quantity.toFixed(4),\n });\n }\n\n return quantity;\n }\n\n private calculateATR(candles: OHLCV[]): number {\n if (candles.length < 2) return 0;\n\n const trueRanges = [];\n for (let i = 1; i < candles.length; i++) {\n const highLow = candles[i].high - candles[i].low;\n const highClose = Math.abs(candles[i].high - candles[i - 1].close);\n const lowClose = Math.abs(candles[i].low - candles[i - 1].close);\n trueRanges.push(Math.max(highLow, highClose, lowClose));\n }\n\n return trueRanges.reduce((a, b) => a + b) / trueRanges.length;\n }\n\n private calculateEMA(values: number[], period: number): number {\n if (values.length < period) return values[values.length - 1];\n\n const multiplier = 2 / (period + 1);\n let ema = values.slice(0, period).reduce((a, b) => a + b) / period;\n\n for (let i = period; i < values.length; i++) {\n ema = (values[i] - ema) * multiplier + ema;\n }\n\n return ema;\n }\n\n private calculateADX(candles: OHLCV[], period: number = 14): number {\n if (candles.length < period + 1) return 0;\n\n // Simplified ADX calculation\n const priceChanges = [];\n for (let i = 1; i < candles.length; i++) {\n priceChanges.push(Math.abs(candles[i].close - candles[i - 1].close) / candles[i - 1].close);\n }\n\n const avgChange = priceChanges.reduce((a, b) => a + b) / priceChanges.length;\n return Math.min(avgChange * 1000, 100); // Scale to 0-100\n }\n}\n"],"mappings":"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","names":[]}