Reading results
After calculate(), each metric is a public property on the Analytics instance — null when its calculator didn’t run:
| Accessor | Calculator | toArray() key | Result |
|---|---|---|---|
counts | Counts | counts | Number of trades, open and closed. |
wins | Wins | wins | Winning closed trades and the win ratio: wins over closed trades. |
volume | TradingVolume | volume | Traded size aggregates, over every trade. |
value | TradingValue | value | Notional value (size × open price) aggregates, over every trade. |
commission | Commissions | commission | Commission per trade; a trade without one counts as 0. |
unrealizedProfitAndLoss | UnrealizedGrossProfitAndLoss, UnrealizedNetProfitAndLoss | profit_and_loss.unrealized | Gross and net P&L of the open trades (at their close price), averaged over them. |
realizedProfitAndLoss | RealizedGrossProfitAndLoss, RealizedNetProfitAndLoss | profit_and_loss.realized | Gross and net P&L of the closed trades, averaged over them. |
profitFactor | ProfitFactor | profit_factor | Realized gross profit divided by realized gross loss (0 without a loss). |
cumulativeReturn | GrossCumulativeReturn, NetCumulativeReturn | cumulative_return | Compounded return in percent, gross and net, its geometric mean per trade and the highest / lowest running return. |
frequency | TradingFrequency | frequency | How often trades are opened, over the span of open times. |
duration | TradesDuration | duration | How long closed trades stayed open, in seconds. |
streaks | Streaks | streaks | Longest runs of winning and of losing closed trades. |
expectancy | Expectancy | expectancy | Expected P&L of an average closed trade. |
riskRewardRatio | RiskRewardRatio | risk_reward_ratio | Average win divided by average loss. |
winRateByPeriod | WinRateByPeriod | win_rate_by_period | Win rate of closed trades, bucketed by the day, week or month they opened. |
maxDrawdown | MaxDrawdown | max_drawdown | Largest peak-to-trough drop of the realized (net) equity curve. |
riskAdjustedReturns | RiskAdjustedReturns | risk_adjusted_returns | Sharpe and Sortino ratios of the realized net returns. |
The comments in these examples show each value’s string form for the three example trades from Running the engine, at ->scale(2).
Breakdowns
Where it applies, a metric exposes a ->global figure plus ->forPair(), ->forBaseCurrency() and ->forQuoteCurrency() breakdowns, each split into total, buy and sell. Pairs are written BASE/QUOTE, exactly as Trade::pair() returns them:
// Every level splits into total, buy and sell
$analytics->counts->global->total; // '3' — every trade
$analytics->counts->global->buy; // '2'
$analytics->counts->global->sell; // '1'
$analytics->counts->forPair('ETH/USD')->total; // '2'
$analytics->counts->forBaseCurrency('ETH')->sell; // '1'
$analytics->counts->forQuoteCurrency('USD')->total; // '3'Aggregates
Amount-like metrics — volume, value, commission, duration, P&L and cumulative return — hold an aggregate at every level, with the pair responsible for the extremes. Every aggregate also carries a count — the trades it was built from — and its average divides by exactly those: realized figures by the closed trades, unrealized ones by the open trades:
$pnl = $analytics->realizedProfitAndLoss->net->global->total;
$pnl->total; // '781.00' — the sum
$pnl->count; // 3 — the trades that fed it: here, the closed trades
$pnl->average; // '260.33' — the total divided by that count
$pnl->highest; // '990.00' — the best single trade
$pnl->highestPair; // 'BTC/USD'
$pnl->lowest; // '-306.00' — the worst single trade
$pnl->lowestPair; // 'ETH/USD'Break-even trades and empty figures
- A break-even trade (P&L exactly 0) is a closed trade that neither won nor lost: it counts towards the win ratio’s denominator and the expectancy, but not towards the average loss of the risk/reward ratio, and it ends both the winning and the losing streak.
- Fewer than two trades, or trades that all opened in the same second, leave the frequency at 0 with no unit.
- A figure with nothing to divide — no loss for the profit factor or risk/reward ratio, no deviation for Sharpe or Sortino — stays 0.
Result shapes
| Results | Access path | Note |
|---|---|---|
| counts, wins, wins->closed, wins->winRatio, streaks->wins, streaks->losses | ->global, ->forPair(), ->forBaseCurrency(), ->forQuoteCurrency() → ->total, ->buy, ->sell | Every end value is a NumericValueAsString. |
| volume, value, commission, duration, P&L gross / net, cumulativeReturn gross / net | ->global, ->forPair(), … → ->total, ->buy, ->sell → ->total, ->count, ->average, ->highest, ->highestPair, ->lowest, ->lowestPair | count is an int — the trades the aggregate was built from; highestPair and lowestPair are plain strings. |
| profitFactor, frequency, P&L grossProfits / grossLosses / netProfits / netLosses | ->total, ->forPair(), ->forBaseCurrency(), ->forQuoteCurrency() | No buy / sell split. |
Iterating breakdowns
The public perPair, perBaseCurrency and perQuoteCurrency arrays hold every breakdown the run produced, keyed by pair or currency:
foreach ($analytics->realizedProfitAndLoss->net->perPair as $pair => $pnl) {
echo $pair, ': ', $pnl->total->total, PHP_EOL;
}
// BTC/USD: 990.00
// ETH/USD: -209.00
array_keys($analytics->counts->perBaseCurrency); // ['BTC', 'ETH']
array_keys($analytics->counts->perQuoteCurrency); // ['USD']forPair() and the other breakdown methods return zero values — never null — for a pair or currency without trades.
Several quote currencies
Global totals add amounts from every quote currency as they are. If you trade against more than one quote currency, read amounts per quote currency with forQuoteCurrency().
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 cryptoBy 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.