NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

By default every calculator runs. Pass the calculator classes you want; their dependencies are pulled in automatically — transitively — and the canonical run order is kept:

use RoundlyConsulting\TradingAnalytics\Analytics\Counts;
use RoundlyConsulting\TradingAnalytics\Analytics\Streaks;

$analytics = TradingAnalytics::calculate($trades, only: [Counts::class]);
$analytics = TradingAnalytics::for($trades)->only([Counts::class])->calculate(); // same, fluent
// $analytics->counts is populated; metrics you didn't request stay null

$analytics = TradingAnalytics::for($trades)->except([Streaks::class])->calculate();

Passing a class that is not a registered calculator throws UnknownCalculatorException. To discover what only() / except() accept, call TradingAnalytics::metrics() (or metrics() on an engine instance):

$available = TradingAnalytics::metrics(); // list of calculator class-strings

// Counts, TradingVolume, TradingValue, Commissions,
// UnrealizedGrossProfitAndLoss, UnrealizedNetProfitAndLoss,
// RealizedGrossProfitAndLoss, RealizedNetProfitAndLoss, Wins, ProfitFactor,
// GrossCumulativeReturn, NetCumulativeReturn, TradingFrequency, TradesDuration,
// Streaks, Expectancy, RiskRewardRatio, WinRateByPeriod, MaxDrawdown,
// RiskAdjustedReturns — all in RoundlyConsulting\TradingAnalytics\Analytics\

Dependencies

Every calculator runs on its own: every aggregate counts the trades it averages over itself. Only three calculators depend on another — ProfitFactor, Expectancy and RiskRewardRatio are built from the realized gross P&L, so they bring RealizedGrossProfitAndLoss with them:

Calculatoronly() also runs
ProfitFactorRealizedGrossProfitAndLoss
ExpectancyRealizedGrossProfitAndLoss
RiskRewardRatioRealizedGrossProfitAndLoss

except() still runs an excluded calculator that a remaining one depends on — except([RealizedGrossProfitAndLoss::class]) keeps it while the profit factor needs it. The figures match a full run either way:

use RoundlyConsulting\TradingAnalytics\Analytics\Expectancy;
use RoundlyConsulting\TradingAnalytics\Analytics\MaxDrawdown;
use RoundlyConsulting\TradingAnalytics\Analytics\RealizedGrossProfitAndLoss;
use RoundlyConsulting\TradingAnalytics\Analytics\RealizedNetProfitAndLoss;

$analytics = TradingAnalytics::for($trades)
    ->scale(2)
    ->only([
        RealizedNetProfitAndLoss::class,  // averages over the closed trades it counted itself
        Expectancy::class,                // pulls in RealizedGrossProfitAndLoss
        MaxDrawdown::class,
    ])
    ->calculate();

array_keys($analytics->toArray());
// ['profit_and_loss', 'expectancy', 'max_drawdown']

$analytics->realizedProfitAndLoss->net->global->total->average; // '260.33' — same as a full run
$analytics->counts;                                             // null — not requested

// except() keeps a dependency a remaining calculator still needs
$analytics = TradingAnalytics::for($trades)->except([RealizedGrossProfitAndLoss::class])->calculate();
$analytics->realizedProfitAndLoss->gross; // still calculated — the profit factor needs it

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.