· 5 years ago · Oct 20, 2020, 09:18 PM
1# ======================================================================================================================
2# IMPORTS
3# ======================================================================================================================
4
5import backtrader as bt
6from datetime import datetime
7from dateutil.relativedelta import relativedelta
8import io
9import pandas as pd
10import requests
11
12# ======================================================================================================================
13# CONFIGURATION
14# ======================================================================================================================
15
16# Set your Amberdata API_KEY here
17Amberdata_API_KEY = 'UAKb1d20a4aa846f5668e29dd87284f84b7'
18
19# Set initial capital
20icap = 100000
21
22# Set position size - Percent of capital to deploy per trade
23PercSize = 100
24
25# Set percent trailing stop
26PercTrail = 0.40
27
28# Timeframe for the analysis
29start_date = "2015-01-20"
30end_date = "2020-05-09"
31
32
33# ======================================================================================================================
34# HELPERS - DATA SOURCES
35# ======================================================================================================================
36
37class CustomPandas(bt.feeds.PandasData):
38 # Add a 'stf' line to the inherited ones from the base class
39 lines = ('stf',)
40
41 # openinterest in GenericCSVData has index 7 ... add 1
42 # add the parameter to the parameters inherited from the base class
43 #params = (('stf2sd', 8),)
44 params = (('stf', 8),)
45
46# Call Amberdata's API
47def amberdata(url, queryString, apiKey):
48 try:
49 headers = {'x-api-key': apiKey}
50 response = requests.request("GET", url, headers=headers, params=queryString)
51 return response.text
52 except Exception as e:
53 raise e
54
55
56# Get Market data from Amberdata
57def amberdata_ohlcv(exchange, symbol, startDate, endDate):
58 format = "%Y-%m-%dT%H:%M:%S"
59 startTimestamp = datetime.strptime(startDate, '%Y-%m-%d')
60 endTimestamp = datetime.strptime(endDate, '%Y-%m-%d')
61
62 current = startTimestamp
63 next = current
64 fields = "timestamp,open,high,low,close,volume"
65 payload = fields
66 while (current < endTimestamp):
67 next += relativedelta(years=1)
68 if (next > endTimestamp):
69 next = endTimestamp
70 print('Retrieving OHLCV between', current, ' and ', next)
71 result = amberdata(
72 "https://web3api.io/api/v2/market/ohlcv/" + symbol + "/historical",
73 {"exchange": exchange, "timeInterval": "days", "timeFormat": "iso", "format": "raw_csv", "fields": fields, "startDate": current.strftime(format), "endDate": next.strftime(format)},
74 Amberdata_API_KEY
75 )
76 payload += "\n" + result
77 current = next
78
79 return payload
80
81# Get On-chain data from Amberdata - Stock to flow valuation model
82def amberdata_stf(symbol, startDate, endDate):
83 print('Retrieving STF between', startDate, ' and ', endDate)
84 return amberdata(
85 "https://web3api.io/api/v2/market/metrics/" + symbol + "/historical/stock-to-flow",
86 {"format": "csv", "timeFrame": "day", "startDate": startDate, "endDate": endDate},
87 Amberdata_API_KEY
88 )
89
90def to_pandas(csv):
91 return pd.read_csv(io.StringIO(csv), index_col='timestamp', parse_dates=True)
92
93
94# ======================================================================================================================
95# HELPERS - TRADING
96# ======================================================================================================================
97
98def pretty_print(format, *args):
99 print(format.format(*args))
100
101def exists(object, *properties):
102 for property in properties:
103 if not property in object: return False
104 object = object.get(property)
105 return True
106
107def printTradeAnalysis(cerebro, analyzers):
108 format = " {:<24} : {:<24}"
109 NA = '-'
110
111 print('Backtesting Results')
112 if hasattr(analyzers, 'ta'):
113 ta = analyzers.ta.get_analysis()
114
115 openTotal = ta.total.open if exists(ta, 'total', 'open' ) else None
116 closedTotal = ta.total.closed if exists(ta, 'total', 'closed') else None
117 wonTotal = ta.won.total if exists(ta, 'won', 'total' ) else None
118 lostTotal = ta.lost.total if exists(ta, 'lost', 'total' ) else None
119
120 streakWonLongest = ta.streak.won.longest if exists(ta, 'streak', 'won', 'longest') else None
121 streakLostLongest = ta.streak.lost.longest if exists(ta, 'streak', 'lost', 'longest') else None
122
123 pnlNetTotal = ta.pnl.net.total if exists(ta, 'pnl', 'net', 'total' ) else None
124 pnlNetAverage = ta.pnl.net.average if exists(ta, 'pnl', 'net', 'average') else None
125
126 pretty_print(format, 'Open Positions', openTotal or NA)
127 pretty_print(format, 'Closed Trades', closedTotal or NA)
128 pretty_print(format, 'Winning Trades', wonTotal or NA)
129 pretty_print(format, 'Loosing Trades', lostTotal or NA)
130 print('\n')
131
132 pretty_print(format, 'Longest Winning Streak', streakWonLongest or NA)
133 pretty_print(format, 'Longest Loosing Streak', streakLostLongest or NA)
134 pretty_print(format, 'Strike Rate (Win/closed)', (wonTotal / closedTotal) * 100 if wonTotal and closedTotal else NA)
135 print('\n')
136
137 pretty_print(format, 'Inital Portfolio Value', '${}'.format(icap))
138 pretty_print(format, 'Final Portfolio Value', '${}'.format(cerebro.broker.getvalue()))
139 pretty_print(format, 'Net P/L', '${}'.format(round(pnlNetTotal, 2)) if pnlNetTotal else NA)
140 pretty_print(format, 'P/L Average per trade', '${}'.format(round(pnlNetAverage, 2)) if pnlNetAverage else NA)
141 print('\n')
142
143 if hasattr(analyzers, 'drawdown'):
144 pretty_print(format, 'Drawdown', '${}'.format(analyzers.drawdown.get_analysis()['drawdown']))
145 if hasattr(analyzers, 'sharpe'):
146 pretty_print(format, 'Sharpe Ratio:', analyzers.sharpe.get_analysis()['sharperatio'])
147 if hasattr(analyzers, 'vwr'):
148 pretty_print(format, 'VRW', analyzers.vwr.get_analysis()['vwr'])
149 if hasattr(analyzers, 'sqn'):
150 pretty_print(format, 'SQN', analyzers.sqn.get_analysis()['sqn'])
151 print('\n')
152
153 print('Transactions')
154 format = " {:<24} {:<24} {:<16} {:<8} {:<8} {:<16}"
155 pretty_print(format, 'Date', 'Amount', 'Price', 'SID', 'Symbol', 'Value')
156 for key, value in analyzers.txn.get_analysis().items():
157 pretty_print(format, key.strftime("%Y/%m/%d %H:%M:%S"), value[0][0], value[0][1], value[0][2], value[0][3], value[0][4])
158
159
160# ======================================================================================================================
161# STRATEGY
162# ======================================================================================================================
163
164class Strategy(bt.Strategy):
165 params = (
166 ('macd1', 12),
167 ('macd2', 26),
168 ('macdsig', 9),
169 ('trailpercent', PercTrail),
170 ('smaperiod', 30),
171 ('dirperiod', 10),
172 )
173
174 def notify_order(self, order):
175 if order.status == order.Completed:
176 pass
177
178 if not order.alive():
179 self.order = None # No pending orders
180
181 def __init__(self):
182 self.macd = bt.indicators.MACD(self.data,
183 period_me1=self.p.macd1,
184 period_me2=self.p.macd2,
185 period_signal=self.p.macdsig)
186
187 # Cross of macd.macd and macd.signal
188 self.mcross = bt.indicators.CrossOver(self.macd.macd, self.macd.signal)
189
190 # Control market trend
191 self.sma = bt.indicators.SMA(self.data, period=self.p.smaperiod)
192 self.smadir = self.sma - self.sma(-self.p.dirperiod)
193
194 def start(self):
195 self.order = None # Avoid operrations on pending order
196
197 def next(self):
198 if self.order:
199 return # pending order execution
200
201 if not self.position: # not in the market
202 if self.mcross[0] > 0.0 and self.smadir < 0.0 and self.data.close < self.data.stf:
203 self.order = self.buy()
204 self.order = 'none'
205
206 elif self.order is None: # Position in Market
207 self.order = self.sell(exectype=bt.Order.StopTrail,trailpercent=self.p.trailpercent)
208 tcheck = self.data.close * (1.0 - self.p.trailpercent)
209
210
211# ======================================================================================================================
212# MAIN
213# ======================================================================================================================
214
215# Create an instance of cerebro
216cerebro = bt.Cerebro(stdstats=False)
217
218# Be selective about what we chart
219#cerebro.addobserver(bt.observers.Broker)
220cerebro.addobserver(bt.observers.BuySell)
221cerebro.addobserver(bt.observers.Value)
222cerebro.addobserver(bt.observers.DrawDown)
223cerebro.addobserver(bt.observers.Trades)
224
225# Set the investment capital
226cerebro.broker.setcash(icap)
227
228# Set position size
229cerebro.addsizer(bt.sizers.PercentSizer, percents=PercSize)
230
231# Add our strategy
232cerebro.addstrategy(Strategy)
233
234# Read market and on-chain data into dataframe
235btc = to_pandas(amberdata_ohlcv("gdax", "btc_usd", start_date, end_date))
236btc_stf = to_pandas(amberdata_stf("btc", start_date, end_date))
237btc['stf'] = btc_stf['stockToFlow_price']
238
239# Feed Cerebro our data
240#cerebro.adddata(CustomPandas(dataname=btc, openinterest=None, stf2sd='stf2sd'))
241cerebro.adddata(CustomPandas(dataname=btc, openinterest=None, stf='stf'))
242
243# Add analyzers
244cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='ta')
245cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
246cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe', riskfreerate=0.0, annualize=True, timeframe=bt.TimeFrame.Days)
247cerebro.addanalyzer(bt.analyzers.VWR, _name='vwr')
248cerebro.addanalyzer(bt.analyzers.SQN, _name='sqn')
249cerebro.addanalyzer(bt.analyzers.Transactions, _name='txn')
250
251# Run our Backtest
252backtest = cerebro.run()
253backtest_results = backtest[0]
254
255# Print some analytics
256printTradeAnalysis(cerebro, backtest_results.analyzers)
257
258# Finally plot the end results
259cerebro.plot(style='candlestick', volume=False)
260
261# =================================================================================================================