/**
 * Market Statistics and Microstructure Analysis
 * Tools for analyzing market behavior, liquidity, and trading patterns
 */

// Local interface definition to avoid circular dependency
interface OHLCVData {
  open: number;
  high: number;
  low: number;
  close: number;
  volume: number;
  timestamp: Date;
}

export interface LiquidityMetrics {
  bidAskSpread: number;
  relativeSpread: number;
  effectiveSpread: number;
  priceImpact: number;
  marketDepth: number;
  turnoverRatio: number;
  volumeWeightedSpread: number;
}

export interface MarketMicrostructure {
  tickSize: number;
  averageTradeSize: number;
  tradingFrequency: number;
  marketImpactCoefficient: number;
  informationShare: number;
  orderImbalance: number;
}

export interface TradingSessionStats {
  openPrice: number;
  closePrice: number;
  highPrice: number;
  lowPrice: number;
  volume: number;
  vwap: number;
  numberOfTrades: number;
  averageTradeSize: number;
  volatility: number;
}

export interface MarketRegime {
  regime: 'trending' | 'ranging' | 'volatile' | 'quiet';
  confidence: number;
  trendDirection?: 'up' | 'down';
  volatilityLevel: 'low' | 'medium' | 'high';
}

/**
 *  Volume Weighted Average Price (VWAP)
 */
export function VWAP(ohlcv: OHLCVData[]): number[] {
  if (ohlcv.length === 0) {
    return [];
  }

  const vwap: number[] = [];
  let cumulativeVolumePrice = 0;
  let cumulativeVolume = 0;

  for (const candle of ohlcv) {
    const typicalPrice = (candle.high + candle.low + candle.close) / 3;
    cumulativeVolumePrice += typicalPrice * candle.volume;
    cumulativeVolume += candle.volume;

    vwap.push(cumulativeVolume > 0 ? cumulativeVolumePrice / cumulativeVolume : typicalPrice);
  }

  return vwap;
}

/**
 *  Time Weighted Average Price (TWAP)
 */
export function TWAP(prices: number[], timeWeights?: number[]): number {
  if (prices.length === 0) {
    return 0;
  }

  if (!timeWeights) {
    return prices.reduce((sum, price) => sum + price, 0) / prices.length;
  }

  if (prices.length !== timeWeights.length) {
    throw new Error('Prices and time weights arrays must have the same length');
  }

  const totalWeight = timeWeights.reduce((sum, weight) => sum + weight, 0);
  const weightedSum = prices.reduce((sum, price, index) => sum + price * timeWeights[index], 0);

  return totalWeight > 0 ? weightedSum / totalWeight : 0;
}

/**
 *  market impact of trades
 */
export function MarketImpact(
  trades: Array<{ price: number; volume: number; side: 'buy' | 'sell'; timestamp: Date }>,
  benchmarkPrice: number
): {
  temporaryImpact: number;
  permanentImpact: number;
  totalImpact: number;
  priceImprovement: number;
} {
  if (trades.length === 0) {
    return {
      temporaryImpact: 0,
      permanentImpact: 0,
      totalImpact: 0,
      priceImprovement: 0,
    };
  }

  const volumeWeightedPrice =
    trades.reduce((sum, trade) => sum + trade.price * trade.volume, 0) /
    trades.reduce((sum, trade) => sum + trade.volume, 0);

  const totalImpact = (volumeWeightedPrice - benchmarkPrice) / benchmarkPrice;

  // Simplified impact calculation
  const temporaryImpact = totalImpact * 0.6; // Temporary component
  const permanentImpact = totalImpact * 0.4; // Permanent component

  const priceImprovement =
    trades.reduce((sum, trade) => {
      const improvement =
        trade.side === 'buy'
          ? Math.max(0, benchmarkPrice - trade.price)
          : Math.max(0, trade.price - benchmarkPrice);
      return sum + improvement * trade.volume;
    }, 0) / trades.reduce((sum, trade) => sum + trade.volume, 0);

  return {
    temporaryImpact,
    permanentImpact,
    totalImpact,
    priceImprovement,
  };
}

/**
 *  liquidity metrics
 */
export function LiquidityMetrics(
  ohlcv: OHLCVData[],
  bidPrices: number[],
  askPrices: number[],
  bidSizes: number[],
  askSizes: number[]
): LiquidityMetrics {
  if (ohlcv.length === 0 || bidPrices.length === 0) {
    return {
      bidAskSpread: 0,
      relativeSpread: 0,
      effectiveSpread: 0,
      priceImpact: 0,
      marketDepth: 0,
      turnoverRatio: 0,
      volumeWeightedSpread: 0,
    };
  }

  // Average bid-ask spread
  const spreads = bidPrices.map((bid, index) => askPrices[index] - bid);
  const bidAskSpread = spreads.reduce((sum, spread) => sum + spread, 0) / spreads.length;

  // Relative spread
  const midPrices = bidPrices.map((bid, index) => (bid + askPrices[index]) / 2);
  const averageMidPrice = midPrices.reduce((sum, mid) => sum + mid, 0) / midPrices.length;
  const relativeSpread = averageMidPrice > 0 ? bidAskSpread / averageMidPrice : 0;

  // Market depth
  const averageBidSize = bidSizes.reduce((sum, size) => sum + size, 0) / bidSizes.length;
  const averageAskSize = askSizes.reduce((sum, size) => sum + size, 0) / askSizes.length;
  const marketDepth = (averageBidSize + averageAskSize) / 2;

  // Turnover ratio
  const averageVolume = ohlcv.reduce((sum, candle) => sum + candle.volume, 0) / ohlcv.length;
  const averagePrice = ohlcv.reduce((sum, candle) => sum + candle.close, 0) / ohlcv.length;
  const marketCap = averagePrice * 1000000; // Simplified market cap
  const turnoverRatio = marketCap > 0 ? (averageVolume * averagePrice) / marketCap : 0;

  return {
    bidAskSpread,
    relativeSpread: relativeSpread * 100, // Convert to percentage
    effectiveSpread: bidAskSpread * 0.8, // Simplified effective spread
    priceImpact: relativeSpread * 2, // Simplified price impact
    marketDepth,
    turnoverRatio: turnoverRatio * 100, // Convert to percentage
    volumeWeightedSpread: bidAskSpread, // Simplified
  };
}

/**
 * Identify market regimes
 */
export function identifyMarketRegime(
  ohlcv: OHLCVData[],
  lookbackPeriod: number = 20
): MarketRegime {
  if (ohlcv.length < lookbackPeriod) {
    return {
      regime: 'quiet',
      confidence: 0,
      volatilityLevel: 'low',
    };
  }

  const recentData = ohlcv.slice(-lookbackPeriod);
  const prices = recentData.map(candle => candle.close);
  const volumes = recentData.map(candle => candle.volume);
  //  returns and volatility
  const returns = [];
  for (let i = 1; i < prices.length; i++) {
    returns.push((prices[i] - prices[i - 1]) / prices[i - 1]);
  }

  const volatility = calculateVolatility(returns);
  const averageVolume = volumes.reduce((sum, vol) => sum + vol, 0) / volumes.length;

  // Trend analysis
  const firstPrice = prices[0];
  const lastPrice = prices[prices.length - 1];
  const trendStrength = Math.abs((lastPrice - firstPrice) / firstPrice);

  // Determine volatility level
  let volatilityLevel: 'low' | 'medium' | 'high';
  if (volatility < 0.01) {
    volatilityLevel = 'low';
  } else if (volatility < 0.03) {
    volatilityLevel = 'medium';
  } else {
    volatilityLevel = 'high';
  }

  // Determine regime
  let regime: 'trending' | 'ranging' | 'volatile' | 'quiet';
  let confidence = 0;
  let trendDirection: 'up' | 'down' | undefined;

  if (volatility < 0.005) {
    regime = 'quiet';
    confidence = 0.8;
  } else if (volatility > 0.04) {
    regime = 'volatile';
    confidence = 0.7;
  } else if (trendStrength > 0.05) {
    regime = 'trending';
    trendDirection = lastPrice > firstPrice ? 'up' : 'down';
    confidence = Math.min(0.9, trendStrength * 10);
  } else {
    regime = 'ranging';
    confidence = 0.6;
  }

  return {
    regime,
    confidence,
    trendDirection,
    volatilityLevel,
  };
}

/**
 *  order book imbalance
 */
export function OrderBookImbalance(
  bidPrices: number[],
  askPrices: number[],
  bidSizes: number[],
  askSizes: number[],
  levels: number = 5
): number {
  const levelsToAnalyze = Math.min(levels, bidPrices.length, askPrices.length);

  let totalBidVolume = 0;
  let totalAskVolume = 0;

  for (let i = 0; i < levelsToAnalyze; i++) {
    totalBidVolume += bidSizes[i];
    totalAskVolume += askSizes[i];
  }

  const totalVolume = totalBidVolume + totalAskVolume;

  if (totalVolume === 0) {
    return 0;
  }

  return (totalBidVolume - totalAskVolume) / totalVolume;
}

/**
 *  intraday patterns
 */
export function IntradayPatterns(ohlcv: OHLCVData[]): {
  hourlyReturns: { [hour: number]: number };
  hourlyVolatility: { [hour: number]: number };
  hourlyVolume: { [hour: number]: number };
  openingGap: number;
  closingDrift: number;
} {
  const hourlyData: { [hour: number]: { returns: number[]; volumes: number[] } } = {};

  // Initialize hourly buckets
  for (let hour = 0; hour < 24; hour++) {
    hourlyData[hour] = { returns: [], volumes: [] };
  }

  // Aggregate data by hour
  for (let i = 1; i < ohlcv.length; i++) {
    const hour = ohlcv[i].timestamp.getHours();
    const return_ = (ohlcv[i].close - ohlcv[i - 1].close) / ohlcv[i - 1].close;

    hourlyData[hour].returns.push(return_);
    hourlyData[hour].volumes.push(ohlcv[i].volume);
  }

  //  statistics for each hour
  const hourlyReturns: { [hour: number]: number } = {};
  const hourlyVolatility: { [hour: number]: number } = {};
  const hourlyVolume: { [hour: number]: number } = {};

  for (let hour = 0; hour < 24; hour++) {
    const data = hourlyData[hour];
    hourlyReturns[hour] =
      data.returns.length > 0
        ? data.returns.reduce((sum, ret) => sum + ret, 0) / data.returns.length
        : 0;

    hourlyVolatility[hour] = calculateVolatility(data.returns);

    hourlyVolume[hour] =
      data.volumes.length > 0
        ? data.volumes.reduce((sum, vol) => sum + vol, 0) / data.volumes.length
        : 0;
  }
  //  opening gap and closing drift
  const openingGap = ohlcv.length > 1 ? (ohlcv[1].open - ohlcv[0].close) / ohlcv[0].close : 0;

  const lastCandle = ohlcv[ohlcv.length - 1];
  const closingDrift = (lastCandle.close - lastCandle.open) / lastCandle.open;

  return {
    hourlyReturns,
    hourlyVolatility,
    hourlyVolume,
    openingGap,
    closingDrift,
  };
}

/**
 *  price discovery metrics
 */
export function PriceDiscovery(
  prices1: number[], // Prices from market 1
  prices2: number[] // Prices from market 2
): {
  informationShare1: number;
  informationShare2: number;
  priceLeadLag: number; // Positive if market 1 leads
  cointegrationStrength: number;
} {
  if (prices1.length !== prices2.length || prices1.length < 2) {
    return {
      informationShare1: 0.5,
      informationShare2: 0.5,
      priceLeadLag: 0,
      cointegrationStrength: 0,
    };
  }

  //  returns
  const returns1 = [];
  const returns2 = [];

  for (let i = 1; i < prices1.length; i++) {
    returns1.push((prices1[i] - prices1[i - 1]) / prices1[i - 1]);
    returns2.push((prices2[i] - prices2[i - 1]) / prices2[i - 1]);
  }
  //  correlations with lags
  const correlation0 = calculateCorrelation(returns1, returns2);
  const correlation1 =
    returns1.length > 1 ? calculateCorrelation(returns1.slice(1), returns2.slice(0, -1)) : 0;
  const correlationMinus1 =
    returns1.length > 1 ? calculateCorrelation(returns1.slice(0, -1), returns2.slice(1)) : 0;

  // Price lead-lag (simplified)
  const priceLeadLag = correlation1 - correlationMinus1;

  // Information shares (simplified Hasbrouck methodology)
  const variance1 = calculateVariance(returns1);
  const variance2 = calculateVariance(returns2);
  const covariance = calculateCovariance(returns1, returns2);

  const totalVariance = variance1 + variance2 + 2 * covariance;
  const informationShare1 = totalVariance > 0 ? (variance1 + covariance) / totalVariance : 0.5;
  const informationShare2 = 1 - informationShare1;

  // Cointegration strength (simplified)
  const cointegrationStrength = Math.abs(correlation0);

  return {
    informationShare1,
    informationShare2,
    priceLeadLag,
    cointegrationStrength,
  };
}

/**
 *  market stress indicators
 */
export function MarketStress(
  ohlcv: OHLCVData[],
  lookbackPeriod: number = 20
): {
  stressLevel: 'low' | 'medium' | 'high' | 'extreme';
  volatilityStress: number;
  liquidityStress: number;
  correlationStress: number;
  overallStress: number;
} {
  if (ohlcv.length < lookbackPeriod) {
    return {
      stressLevel: 'low',
      volatilityStress: 0,
      liquidityStress: 0,
      correlationStress: 0,
      overallStress: 0,
    };
  }

  const recentData = ohlcv.slice(-lookbackPeriod);
  const returns = [];
  const volumes = [];

  for (let i = 1; i < recentData.length; i++) {
    returns.push((recentData[i].close - recentData[i - 1].close) / recentData[i - 1].close);
    volumes.push(recentData[i].volume);
  }
  // Volatility stress
  const volatility = calculateVolatility(returns);
  const volatilityStress = Math.min(1, volatility / 0.05); // Normalize to 5% daily vol

  // Liquidity stress (volume-based)
  const averageVolume = volumes.reduce((sum, vol) => sum + vol, 0) / volumes.length;
  const volumeVariability = calculateVolatility(volumes.map(vol => vol / averageVolume));
  const liquidityStress = Math.min(1, volumeVariability);

  // Correlation stress (simplified - would need multiple assets)
  const correlationStress = 0.3; // Placeholder

  // Overall stress
  const overallStress = volatilityStress * 0.4 + liquidityStress * 0.3 + correlationStress * 0.3;

  let stressLevel: 'low' | 'medium' | 'high' | 'extreme';
  if (overallStress < 0.25) {
    stressLevel = 'low';
  } else if (overallStress < 0.5) {
    stressLevel = 'medium';
  } else if (overallStress < 0.75) {
    stressLevel = 'high';
  } else {
    stressLevel = 'extreme';
  }

  return {
    stressLevel,
    volatilityStress,
    liquidityStress,
    correlationStress,
    overallStress,
  };
}

/**
 *  realized spread
 */
export function RealizedSpread(
  trades: Array<{ price: number; side: 'buy' | 'sell'; timestamp: Date }>,
  midPrices: number[],
  timeWindow: number = 5 // minutes
): number {
  if (trades.length === 0 || midPrices.length === 0) {
    return 0;
  }

  let totalSpread = 0;
  let count = 0;

  for (const trade of trades) {
    // Find corresponding mid price
    const midPrice = midPrices[0]; // Simplified - should match by timestamp

    const spread =
      trade.side === 'buy' ? 2 * (trade.price - midPrice) : 2 * (midPrice - trade.price);

    totalSpread += spread;
    count++;
  }

  return count > 0 ? totalSpread / count : 0;
}

/**
 *  implementation shortfall
 */
export function ImplementationShortfall(
  decisionPrice: number,
  executionPrices: number[],
  volumes: number[],
  commissions: number[],
  marketImpact: number[]
): {
  totalShortfall: number;
  delayComponent: number;
  marketImpactComponent: number;
  timingComponent: number;
  commissionComponent: number;
} {
  if (executionPrices.length !== volumes.length) {
    throw new Error('Execution prices and volumes must have same length');
  }

  const totalVolume = volumes.reduce((sum, vol) => sum + vol, 0);
  const weightedExecutionPrice =
    executionPrices.reduce((sum, price, i) => sum + price * volumes[i], 0) / totalVolume;

  const totalCommissions = commissions.reduce((sum, comm) => sum + comm, 0);
  const totalMarketImpact = marketImpact.reduce((sum, impact, i) => sum + impact * volumes[i], 0);

  const delayComponent = weightedExecutionPrice - decisionPrice;
  const marketImpactComponent = totalMarketImpact / totalVolume;
  const timingComponent = 0; // Simplified - would need benchmark price evolution
  const commissionComponent = totalCommissions / totalVolume;

  const totalShortfall =
    delayComponent + marketImpactComponent + timingComponent + commissionComponent;

  return {
    totalShortfall,
    delayComponent,
    marketImpactComponent,
    timingComponent,
    commissionComponent,
  };
}

/**
 * Amihud Illiquidity Measure (price impact per unit of volume)
 */
export function amihudIlliquidity(ohlcv: OHLCVData[], lookbackPeriod: number = 252): number {
  if (ohlcv.length < lookbackPeriod) {
    return 0;
  }

  const recentData = ohlcv.slice(-lookbackPeriod);
  let illiquiditySum = 0;
  let validDays = 0;

  for (const candle of recentData) {
    if (candle.volume > 0) {
      const dailyReturn = Math.abs((candle.close - candle.open) / candle.open);
      const dollarVolume = candle.volume * candle.close;

      if (dollarVolume > 0) {
        illiquiditySum += dailyReturn / dollarVolume;
        validDays++;
      }
    }
  }

  return validDays > 0 ? (illiquiditySum / validDays) * 1000000 : 0; // Scale to millions
}

/**
 * Roll's Spread Estimator (effective spread from serial covariance)
 */
export function rollSpreadEstimator(prices: number[]): number {
  if (prices.length < 3) {
    return 0;
  }

  // Calculate price changes
  const priceChanges: number[] = [];
  for (let i = 1; i < prices.length; i++) {
    priceChanges.push(prices[i] - prices[i - 1]);
  }

  // Calculate serial covariance
  let covariance = 0;
  for (let i = 1; i < priceChanges.length; i++) {
    covariance += priceChanges[i] * priceChanges[i - 1];
  }
  covariance /= priceChanges.length - 1;

  // Roll's estimator: spread = 2 * sqrt(-covariance)
  const spread = covariance < 0 ? 2 * Math.sqrt(-covariance) : 0;

  return spread;
}

/**
 * Kyle's Lambda (price impact coefficient)
 */
export function kyleLambda(
  priceChanges: number[],
  orderFlow: number[] // Signed order flow (positive for buys, negative for sells)
): number {
  if (priceChanges.length !== orderFlow.length || priceChanges.length < 2) {
    return 0;
  }

  // Calculate regression: priceChange = lambda * orderFlow + error
  const n = priceChanges.length;
  const meanPrice = priceChanges.reduce((sum, p) => sum + p, 0) / n;
  const meanFlow = orderFlow.reduce((sum, f) => sum + f, 0) / n;

  let numerator = 0;
  let denominator = 0;

  for (let i = 0; i < n; i++) {
    const priceDeviation = priceChanges[i] - meanPrice;
    const flowDeviation = orderFlow[i] - meanFlow;

    numerator += priceDeviation * flowDeviation;
    denominator += flowDeviation * flowDeviation;
  }

  return denominator > 0 ? numerator / denominator : 0;
}

/**
 * Probability of Informed Trading (PIN) - simplified version
 */
export function probabilityInformedTrading(
  buyVolumes: number[],
  sellVolumes: number[],
  period: number = 20
): number {
  if (buyVolumes.length !== sellVolumes.length || buyVolumes.length < period) {
    return 0;
  }

  const recentBuys = buyVolumes.slice(-period);
  const recentSells = sellVolumes.slice(-period);

  let totalImbalance = 0;
  let totalVolume = 0;

  for (let i = 0; i < period; i++) {
    const imbalance = Math.abs(recentBuys[i] - recentSells[i]);
    const volume = recentBuys[i] + recentSells[i];

    totalImbalance += imbalance;
    totalVolume += volume;
  }

  // Simplified PIN estimate based on order imbalance
  return totalVolume > 0 ? totalImbalance / totalVolume : 0;
}

/**
 * Herfindahl-Hirschman Index for Volume Concentration
 */
export function volumeConcentrationHHI(exchanges: Array<{ name: string; volume: number }>): number {
  if (exchanges.length === 0) {
    return 0;
  }

  const totalVolume = exchanges.reduce((sum, exchange) => sum + exchange.volume, 0);

  if (totalVolume === 0) {
    return 0;
  }

  let hhi = 0;
  for (const exchange of exchanges) {
    const marketShare = exchange.volume / totalVolume;
    hhi += marketShare * marketShare;
  }

  return hhi * 10000; // Scale to 0-10000 range
}
/**
 * Volume Profile
 */
export function volumeProfile(
  ohlcv: OHLCVData[],
  priceLevels: number
): { [price: number]: number } {
  const profile: { [price: number]: number } = {};

  if (ohlcv.length === 0) {
    return profile;
  }

  const minPrice = Math.min(...ohlcv.map(candle => candle.low));
  const maxPrice = Math.max(...ohlcv.map(candle => candle.high));
  const priceRange = maxPrice - minPrice;
  const priceIncrement = priceRange / priceLevels;

  for (let i = 0; i < priceLevels; i++) {
    const priceLevel = minPrice + i * priceIncrement;
    profile[priceLevel] = 0;
  }

  for (const candle of ohlcv) {
    const typicalPrice = (candle.high + candle.low + candle.close) / 3;
    const priceLevel =
      minPrice + Math.floor((typicalPrice - minPrice) / priceIncrement) * priceIncrement;
    if (profile[priceLevel] !== undefined) {
      profile[priceLevel] += candle.volume;
    }
  }

  return profile;
}

/**
 * Delta Neutral Hedging Ratio
 */
export function deltaNeutralHedgingRatio(optionDelta: number): number {
  return -optionDelta;
}

/**
 * Gamma Scalping Range
 */
export function gammaScalpingRange(gamma: number, theta: number, timeIncrement: number): number {
  return Math.sqrt((2 * Math.abs(theta) * timeIncrement) / gamma);
}

/**
 * Optimal Order Size (based on market impact)
 */
export function optimalOrderSize(alpha: number, lambda: number): number {
  return alpha / (2 * lambda);
}

/**
 * Adverse Selection Component of the Spread
 */
export function adverseSelectionComponent(
  probabilityOfInformedTrader: number,
  spread: number
): number {
  return probabilityOfInformedTrader * spread;
}

/**
 * Inventory Risk Component of the Spread
 */
export function inventoryRiskComponent(
  inventoryHoldingCost: number,
  orderArrivalRate: number
): number {
  return inventoryHoldingCost * Math.sqrt(orderArrivalRate);
}

/**
 * Quote Age
 */
export function quoteAge(lastUpdate: Date): number {
  return Date.now() - lastUpdate.getTime();
}

/**
 * Trade Classification (Lee-Ready algorithm)
 */
export function tradeClassification(
  tradePrice: number,
  bidPrice: number,
  askPrice: number,
  previousTradePrice: number
): 'buy' | 'sell' | 'unknown' {
  if (tradePrice > askPrice) {
    return 'buy';
  } else if (tradePrice < bidPrice) {
    return 'sell';
  } else if (tradePrice >= previousTradePrice) {
    return 'buy';
  } else {
    return 'sell';
  }
}

/**
 * Tick Rule
 */
export function tickRule(
  tradePrice: number,
  previousTradePrice: number
): 'buy' | 'sell' | 'unknown' {
  if (tradePrice > previousTradePrice) {
    return 'buy';
  } else if (tradePrice < previousTradePrice) {
    return 'sell';
  } else {
    return 'unknown';
  }
}

/**
 * Amihud's Lambda Variation with High-Frequency Data
 */
export function amihudIlliquidityHFT(
  priceChanges: number[],
  dollarVolumes: number[],
  timeDeltas: number[]
): number {
  let illiquiditySum = 0;
  let validTrades = 0;

  for (let i = 0; i < priceChanges.length; i++) {
    if (dollarVolumes[i] > 0 && timeDeltas[i] > 0) {
      illiquiditySum += Math.abs(priceChanges[i]) / (dollarVolumes[i] * timeDeltas[i]);
      validTrades++;
    }
  }

  return validTrades > 0 ? illiquiditySum / validTrades : 0;
}

/**
 * Garman-Klass Volatility
 */
export function garmanKlassVolatility(
  openPrices: number[],
  highPrices: number[],
  lowPrices: number[],
  closePrices: number[]
): number {
  if (
    openPrices.length !== highPrices.length ||
    openPrices.length !== lowPrices.length ||
    openPrices.length !== closePrices.length ||
    openPrices.length < 2
  ) {
    return 0;
  }

  let sumSquaredTerm1 = 0;
  let sumSquaredTerm2 = 0;
  const sumSquaredTerm3 = 0;

  for (let i = 0; i < openPrices.length; i++) {
    const logHO = Math.log(highPrices[i] / openPrices[i]);
    const logLO = Math.log(lowPrices[i] / openPrices[i]);
    const logCO = Math.log(closePrices[i] / openPrices[i]);

    sumSquaredTerm1 += 0.5 * (logHO * logHO + logLO * logLO);
    sumSquaredTerm2 += -(2 * Math.log(2) - 1) * (logCO * logCO);
  }

  const garmanKlassVariance = (1 / openPrices.length) * (sumSquaredTerm1 + sumSquaredTerm2);
  return Math.sqrt(garmanKlassVariance);
}

/**
 * Yang-Zhang Volatility
 */
export function yangZhangVolatility(
  openPrices: number[],
  highPrices: number[],
  lowPrices: number[],
  closePrices: number[],
  previousClosePrices: number[]
): number {
  if (
    openPrices.length !== highPrices.length ||
    openPrices.length !== lowPrices.length ||
    openPrices.length !== closePrices.length ||
    openPrices.length !== previousClosePrices.length ||
    openPrices.length < 2
  ) {
    return 0;
  }

  const k = 0.34 / (1.34 + (openPrices.length + 1) / (previousClosePrices.length - 1));

  let sumSquaredTerm1 = 0;
  let sumSquaredTerm2 = 0;
  let sumSquaredTerm3 = 0;

  for (let i = 0; i < openPrices.length; i++) {
    const overnightReturn = Math.log(openPrices[i] / previousClosePrices[i]);
    const openToHigh = Math.log(highPrices[i] / openPrices[i]);
    const openToLow = Math.log(lowPrices[i] / openPrices[i]);
    const closeToOpen = Math.log(closePrices[i] / openPrices[i]);

    sumSquaredTerm1 += overnightReturn * overnightReturn;
    sumSquaredTerm2 += openToHigh * openToHigh;
    sumSquaredTerm3 += openToLow * openToLow;
  }

  const variance = sumSquaredTerm1 + k * sumSquaredTerm2 + (1 - k) * sumSquaredTerm3;
  return Math.sqrt(variance);
}

/**
 * Volume Order Imbalance (VOI)
 */
export function volumeOrderImbalance(buyVolumes: number[], sellVolumes: number[]): number[] {
  if (buyVolumes.length !== sellVolumes.length) {
    return [];
  }

  const voi: number[] = [];
  for (let i = 0; i < buyVolumes.length; i++) {
    voi.push(buyVolumes[i] - sellVolumes[i]);
  }
  return voi;
}

/**
 * Cumulative Volume Delta (CVD)
 */
export function cumulativeVolumeDelta(buyVolumes: number[], sellVolumes: number[]): number[] {
  if (buyVolumes.length !== sellVolumes.length) {
    return [];
  }

  const cvd: number[] = [];
  let cumulativeDelta = 0;
  for (let i = 0; i < buyVolumes.length; i++) {
    cumulativeDelta += buyVolumes[i] - sellVolumes[i];
    cvd.push(cumulativeDelta);
  }
  return cvd;
}

/**
 * Market Order Ratio
 */
export function marketOrderRatio(marketOrders: number[], limitOrders: number[]): number[] {
  if (marketOrders.length !== limitOrders.length) {
    return [];
  }

  const ratios: number[] = [];
  for (let i = 0; i < marketOrders.length; i++) {
    const totalOrders = marketOrders[i] + limitOrders[i];
    ratios.push(totalOrders > 0 ? marketOrders[i] / totalOrders : 0);
  }
  return ratios;
}

/**
 * Helper function to calculate the average of an array of numbers
 */

function average(arr: number[]): number {
  if (arr.length === 0) {
    return 0;
  }
  return arr.reduce((a, b) => a + b, 0) / arr.length;
}

function calculateVolatility(returns: number[]): number {
  if (returns.length < 2) {
    return 0;
  }

  const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
  const variance =
    returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / (returns.length - 1);

  return Math.sqrt(variance);
}

function calculateCorrelation(x: number[], y: number[]): number {
  if (x.length !== y.length || x.length < 2) {
    return 0;
  }

  const n = x.length;
  const meanX = x.reduce((sum, val) => sum + val, 0) / n;
  const meanY = y.reduce((sum, val) => sum + val, 0) / n;

  let numerator = 0;
  let sumXSquared = 0;
  let sumYSquared = 0;

  for (let i = 0; i < n; i++) {
    const xDiff = x[i] - meanX;
    const yDiff = y[i] - meanY;

    numerator += xDiff * yDiff;
    sumXSquared += xDiff * xDiff;
    sumYSquared += yDiff * yDiff;
  }

  const denominator = Math.sqrt(sumXSquared * sumYSquared);

  return denominator > 0 ? numerator / denominator : 0;
}

function calculateVariance(values: number[]): number {
  if (values.length < 2) {
    return 0;
  }

  const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
  return values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / (values.length - 1);
}

function calculateCovariance(x: number[], y: number[]): number {
  if (x.length !== y.length || x.length < 2) {
    return 0;
  }

  const n = x.length;
  const meanX = x.reduce((sum, val) => sum + val, 0) / n;
  const meanY = y.reduce((sum, val) => sum + val, 0) / n;

  return x.reduce((sum, val, i) => sum + (val - meanX) * (y[i] - meanY), 0) / (n - 1);
}
