| Overall Statistics |
|
Total Orders 26 Average Win 10.18% Average Loss -10.78% Compounding Annual Return 2.759% Drawdown 47.700% Expectancy 0.389 Start Equity 100000 End Equity 154283.34 Net Profit 54.283% Sharpe Ratio 0.1 Sortino Ratio 0.104 Probabilistic Sharpe Ratio 0.006% Loss Rate 29% Win Rate 71% Profit-Loss Ratio 0.95 Alpha -0.01 Beta 0.345 Annual Standard Deviation 0.198 Annual Variance 0.039 Information Ratio -0.315 Tracking Error 0.214 Treynor Ratio 0.058 Total Fees $147.46 Estimated Strategy Capacity $0 Lowest Capacity Asset 493.QuantpediaEquity 2S Portfolio Turnover 0.39% Drawdown Recovery 2622 |
# 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(100_000)
# 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(5)
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)
self._log_after_rebalance(msg='Rebalance')
else:
self._log_after_rebalance(msg='Liquidation')
self.Liquidate()
def _log_after_rebalance(self, msg: str, log_from_year: int = 2000) -> None:
if self.time.year < log_from_year:
return
holdings: List[Tuple] = [(symbol, holdings) for symbol, holdings in self.portfolio.items() if holdings.invested]
holdings_str: str = f'{len(holdings)} Holdings {self.time} - {msg} - '
for symbol, holding in holdings:
# holding_perc: float = holdings.absolute_holdings_value / self.portfolio.total_holdings_value
holding_perc: float = holding.absolute_holdings_value / self.portfolio.total_portfolio_value * 100
holdings_str += f' {symbol.value}:{holding_perc:2.2f}%'
self.log(holdings_str)
# 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"))