book
Checkout our new book! Hands on AI Trading with Python, QuantConnect, and AWS Learn More arrow

Statistics

Trade Statistics

Introduction

The TradeBuilder tracks the trades of your algorithm and calculates some statistics. Some of these statistics are a function of the risk free interest rate and the number of trading days per year. You can adjust how the TradeBuilder defines a trade and how it matches offsetting order fills.

Set Trade Builder

To set the TradeBuilder, in the Initializeinitialize method, call the SetTradeBuilderset_trade_builder method.

SetTradeBuilder(new TradeBuilder(groupingMethod, matchingMethod));
self.set_trade_builder(TradeBuilder(grouping_method, matching_method))

The following table describes the arguments the TradeBuilder constructor accepts:

ArgumentData TypeDescriptionDefault Value
groupingMethodgrouping_methodFillGroupingMethodThe method used to group order fills into trades
matchingMethodmatching_methodFillMatchingMethodThe method used to match offsetting order fills

The FillGroupingMethod enumeration has the following members:

The FillMatchingMethod enumeration has the following members:

Default Behavior

The default TradeBuilder uses FillGroupingMethod.FillToFillFillGroupingMethod.FILL_TO_FILL and FillMatchingMethod.FIFO.

Check Open Positions

To check if you have a position open for a security as defined by the FillGroupingMethod, call the HasOpenPositionhas_open_position method.

// Check if the trade builder has recorded any active trades (unclosed positions)
// for a security.
var hasOpenPosition = TradeBuilder.HasOpenPosition(_symbol);
# Check if the trade builder has recorded any active trades (unclosed positions)
# for a security.
has_open_position = self.trade_builder.has_open_position(self._symbol)

Get Closed Trades

To get your closed trades, use the ClosedTradesclosed_trades property.

var trades = TradeBuilder.ClosedTrades;
trades = self.trade_builder.closed_trades

This property returns a list of Trade objects, which have the following attributes:

Examples

The following examples demonstrate common practices for using trade statistics.

Example 1: Kelly Criterion Sizing Using Previous Trades

The following algorithm uses the previous trades to update the win probability and expected return of each trade of the EMA cross strategy of SPY, by the concept of Bayes Theorem. The updated figures are used for position sizing through Kelly Criterion.

public class TradeStatisticsAlgorithm : QCAlgorithm
{
    private Symbol _spy;
    private ExponentialMovingAverage _ema;

    public override void Initialize()
    {
        SetStartDate(2024, 8, 12);
        SetEndDate(2024, 9, 1);
        SetCash(1000000);

        // Request SPY data to trade.
        _spy = AddEquity("SPY").Symbol;
        // Create an EMA indicator to generate trade signals.
        _ema = EMA(_spy, 20, Resolution.Daily);
        // Warm-up indicator for immediate readiness.
        WarmUpIndicator(_spy, _ema, Resolution.Daily);

        // Set up a trade builder to track the trade statistics; we are interested in open-to-close round trade.
        SetTradeBuilder(new TradeBuilder(FillGroupingMethod.FlatToFlat, FillMatchingMethod.FIFO));
    }

    public override void OnData(Slice slice)
    {
        if (slice.Bars.TryGetValue(_spy, out var bar))
        {
            // Trend-following strategy using price and EMA.
            // If the price is above EMA, SPY is in an uptrend, and we buy it.
            var sign = 0m;
            if (bar.Close > _ema && !Portfolio[_spy].IsLong)
            {
                sign = 1m;
            }
            else if (bar.Close < _ema && !Portfolio[_spy].IsShort)
            {
                sign = -1m;
            }
            else
            {
                return;
            }
            
            var size = 1m;
            var trades = TradeBuilder.ClosedTrades;
            
            if (trades.Count > 4)
            {
                // Use the trade builder to obtain the win rate and % return of the last five trades to calculate position size.
                var lastFiveTrades = trades.OrderBy(x => x.ExitTime).TakeLast(5);
                var probWin = lastFiveTrades.Count(x => x.IsWin) / 5m;
                var winSize = lastFiveTrades.Average(x => x.ProfitLoss / x.EntryPrice);
                // Use the Kelly Criterion to calculate the order size.
                size = probWin - (1 - probWin) / winSize;
            }
            
            SetHoldings(_spy, sign * size);
        }
    }
}
class TradeStatisticsAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.set_start_date(2024, 8, 12)
        self.set_end_date(2024, 9, 1)
        self.set_cash(1000000)

        # Request SPY data to trade.
        self.spy = self.add_equity("SPY").symbol
        # Create an EMA indicator to generate trade signals.
        self._ema = self.ema(self.spy, 20, Resolution.DAILY)
        # Warm-up indicator for immediate readiness.
        self.warm_up_indicator(self.spy, self._ema, Resolution.DAILY)

        # Set up a trade builder to track the trade statistics; we are interested in open-to-close round trade.
        self.set_trade_builder(TradeBuilder(FillGroupingMethod.FLAT_TO_FLAT, FillMatchingMethod.FIFO))

    def on_data(self, slice: Slice) -> None:
        bar = slice.bars.get(self.spy)
        if not bar:
            return
        # Trend-following strategy using price and EMA.
        # If the price is above EMA, SPY is in an uptrend, and we buy it.
        sign = 0
        if bar.close > self._ema.current.value and not self.portfolio[self.spy].is_long:
            sign = 1
        elif bar.close < self._ema.current.value and not self.portfolio[self.spy].is_short:
            sign = -1
        else:
            return

        size = 1
        trades = self.trade_builder.closed_trades

        if len(trades) > 4:
            # Use the trade builder to obtain the win rate and % return of the last five trades to calculate position size.
            last_five_trades = sorted(trades, key=lambda x: x.exit_time)[-5:]
            prob_win = len([x for x in last_five_trades if x.is_win]) / 5
            win_size = np.mean([x.profit_loss / x.entry_price for x in last_five_trades])
            # Use the Kelly Criterion to calculate the order size.
            size = max(0, prob_win - (1 - prob_win) / win_size)

        self.set_holdings(self.spy, size * sign)

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: