Audit items #7 and #5. #7 — BrentPricePredictor::generatePrediction previously wrote both an EWMA row and an LLM row to price_predictions on every run. The downstream OilSignal already prefers llm_with_context > llm > ewma, so the EWMA row was dead weight 95% of the time. Now we try LLM first; if it returns null (no API key, parse failure, etc.) we compute and persist EWMA as a real fallback. This also avoids redundant work on the success path. Updated the "stores both" test to "stores only LLM" — asserts no EWMA row is written when the provider succeeds. #5 — BrentPricePredictor and AnthropicPredictionProvider both had byte-identical computeEwma() methods with identical EWMA_ALPHA = 0.3 constants. Extracted to App\Services\Ewma::compute() and dropped both private methods + their alpha constants. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
26 lines
661 B
PHP
26 lines
661 B
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
/**
|
|
* Exponentially-weighted moving average. Pure function — used by
|
|
* BrentPricePredictor for the EWMA fallback prediction and by
|
|
* AnthropicPredictionProvider to enrich the basic-flow prompt.
|
|
*/
|
|
final class Ewma
|
|
{
|
|
public const float DEFAULT_ALPHA = 0.3;
|
|
|
|
/** @param float[] $prices Chronological order (oldest first). */
|
|
public static function compute(array $prices, float $alpha = self::DEFAULT_ALPHA): float
|
|
{
|
|
$ema = $prices[0];
|
|
|
|
foreach (array_slice($prices, 1) as $price) {
|
|
$ema = $alpha * $price + (1 - $alpha) * $ema;
|
|
}
|
|
|
|
return round($ema, 4);
|
|
}
|
|
}
|