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

The maximum drawdown (the realized equity curve), the winning and losing streaks and the running cumulative return follow the order trades close in. While any of them runs — they all do by default — realized trades must arrive in close-time order: the first one that closed before the trade read ahead of it throws UnorderedTradeSourceException, instead of reporting figures for a sequence that never happened. Sorting inside the engine would mean holding the whole history in memory, which the single pass never does.

  • Open trades (no close time) and trades with equal close times may come in any order.
  • Order a query with ->orderBy('close_time')->orderBy('id'); sort an in-memory collection with ->sortBy('close_time')->values() first.
  • A run without those calculators — except() MaxDrawdown, Streaks, GrossCumulativeReturn and NetCumulativeReturn, or an only() without them — accepts any order. Every other metric, the trading frequency included, is order-independent.
use RoundlyConsulting\TradingAnalytics\Analytics\GrossCumulativeReturn;
use RoundlyConsulting\TradingAnalytics\Analytics\MaxDrawdown;
use RoundlyConsulting\TradingAnalytics\Analytics\NetCumulativeReturn;
use RoundlyConsulting\TradingAnalytics\Analytics\Streaks;

// An in-memory collection: sort it by close time first
TradingAnalytics::calculate(collect($rows)->sortBy('close_time')->values());

// Out of order? The first trade that closed before the one read ahead of it throws:
// UnorderedTradeSourceException: Trade analytics follow the order trades close in, but a trade
// closed at [2024-03-02 12:00:00] arrived after one closed at [2024-03-05 08:00:00]. …

// Without the order-sensitive calculators, any order is accepted
TradingAnalytics::for($rows)
    ->except([MaxDrawdown::class, Streaks::class, GrossCumulativeReturn::class, NetCumulativeReturn::class])
    ->calculate();

The order-sensitive calculators implement the SequentialAnalyticsInterface marker; the engine checks the order only while one of them is active.

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.