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 initialize
method, call the set_trade_builder
method.
self.set_trade_builder(TradeBuilder(grouping_method, matching_method))
The following table describes the arguments the TradeBuilder
constructor accepts:
Argument | Data Type | Description | Default Value |
---|---|---|---|
grouping_method | FillGroupingMethod | The method used to group order fills into trades | |
matching_method | FillMatchingMethod | The method used to match offsetting order fills |
The FillGroupingMethod
enumeration has the following members:
The FillMatchingMethod
enumeration has the following members:
Check Open Positions
To check if you have a position open for a security as defined by the FillGroupingMethod
, call the has_open_position
method.
# 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)
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.
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)