| Overall Statistics |
|
Total Trades
1139
Average Win
0.36%
Average Loss
-0.24%
Compounding Annual Return
2.762%
Drawdown
32.000%
Expectancy
0.565
Net Profit
91.979%
Sharpe Ratio
0.015
Probabilistic Sharpe Ratio
0.001%
Loss Rate
37%
Win Rate
63%
Profit-Loss Ratio
1.48
Alpha
-0.008
Beta
0.23
Annual Standard Deviation
0.077
Annual Variance
0.006
Information Ratio
-0.268
Tracking Error
0.141
Treynor Ratio
0.005
Total Fees
$187.06
Estimated Strategy Capacity
$0
Lowest Capacity Asset
CME_MP1.QuantpediaFutures 2S
Portfolio Turnover
0.32%
|
#region imports
from AlgorithmImports import *
from dateutil.relativedelta import relativedelta
#endregion
# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaFutures(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/futures/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config, line, date, isLiveMode):
data = QuantpediaFutures()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = 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])
return data
# Custom fee model.
class CustomFeeModel():
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))
class InterestRate3M(PythonData):
_last_update_date:Dict[str, datetime.date] = {}
@staticmethod
def get_last_update_date() -> Dict[str, datetime.date]:
return InterestRate3M._last_update_date
def GetSource(self, config:SubscriptionDataConfig, date:datetime, isLiveMode:bool) -> SubscriptionDataSource:
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/interbank_rate/{0}.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)
def Reader(self, config:SubscriptionDataConfig, line:str, date:datetime, isLiveMode:bool) -> BaseData:
data = InterestRate3M()
data.Symbol = config.Symbol
if not line[0].isdigit(): return None
split = line.split(';')
data.Time = datetime.strptime(split[0], "%Y-%m-%d") + relativedelta(months=2)
data['value'] = float(split[1])
data.Value = float(split[1])
# store last update date
if config.Symbol.Value not in InterestRate3M._last_update_date:
InterestRate3M._last_update_date[config.Symbol.Value] = datetime(1,1,1).date()
if data.Time.date() > InterestRate3M._last_update_date[config.Symbol.Value]:
InterestRate3M._last_update_date[config.Symbol.Value] = data.Time.date()
return data
# https://quantpedia.com/strategies/fx-carry-trade/
#
# Create an investment universe consisting of several currencies (10-20). Go long three currencies with the highest central bank prime rates and
# go short three currencies with the lowest central bank prime rates. The cash not used as the margin is invested in overnight rates. The strategy
# is rebalanced monthly.
#region imports
from AlgorithmImports import *
import data_tools
#endregion
class ForexCarryTrade(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
# Source: https://fred.stlouisfed.org/series/IR3TIB01AUM156N
self.symbols = {
"CME_AD1" : "IR3TIB01AUM156N", # Australian Dollar Futures, Continuous Contract #1
"CME_BP1" : "LIOR3MUKM", # British Pound Futures, Continuous Contract #1
"CME_CD1" : "IR3TIB01CAM156N", # Canadian Dollar Futures, Continuous Contract #1
"CME_EC1" : "IR3TIB01EZM156N", # Euro FX Futures, Continuous Contract #1
"CME_JY1" : "IR3TIB01JPM156N", # Japanese Yen Futures, Continuous Contract #1
"CME_MP1" : "IR3TIB01MXM156N", # Mexican Peso Futures, Continuous Contract #1
"CME_NE1" : "IR3TIB01NZM156N", # New Zealand Dollar Futures, Continuous Contract #1
"CME_SF1" : "IR3TIB01CHM156N" # Swiss Franc Futures, Continuous Contract #1
}
self.traded_count:int = 3
self.leverage:int = 5
for symbol, rate_symbol in self.symbols.items():
self.AddData(data_tools.InterestRate3M, rate_symbol, Resolution.Daily)
data = self.AddData(data_tools.QuantpediaFutures, symbol, Resolution.Daily)
data.SetFeeModel(data_tools.CustomFeeModel())
data.SetLeverage(self.leverage)
self.recent_month = -1
def OnData(self, data):
rebalance_flag:bool = False
rate:dict[str, float] = {}
ir_last_update_date:Dict[str, datetime.date] = data_tools.InterestRate3M.get_last_update_date()
for symbol, int_rate in self.symbols.items():
# futures data is present in the algorithm
if symbol in data and data[symbol]:
if self.recent_month != self.Time.month:
rebalance_flag = True
self.recent_month = self.Time.month
if rebalance_flag:
# IR data is still comming in
if self.Securities[int_rate].GetLastData() and ir_last_update_date[int_rate] > self.Time.date():
rate[symbol] = self.Securities[int_rate].Price
if rebalance_flag:
long = []
short = []
if len(rate) >= self.traded_count:
# interbank rate sorting
sorted_by_rate = sorted(rate.items(), key = lambda x: x[1], reverse = True)
long = [x[0] for x in sorted_by_rate[:self.traded_count]]
short = [x[0] for x in sorted_by_rate[-self.traded_count:]]
# trade execution
invested = [x.Key.Value for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
if symbol not in long + short:
self.Liquidate(symbol)
for symbol in long:
self.SetHoldings(symbol, 1 / len(long))
for symbol in short:
self.SetHoldings(symbol, -1 / len(short))