Skip to content

Commit d8f8d32

Browse files
Rewrote OANDAHistoricalInstrumentReader to use OANDA REST V20 instead of OANDA REST
1 parent 98b66d5 commit d8f8d32

File tree

3 files changed

+71
-61
lines changed

3 files changed

+71
-61
lines changed

pandas_datareader/oandarest.py

Lines changed: 61 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import datetime as datetime
44
import time
55
import re
6-
import oandapy
6+
7+
from oandapyV20 import API
8+
import oandapyV20.endpoints.instruments as instruments
9+
710
from pandas.compat import StringIO, string_types
811
from ._utils import _init_session, _sanitize_dates
912

@@ -12,7 +15,7 @@
1215
class OANDARestHistoricalInstrumentReader(_BaseReader):
1316
"""
1417
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/
1619
symbols : Dict of strings.
1720
Each string is a currency pair with format BASE_QUOTE. Eg: ["EUR_USD", "JPY_USD"]
1821
symbolsTypes: Dict of strings.
@@ -27,14 +30,15 @@ class OANDARestHistoricalInstrumentReader(_BaseReader):
2730
Default: See DEFAULT_GRANULARITY
2831
candleFormat: string
2932
Candlesticks representation
33+
Valid values: M,B,A
3034
Default: See DEFAULT_CANDLE_FORMAT
3135
access_credential: Dict of strings
3236
Credential to query the api
3337
credential["accountType"]="practise". Mandatory. Valid values: practice, live
3438
credential["apiToken"]="Your OANDA API token". Mandatory. Valid value: See your OANDA Account's API Token
3539
"""
3640
DEFAULT_GRANULARITY="S5"
37-
DEFAULT_CANDLE_FORMAT="midpoint"
41+
DEFAULT_CANDLE_FORMAT="M"
3842

3943
def __init__(self,symbols, symbolsTypes=None,
4044
start=None, end=None,
@@ -97,15 +101,6 @@ def _read_historical_currencypair_rates(self, start, end, granularity=None,
97101
quote_currency=None, base_currency=None,
98102
candleFormat=None, reversed=False,
99103
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-
"""
109104
session = _init_session(session)
110105
start, end = _sanitize_dates(start, end)
111106

@@ -116,17 +111,14 @@ def _read_historical_currencypair_rates(self, start, end, granularity=None,
116111

117112
currencyPair = "%s_%s" % (base_currency, quote_currency)
118113

119-
if candleFormat is None:
120-
candleFormat = "midpoint"
121-
122114
if access_credential is None:
123115
raise Exception('No access_crendtial provided. Historical data cannot be fetched')
124116

125117
credential = access_credential
126118
#print(credential)
127119

128-
oanda = oandapy.API(environment=credential['accountType'],
129-
access_token=credential['apiToken'])
120+
oanda = API(access_token=credential['apiToken'],
121+
environment=credential['accountType'])
130122

131123
current_start = start
132124
current_end = end
@@ -146,15 +138,12 @@ def _read_historical_currencypair_rates(self, start, end, granularity=None,
146138
current_start_rfc3339 = current_start.strftime(rfc3339)
147139
current_end_rfc3339 = current_end.strftime(rfc3339)
148140

149-
#print("%s(%s) -> %s(%s)" % (current_start, current_start_timestamp, current_end, current_end_timestamp))
150-
#print(type(current_start))
151-
152141
params = {
153-
"instrument": currencyPair,
154142
"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",
158147
"includeFirst": "true" if includeCandleOnStart else "false",
159148
"dailyAlignment" : "17",
160149
"alignmentTimezone" : "America/New_York",
@@ -164,21 +153,24 @@ def _read_historical_currencypair_rates(self, start, end, granularity=None,
164153
#print(params)
165154

166155
try:
167-
response = oanda.get_history(**params)
156+
request = instruments.InstrumentsCandles(
157+
instrument=currencyPair,
158+
params=params)
159+
160+
response = oanda.request(request)
168161
current_start = current_end
169162
includeCandleOnStart = False
170163
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:
177168
# Problem: OANDA caps returned range to 5000 results max
178169
# Solution: Reduce requested date interval to return less than 5000 results
179-
current_duration /= 2
180-
continue
170+
current_duration /= 2
171+
continue
181172
else:
173+
print("ERROR OANDA: "+ str(error))
182174
print(str(error))
183175
raise error
184176

@@ -187,38 +179,53 @@ def _read_historical_currencypair_rates(self, start, end, granularity=None,
187179
if not response:
188180
continue
189181

190-
candles = response['candles']
191-
candlesJSON = json.dumps(candles)
182+
candles = response['candles']
192183

193-
#print(candlesJSON)
184+
if not candles:
185+
continue
186+
187+
#print(candles)
194188

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'])
196195

197196
with pd.option_context('display.max_columns', None):
198197
pass #print(df)
199198

200199
dfs.append(df)
201200

202201
df = pd.concat(dfs);
203-
204-
df = df.rename(columns={
205-
"time": "date",
206-
})
207-
df = df.set_index('date')
208202

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+
})
211220

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)
213226

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)
222229

223230
return df
224231

@@ -229,5 +236,5 @@ def _reverse_pair(s, sep="_"):
229236
def _split_currency_pair(self, s, sep="_"):
230237
lst = s.split(sep)
231238
return (lst[0], lst[1])
232-
239+
233240

pandas_datareader/tests/test_oandarest.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,16 @@ def get_credential(self):
1010
return {'accountType':"practice",
1111
'accountVersion':"0"
1212
}
13-
1413

1514
def test_oanda_historical_currencypair(self):
16-
start = "2014-03-21T10:00:00Z"
17-
end = "2014-03-23T10:00:00Z"
15+
start = "2014-03-19T09:00:00Z"
16+
end = "2014-03-21T9:00:00Z"
1817
symbols = ["EUR_USD"]
1918

2019
pn = OANDARestHistoricalInstrumentReader(
2120
symbols=symbols,
2221
start=start, end=end,
2322
granularity="S5",
24-
candleFormat="midpoint",
2523
access_credential=self.get_credential()
2624
).read()
2725

@@ -31,8 +29,8 @@ def test_oanda_historical_currencypair(self):
3129
self.assertTrue(df_rates.index[-1] <= pd.to_datetime(end))
3230

3331
def test_oanda_historical_currencypair2(self):
34-
start = "2014-03-21T10:00:00Z"
35-
end = "2014-03-23T10:00:00Z"
32+
start = "2014-03-19T09:00:00Z"
33+
end = "2014-03-21T09:00:00Z"
3634
symbols = ["EUR_USD"]
3735

3836
pn = web.DataReader(

setup.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ def readme():
2424
return f.read()
2525

2626
INSTALL_REQUIRES = (
27-
['pandas', 'requests>=2.3.0', 'requests-file', 'requests-ftp', 'oandapy']
27+
['pandas', 'requests>=2.3.0', 'requests-file', 'requests-ftp', 'oandapyV20>=0.1.0']
28+
)
29+
30+
DEPENDENCY_LINKS = (
31+
['https://github.com/hootnot/oanda-api-v20/archive/af035ef99ae73d998172569bc702326d2df96d4e.zip#egg=oandapyV20-0.1.0']
2832
)
2933

3034
setup(
@@ -53,6 +57,7 @@ def readme():
5357
],
5458
keywords='data',
5559
install_requires=INSTALL_REQUIRES,
60+
dependency_links=DEPENDENCY_LINKS,
5661
packages=find_packages(exclude=['contrib', 'docs', 'tests*']),
5762
test_suite='tests',
5863
zip_safe=False,

0 commit comments

Comments
 (0)