@elizaos/plugin-auto-trader 2.0.0-alpha.2 → 2.0.0-alpha.3

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 (34) hide show
  1. package/dist/MeanReversionStrategy-DWMTVZBR.js +9 -0
  2. package/dist/MomentumBreakoutStrategy-FCWYC6D6.js +9 -0
  3. package/dist/RandomStrategy-JJCJEW5V.js +9 -0
  4. package/dist/RuleBasedStrategy-3OB4HZLT.js +9 -0
  5. package/dist/{chunk-AS6N6A3H.js → chunk-5LMMYSSD.js} +1 -1
  6. package/dist/chunk-5LMMYSSD.js.map +1 -0
  7. package/dist/{chunk-KB2EBQUN.js → chunk-7YO3Q6ZS.js} +1 -1
  8. package/dist/chunk-7YO3Q6ZS.js.map +1 -0
  9. package/dist/{chunk-7GI4G3ZN.js → chunk-GAGDUXA5.js} +8 -3
  10. package/dist/chunk-GAGDUXA5.js.map +1 -0
  11. package/dist/{chunk-GYTZTIWK.js → chunk-IQWCSPU4.js} +25 -8
  12. package/dist/chunk-IQWCSPU4.js.map +1 -0
  13. package/dist/{chunk-Z6SUPK5O.js → chunk-PPB7RNSR.js} +56 -34
  14. package/dist/chunk-PPB7RNSR.js.map +1 -0
  15. package/dist/index.js +657 -311
  16. package/dist/index.js.map +1 -1
  17. package/dist/{index.node-YLFTRMAZ.js → index.node-P3CSHIDI.js} +4 -6
  18. package/dist/index.node-P3CSHIDI.js.map +1 -0
  19. package/package.json +14 -6
  20. package/LICENSE +0 -21
  21. package/dist/MeanReversionStrategy-VQJGG3DE.js +0 -9
  22. package/dist/MomentumBreakoutStrategy-O7I3EMGB.js +0 -9
  23. package/dist/RandomStrategy-GPGG56MV.js +0 -9
  24. package/dist/RuleBasedStrategy-7O47DGTU.js +0 -9
  25. package/dist/chunk-7GI4G3ZN.js.map +0 -1
  26. package/dist/chunk-AS6N6A3H.js.map +0 -1
  27. package/dist/chunk-GYTZTIWK.js.map +0 -1
  28. package/dist/chunk-KB2EBQUN.js.map +0 -1
  29. package/dist/chunk-Z6SUPK5O.js.map +0 -1
  30. package/dist/index.node-YLFTRMAZ.js.map +0 -1
  31. /package/dist/{MeanReversionStrategy-VQJGG3DE.js.map → MeanReversionStrategy-DWMTVZBR.js.map} +0 -0
  32. /package/dist/{MomentumBreakoutStrategy-O7I3EMGB.js.map → MomentumBreakoutStrategy-FCWYC6D6.js.map} +0 -0
  33. /package/dist/{RandomStrategy-GPGG56MV.js.map → RandomStrategy-JJCJEW5V.js.map} +0 -0
  34. /package/dist/{RuleBasedStrategy-7O47DGTU.js.map → RuleBasedStrategy-3OB4HZLT.js.map} +0 -0
@@ -0,0 +1,9 @@
1
+ import {
2
+ MeanReversionStrategy
3
+ } from "./chunk-GAGDUXA5.js";
4
+ import "./chunk-5LMMYSSD.js";
5
+ import "./chunk-PZ5AY32C.js";
6
+ export {
7
+ MeanReversionStrategy
8
+ };
9
+ //# sourceMappingURL=MeanReversionStrategy-DWMTVZBR.js.map
@@ -0,0 +1,9 @@
1
+ import {
2
+ MomentumBreakoutStrategy
3
+ } from "./chunk-PPB7RNSR.js";
4
+ import "./chunk-5LMMYSSD.js";
5
+ import "./chunk-PZ5AY32C.js";
6
+ export {
7
+ MomentumBreakoutStrategy
8
+ };
9
+ //# sourceMappingURL=MomentumBreakoutStrategy-FCWYC6D6.js.map
@@ -0,0 +1,9 @@
1
+ import {
2
+ RandomStrategy
3
+ } from "./chunk-7YO3Q6ZS.js";
4
+ import "./chunk-5LMMYSSD.js";
5
+ import "./chunk-PZ5AY32C.js";
6
+ export {
7
+ RandomStrategy
8
+ };
9
+ //# sourceMappingURL=RandomStrategy-JJCJEW5V.js.map
@@ -0,0 +1,9 @@
1
+ import {
2
+ RuleBasedStrategy
3
+ } from "./chunk-IQWCSPU4.js";
4
+ import "./chunk-5LMMYSSD.js";
5
+ import "./chunk-PZ5AY32C.js";
6
+ export {
7
+ RuleBasedStrategy
8
+ };
9
+ //# sourceMappingURL=RuleBasedStrategy-3OB4HZLT.js.map
@@ -15,4 +15,4 @@ export {
15
15
  TradeType,
16
16
  OrderType
17
17
  };
18
- //# sourceMappingURL=chunk-AS6N6A3H.js.map
18
+ //# sourceMappingURL=chunk-5LMMYSSD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { IAgentRuntime as AgentRuntime, UUID } from \"@elizaos/core\";\n\nexport interface TokenBalance {\n tokenAddress: string;\n amount: string;\n decimals?: number;\n}\n\nexport interface TokenData {\n address: string;\n symbol?: string;\n name?: string;\n decimals?: number;\n}\n\nexport interface ITokenDataService {\n getTokenData(address: string): Promise<TokenData | null>;\n getTokenBalance?(address: string): Promise<TokenBalance | null>;\n}\n\nexport interface WalletAsset {\n tokenAddress: string;\n amount: number;\n usdValue?: number;\n}\n\nexport interface IWalletService {\n getPortfolio?(agentId: UUID): Promise<WalletPortfolio | null>;\n}\n\n// #region --- Portfolio and Trading Data Interfaces ---\n\n/**\n * Core types for simulations and backtesting\n */\n\nexport interface OHLCV {\n timestamp: number;\n open: number;\n high: number;\n low: number;\n close: number;\n volume: number;\n}\n\nexport interface StrategyContextMarketData {\n currentPrice: number;\n lastPrices: number[];\n indicators?: { [indicatorName: string]: number };\n priceData?: OHLCV[]; // Full price data for custom analysis\n}\n\nexport interface AgentState {\n portfolioValue: number;\n volatility: number;\n confidenceLevel: number;\n recentTrades: number;\n lastAction?: string;\n sentiment?: number;\n}\n\nexport interface TradingStrategy {\n id: string;\n name: string;\n description: string;\n decide(params: {\n marketData: StrategyContextMarketData;\n agentState: AgentState;\n portfolioSnapshot: PortfolioSnapshot;\n agentRuntime?: AgentRuntime;\n }): Promise<TradeOrder | null>;\n initialize?(agentRuntime?: AgentRuntime): Promise<void>;\n isReady(): boolean;\n configure?(params: Record<string, unknown>): void;\n}\n\nexport enum TradeType {\n BUY = \"BUY\",\n SELL = \"SELL\",\n}\n\nexport enum OrderType {\n MARKET = \"MARKET\",\n LIMIT = \"LIMIT\",\n STOP = \"STOP\",\n}\n\nexport interface TradeOrder {\n pair: string;\n action: TradeType;\n quantity: number;\n orderType: OrderType;\n price?: number;\n stopPrice?: number;\n timestamp: number;\n reason?: string;\n}\n\nexport interface Trade extends TradeOrder {\n executedPrice: number;\n executedTimestamp: number;\n fees: number;\n feeCurrency?: string; // Add optional feeCurrency\n tradeId?: string; // Add optional tradeId\n realizedPnl?: number; // Profit or loss realized on this trade (typically for SELL trades)\n}\n\nexport interface PortfolioSnapshot {\n timestamp: number;\n holdings: { [assetSymbol: string]: number };\n totalValue: number;\n}\n\nexport interface PerformanceMetrics {\n totalPnlAbsolute: number;\n totalPnlPercentage: number;\n sharpeRatio?: number;\n sortinoRatio?: number;\n winLossRatio: number;\n averageWinAmount: number;\n averageLossAmount: number;\n maxDrawdown: number;\n totalTrades: number;\n winningTrades: number;\n losingTrades: number;\n firstAssetPrice?: number;\n lastAssetPrice?: number;\n buyAndHoldPnlPercentage?: number;\n}\n\nexport interface SimulationReport {\n strategy: string;\n pair: string;\n startDate: number;\n endDate: number;\n trades: Trade[];\n portfolioSnapshots: PortfolioSnapshot[];\n finalPortfolioValue: number;\n metrics: PerformanceMetrics;\n}\n\n// HistoricalDataService\nexport interface HistoricalDataService {\n fetchData(\n pair: string,\n interval: string,\n startDate: Date,\n endDate: Date,\n dataSource: string,\n ): Promise<OHLCV[]>;\n}\n\n// #endregion --- Portfolio and Trading Data Interfaces ---\n\n// #region --- Service & Other Interfaces ---\n\nexport interface TradeSimulationResult {\n isValid: boolean;\n reason?: string;\n updatedBalance?: number;\n updatedPortfolio?: { [assetSymbol: string]: PortfolioAssetHolding };\n}\n\n// Portfolio view for strategy contexts\nexport interface WalletPortfolio {\n items: PortfolioAssetHolding[];\n totalValue?: number;\n}\n\nexport interface PortfolioAssetHolding extends WalletAsset {\n averagePrice: number;\n symbol?: string;\n assetAddress: string;\n}\n\nexport interface Position {\n id: UUID;\n tokenAddress: string;\n amount: number;\n entryPrice: number;\n currentPrice?: number;\n unrealizedPnl?: number;\n realizedPnl?: number;\n}\n\nexport interface WalletOperationResult {\n success: boolean;\n transactionHash?: string;\n error?: string;\n}\n\nexport interface PortfolioHolding {\n tokenAddress: string;\n tokenSymbol: string;\n tokenName: string;\n amount: number;\n decimals: number;\n usdValue: number;\n tokenPriceUsd: number;\n}\n\nexport interface TrackedPosition {\n tokenAddress: string;\n symbol: string;\n entryPrice: number;\n currentPrice: number;\n quantity: number;\n value: number;\n pnl: number;\n pnlPercentage: number;\n}\n\n// Strategy-specific parameters\nexport interface RandomStrategyParams {\n buyProbability?: number;\n sellProbability?: number;\n maxTradeSize?: number;\n minTradeSize?: number;\n randomSeed?: number;\n}\n\nexport interface RuleBasedStrategyParams {\n indicators?: string[];\n buyConditions?: {\n [indicator: string]: { threshold: number; condition: \"above\" | \"below\" };\n };\n sellConditions?: {\n [indicator: string]: { threshold: number; condition: \"above\" | \"below\" };\n };\n riskSettings?: {\n maxPositionSize?: number;\n stopLossPercentage?: number;\n takeProfitPercentage?: number;\n };\n}\n\nexport interface LLMStrategyParams {\n modelName?: string;\n customPromptPrefix?: string;\n customPromptSuffix?: string;\n maxTokens?: number;\n temperature?: number;\n defaultTradeSizePercentage?: number;\n defaultFixedTradeQuantity?: number;\n structuredOutputSchema?: Record<string, unknown>;\n systemPrompt?: string;\n}\n\n// #endregion --- Service & Other Interfaces ---\n"],"mappings":";AA4EO,IAAK,YAAL,kBAAKA,eAAL;AACL,EAAAA,WAAA,SAAM;AACN,EAAAA,WAAA,UAAO;AAFG,SAAAA;AAAA,GAAA;AAKL,IAAK,YAAL,kBAAKC,eAAL;AACL,EAAAA,WAAA,YAAS;AACT,EAAAA,WAAA,WAAQ;AACR,EAAAA,WAAA,UAAO;AAHG,SAAAA;AAAA,GAAA;","names":["TradeType","OrderType"]}
@@ -97,4 +97,4 @@ var RandomStrategy = class {
97
97
  export {
98
98
  RandomStrategy
99
99
  };
100
- //# sourceMappingURL=chunk-KB2EBQUN.js.map
100
+ //# sourceMappingURL=chunk-7YO3Q6ZS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/strategies/RandomStrategy.ts"],"sourcesContent":["import {\n type AgentState,\n OrderType,\n type PortfolioSnapshot,\n type StrategyContextMarketData,\n type TradeOrder,\n TradeType,\n type TradingStrategy,\n} from \"../types.ts\";\n\nexport interface RandomStrategyParams {\n /**\n * Probability (0.0 to 1.0) of attempting a trade at any given `shouldExecute` call.\n * Default: 0.1 (10% chance)\n */\n tradeAttemptProbability?: number;\n /**\n * Probability (0.0 to 1.0) of the attempted trade being a BUY order. SELL is 1 - this value.\n * Default: 0.5 (50% chance of BUY, 50% chance of SELL)\n */\n buyProbability?: number;\n /**\n * Maximum percentage of available capital to use for a single trade.\n * Expressed as a decimal (e.g., 0.05 for 5%).\n * Default: 0.01 (1% of available capital)\n * Requires `agentState.availableCapital` to be set.\n */\n maxTradeSizePercentage?: number;\n /**\n * Fixed quantity to trade if `maxTradeSizePercentage` is not used or capital is not available.\n * If this is set, `maxTradeSizePercentage` might be ignored or used as a cap.\n * Default: 1 (e.g., 1 unit of the base asset)\n */\n fixedTradeQuantity?: number;\n}\n\nconst DEFAULT_TRADE_ATTEMPT_PROBABILITY = 0.1;\nconst DEFAULT_BUY_PROBABILITY = 0.5;\nconst DEFAULT_MAX_TRADE_SIZE_PERCENTAGE = 0.01; // 1%\nconst DEFAULT_FIXED_TRADE_QUANTITY = 1;\nconst MIN_TRADE_QUANTITY_THRESHOLD = 1e-8; // Define a threshold for minimum tradeable quantity\n\nexport class RandomStrategy implements TradingStrategy {\n public readonly id = \"random-v1\";\n public readonly name = \"Random Trading Strategy\";\n public readonly description =\n \"Makes random buy or sell decisions based on configured probabilities.\";\n\n private params: RandomStrategyParams = {\n tradeAttemptProbability: DEFAULT_TRADE_ATTEMPT_PROBABILITY,\n buyProbability: DEFAULT_BUY_PROBABILITY,\n maxTradeSizePercentage: DEFAULT_MAX_TRADE_SIZE_PERCENTAGE,\n fixedTradeQuantity: DEFAULT_FIXED_TRADE_QUANTITY,\n };\n\n private useFixedQuantity = false;\n\n configure(params: RandomStrategyParams): void {\n if (params.tradeAttemptProbability !== undefined) {\n if (\n params.tradeAttemptProbability < 0 ||\n params.tradeAttemptProbability > 1\n ) {\n throw new Error(\"tradeAttemptProbability must be between 0 and 1.\");\n }\n this.params.tradeAttemptProbability = params.tradeAttemptProbability;\n }\n if (params.buyProbability !== undefined) {\n if (params.buyProbability < 0 || params.buyProbability > 1) {\n throw new Error(\"buyProbability must be between 0 and 1.\");\n }\n this.params.buyProbability = params.buyProbability;\n }\n if (params.maxTradeSizePercentage !== undefined) {\n if (\n params.maxTradeSizePercentage < 0 ||\n params.maxTradeSizePercentage > 1\n ) {\n throw new Error(\"maxTradeSizePercentage must be between 0 and 1.\");\n }\n this.params.maxTradeSizePercentage = params.maxTradeSizePercentage;\n // If percentage is explicitly set, prefer it over fixed quantity\n this.useFixedQuantity = false;\n }\n if (params.fixedTradeQuantity !== undefined) {\n if (params.fixedTradeQuantity <= 0) {\n throw new Error(\"fixedTradeQuantity must be positive.\");\n }\n this.params.fixedTradeQuantity = params.fixedTradeQuantity;\n // Only use fixed quantity if percentage is not explicitly set in this configure call\n if (params.maxTradeSizePercentage === undefined) {\n this.useFixedQuantity = true;\n }\n }\n }\n\n isReady(): boolean {\n return true; // Random strategy is always ready\n }\n\n async decide(params: {\n marketData: StrategyContextMarketData;\n agentState: AgentState;\n portfolioSnapshot: PortfolioSnapshot;\n agentRuntime?: unknown;\n }): Promise<TradeOrder | null> {\n const { marketData, portfolioSnapshot } = params;\n\n if (\n Math.random() >=\n (this.params.tradeAttemptProbability ?? DEFAULT_TRADE_ATTEMPT_PROBABILITY)\n ) {\n return null; // No trade attempt this time\n }\n\n const tradeType: TradeType =\n Math.random() < (this.params.buyProbability ?? DEFAULT_BUY_PROBABILITY)\n ? TradeType.BUY\n : TradeType.SELL;\n\n // Calculate quantity\n let quantity: number | undefined;\n if (\n this.useFixedQuantity &&\n this.params.fixedTradeQuantity &&\n this.params.fixedTradeQuantity > 0\n ) {\n // Use fixed quantity when explicitly set\n quantity = this.params.fixedTradeQuantity;\n } else if (\n !this.useFixedQuantity &&\n this.params.maxTradeSizePercentage &&\n portfolioSnapshot.totalValue > 0 &&\n marketData.currentPrice &&\n marketData.currentPrice > 0\n ) {\n // Calculate percentage-based quantity\n const tradeValue =\n portfolioSnapshot.totalValue * this.params.maxTradeSizePercentage;\n quantity = tradeValue / marketData.currentPrice;\n } else {\n // Default: 1% of portfolio or minimal amount\n const defaultPercentage = 0.01;\n const tradeValue = portfolioSnapshot.totalValue * defaultPercentage;\n quantity =\n marketData.currentPrice > 0\n ? tradeValue / marketData.currentPrice\n : 0.01;\n }\n\n // Check minimum quantity threshold\n if (!quantity || quantity <= MIN_TRADE_QUANTITY_THRESHOLD) {\n return null;\n }\n\n // Extract asset from portfolio snapshot or use a default\n const assetSymbol =\n Object.keys(portfolioSnapshot.holdings).find(\n (key) => key !== \"USDC\" && portfolioSnapshot.holdings[key] > 0,\n ) || \"SOL\";\n\n const pair = `${assetSymbol}/USDC`;\n\n // For SELL orders, check if we have sufficient holdings\n if (tradeType === TradeType.SELL) {\n const holding = portfolioSnapshot.holdings[assetSymbol] || 0;\n if (holding < quantity) {\n return null;\n }\n }\n\n // Apply precision limit\n const roundedQuantity = parseFloat(quantity.toFixed(8));\n\n // Check again after rounding\n if (roundedQuantity <= MIN_TRADE_QUANTITY_THRESHOLD) {\n return null;\n }\n\n return {\n pair,\n action: tradeType,\n quantity: roundedQuantity,\n orderType: OrderType.MARKET, // Random strategy uses market orders for simplicity\n timestamp: Date.now(),\n reason: \"Random trading decision\",\n };\n }\n}\n"],"mappings":"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","names":[]}
@@ -68,8 +68,13 @@ var MeanReversionStrategy = class {
68
68
  if (currentPrice <= lower * (1 + (1 - this.config.bbEntryThreshold))) {
69
69
  const rsiCondition = !this.config.rsiConfirmation || currentRSI < this.config.rsiOversold;
70
70
  if (rsiCondition) {
71
- const positionSize = this.calculatePositionSize(portfolioSnapshot.totalValue, currentPrice);
72
- console.log(`[${this.name}] BUY SIGNAL - Price at lower BB, RSI: ${currentRSI.toFixed(2)}`);
71
+ const positionSize = this.calculatePositionSize(
72
+ portfolioSnapshot.totalValue,
73
+ currentPrice
74
+ );
75
+ console.log(
76
+ `[${this.name}] BUY SIGNAL - Price at lower BB, RSI: ${currentRSI.toFixed(2)}`
77
+ );
73
78
  return {
74
79
  action: "BUY" /* BUY */,
75
80
  orderType: "MARKET" /* MARKET */,
@@ -158,4 +163,4 @@ var MeanReversionStrategy = class {
158
163
  export {
159
164
  MeanReversionStrategy
160
165
  };
161
- //# sourceMappingURL=chunk-7GI4G3ZN.js.map
166
+ //# sourceMappingURL=chunk-GAGDUXA5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/strategies/MeanReversionStrategy.ts"],"sourcesContent":["import type { AgentRuntime } from \"@elizaos/core\";\nimport * as talib from \"technicalindicators\";\nimport {\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\nexport interface MeanReversionConfig {\n // Bollinger Bands settings\n bbPeriod: number;\n bbStdDev: number;\n\n // RSI settings\n rsiPeriod: number;\n rsiOversold: number;\n rsiOverbought: number;\n\n // Risk management\n positionSizePercent: number;\n stopLossPercent: number;\n takeProfitPercent: number;\n\n // Market condition filters\n minVolatility: number;\n maxVolatility: number;\n minVolumeRatio: number;\n\n // Entry/exit thresholds\n bbEntryThreshold: number; // How far outside BB for entry\n rsiConfirmation: boolean;\n}\n\nexport class MeanReversionStrategy implements TradingStrategy {\n public readonly id = \"mean-reversion-strategy\";\n public readonly name = \"MeanReversionStrategy\";\n public readonly description =\n \"A strategy that trades on mean reversion patterns using Bollinger Bands and RSI\";\n private config: MeanReversionConfig;\n private initialized = false;\n\n constructor(config?: Partial<MeanReversionConfig>) {\n this.config = {\n // Default configuration\n bbPeriod: 20,\n bbStdDev: 2,\n rsiPeriod: 14,\n rsiOversold: 30,\n rsiOverbought: 70,\n positionSizePercent: 0.02,\n stopLossPercent: 0.03,\n takeProfitPercent: 0.02,\n minVolatility: 0.01,\n maxVolatility: 0.05,\n minVolumeRatio: 1.2,\n bbEntryThreshold: 0.95,\n rsiConfirmation: true,\n ...config,\n };\n }\n\n async initialize(runtime: AgentRuntime): Promise<void> {\n this.runtime = runtime;\n this.initialized = true;\n console.log(`[${this.name}] Initialized with config:`, this.config);\n }\n\n isReady(): boolean {\n return this.initialized;\n }\n\n async decide(params: {\n marketData: StrategyContextMarketData;\n agentState: AgentState;\n portfolioSnapshot: PortfolioSnapshot;\n agentRuntime?: AgentRuntime;\n }): Promise<TradeOrder | null> {\n const { marketData, agentState, portfolioSnapshot } = params;\n const { priceData, currentPrice } = marketData;\n\n if (\n !priceData ||\n priceData.length <\n Math.max(this.config.bbPeriod, this.config.rsiPeriod) + 10\n ) {\n return null;\n }\n\n // Extract price arrays\n const closes = priceData.map((c: OHLCV) => c.close);\n const _highs = priceData.map((c: OHLCV) => c.high);\n const _lows = priceData.map((c: OHLCV) => c.low);\n const volumes = priceData.map((c: OHLCV) => c.volume);\n\n // Calculate indicators\n const bb = this.calculateBollingerBands(closes);\n const rsi = this.calculateRSI(closes);\n const volatility = agentState.volatility;\n const volumeRatio = this.calculateVolumeRatio(volumes);\n\n if (!bb || !rsi || rsi.length === 0) return null;\n\n const currentRSI = rsi[rsi.length - 1];\n const { upper, lower, middle } = bb;\n\n // Market condition checks\n if (\n volatility < this.config.minVolatility ||\n volatility > this.config.maxVolatility\n ) {\n console.log(\n `[${this.name}] Volatility ${volatility.toFixed(4)} outside range [${this.config.minVolatility}, ${this.config.maxVolatility}]`,\n );\n return null;\n }\n\n if (volumeRatio < this.config.minVolumeRatio) {\n console.log(\n `[${this.name}] Volume ratio ${volumeRatio.toFixed(2)} below minimum ${this.config.minVolumeRatio}`,\n );\n return null;\n }\n\n // Check for mean reversion opportunities\n const distanceFromUpper = (upper - currentPrice) / currentPrice;\n const distanceFromLower = (currentPrice - lower) / currentPrice;\n const distanceFromMiddle = Math.abs(currentPrice - middle) / middle;\n\n // Buy signal: Price near lower band + RSI oversold\n if (currentPrice <= lower * (1 + (1 - this.config.bbEntryThreshold))) {\n const rsiCondition =\n !this.config.rsiConfirmation || currentRSI < this.config.rsiOversold;\n\n if (rsiCondition) {\n const positionSize = this.calculatePositionSize(\n portfolioSnapshot.totalValue,\n currentPrice,\n );\n\n console.log(\n `[${this.name}] BUY SIGNAL - Price at lower BB, RSI: ${currentRSI.toFixed(2)}`,\n );\n\n return {\n action: TradeType.BUY,\n orderType: OrderType.MARKET,\n pair: \"SOL/USDC\", // This should come from context\n quantity: positionSize,\n reason: `Mean reversion buy: Price ${distanceFromLower.toFixed(2)}% below lower BB, RSI: ${currentRSI.toFixed(2)}`,\n timestamp: Date.now(),\n };\n }\n }\n\n // Sell signal: Price near upper band + RSI overbought\n if (currentPrice >= upper * (1 - (1 - this.config.bbEntryThreshold))) {\n const rsiCondition =\n !this.config.rsiConfirmation || currentRSI > this.config.rsiOverbought;\n\n if (rsiCondition) {\n const currentHolding = portfolioSnapshot.holdings.SOL || 0;\n\n if (currentHolding > 0) {\n console.log(\n `[${this.name}] SELL SIGNAL - Price at upper BB, RSI: ${currentRSI.toFixed(2)}`,\n );\n\n return {\n action: TradeType.SELL,\n orderType: OrderType.MARKET,\n pair: \"SOL/USDC\",\n quantity: currentHolding,\n reason: `Mean reversion sell: Price ${distanceFromUpper.toFixed(2)}% above upper BB, RSI: ${currentRSI.toFixed(2)}`,\n timestamp: Date.now(),\n };\n }\n }\n }\n\n // Exit position if price returns to mean\n const currentHolding = portfolioSnapshot.holdings.SOL || 0;\n if (currentHolding > 0 && distanceFromMiddle < 0.01) {\n console.log(`[${this.name}] Price returned to mean, exiting position`);\n\n return {\n action: TradeType.SELL,\n orderType: OrderType.MARKET,\n pair: \"SOL/USDC\",\n quantity: currentHolding,\n reason: `Price returned to mean (${distanceFromMiddle.toFixed(2)}% from middle BB)`,\n timestamp: Date.now(),\n };\n }\n\n return null;\n }\n\n private calculateBollingerBands(\n closes: number[],\n ): { upper: number; middle: number; lower: number } | null {\n if (closes.length < this.config.bbPeriod) return null;\n\n const bb = talib.BollingerBands.calculate({\n period: this.config.bbPeriod,\n values: closes,\n stdDev: this.config.bbStdDev,\n });\n\n if (!bb || bb.length === 0) return null;\n\n const lastBB = bb[bb.length - 1];\n return {\n upper: lastBB.upper,\n middle: lastBB.middle,\n lower: lastBB.lower,\n };\n }\n\n private calculateRSI(closes: number[]): number[] {\n const rsi = talib.RSI.calculate({\n period: this.config.rsiPeriod,\n values: closes,\n });\n return rsi;\n }\n\n private calculateVolumeRatio(volumes: number[]): number {\n if (volumes.length < 20) return 0;\n\n const recentVolume = volumes.slice(-5).reduce((a, b) => a + b, 0) / 5;\n const avgVolume = volumes.slice(-20).reduce((a, b) => a + b, 0) / 20;\n\n return avgVolume > 0 ? recentVolume / avgVolume : 0;\n }\n\n private calculatePositionSize(portfolioValue: number, price: number): number {\n const positionValue = portfolioValue * this.config.positionSizePercent;\n return positionValue / price;\n }\n\n updateConfig(config: Partial<MeanReversionConfig>): void {\n this.config = { ...this.config, ...config };\n console.log(`[${this.name}] Config updated:`, this.config);\n }\n\n getConfig(): MeanReversionConfig {\n return { ...this.config };\n }\n}\n"],"mappings":"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","names":["currentHolding"]}
@@ -33,7 +33,8 @@ var RuleBasedStrategy = class {
33
33
  isReady() {
34
34
  return true;
35
35
  }
36
- configure(params) {
36
+ configure(rawParams) {
37
+ const params = rawParams;
37
38
  console.error(
38
39
  "[RuleBasedStrategy DIAG] configure called. Incoming params:",
39
40
  JSON.stringify(params)
@@ -54,10 +55,14 @@ var RuleBasedStrategy = class {
54
55
  throw new Error("Long MA period must be positive.");
55
56
  if (rule.type === "SMA_CROSSOVER" || rule.type === "EMA_CROSSOVER") {
56
57
  if (!rule.shortMAPeriod || !rule.longMAPeriod) {
57
- throw new Error("Short and Long MA periods are required for crossover rules.");
58
+ throw new Error(
59
+ "Short and Long MA periods are required for crossover rules."
60
+ );
58
61
  }
59
62
  if (rule.shortMAPeriod >= rule.longMAPeriod) {
60
- throw new Error("Short MA period must be less than Long MA period for crossovers.");
63
+ throw new Error(
64
+ "Short MA period must be less than Long MA period for crossovers."
65
+ );
61
66
  }
62
67
  if (!rule.maType) {
63
68
  console.warn(
@@ -74,7 +79,9 @@ var RuleBasedStrategy = class {
74
79
  throw new Error("RSI oversold must be less than RSI overbought");
75
80
  });
76
81
  if (params.tradeSizePercentage !== void 0 && (params.tradeSizePercentage <= 0 || params.tradeSizePercentage > 1)) {
77
- throw new Error("tradeSizePercentage must be between 0 (exclusive) and 1 (inclusive).");
82
+ throw new Error(
83
+ "tradeSizePercentage must be between 0 (exclusive) and 1 (inclusive)."
84
+ );
78
85
  }
79
86
  if (params.fixedTradeQuantity !== void 0 && params.fixedTradeQuantity <= 0) {
80
87
  throw new Error("fixedTradeQuantity must be positive.");
@@ -126,7 +133,11 @@ var RuleBasedStrategy = class {
126
133
  }
127
134
  calculateIndicators(ohlcvData, rules) {
128
135
  const minDataPointsForAnyCalc = rules.reduce((minOverall, r) => {
129
- const periodReq = Math.max(r.longMAPeriod || 0, r.rsiPeriod || 0, r.macdSlowPeriod || 0);
136
+ const periodReq = Math.max(
137
+ r.longMAPeriod || 0,
138
+ r.rsiPeriod || 0,
139
+ r.macdSlowPeriod || 0
140
+ );
130
141
  return Math.min(minOverall, periodReq > 0 ? periodReq + 1 : Infinity);
131
142
  }, Infinity);
132
143
  if (ohlcvData.length < (this.params.minIndicatorDataPoints ?? DEFAULT_MIN_INDICATOR_DATA_POINTS) || ohlcvData.length < minDataPointsForAnyCalc) {
@@ -195,7 +206,10 @@ var RuleBasedStrategy = class {
195
206
  if (!marketData.priceData || marketData.priceData.length < 20) {
196
207
  return null;
197
208
  }
198
- const indicators = this.calculateIndicators(marketData.priceData, this.params.rules);
209
+ const indicators = this.calculateIndicators(
210
+ marketData.priceData,
211
+ this.params.rules
212
+ );
199
213
  let shouldBuy = false;
200
214
  let shouldSell = false;
201
215
  let buyReason = "";
@@ -225,7 +239,10 @@ var RuleBasedStrategy = class {
225
239
  if (shouldBuy) {
226
240
  const cashHolding = portfolioSnapshot.holdings.USDC || 0;
227
241
  if (cashHolding > 10 && marketData.currentPrice) {
228
- const quantity = this.calculateTradeQuantity(marketData, portfolioSnapshot);
242
+ const quantity = this.calculateTradeQuantity(
243
+ marketData,
244
+ portfolioSnapshot
245
+ );
229
246
  if (quantity && quantity > MIN_TRADE_QUANTITY_THRESHOLD) {
230
247
  return {
231
248
  pair,
@@ -273,4 +290,4 @@ var RuleBasedStrategy = class {
273
290
  export {
274
291
  RuleBasedStrategy
275
292
  };
276
- //# sourceMappingURL=chunk-GYTZTIWK.js.map
293
+ //# sourceMappingURL=chunk-IQWCSPU4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/strategies/RuleBasedStrategy.ts"],"sourcesContent":["import * as ti from \"technicalindicators\";\nimport {\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\n// Mock/placeholder for technical indicator calculation results\ninterface TAResults {\n rsi?: number;\n smaShort?: number;\n smaLong?: number;\n prevSmaShort?: number;\n prevSmaLong?: number;\n emaShort?: number;\n emaLong?: number;\n prevEmaShort?: number;\n prevEmaLong?: number;\n macd?: {\n MACD?: number;\n signal?: number;\n histogram?: number;\n };\n prevMacd?: {\n MACD?: number;\n signal?: number;\n histogram?: number;\n };\n // ema?: number[]; // Keep as array if EMA logic will also be array-based initially\n // macd?: { macd: number[]; signal: number[]; histogram: number[] };\n // Add other indicators as needed\n}\n\n// Assume a technicalindicators-like library structure\n// This is a conceptual placeholder for actual library usage.\n// REMOVED - no longer needed\n// interface TechnicalIndicatorsLib { ... }\n// const tiLibrary: TechnicalIndicatorsLib | null = null;\n\nexport interface RuleCondition {\n type:\n | \"RSI\"\n | \"SMA_CROSSOVER\"\n | \"EMA_CROSSOVER\"\n | \"VOLUME\"\n | \"PRICE_ACTION\"\n | \"MACD_CROSS\";\n // RSI\n rsiPeriod?: number;\n rsiOverbought?: number; // e.g., 70 (for sell)\n rsiOversold?: number; // e.g., 30 (for buy)\n // SMA/EMA\n shortMAPeriod?: number;\n longMAPeriod?: number;\n maType?: \"SMA\" | \"EMA\"; // Specify MA type for crossover rules\n // VOLUME\n minVolume24h?: number;\n // PRICE_ACTION\n priceBreaksNDayHigh?: number; // N days for high (e.g., 7-day high)\n priceBreaksNDayLow?: number; // N days for low\n // MACD\n macdFastPeriod?: number;\n macdSlowPeriod?: number;\n macdSignalPeriod?: number;\n\n action: TradeType; // BUY or SELL if this condition is met\n}\n\nexport interface StopLossTakeProfitConfig {\n stopLossPercentage?: number; // e.g., 0.05 for 5%\n takeProfitPercentage?: number; // e.g., 0.10 for 10%\n}\n\nexport interface RuleBasedStrategyParams {\n rules: RuleCondition[];\n stopLossTakeProfit?: StopLossTakeProfitConfig;\n /** Percentage of available capital for a trade, or fixed quantity if capital/price info is missing */\n tradeSizePercentage?: number;\n fixedTradeQuantity?: number;\n /** Minimum number of data points required for indicators to be considered valid */\n minIndicatorDataPoints?: number;\n indicators?: string[];\n buyConditions?: Record<string, { threshold: number; condition: string }>;\n sellConditions?: Record<string, { threshold: number; condition: string }>;\n riskSettings?: {\n maxPositionSize?: number;\n stopLossPercentage?: number;\n takeProfitPercentage?: number;\n };\n}\n\nconst DEFAULT_TRADE_SIZE_PERCENTAGE = 0.01; // 1%\nconst DEFAULT_FIXED_TRADE_QUANTITY = 1;\nconst DEFAULT_MIN_INDICATOR_DATA_POINTS = 20;\nconst MIN_TRADE_QUANTITY_THRESHOLD = 1e-8; // Added threshold\n\nexport class RuleBasedStrategy implements TradingStrategy {\n public readonly id = \"rule-based-v1\";\n public readonly name = \"Rule-Based Trading Strategy\";\n public readonly description =\n \"Makes trading decisions based on technical indicators and thresholds.\";\n\n private params: RuleBasedStrategyParams = {\n rules: [],\n tradeSizePercentage: DEFAULT_TRADE_SIZE_PERCENTAGE,\n fixedTradeQuantity: DEFAULT_FIXED_TRADE_QUANTITY,\n minIndicatorDataPoints: DEFAULT_MIN_INDICATOR_DATA_POINTS,\n indicators: [\"sma\", \"ema\", \"rsi\", \"macd\"],\n buyConditions: {\n rsi: { threshold: 30, condition: \"below\" },\n macd: { threshold: 0, condition: \"above\" },\n },\n sellConditions: {\n rsi: { threshold: 70, condition: \"above\" },\n macd: { threshold: 0, condition: \"below\" },\n },\n riskSettings: {\n maxPositionSize: 0.05,\n stopLossPercentage: 0.02,\n takeProfitPercentage: 0.05,\n },\n };\n\n // The actual TI library is now used directly.\n private indicators: typeof ti = ti;\n\n isReady(): boolean {\n return true; // Rule-based strategy is always ready\n }\n\n configure(rawParams: Record<string, unknown>): void {\n const params = rawParams as unknown as RuleBasedStrategyParams;\n console.error(\n \"[RuleBasedStrategy DIAG] configure called. Incoming params:\",\n JSON.stringify(params),\n );\n console.error(\n \"[RuleBasedStrategy DIAG] configure: this.params BEFORE merge:\",\n JSON.stringify(this.params),\n );\n if (!params.rules || params.rules.length === 0) {\n throw new Error(\"At least one rule must be configured.\");\n }\n\n // Validate individual rule parameters first\n params.rules.forEach((rule) => {\n if (rule.rsiPeriod !== undefined && rule.rsiPeriod <= 0)\n throw new Error(\"RSI period must be positive.\");\n if (rule.shortMAPeriod !== undefined && rule.shortMAPeriod <= 0)\n throw new Error(\"Short MA period must be positive.\");\n if (rule.longMAPeriod !== undefined && rule.longMAPeriod <= 0)\n throw new Error(\"Long MA period must be positive.\");\n if (rule.type === \"SMA_CROSSOVER\" || rule.type === \"EMA_CROSSOVER\") {\n if (!rule.shortMAPeriod || !rule.longMAPeriod) {\n throw new Error(\n \"Short and Long MA periods are required for crossover rules.\",\n );\n }\n if (rule.shortMAPeriod >= rule.longMAPeriod) {\n throw new Error(\n \"Short MA period must be less than Long MA period for crossovers.\",\n );\n }\n if (!rule.maType) {\n console.warn(\n `[RuleBasedStrategy] maType not specified for crossover rule, defaulting to SMA.`,\n );\n rule.maType = \"SMA\";\n }\n }\n if (\n rule.rsiOverbought !== undefined &&\n (rule.rsiOverbought <= 0 || rule.rsiOverbought > 100)\n )\n throw new Error(\"RSI overbought must be between 0 and 100\");\n if (\n rule.rsiOversold !== undefined &&\n (rule.rsiOversold <= 0 || rule.rsiOversold > 100)\n )\n throw new Error(\"RSI oversold must be between 0 and 100\");\n if (\n rule.rsiOversold &&\n rule.rsiOverbought &&\n rule.rsiOversold >= rule.rsiOverbought\n )\n throw new Error(\"RSI oversold must be less than RSI overbought\");\n });\n\n // Validate overall strategy parameters from input params\n if (\n params.tradeSizePercentage !== undefined &&\n (params.tradeSizePercentage <= 0 || params.tradeSizePercentage > 1)\n ) {\n throw new Error(\n \"tradeSizePercentage must be between 0 (exclusive) and 1 (inclusive).\",\n );\n }\n if (\n params.fixedTradeQuantity !== undefined &&\n params.fixedTradeQuantity <= 0\n ) {\n throw new Error(\"fixedTradeQuantity must be positive.\");\n }\n // Validate minIndicatorDataPoints from input params before merging params\n if (\n params.minIndicatorDataPoints !== undefined &&\n params.minIndicatorDataPoints < 1\n ) {\n throw new Error(\"minIndicatorDataPoints must be at least 1.\");\n }\n\n const tempParams = { ...this.params, ...params }; // Merge incoming params over current defaults/settings\n\n const currentRulesLongestPeriod = tempParams.rules.reduce(\n (max, r) => Math.max(max, r.longMAPeriod || 0, r.rsiPeriod || 0),\n 0,\n );\n const pointsNeededByCurrentRules =\n currentRulesLongestPeriod > 0 ? currentRulesLongestPeriod + 1 : 1;\n\n if (params.minIndicatorDataPoints !== undefined) {\n if (params.minIndicatorDataPoints < pointsNeededByCurrentRules) {\n console.warn(\n `[RuleBasedStrategy] User-defined minIndicatorDataPoints (${params.minIndicatorDataPoints}) is less than required by current rules (${pointsNeededByCurrentRules}). Adjusting to ${pointsNeededByCurrentRules}.`,\n );\n tempParams.minIndicatorDataPoints = pointsNeededByCurrentRules;\n } else {\n tempParams.minIndicatorDataPoints = params.minIndicatorDataPoints;\n }\n } else {\n tempParams.minIndicatorDataPoints = Math.max(\n DEFAULT_MIN_INDICATOR_DATA_POINTS,\n pointsNeededByCurrentRules,\n );\n }\n\n if (params.indicators) {\n this.params.indicators = params.indicators;\n }\n if (params.buyConditions) {\n this.params.buyConditions = { ...params.buyConditions };\n }\n if (params.sellConditions) {\n this.params.sellConditions = { ...params.sellConditions };\n }\n if (params.riskSettings) {\n this.params.riskSettings = {\n ...this.params.riskSettings,\n ...params.riskSettings,\n };\n }\n\n this.params = tempParams; // Assign fully validated and adjusted params\n console.error(\n \"[RuleBasedStrategy DIAG] configure: this.params AFTER merge and adjustments:\",\n JSON.stringify(this.params),\n );\n }\n\n private calculateIndicators(\n ohlcvData: OHLCV[],\n rules: RuleCondition[],\n ): TAResults {\n // No need to check for this.indicators, we are using the imported library directly.\n\n const minDataPointsForAnyCalc = rules.reduce((minOverall, r) => {\n const periodReq = Math.max(\n r.longMAPeriod || 0,\n r.rsiPeriod || 0,\n r.macdSlowPeriod || 0,\n );\n return Math.min(minOverall, periodReq > 0 ? periodReq + 1 : Infinity);\n }, Infinity);\n\n if (\n ohlcvData.length <\n (this.params.minIndicatorDataPoints ??\n DEFAULT_MIN_INDICATOR_DATA_POINTS) ||\n ohlcvData.length < minDataPointsForAnyCalc\n ) {\n return {};\n }\n\n const closePrices = ohlcvData.map((d) => d.close);\n const results: TAResults = {};\n\n // --- RSI Calculation ---\n const rsiRule = rules.find((r) => r.type === \"RSI\" && r.rsiPeriod);\n if (rsiRule?.rsiPeriod && closePrices.length >= rsiRule.rsiPeriod) {\n const rsiResult = this.indicators.rsi({\n values: closePrices,\n period: rsiRule.rsiPeriod,\n });\n if (rsiResult.length > 0) {\n results.rsi = rsiResult[rsiResult.length - 1];\n }\n }\n\n // --- MA Crossover Calculation (SMA & EMA) ---\n const crossoverRules = rules.filter(\n (r) =>\n (r.type === \"SMA_CROSSOVER\" || r.type === \"EMA_CROSSOVER\") &&\n r.shortMAPeriod &&\n r.longMAPeriod,\n );\n\n for (const rule of crossoverRules) {\n if (\n rule.shortMAPeriod &&\n rule.longMAPeriod &&\n closePrices.length >= rule.longMAPeriod + 1\n ) {\n const maIndicator =\n rule.maType === \"EMA\" ? this.indicators.ema : this.indicators.sma;\n\n const shortMA = maIndicator({\n values: closePrices,\n period: rule.shortMAPeriod,\n });\n const longMA = maIndicator({\n values: closePrices,\n period: rule.longMAPeriod,\n });\n\n if (shortMA.length >= 2 && longMA.length >= 2) {\n if (rule.maType === \"EMA\") {\n results.emaShort = shortMA[shortMA.length - 1];\n results.prevEmaShort = shortMA[shortMA.length - 2];\n results.emaLong = longMA[longMA.length - 1];\n results.prevEmaLong = longMA[longMA.length - 2];\n } else {\n results.smaShort = shortMA[shortMA.length - 1];\n results.prevSmaShort = shortMA[shortMA.length - 2];\n results.smaLong = longMA[longMA.length - 1];\n results.prevSmaLong = longMA[longMA.length - 2];\n }\n }\n }\n }\n\n // --- MACD Calculation ---\n const macdRule = rules.find((r) => r.type === \"MACD_CROSS\");\n if (\n macdRule?.macdFastPeriod &&\n macdRule.macdSlowPeriod &&\n macdRule.macdSignalPeriod &&\n closePrices.length >= macdRule.macdSlowPeriod\n ) {\n const macdResult = this.indicators.macd({\n values: closePrices,\n fastPeriod: macdRule.macdFastPeriod,\n slowPeriod: macdRule.macdSlowPeriod,\n signalPeriod: macdRule.macdSignalPeriod,\n SimpleMAOscillator: false,\n SimpleMASignal: false,\n });\n if (macdResult.length >= 2) {\n results.macd = macdResult[macdResult.length - 1];\n results.prevMacd = macdResult[macdResult.length - 2];\n }\n }\n\n return results;\n }\n\n async decide(params: {\n marketData: StrategyContextMarketData;\n agentState: AgentState;\n portfolioSnapshot: PortfolioSnapshot;\n agentRuntime?: unknown;\n }): Promise<TradeOrder | null> {\n const { marketData, portfolioSnapshot } = params;\n\n if (!marketData.priceData || marketData.priceData.length < 20) {\n return null; // Not enough data for indicators\n }\n\n const indicators = this.calculateIndicators(\n marketData.priceData,\n this.params.rules,\n );\n\n // Check each rule to see if any conditions are met\n let shouldBuy = false;\n let shouldSell = false;\n let buyReason = \"\";\n let sellReason = \"\";\n\n for (const rule of this.params.rules) {\n switch (rule.type) {\n case \"RSI\":\n if (indicators.rsi !== undefined) {\n if (\n rule.rsiOversold &&\n indicators.rsi < rule.rsiOversold &&\n rule.action === TradeType.BUY\n ) {\n shouldBuy = true;\n buyReason = `RSI oversold (${indicators.rsi.toFixed(2)} < ${rule.rsiOversold})`;\n }\n if (\n rule.rsiOverbought &&\n indicators.rsi > rule.rsiOverbought &&\n rule.action === TradeType.SELL\n ) {\n shouldSell = true;\n sellReason = `RSI overbought (${indicators.rsi.toFixed(2)} > ${rule.rsiOverbought})`;\n }\n }\n break;\n case \"VOLUME\":\n // Volume rules are not currently implemented in the indicator calculation\n // so they should not trigger\n break;\n // Add other rule types as needed\n }\n }\n\n // Extract asset from portfolio snapshot or use a default\n const assetSymbol =\n Object.keys(portfolioSnapshot.holdings).find(\n (key) => key !== \"USDC\" && portfolioSnapshot.holdings[key] > 0,\n ) || \"SOL\";\n\n const pair = `${assetSymbol}/USDC`;\n\n // Execute buy if conditions are met\n if (shouldBuy) {\n const cashHolding = portfolioSnapshot.holdings.USDC || 0;\n if (cashHolding > 10 && marketData.currentPrice) {\n const quantity = this.calculateTradeQuantity(\n marketData,\n portfolioSnapshot,\n );\n\n if (quantity && quantity > MIN_TRADE_QUANTITY_THRESHOLD) {\n return {\n pair,\n action: TradeType.BUY,\n quantity: parseFloat(quantity.toFixed(8)),\n orderType: OrderType.MARKET,\n timestamp: Date.now(),\n reason: buyReason || \"Technical indicators suggest buy signal\",\n };\n }\n }\n }\n\n // Execute sell if conditions are met\n if (shouldSell) {\n const holding = portfolioSnapshot.holdings[assetSymbol] || 0;\n if (holding > 0) {\n return {\n pair,\n action: TradeType.SELL,\n quantity: holding,\n orderType: OrderType.MARKET,\n timestamp: Date.now(),\n reason: sellReason || \"Technical indicators suggest sell signal\",\n };\n }\n }\n\n return null;\n }\n\n private calculateTradeQuantity(\n marketData: StrategyContextMarketData,\n portfolioSnapshot: PortfolioSnapshot,\n ): number | null {\n // Use USDC balance from portfolio snapshot\n const usdcBalance = portfolioSnapshot.holdings.USDC || 0;\n\n if (\n this.params.tradeSizePercentage &&\n usdcBalance > 0 &&\n marketData.currentPrice &&\n marketData.currentPrice > 0\n ) {\n const capitalToUse = usdcBalance * this.params.tradeSizePercentage;\n const quantity = capitalToUse / marketData.currentPrice;\n if (quantity < MIN_TRADE_QUANTITY_THRESHOLD) {\n return null;\n }\n return quantity;\n } else if (\n this.params.fixedTradeQuantity &&\n this.params.fixedTradeQuantity > 0\n ) {\n return this.params.fixedTradeQuantity;\n } else {\n // Default to small fixed quantity\n return 0.1;\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -36,7 +36,9 @@ var MomentumBreakoutStrategy = class {
36
36
  if (potentialToken) {
37
37
  return potentialToken;
38
38
  }
39
- console.warn("[MomentumBreakout] Could not infer trading pair from portfolio");
39
+ console.warn(
40
+ "[MomentumBreakout] Could not infer trading pair from portfolio"
41
+ );
40
42
  return "UNKNOWN";
41
43
  }
42
44
  async decide(params) {
@@ -53,43 +55,58 @@ var MomentumBreakoutStrategy = class {
53
55
  }
54
56
  if (!priceData || priceData.length < 100) {
55
57
  if (this.debugMode && this.tradeAttempts === 0) {
56
- console.log(`[MomentumBreakout] Not enough data: ${priceData?.length || 0} candles`);
58
+ console.log(
59
+ `[MomentumBreakout] Not enough data: ${priceData?.length || 0} candles`
60
+ );
57
61
  }
58
62
  return null;
59
63
  }
60
64
  const tradingPair = this.inferTradingPair(portfolioSnapshot);
61
65
  const indicators = this.calculateMomentumIndicators(priceData);
62
66
  const holdings = Object.entries(portfolioSnapshot.holdings);
63
- const assetHolding = holdings.find(([key, value]) => key !== "USDC" && value > 0);
67
+ const assetHolding = holdings.find(
68
+ ([key, value]) => key !== "USDC" && value > 0
69
+ );
64
70
  const hasPosition = assetHolding && assetHolding[1] > 0;
65
71
  const assetSymbol = assetHolding ? assetHolding[0] : null;
66
72
  if (hasPosition && this.activePosition && assetSymbol) {
67
- return this.managePosition(currentPrice, indicators, assetSymbol, portfolioSnapshot);
73
+ return this.managePosition(
74
+ currentPrice,
75
+ indicators,
76
+ assetSymbol,
77
+ portfolioSnapshot
78
+ );
68
79
  }
69
80
  const shouldEnter = this.shouldEnter(indicators, currentPrice);
70
81
  if (this.debugMode && this.tradeAttempts < 5) {
71
82
  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`
83
+ console.log(
84
+ `[MomentumBreakout] Early evaluation #${this.tradeAttempts + 1}:`,
85
+ {
86
+ shouldEnter,
87
+ price: currentPrice.toFixed(6),
88
+ indicators: {
89
+ priceChange5m: `${(indicators.priceChange5m * 100).toFixed(3)}%`,
90
+ priceChange15m: `${(indicators.priceChange15m * 100).toFixed(3)}%`,
91
+ volumeRatio: indicators.volumeRatio.toFixed(2),
92
+ trend: indicators.trendDirection,
93
+ adx: indicators.adx.toFixed(1)
94
+ },
95
+ conditions: {
96
+ momentum: conditions.hasMomentum,
97
+ volume: conditions.hasVolume,
98
+ trend: conditions.trendAligned,
99
+ notAtResistance: conditions.goodEntry,
100
+ metCount: `${conditions.metConditions}/4`
101
+ }
88
102
  }
89
- });
103
+ );
90
104
  }
91
105
  if (!hasPosition && shouldEnter) {
92
- const positionSize = this.calculatePositionSize(portfolioSnapshot.totalValue, currentPrice);
106
+ const positionSize = this.calculatePositionSize(
107
+ portfolioSnapshot.totalValue,
108
+ currentPrice
109
+ );
93
110
  if (positionSize > 1e-3) {
94
111
  this.activePosition = {
95
112
  entryPrice: currentPrice,
@@ -122,17 +139,20 @@ var MomentumBreakoutStrategy = class {
122
139
  this.tradeAttempts++;
123
140
  if (this.debugMode && this.tradeAttempts % 200 === 0) {
124
141
  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`
142
+ console.log(
143
+ `[MomentumBreakout] Periodic evaluation #${this.tradeAttempts}:`,
144
+ {
145
+ shouldEnter: conditions.metConditions >= 2,
146
+ price: currentPrice.toFixed(6),
147
+ conditions: {
148
+ momentum: conditions.hasMomentum,
149
+ volume: conditions.hasVolume,
150
+ trend: conditions.trendAligned,
151
+ notAtResistance: conditions.goodEntry,
152
+ metCount: `${conditions.metConditions}/4`
153
+ }
134
154
  }
135
- });
155
+ );
136
156
  }
137
157
  return null;
138
158
  }
@@ -297,7 +317,9 @@ var MomentumBreakoutStrategy = class {
297
317
  if (candles.length < period + 1) return 0;
298
318
  const priceChanges = [];
299
319
  for (let i = 1; i < candles.length; i++) {
300
- priceChanges.push(Math.abs(candles[i].close - candles[i - 1].close) / candles[i - 1].close);
320
+ priceChanges.push(
321
+ Math.abs(candles[i].close - candles[i - 1].close) / candles[i - 1].close
322
+ );
301
323
  }
302
324
  const avgChange = priceChanges.reduce((a, b) => a + b) / priceChanges.length;
303
325
  return Math.min(avgChange * 1e3, 100);
@@ -307,4 +329,4 @@ var MomentumBreakoutStrategy = class {
307
329
  export {
308
330
  MomentumBreakoutStrategy
309
331
  };
310
- //# sourceMappingURL=chunk-Z6SUPK5O.js.map
332
+ //# sourceMappingURL=chunk-PPB7RNSR.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(\n \"[MomentumBreakout] Could not infer trading pair from portfolio\",\n );\n return \"UNKNOWN\";\n }\n\n async decide(params: {\n marketData: StrategyContextMarketData;\n agentState: AgentState;\n portfolioSnapshot: PortfolioSnapshot;\n agentRuntime?: unknown;\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(\n `[MomentumBreakout] Not enough data: ${priceData?.length || 0} candles`,\n );\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(\n ([key, value]) => key !== \"USDC\" && value > 0,\n );\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(\n currentPrice,\n indicators,\n assetSymbol,\n portfolioSnapshot,\n );\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(\n `[MomentumBreakout] Early evaluation #${this.tradeAttempts + 1}:`,\n {\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\n if (!hasPosition && shouldEnter) {\n const positionSize = this.calculatePositionSize(\n portfolioSnapshot.totalValue,\n currentPrice,\n );\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(\n `[MomentumBreakout] Periodic evaluation #${this.tradeAttempts}:`,\n {\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\n return null;\n }\n\n private evaluateEntryConditions(indicators: MomentumIndicators) {\n const hasMomentum =\n indicators.priceChange5m > this.minPriceChange &&\n 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 =\n !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 =\n priceData[Math.max(0, priceData.length - 5)]?.close || currentPrice;\n const price15mAgo =\n priceData[Math.max(0, priceData.length - 15)]?.close || currentPrice;\n const price60mAgo =\n 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 =\n 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(\n indicators: MomentumIndicators,\n _currentPrice: number,\n ): 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(\n portfolioValue: number,\n currentPrice: number,\n ): 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(\n Math.abs(candles[i].close - candles[i - 1].close) /\n candles[i - 1].close,\n );\n }\n\n const avgChange =\n 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":[]}