| Overall Statistics |
|
Total Orders 662 Average Win 0.01% Average Loss -0.02% Compounding Annual Return -16.987% Drawdown 12.300% Expectancy -0.792 Start Equity 100000 End Equity 93980.88 Net Profit -6.019% Sharpe Ratio -1.646 Sortino Ratio -2.298 Probabilistic Sharpe Ratio 7.142% Loss Rate 86% Win Rate 14% Profit-Loss Ratio 0.45 Alpha -0.122 Beta -0.978 Annual Standard Deviation 0.104 Annual Variance 0.011 Information Ratio -1.055 Tracking Error 0.21 Treynor Ratio 0.175 Total Fees $662.00 Estimated Strategy Capacity $13000000.00 Lowest Capacity Asset SPY R735QTJ8XC9X Portfolio Turnover 4.17% Drawdown Recovery 1 |
# region imports
from AlgorithmImports import *
# endregion
class CustomIndicatorsAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
# Request daily SPY data to feed the indicators to generate trade signals and trade.
self._spy = self.add_equity("SPY")
# Create a custom money flow index to generate a trade signal.
self._custom_mfi = CustomMoneyFlowIndex(20)
# Warm up for immediate usage of indicators.
self.set_warm_up(20, Resolution.DAILY)
def on_data(self, slice: Slice) -> None:
bar = slice.bars.get(self._spy)
if not bar:
return
# Update the custom MFI with the updated trade bar to obtain the updated trade signal.
self._custom_mfi.update(bar)
# Don't trade until warm-up is done.
if self.is_warming_up:
return
# Buy if the positive money flow is above negative, indicating demand is greater than supply, driving up the price.
if self._custom_mfi.current.value > 50:
self.set_holdings(self._spy, 1)
# Sell if the positive money flow is below negative, indicating demand is less than supply, driving down the price.
else:
self.set_holdings(self._spy, -1)
class CustomMoneyFlowIndex(PythonIndicator):
def __init__(self, period: int) -> None:
super().__init__()
self.value = 0
self._previous_typical_price = 0
self._negative_money_flow: RollingWindow[float] = RollingWindow(period)
self._positive_money_flow: RollingWindow[float] = RollingWindow(period)
def update(self, input: BaseData) -> bool:
if not isinstance(input, TradeBar):
raise TypeError('CustomMoneyFlowIndex.update: input must be a TradeBar')
# Estimate the money flow by averaging the price multiplied by volume.
typical_price = (input.high + input.low + input.close) / 3
money_flow = typical_price * input.volume
# We need to avoid double-rounding errors.
if abs(self._previous_typical_price / typical_price - 1) < 1e-10:
self._previous_typical_price = typical_price
# Add the period money flow to calculate the aggregated money flow.
self._negative_money_flow.add(money_flow if typical_price < self._previous_typical_price else 0)
self._positive_money_flow.add(money_flow if typical_price > self._previous_typical_price else 0)
self._previous_typical_price = typical_price
positive_money_flow_sum = sum(self._positive_money_flow)
total_money_flow = positive_money_flow_sum + sum(self._negative_money_flow)
# Set the value to be the positive money flow ratio.
self.value = 100
if total_money_flow != 0:
self.value *= positive_money_flow_sum / total_money_flow
# Set the is_ready property to receive the required bars to fill all windows.
return self._positive_money_flow.is_ready