Trading strategy performance analytics and statistics engine — zero-dependency computation library.
This package is a pure computation engine. It operates on plain data structures (arrays, DTOs, Ds\Vector) and knows nothing about:
- Laravel / Eloquent / any ORM
- Database schemas or SQL
- Carbon dates or formatting
- CLI/TUI/HTTP presentation layers
- Exchanges, orders, or portfolio management
If you delete the AlphaForge CLI tomorrow, this package remains fully functional as a standalone library usable from any PHP 8.3+ project.
┌──────────────────────────┐
│ Your Application │
│ (Laravel, Symfony, CLI, │
│ desktop, Python bridge) │
└──────────┬───────────────┘
│
┌──────────▼───────────────┐
│ Adapter Layer │
│ (DB entities → DTOs) │
└──────────┬───────────────┘
│
┌──────────────────────▼──────────────────────┐
│ Alphaforge Statistics │
│ ┌────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Math │ │ Statistics│ │ Monte Carlo │ │
│ └────────┘ └──────────┘ └──────────────┘ │
│ ┌────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Series │ │ Walk- │ │ Sensitivity │ │
│ │ Metrics│ │ Forward │ │ Analysis │ │
│ └────────┘ └──────────┘ └──────────────┘ │
└─────────────────────────────────────────────┘
Your App ──► Adapter Layer ──► Alphaforge Statistics
│
(no framework, no ORM,
no Carbon, no DB)
The package consumes only its own data types:
| Input type | Example |
|---|---|
TradeInput |
new TradeInput(id: '1', direction: 'long', entryTimestamp: 1704067200, ...) |
array<float> |
[0.02, -0.01, 0.03, ...] |
array<string> |
['50000', '51000', '50500', ...] |
Ds\Vector |
new Vector(['10000', '10500', '11000']) |
src/
├── Math/ Bcmath-precision statistical functions
├── Statistics/ Full backtest performance analysis
├── MonteCarlo/ Bootstrap Monte Carlo simulation
├── Series/ Time-series metrics (descriptive stats)
├── WalkForward/ Walk-forward analysis tools + strategy grading
├── Sensitivity/ Parameter importance and interaction analysis
└── Trade/ Trade input DTO
PHP ^8.3
ext-bcmath *
ext-ds *
No framework dependencies. Installable standalone.
use RobertGDev\AlphaforgeStatistics\Statistics\StatisticsService;
use RobertGDev\AlphaforgeStatistics\Trade\TradeInput;
use Ds\Vector;
$positions = new Vector;
$positions->push(new TradeInput(
id: '1',
direction: 'long',
entryPrice: '50000',
exitPrice: '55000',
entryTimestamp: strtotime('2024-01-01'),
exitTimestamp: strtotime('2024-01-15'),
realizedPnl: '1000',
));
$service = new StatisticsService;
$stats = $service->calculate(
positions: $positions,
initialCapital: '10000',
finalCapital: '11000',
);
echo $stats['total_return_percent']; // "10.000000"
echo $stats['sharpe_ratio'];
echo $stats['max_drawdown_percent'];
echo $stats['profit_factor'];
echo $stats['win_rate'];use RobertGDev\AlphaforgeStatistics\MonteCarlo\MonteCarloService;
$pnlValues = ['100', '-50', '200', '-30', '150', '-20', '80', '-10'];
$service = new MonteCarloService($pnlValues, '10000');
$report = $service->analyze(iterations: 2000, seed: 42);
foreach ($report->metrics as $metric) {
echo "{$metric->label}: median={$metric->median}, "
. "p5={$metric->p5}, p95={$metric->p95}\n";
if ($metric->isSignificant()) {
echo " → statistically significant\n";
}
}use RobertGDev\AlphaforgeStatistics\Series\SeriesMetricService;
$service = new SeriesMetricService;
$metrics = $service->calculate(['50000', '51000', '50500', '51500']);
echo $metrics['mean']; // bcmath string
echo $metrics['median'];
echo $metrics['std_dev'];
echo $metrics['skewness'];
echo $metrics['kurtosis'];
// Float-based helpers:
$returns = [0.02, -0.01, 0.03, 0.005, -0.02];
$sharpe = $service->sharpeRatioFromReturns($returns);
$sortino = $service->sortinoRatioFromReturns($returns);
$maxDD = $service->maxDrawdownFromReturns($returns);
$stats = $service->tradeWinLossStats($returns);
// ['total_trades' => 5, 'winning_trades' => 3, 'losing_trades' => 2, ...]use RobertGDev\AlphaforgeStatistics\WalkForward\WalkForwardAnalysis;
use RobertGDev\AlphaforgeStatistics\WalkForward\StrategyGrader;
$analysis = new WalkForwardAnalysis(
results: [],
oosIsRatio: 60.0,
robustCount: 3,
robustRatio: 0.6,
beatBuyHoldCount: 2,
beatBuyHoldRatio: 0.4,
returnGt10Count: 1,
returnGt10Ratio: 0.2,
sharpeBeatBenchmarkCount: 1,
sharpeBeatBenchmarkRatio: 0.2,
medianIsScore: 2.0,
medianOosScore: 1.5,
avgDegradation: 20.0,
medianDegradation: 15.0,
bestOosRank: 1,
stabilityClassification: 'good',
rankCorrelation: 0.5,
benchmarkReturn: 20.0,
benchmarkSharpe: 1.0,
benchmarkHasData: true,
captureRatio: 15.0,
medianOosReturn: 5.0,
medianOosSharpe: 0.8,
medianOosMaxDd: 15.0,
);
$grade = StrategyGrader::grade($analysis);
// ['score' => 46.7, 'stars' => '\u2605\u2605\u2605\u2606\u2606', 'label' => '...', ...]use RobertGDev\AlphaforgeStatistics\Sensitivity\ParameterSensitivityService;
$runs = [
['params' => ['fast' => 5, 'slow' => 20], 'stats' => ['score' => '3.5']],
['params' => ['fast' => 5, 'slow' => 30], 'stats' => ['score' => '3.8']],
['params' => ['fast' => 10, 'slow' => 20], 'stats' => ['score' => '6.2']],
['params' => ['fast' => 10, 'slow' => 30], 'stats' => ['score' => '6.5']],
];
$service = new ParameterSensitivityService($runs);
// Which parameter matters most?
$importance = $service->importance('score');
// fast \u2192 92.3%, slow \u2192 7.7%
// 2D score surface
$surface = $service->surface('fast', 'slow', 'score');
// ['rows' => [5,10], 'cols' => [20,30], 'grid' => [[3.5,3.8],[6.2,6.5]]]
// Interaction between parameters
$interactions = $service->interactions('score');
// [{param_a: 'fast', param_b: 'slow', interaction: 0.0024}]use RobertGDev\AlphaforgeStatistics\WalkForward\WalkForwardAnalyzer;
// Rank correlation
$corr = WalkForwardAnalyzer::spearmanRankCorrelation(
[2.0, 1.8, 1.6], [1.5, 1.3, 1.1]
);
// Robustness classification
$class = WalkForwardAnalyzer::classifyRobustness(65.0, 70.0, 0.7); // "good"
// Parameter boundary warnings
$warnings = WalkForwardAnalyzer::boundaryWarnings(
[['parameters' => ['fast' => 195]], ['parameters' => ['fast' => 50]]],
['fast' => ['min' => 5, 'max' => 200]]
);
// Flag suspicious Sharpe
$suspicious = WalkForwardAnalyzer::detectSuspiciousSharpe([
'sharpe_ratio' => '8.5',
'total_return_percent' => '0.3',
]); // trueuse RobertGDev\AlphaforgeStatistics\Math\Math;
Math::mean(['10', '20', '30'], 12); // "20.000000000000"
Math::standardDeviation(['2', '4', '4', '5'], 6);
Math::variance(['2', '4', '4', '5'], 6);
Math::covariance(['1', '2', '3'], ['2', '4', '6'], 6);
Math::percentage('500', '10000', 4); // "5.0000"All Math methods use bcmath for precision — no floating-point rounding errors.
| Property | Type | Description |
|---|---|---|
id |
string |
Trade identifier |
direction |
string |
'long' or 'short' |
entryPrice |
string |
Entry price (bcmath string) |
exitPrice |
?string |
Exit price (null if open) |
entryTimestamp |
int |
Unix seconds — no Carbon |
exitTimestamp |
?int |
Unix seconds (null if open) |
realizedPnl |
string |
Realized P&L (bcmath string) |
exitTag |
?string |
Optional exit reason tag |
Implements StatisticsServiceInterface.
calculate(
Vector $positions, // Vector<TradeInput>
string $initialCapital,
string $finalCapital,
?string $riskFreeRate = null,
int $tradingDaysPerYear = 252,
?Vector $barEquityCurve = null, // Vector<string>
): arrayReturns 40+ fields including:
total_return, total_return_percent, cagr, total_trades, winning_trades,
losing_trades, win_rate, gross_profit, gross_loss, net_profit,
profit_factor, average_win, average_loss, largest_win, largest_loss,
max_drawdown, max_drawdown_percent, avg_drawdown,
max_drawdown_duration, avg_drawdown_duration,
sharpe_ratio, sortino_ratio, active_sharpe_ratio, active_sortino_ratio,
calmar_ratio, volatility, alpha, beta,
time_in_market_percent, idle_capital_percent,
average_trade_duration, median_trade_duration, min_trade_duration,
max_trade_duration, max_consecutive_wins, max_consecutive_losses,
expectancy, long_trades, short_trades, long_win_rate, short_win_rate.
Risk metrics require >=10 data points. Volatility < 0.1% annualized yields zero ratios.
__construct(array<int, string> $tradePnlValues, string $initialCapital)
analyze(int $iterations = 1000, ?int $seed = null): MonteCarloReport| Property | Type |
|---|---|
totalTrades |
int |
iterations |
int |
metrics |
array<string, MonteCarloMetric> |
Method: hasTrades(): bool
| Property | Type | Description |
|---|---|---|
label |
string |
Human-readable name |
p5 |
float |
5th percentile |
p25 |
float |
25th percentile |
median |
float |
Median |
p75 |
float |
75th percentile |
p95 |
float |
95th percentile |
probNegative |
float |
% iterations with negative outcome |
Method: isSignificant(): bool — true when probNegative < 5% AND p5 > 0.
Metrics produced: total_return_pct, win_rate, max_drawdown_pct, profit_factor, avg_trade_pnl, positive_trades.
Implements SeriesMetricServiceInterface.
calculate(array<int, string> $values): arrayReturns: count, min, max, mean, median, std_dev, variance,
sum, range, quartiles{q1,q2,q3}, skewness, kurtosis.
Float helpers:
sharpeRatioFromReturns(array<int, float> $returns): float
sortinoRatioFromReturns(array<int, float> $returns): float
maxDrawdownFromReturns(array<int, float> $returns): float
performanceStabilityFromTrades(array<int, array{timestamp:int,pnl:float}> $trades): float
tradeWinLossStats(array<int, float> $returns): arraystatic grade(WalkForwardAnalysis $analysis): arrayWeights: economic (40%), robustness (30%), risk (20%), optimization (10%). Robustness acts as a score ceiling.
Returns: {score, stars, label, stars_by_category, breakdown}.
Pure DTO — 38 constructor properties. See source for full list.
Stateless static functions:
| Method | Purpose |
|---|---|
median(array<float>): float |
Compute median |
spearmanRankCorrelation(array, array): ?float |
IS-OOS rank correlation |
rankValues(array<float>): array<float> |
Fractional rank assignment |
pearsonCorrelation(array, array): float |
Pearson r |
classifyRobustness(float, float, ?float): string |
excellent/good/moderate/weak/likely_overfit |
interpretClassification(string): string |
Human-readable classification |
classifyRankStability(?float): string |
stable/moderate/unstable |
boundaryWarnings(array, array): array |
Parameter boundary clustering |
detectInflatedOosIsRatio(float, float, float, ?array): bool |
Inflated OOS/IS flag |
classifyEconomicPerformance(?array, array): string |
strong/moderate/poor vs benchmark |
interpretEconomicPerformance(string, ?array, array): string |
Human-readable interpretation |
detectSuspiciousSharpe(?array): bool |
High Sharpe + tiny return |
hasLowTradeWarning(?array): bool |
< 30 trades in OOS |
__construct(list<array{params, stats}> $runs)
importance(string $metric = 'optimization_score'): array
surface(string $paramA, string $paramB, string $metric = 'optimization_score'): array
interactions(string $metric = 'optimization_score'): arrayAll-static bcmath methods:
| Method | Signature |
|---|---|
mean |
(array<string>, int $scale): string |
variance |
(array<string>, int $scale, bool $sample=true): string |
standardDeviation |
(array<string>, int $scale, bool $sample=true): string |
covariance |
(array<string>, array<string>, int $scale, bool $sample=true): string |
percentage |
(string $value, string $base, int $scale=10): string |
Adapters exist in app/AlphaForge/Backtesting/Adapter/:
TradeInputAdapter::fromPositions(Vector $positions): Vector<TradeInput>— convertsPositionDto+ Carbon toTradeInput+ unix timestampsMonteCarloService::fromBacktestRunId(string $id)— Eloquent -> package delegationParameterSensitivityService::fromOptimizationRunId(string $id)— Eloquent -> package delegation
cd src
composer install
php vendor/bin/pest