Overall Statistics
Total Orders
1389
Average Win
0.54%
Average Loss
-0.70%
Compounding Annual Return
-0.287%
Drawdown
45.900%
Expectancy
-0.028
Start Equity
100000
End Equity
93096.11
Net Profit
-6.904%
Sharpe Ratio
-0.331
Sortino Ratio
-0.376
Probabilistic Sharpe Ratio
0.000%
Loss Rate
45%
Win Rate
55%
Profit-Loss Ratio
0.78
Alpha
-0.02
Beta
-0.053
Annual Standard Deviation
0.066
Annual Variance
0.004
Information Ratio
-0.36
Tracking Error
0.18
Treynor Ratio
0.416
Total Fees
$1046.26
Estimated Strategy Capacity
$0
Lowest Capacity Asset
CME_MP1.QuantpediaFutures 2S
Portfolio Turnover
2.20%
# https://quantpedia.com/strategies/currency-momentum-factor/
#
# Create an investment universe consisting of several currencies (10-20). Go long three currencies with the highest 12-month momentum against USD
# and go short three currencies with the lowest 12-month momentum against USD. Cash not used as margin invest on overnight rates. Rebalance monthly.

import data_tools
from AlgorithmImports import *

class CurrencyMomentumFactor(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2000, 1, 1)
        self.set_cash(100_000)

        period: int = 12 * 21
        self.set_warm_up(period, Resolution.DAILY)

        tickers: List[str] = [
            "CME_AD1", # Australian Dollar Futures, Continuous Contract #1
            "CME_BP1", # British Pound Futures, Continuous Contract #1
            "CME_CD1", # Canadian Dollar Futures, Continuous Contract #1
            "CME_EC1", # Euro FX Futures, Continuous Contract #1
            "CME_JY1", # Japanese Yen Futures, Continuous Contract #1
            "CME_MP1", # Mexican Peso Futures, Continuous Contract #1
            "CME_NE1", # New Zealand Dollar Futures, Continuous Contract #1
            "CME_SF1"  # Swiss Franc Futures, Continuous Contract #1
        ]
        
        self._securities: List[Security] = []
        self._traded_count: int = 3

        for symbol in tickers:
            data: Security = self.add_data(data_tools.QuantpediaFutures, symbol, Resolution.DAILY)
            data.set_fee_model(data_tools.CustomFeeModel())
            data.set_leverage(5)
            data.roc: RateOfChange = self.ROC(symbol, period, Resolution.DAILY)
            self._securities.append(data)
        
        self._recent_month: int = -1
        
    def on_data(self, slice: Slice) -> None:
        if self.is_warming_up:
            return

        # rebalance monthly
        if self.time.month == self._recent_month:
            return
        self._recent_month = self.time.month
        
        perf: Dict[Symbol, float] = {
            sec.symbol : sec.roc.current.value for sec in self._securities if 
            sec.roc.is_ready and 
            slice.contains_key(sec.symbol) and 
            slice[sec.symbol]
        }
        
        long: List[Symbol] = []
        short: List[Symbol] = []

        if len(perf) >= self._traded_count * 2:
            sorted_by_performance: List[Symbol] = sorted(perf, key=perf.get, reverse=True)
            long = sorted_by_performance[:self._traded_count]
            short = sorted_by_performance[-self._traded_count:]

        # trade execution
        targets: List[PortfolioTarget] = []
        for i, portfolio in enumerate([long, short]):
            for symbol in portfolio:
                targets.append(PortfolioTarget(symbol, ((-1) ** i) / len(portfolio)))
        
        self.set_holdings(targets, True)
        
#region imports
from AlgorithmImports import *
#endregion

# Custom fee model
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))

# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
    _last_update_date: Dict[str, datetime.date] = {}

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

    def GetSource(self, config: SubscriptionDataConfig, date: datetime, is_live_mode: bool) -> SubscriptionDataSource:
        return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)

    def Reader(self, config: SubscriptionDataConfig, line: str, date: datetime, is_live_mode: bool) -> BaseData:
        data = QuantpediaFutures()
        data.Symbol = config.Symbol
        
        if not line[0].isdigit(): return None
        split: List[str] = line.split(';')
        
        data.time = datetime.strptime(split[0], "%d.%m.%Y") + timedelta(days=1)
        data['back_adjusted'] = float(split[1])
        data['spliced'] = float(split[2])
        data.value = float(split[1])

        # store last update date
        if config.symbol.value not in QuantpediaFutures._last_update_date:
            QuantpediaFutures._last_update_date[config.symbol.value] = datetime(1,1,1).date()

        if data.time.date() > QuantpediaFutures._last_update_date[config.symbol.value]:
            QuantpediaFutures._last_update_date[config.symbol.value] = data.Time.date()

        return data