3
3
import datetime as datetime
4
4
import time
5
5
import re
6
- import oandapy
6
+
7
+ from oandapyV20 import API
8
+ import oandapyV20 .endpoints .instruments as instruments
9
+
7
10
from pandas .compat import StringIO , string_types
8
11
from ._utils import _init_session , _sanitize_dates
9
12
12
15
class OANDARestHistoricalInstrumentReader (_BaseReader ):
13
16
"""
14
17
Historical Currency Pair Reader using OANDA's REST API.
15
- See details at http://developer.oanda.com/rest-live/rates/#retrieveInstrumentHistory
18
+ See details at http://developer.oanda.com/rest-live-v20/instrument-ep/
16
19
symbols : Dict of strings.
17
20
Each string is a currency pair with format BASE_QUOTE. Eg: ["EUR_USD", "JPY_USD"]
18
21
symbolsTypes: Dict of strings.
@@ -27,14 +30,15 @@ class OANDARestHistoricalInstrumentReader(_BaseReader):
27
30
Default: See DEFAULT_GRANULARITY
28
31
candleFormat: string
29
32
Candlesticks representation
33
+ Valid values: M,B,A
30
34
Default: See DEFAULT_CANDLE_FORMAT
31
35
access_credential: Dict of strings
32
36
Credential to query the api
33
37
credential["accountType"]="practise". Mandatory. Valid values: practice, live
34
38
credential["apiToken"]="Your OANDA API token". Mandatory. Valid value: See your OANDA Account's API Token
35
39
"""
36
40
DEFAULT_GRANULARITY = "S5"
37
- DEFAULT_CANDLE_FORMAT = "midpoint "
41
+ DEFAULT_CANDLE_FORMAT = "M "
38
42
39
43
def __init__ (self ,symbols , symbolsTypes = None ,
40
44
start = None , end = None ,
@@ -97,15 +101,6 @@ def _read_historical_currencypair_rates(self, start, end, granularity=None,
97
101
quote_currency = None , base_currency = None ,
98
102
candleFormat = None , reversed = False ,
99
103
access_credential = None , session = None ):
100
- """
101
- access_credential : dict of strings
102
- Format {
103
- "accountType": "practise OR live (mandatory)",
104
- "accountVersion": "0", // 0 for legacy REST API account (supported), v20 for REST-v20 (unsupported for now)
105
- "apiToken"; "Private API token (mandatory)"}
106
- other parameter: *
107
- See http://developer.oanda.com/rest-live/rates/#retrieveInstrumentHistory
108
- """
109
104
session = _init_session (session )
110
105
start , end = _sanitize_dates (start , end )
111
106
@@ -116,17 +111,14 @@ def _read_historical_currencypair_rates(self, start, end, granularity=None,
116
111
117
112
currencyPair = "%s_%s" % (base_currency , quote_currency )
118
113
119
- if candleFormat is None :
120
- candleFormat = "midpoint"
121
-
122
114
if access_credential is None :
123
115
raise Exception ('No access_crendtial provided. Historical data cannot be fetched' )
124
116
125
117
credential = access_credential
126
118
#print(credential)
127
119
128
- oanda = oandapy . API (environment = credential ['accountType ' ],
129
- access_token = credential ['apiToken ' ])
120
+ oanda = API (access_token = credential ['apiToken ' ],
121
+ environment = credential ['accountType ' ])
130
122
131
123
current_start = start
132
124
current_end = end
@@ -146,15 +138,12 @@ def _read_historical_currencypair_rates(self, start, end, granularity=None,
146
138
current_start_rfc3339 = current_start .strftime (rfc3339 )
147
139
current_end_rfc3339 = current_end .strftime (rfc3339 )
148
140
149
- #print("%s(%s) -> %s(%s)" % (current_start, current_start_timestamp, current_end, current_end_timestamp))
150
- #print(type(current_start))
151
-
152
141
params = {
153
- "instrument" : currencyPair ,
154
142
"granularity" : granularity ,
155
- "start" : current_start_rfc3339 ,
156
- "end" : current_end_rfc3339 ,
157
- "candleFormat" : candleFormat ,
143
+ "from" : current_start_rfc3339 ,
144
+ "to" : current_end_rfc3339 ,
145
+ "price" : candleFormat ,
146
+ "smooth" : "false" ,
158
147
"includeFirst" : "true" if includeCandleOnStart else "false" ,
159
148
"dailyAlignment" : "17" ,
160
149
"alignmentTimezone" : "America/New_York" ,
@@ -164,21 +153,24 @@ def _read_historical_currencypair_rates(self, start, end, granularity=None,
164
153
#print(params)
165
154
166
155
try :
167
- response = oanda .get_history (** params )
156
+ request = instruments .InstrumentsCandles (
157
+ instrument = currencyPair ,
158
+ params = params )
159
+
160
+ response = oanda .request (request )
168
161
current_start = current_end
169
162
includeCandleOnStart = False
170
163
except Exception as error :
171
- #print(str(error))
172
- error_code = re .findall ("error code ([0-9]+)" , str (error ))
173
- if not error_code :
174
- print (str (error ))
175
- raise error
176
- elif error_code [0 ] == "36" :
164
+ isExceedResultsLimitError = re .findall ("The results returned satisfying the query exceeds the maximum size" , str (error ))
165
+ isExceedResultsLimitError = True if len (isExceedResultsLimitError ) else False
166
+
167
+ if isExceedResultsLimitError :
177
168
# Problem: OANDA caps returned range to 5000 results max
178
169
# Solution: Reduce requested date interval to return less than 5000 results
179
- current_duration /= 2
180
- continue
170
+ current_duration /= 2
171
+ continue
181
172
else :
173
+ print ("ERROR OANDA: " + str (error ))
182
174
print (str (error ))
183
175
raise error
184
176
@@ -187,38 +179,53 @@ def _read_historical_currencypair_rates(self, start, end, granularity=None,
187
179
if not response :
188
180
continue
189
181
190
- candles = response ['candles' ]
191
- candlesJSON = json .dumps (candles )
182
+ candles = response ['candles' ]
192
183
193
- #print(candlesJSON)
184
+ if not candles :
185
+ continue
186
+
187
+ #print(candles)
194
188
195
- df = pd .read_json (candlesJSON , typ = 'frame' , orient = 'records' , convert_dates = ['time' ])
189
+ ohlc = ['o' ,'h' ,'l' ,'c' ]
190
+ df = pd .io .json .json_normalize (
191
+ candles ,
192
+ meta = [ 'time' , 'volume' , 'complete' , ['mid' , ohlc ], ['ask' , ohlc ], ['bid' , ohlc ]]
193
+ )
194
+ df ['time' ] = pd .to_datetime (df ['time' ])
196
195
197
196
with pd .option_context ('display.max_columns' , None ):
198
197
pass #print(df)
199
198
200
199
dfs .append (df )
201
200
202
201
df = pd .concat (dfs );
203
-
204
- df = df .rename (columns = {
205
- "time" : "date" ,
206
- })
207
- df = df .set_index ('date' )
208
202
209
- # FIXME:Sort by date
210
- #df.sort(['date'], ascending=True)
203
+ df .rename ( inplace = True ,
204
+ index = str ,
205
+ columns = {
206
+ "time" : "date" ,
207
+ "mid.o" : "mid.open" ,
208
+ "mid.h" :"mid.high" ,
209
+ "mid.l" :"mid.low" ,
210
+ "mid.c" :"mid.close" ,
211
+ "ask.o" :"ask.open" ,
212
+ "ask.h" :"ask.high" ,
213
+ "ask.l" :"ask.low" ,
214
+ "ask.c" :"ask.close" ,
215
+ "bid.o" :"bid.open" ,
216
+ "bid.h" :"bid.high" ,
217
+ "bid.l" :"bid.low" ,
218
+ "bid.c" :"bid.close" ,
219
+ })
211
220
212
- #print(df)
221
+ df = df .set_index ('date' )
222
+
223
+ # Sort by date as OANDA REST v20 provides no guarantee
224
+ # returned candles are sorted
225
+ df .sort_index (axis = 0 , level = 'date' , ascending = True , inplace = True )
213
226
214
- #df = df[::-1]
215
- #if reversed:
216
- # df.columns = pd.Index(df.columns.map(_reverse_pair))
217
- # df = 1 / df
218
- #if len(base_currency) == 1:
219
- # return df.iloc[:, 0]
220
- #else:
221
- # return df
227
+ with pd .option_context ('display.max_columns' , None ):
228
+ pass #print(df)
222
229
223
230
return df
224
231
@@ -229,5 +236,5 @@ def _reverse_pair(s, sep="_"):
229
236
def _split_currency_pair (self , s , sep = "_" ):
230
237
lst = s .split (sep )
231
238
return (lst [0 ], lst [1 ])
232
-
239
+
233
240
0 commit comments