Created with Highcharts 12.1.2Equity2010201120122013201420152016201720182019202020212022202320242025202601M2M-40-20000.050.101201M2M9.969.979.989.99
Overall Statistics
Total Orders
23
Average Win
33.02%
Average Loss
-4.36%
Compounding Annual Return
15.951%
Drawdown
33.300%
Expectancy
6.261
Start Equity
100000
End Equity
945589.55
Net Profit
845.590%
Sharpe Ratio
0.548
Sortino Ratio
0.544
Probabilistic Sharpe Ratio
2.915%
Loss Rate
15%
Win Rate
85%
Profit-Loss Ratio
7.58
Alpha
0.078
Beta
0.425
Annual Standard Deviation
0.209
Annual Variance
0.044
Information Ratio
0.135
Tracking Error
0.216
Treynor Ratio
0.27
Total Fees
$286.12
Estimated Strategy Capacity
$0
Lowest Capacity Asset
107.QuantpediaEquity 2S
Portfolio Turnover
0.34%
# https://quantpedia.com/strategies/momentum-effect-in-anomalies-trading-systems/
#
# In each year, the trader searches through the universe of financial journals for implementable trading strategies. The investment universe, which consists of existing anomalies, 
# is then created. The investor then chooses the best performing anomaly for the last two years (based on his backtesting results of all published anomalies) and will trade it in
# the following year.
#
# QC implementation changes:
#   - Investment universe consists of Quantpedia's equity long-short anomalies.

#region imports
from AlgorithmImports import *
#endregion

class MomentumEffectinAnomaliesTradingSystems(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2010, 1, 1)
        self.SetCash(100000)

        # ids with backtest period end year
        self.backtest_to = {}
        
        # daily price data
        self.perf = {}
        self.period = 2 * 12 * 21
        self.SetWarmUp(self.period, Resolution.Daily)
        
        csv_string_file = self.Download('data.quantpedia.com/backtesting_data/equity/quantpedia_strategies/backtest_end_year.csv')
        lines = csv_string_file.split('\r\n')
        last_id = None
        for line in lines[1:]:
            split = line.split(';')
            id = str(split[0])
            backtest_to = int(split[1])
            
            # add quantpedia strategy data
            data = self.AddData(QuantpediaEquity, id, Resolution.Daily)
            data.SetLeverage(10)
            data.SetFeeModel(CustomFeeModel())
            
            self.backtest_to[id] = backtest_to
            self.perf[id] = self.ROC(id, self.period, Resolution.Daily)
            
            if not last_id:
                last_id = id
        
        self.recent_month = -1
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
    
    def OnData(self, data):
        if self.IsWarmingUp:
            return
        if self.Time.month == self.recent_month:
            return
        self.recent_month = self.Time.month
        
        if self.Time.month != 1:
            return
        
        _last_update_date:Dict[str, datetime.date] = QuantpediaEquity.get_last_update_date()

        # calculate performance of those strategies, which were published last year and sooner
        performance = { x : self.perf[x].Current.Value for x in self.perf if \
                    self.perf[x].IsReady and \
                    self.backtest_to[x] < self.Time.year and \
                    x in data and data[x] and \
                    _last_update_date[x] > self.Time.date() }

        # performance sorting
        if len(performance) != 0:
            sorted_by_perf = sorted(performance.items(), key = lambda x: x[1], reverse = True)
            top_performer_id = sorted_by_perf[0][0]

            if not self.Portfolio[top_performer_id].Invested:
                self.Liquidate()

            self.SetHoldings(top_performer_id, 1)
        else:
            self.Liquidate()
        
# Quantpedia strategy equity curve data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaEquity(PythonData):
    def GetSource(self, config, date, isLiveMode):
        return SubscriptionDataSource(
            "data.quantpedia.com/backtesting_data/equity/quantpedia_strategies/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv
        )

    _last_update_date:Dict[str, datetime.date] = {}

    @staticmethod
    def get_last_update_date() -> Dict[str, datetime.date]:
       return QuantpediaEquity._last_update_date

    def Reader(self, config, line, date, isLiveMode):
        data = QuantpediaEquity()
        data.Symbol = config.Symbol
        
        if not line[0].isdigit(): return None
        split = line.split(';')
        
        data.Time = datetime.strptime(split[0], "%Y-%m-%d") + timedelta(days=1)
        data['close'] = float(split[1])
        data.Value = float(split[1])
        
        # store last update date
        if config.Symbol.Value not in QuantpediaEquity._last_update_date:
            QuantpediaEquity._last_update_date[config.Symbol.Value] = datetime(1,1,1).date()

        if data.Time.date() > QuantpediaEquity._last_update_date[config.Symbol.Value]:
            QuantpediaEquity._last_update_date[config.Symbol.Value] = data.Time.date()
        
        return data
        
# Custom fee model.
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))