Example 2: Price Action with Moving Average

This strategy uses historical price data to calculate a simple moving average and makes decisions based on price crossing the average.

class Strategy {
  constructor() {
    this.strategy = {
      data: {
        token: {
          address: "",
          creationTime: 0
        },
        priceTicks: []
      },
      steps: [
        {
          run: (entry, current) => this.buyOnMACross(entry, current),
          label: "Buy when price crosses above 20-period MA"
        },
        {
          run: (entry, current) => this.sellOnMACross(entry, current),
          label: "Sell when price crosses below 20-period MA"
        }
      ],
      context: {}
    }
  }

  /**
   * Calculate simple moving average from priceTicks
   * @param {number} periods - Number of periods to average
   * @returns {number|null} - Average price or null if not enough data
   */
  calculateMA(periods) {
    const ticks = this.strategy.data.priceTicks;

    // Need at least 'periods' ticks to calculate MA
    if (ticks.length < periods) {
      return null;
    }

    // Sum up the last 'periods' prices
    let sum = 0;
    for (let i = 0; i < periods; i++) {
      sum += ticks[i].usdPrice;
    }

    return sum / periods;
  }

  /**
   * Step 1: Buy when price crosses above moving average
   * Confirms that previous price was below MA (actual cross)
   */
  buyOnMACross(signalPrice, currentPrice) {
    const ma = this.calculateMA(20);

    // Not enough data yet
    if (ma === null) {
      return null;
    }

    // Get previous price (if available)
    const ticks = this.strategy.data.priceTicks;
    const previousPrice = ticks.length >= 2 ? ticks[1].usdPrice : null;

    // Check for bullish cross:
    // - Current price is above MA
    // - Previous price was below MA (confirms cross)
    // - Price is within 20% of signal (not chasing too much)
    const isPriceAboveMA = currentPrice > ma;
    const wasPriceBelowMA = previousPrice ? previousPrice < ma : false;
    const isWithinRange = currentPrice <= signalPrice * 1.2;

    if (isPriceAboveMA && wasPriceBelowMA && isWithinRange) {
      // Store MA value for reference
      this.strategy.context.entryMA = ma;
      this.strategy.context.entryTime = Date.now();

      return {
        buy: true,
        partToBuyInPercentage: 100
      };
    }

    return null;
  }

  /**
   * Step 2: Sell when price crosses below moving average
   * Waits for at least 2x profit OR bearish MA cross
   */
  sellOnMACross(entryPrice, currentPrice) {
    const ma = this.calculateMA(20);

    // Not enough data yet
    if (ma === null) {
      return null;
    }

    const multiplier = currentPrice / entryPrice;

    // Take profit at 3x regardless of MA
    if (multiplier >= 3.0) {
      return {
        sell: true,
        partToSellInPercentage: 100
      };
    }

    // Get previous price
    const ticks = this.strategy.data.priceTicks;
    const previousPrice = ticks.length >= 2 ? ticks[1].usdPrice : null;

    // Check for bearish cross:
    // - Current price is below MA
    // - Previous price was above MA
    // - At least 1.5x profit (don't exit too early)
    const isPriceBelowMA = currentPrice < ma;
    const wasPriceAboveMA = previousPrice ? previousPrice > ma : false;
    const hasMinProfit = multiplier >= 1.5;

    if (isPriceBelowMA && wasPriceAboveMA && hasMinProfit) {
      return {
        sell: true,
        partToSellInPercentage: 100
      };
    }

    return null;
  }
}

Key Concepts Demonstrated

  1. Using priceTicks array: Accessing historical price data to calculate indicators

  2. Custom Calculations: Implementing your own technical analysis (moving average)

  3. Lookback Logic: Checking previous price to confirm crosses (not just levels)

  4. Risk Management: Requiring minimum profit before allowing MA-based exits

  5. Hard Take Profit: Having an absolute exit level regardless of indicator

Last updated