forked from paperswithbacktest/awesome-systematic-trading
-
Notifications
You must be signed in to change notification settings - Fork 1
/
currency-value-factor-ppp-strategy.py
129 lines (106 loc) · 4.97 KB
/
currency-value-factor-ppp-strategy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# 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"))
# Quandl "value" data
class QuandlValue(PythonQuandl):
def __init__(self):
self.ValueColumnName = "Value"
# 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
# https://quantpedia.com/strategies/currency-value-factor-ppp-strategy/
#
# Create an investment universe consisting of several currencies (10-20). Use the latest OECD Purchasing Power Parity figure to assess
# the fair value of each currency versus USD in the month of publishing and then use monthly CPI changes and exchange rate changes to
# create fair PPP value for the month prior to the current month. Go long three currencies that are the most undervalued (lowest PPP
# fair value figure) and go short three currencies that are the most overvalued (highest PPP fair value figure). Invest cash not used
# as margin on overnight rates. Rebalance quarterly or monthly.
#
# QC implementation changes:
# - Yearly rebalance instead of quarterly is performed.
import data_tools
from AlgorithmImports import *
class CurrencyValueFactorPPPStrategy(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
# currency future symbol and PPP yearly quandl symbol
# PPP source: https://www.quandl.com/data/ODA-IMF-Cross-Country-Macroeconomic-Statistics?keyword=%20United%20States%20Implied%20PPP%20Conversion%20Rate
self.symbols = {
"CME_AD1": "ODA/AUS_PPPEX", # Australian Dollar Futures, Continuous Contract #1
"CME_BP1": "ODA/GBR_PPPEX", # British Pound Futures, Continuous Contract #1
"CME_CD1": "ODA/CAD_PPPEX", # Canadian Dollar Futures, Continuous Contract #1
"CME_EC1": "ODA/DEU_PPPEX", # Euro FX Futures, Continuous Contract #1
"CME_JY1": "ODA/JPN_PPPEX", # Japanese Yen Futures, Continuous Contract #1
"CME_NE1": "ODA/NZL_PPPEX", # New Zealand Dollar Futures, Continuous Contract #1
"CME_SF1": "ODA/CHE_PPPEX", # Swiss Franc Futures, Continuous Contract #1
}
for symbol in self.symbols:
data = self.AddData(data_tools.QuantpediaFutures, symbol, Resolution.Daily)
data.SetFeeModel(data_tools.CustomFeeModel())
data.SetLeverage(5)
# PPP quandl data.
ppp_symbol = self.symbols[symbol]
self.AddData(data_tools.QuandlValue, ppp_symbol, Resolution.Daily)
self.recent_month = -1
def OnData(self, data):
if self.recent_month == self.Time.month:
return
self.recent_month = self.Time.month
# January rebalance
if self.recent_month == 1:
ppp = {}
for symbol, ppp_symbol in self.symbols.items():
# if symbol in data and data[symbol]:
if (
self.Securities[symbol].GetLastData()
and (
self.Time.date()
- self.Securities[symbol].GetLastData().Time.date()
).days
< 3
):
# new ppp data arrived
if ppp_symbol in data and data[ppp_symbol]:
ppp[symbol] = data[ppp_symbol].Value
count = 3
long = []
short = []
if len(ppp) >= count * 2:
# ppp sorting
sorted_by_ppp = sorted(ppp.items(), key=lambda x: x[1], reverse=True)
long = [x[0] for x in sorted_by_ppp[-count:]]
short = [x[0] for x in sorted_by_ppp[: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))