In [1]:
import configparser

# v20 OANDA API - 3rd party
import json
from oandapyV20 import API    # the client
import oandapyV20.endpoints.trades as trades

config = configparser.ConfigParser()
config.read('oanda.cfg')

client = API(access_token=config['oanda']['access_token'])

In [2]:
import pandas as pd
import datetime
from dateutil import parser
import oandapyV20.endpoints.instruments as instruments

# The v20 api handles from times a little differently - be careful of the timezone
params={"from": parser.parse("2016-12-07 18:00:00 EDT").strftime('%s'),
        "to": parser.parse("2016-12-10 00:000:00 EDT").strftime('%s'),
        "granularity":'M1',
        "price":'A'}
r = instruments.InstrumentsCandles(instrument="EUR_USD",params=params)
data = client.request(r)
results= [{"time":x['time'],"closeAsk":float(x['ask']['c'])} for x in data['candles']]
df = pd.DataFrame(results).set_index('time')

df.index = pd.DatetimeIndex(df.index)

df.info()


<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 2702 entries, 2016-12-07 23:00:00 to 2016-12-09 21:59:00
Data columns (total 1 columns):
closeAsk    2702 non-null float64
dtypes: float64(1)
memory usage: 42.2 KB

In [3]:
import numpy as np

df['returns'] = np.log(df['closeAsk'] / df['closeAsk'].shift(1))

cols = []

for momentum in [15, 30, 60, 120]:
    col = 'position_%s' % momentum
    df[col] = np.sign(df['returns'].rolling(momentum).mean())
    cols.append(col)

In [4]:
%matplotlib inline
import seaborn as sns; sns.set()

strats = ['returns']

for col in cols:
    strat = 'strategy_%s' % col.split('_')[1] 
    df[strat] = df[col].shift(1) * df['returns']
    strats.append(strat)

df[strats].dropna().cumsum().apply(np.exp).plot()


Out[4]:
<matplotlib.axes._subplots.AxesSubplot at 0x1170222e8>

In [6]:
from oandapyV20.endpoints.pricing import PricingStream
import oandapyV20.endpoints.orders as orders
from oandapyV20.contrib.requests import MarketOrderRequest
from oandapyV20.exceptions import V20Error, StreamTerminated
        
class MomentumTrader(PricingStream): 
    def __init__(self, momentum, *args, **kwargs): 
        PricingStream.__init__(self, *args, **kwargs)
        self.ticks = 0 
        self.position = 0
        self.df = pd.DataFrame()
        self.momentum = momentum
        self.units = 100000
        self.connected = False
        self.client = API(access_token=config['oanda']['access_token'])
    def create_order(self, units):
        order = orders.OrderCreate(accountID=config['oanda']['account_id'], data=MarketOrderRequest(instrument="EUR_USD", units=units).data)
        response = self.client.request(order)
        print('\t', response)
    def on_success(self, data):
        self.ticks += 1
        print("ticks=",self.ticks)
        # print(self.ticks, end=', ')
        # appends the new tick data to the DataFrame object
        self.df = self.df.append(pd.DataFrame([{'time': data['time'],'closeoutAsk':data['closeoutAsk']}],
                                 index=[data["time"]]))
        # transforms the time information to a DatetimeIndex object
        self.df.index = pd.DatetimeIndex(self.df["time"])
        
        # Convert items back to numeric (Why, OANDA, why are you returning strings?)
        self.df['closeoutAsk'] = pd.to_numeric(self.df["closeoutAsk"],errors='ignore')
        # resamples the data set to a new, homogeneous interval
        dfr = self.df.resample('5s').last().bfill()
        # calculates the log returns
        dfr['returns'] = np.log(dfr['closeoutAsk'] / dfr['closeoutAsk'].shift(1))        
        # derives the positioning according to the momentum strategy
        dfr['position'] = np.sign(dfr['returns'].rolling( 
                                      self.momentum).mean())
        print("position=",dfr['position'].ix[-1])
        if dfr['position'].ix[-1] == 1:
            print("go long")
            if self.position == 0:
                self.create_order(self.units)
            elif self.position == -1:
                self.create_order(self.units * 2)
            self.position = 1
        elif dfr['position'].ix[-1] == -1:
            print("go short")
            if self.position == 0:
                self.create_order(-self.units)
            elif self.position == 1:
                self.create_order(-self.units * 2)
            self.position = -1
        if self.ticks == 25000:
            print("close out the position")
            if self.position == 1:
                self.create_order(-self.units)
            elif self.position == -1:
                self.create_order(self.units)
            self.disconnect()
    def disconnect(self):
        self.connected=False
    def rates(self, account_id, instruments, **params):
        self.connected = True
        params = params or {}
        ignore_heartbeat = None
        if "ignore_heartbeat" in params:
            ignore_heartbeat = params['ignore_heartbeat']
        while self.connected:
            response = self.client.request(self)
            for tick in response:
                if not self.connected:
                    break
                if not (ignore_heartbeat and tick["type"]=="HEARTBEAT"):
                    print(tick)
                    self.on_success(tick)

In [11]:
# Set momentum to be the number of previous 5 second intervals to calculate against
# For two hours, momentum = 120 * 5 = 600
# I am using momentum = 6 to speed things up (but it is a terrible prediction!)
mt = MomentumTrader(momentum=6,accountID=config['oanda']['account_id'],params={"instruments": "EUR_USD"})

In [12]:
mt.rates(account_id=config['oanda']['account_id'], instruments="EUR_USD", ignore_heartbeat=True)


{'type': 'PRICE', 'time': '2017-03-07T02:33:45.528790510Z', 'bids': [{'price': '1.05836', 'liquidity': 10000000.0}, {'price': '1.05834', 'liquidity': 10000000.0}], 'asks': [{'price': '1.05849', 'liquidity': 10000000.0}, {'price': '1.05851', 'liquidity': 10000000.0}], 'closeoutBid': '1.05832', 'closeoutAsk': '1.05853', 'status': 'tradeable', 'tradeable': True, 'instrument': 'EUR_USD'}
ticks= 1
position= nan
{'type': 'PRICE', 'time': '2017-03-07T02:34:31.539294195Z', 'bids': [{'price': '1.05832', 'liquidity': 10000000.0}, {'price': '1.05830', 'liquidity': 10000000.0}], 'asks': [{'price': '1.05845', 'liquidity': 10000000.0}, {'price': '1.05847', 'liquidity': 10000000.0}], 'closeoutBid': '1.05828', 'closeoutAsk': '1.05849', 'status': 'tradeable', 'tradeable': True, 'instrument': 'EUR_USD'}
ticks= 2
position= 0.0
{'type': 'PRICE', 'time': '2017-03-07T02:34:49.416958320Z', 'bids': [{'price': '1.05833', 'liquidity': 10000000.0}, {'price': '1.05831', 'liquidity': 10000000.0}], 'asks': [{'price': '1.05845', 'liquidity': 10000000.0}, {'price': '1.05847', 'liquidity': 10000000.0}], 'closeoutBid': '1.05829', 'closeoutAsk': '1.05849', 'status': 'tradeable', 'tradeable': True, 'instrument': 'EUR_USD'}
ticks= 3
position= 0.0
{'type': 'PRICE', 'time': '2017-03-07T02:35:56.832000180Z', 'bids': [{'price': '1.05834', 'liquidity': 10000000.0}, {'price': '1.05832', 'liquidity': 10000000.0}], 'asks': [{'price': '1.05848', 'liquidity': 10000000.0}, {'price': '1.05850', 'liquidity': 10000000.0}], 'closeoutBid': '1.05830', 'closeoutAsk': '1.05852', 'status': 'tradeable', 'tradeable': True, 'instrument': 'EUR_USD'}
ticks= 4
position= 0.0
{'type': 'PRICE', 'time': '2017-03-07T02:35:56.937770543Z', 'bids': [{'price': '1.05836', 'liquidity': 10000000.0}, {'price': '1.05834', 'liquidity': 10000000.0}], 'asks': [{'price': '1.05849', 'liquidity': 10000000.0}, {'price': '1.05851', 'liquidity': 10000000.0}], 'closeoutBid': '1.05832', 'closeoutAsk': '1.05853', 'status': 'tradeable', 'tradeable': True, 'instrument': 'EUR_USD'}
ticks= 5
position= 0.0
{'type': 'PRICE', 'time': '2017-03-07T02:36:31.660969675Z', 'bids': [{'price': '1.05832', 'liquidity': 10000000.0}, {'price': '1.05830', 'liquidity': 10000000.0}], 'asks': [{'price': '1.05846', 'liquidity': 10000000.0}, {'price': '1.05848', 'liquidity': 10000000.0}], 'closeoutBid': '1.05828', 'closeoutAsk': '1.05850', 'status': 'tradeable', 'tradeable': True, 'instrument': 'EUR_USD'}
ticks= 6
position= 0.0
{'type': 'PRICE', 'time': '2017-03-07T02:36:32.728770415Z', 'bids': [{'price': '1.05833', 'liquidity': 10000000.0}, {'price': '1.05831', 'liquidity': 10000000.0}], 'asks': [{'price': '1.05846', 'liquidity': 10000000.0}, {'price': '1.05848', 'liquidity': 10000000.0}], 'closeoutBid': '1.05829', 'closeoutAsk': '1.05850', 'status': 'tradeable', 'tradeable': True, 'instrument': 'EUR_USD'}
ticks= 7
position= 0.0
{'type': 'PRICE', 'time': '2017-03-07T02:36:33.131340803Z', 'bids': [{'price': '1.05838', 'liquidity': 10000000.0}, {'price': '1.05836', 'liquidity': 10000000.0}], 'asks': [{'price': '1.05852', 'liquidity': 10000000.0}, {'price': '1.05854', 'liquidity': 10000000.0}], 'closeoutBid': '1.05834', 'closeoutAsk': '1.05856', 'status': 'tradeable', 'tradeable': True, 'instrument': 'EUR_USD'}
ticks= 8
position= 0.0
{'type': 'PRICE', 'time': '2017-03-07T02:36:56.856640643Z', 'bids': [{'price': '1.05846', 'liquidity': 10000000.0}, {'price': '1.05844', 'liquidity': 10000000.0}], 'asks': [{'price': '1.05859', 'liquidity': 10000000.0}, {'price': '1.05861', 'liquidity': 10000000.0}], 'closeoutBid': '1.05842', 'closeoutAsk': '1.05863', 'status': 'tradeable', 'tradeable': True, 'instrument': 'EUR_USD'}
ticks= 9
position= 1.0
go long
	 {'orderCreateTransaction': {'type': 'MARKET_ORDER', 'instrument': 'EUR_USD', 'units': '100000', 'timeInForce': 'FOK', 'positionFill': 'DEFAULT', 'reason': 'CLIENT_ORDER', 'id': '1733', 'userID': 5417463, 'accountID': '101-001-5417463-001', 'batchID': '1733', 'time': '2017-03-07T02:36:57.981701604Z'}, 'orderFillTransaction': {'type': 'ORDER_FILL', 'orderID': '1733', 'instrument': 'EUR_USD', 'units': '100000', 'price': '1.05858', 'pl': '0.0000', 'financing': '0.0000', 'accountBalance': '99795.8866', 'reason': 'MARKET_ORDER', 'tradeOpened': {'tradeID': '1734', 'units': '100000'}, 'id': '1734', 'userID': 5417463, 'accountID': '101-001-5417463-001', 'batchID': '1733', 'time': '2017-03-07T02:36:57.981701604Z'}, 'relatedTransactionIDs': ['1733', '1734'], 'lastTransactionID': '1734'}
{'type': 'PRICE', 'time': '2017-03-07T02:36:57.519508339Z', 'bids': [{'price': '1.05844', 'liquidity': 10000000.0}, {'price': '1.05842', 'liquidity': 10000000.0}], 'asks': [{'price': '1.05858', 'liquidity': 10000000.0}, {'price': '1.05860', 'liquidity': 10000000.0}], 'closeoutBid': '1.05840', 'closeoutAsk': '1.05862', 'status': 'tradeable', 'tradeable': True, 'instrument': 'EUR_USD'}
ticks= 10
position= 1.0
go long
{'type': 'PRICE', 'time': '2017-03-07T02:36:57.981701604Z', 'bids': [{'price': '1.05844', 'liquidity': 10000000.0}, {'price': '1.05842', 'liquidity': 10000000.0}], 'asks': [{'price': '1.05858', 'liquidity': 9900000.0}, {'price': '1.05860', 'liquidity': 10000000.0}], 'closeoutBid': '1.05840', 'closeoutAsk': '1.05862', 'status': 'tradeable', 'tradeable': True, 'instrument': 'EUR_USD'}
ticks= 11
position= 1.0
go long
{'type': 'PRICE', 'time': '2017-03-07T02:36:58.481701604Z', 'bids': [{'price': '1.05844', 'liquidity': 10000000.0}, {'price': '1.05842', 'liquidity': 10000000.0}], 'asks': [{'price': '1.05858', 'liquidity': 10000000.0}, {'price': '1.05860', 'liquidity': 10000000.0}], 'closeoutBid': '1.05840', 'closeoutAsk': '1.05862', 'status': 'tradeable', 'tradeable': True, 'instrument': 'EUR_USD'}
ticks= 12
position= 1.0
go long
{'type': 'PRICE', 'time': '2017-03-07T02:37:02.029224156Z', 'bids': [{'price': '1.05848', 'liquidity': 10000000.0}, {'price': '1.05846', 'liquidity': 10000000.0}], 'asks': [{'price': '1.05861', 'liquidity': 10000000.0}, {'price': '1.05863', 'liquidity': 10000000.0}], 'closeoutBid': '1.05844', 'closeoutAsk': '1.05865', 'status': 'tradeable', 'tradeable': True, 'instrument': 'EUR_USD'}
ticks= 13
position= 1.0
go long
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-12-89e759b82116> in <module>()
----> 1 mt.rates(account_id=config['oanda']['account_id'], instruments="EUR_USD", ignore_heartbeat=True)

<ipython-input-6-0413955cf88f> in rates(self, account_id, instruments, **params)
     69         while self.connected:
     70             response = self.client.request(self)
---> 71             for tick in response:
     72                 if not self.connected:
     73                     break

/Users/benjamin.chodroff/anaconda3/anaconda/lib/python3.6/site-packages/oandapyV20/oandapyV20.py in __stream_request(self, method, url, request_args, headers)
    255                                   headers=headers, stream=True)
    256         lines = response.iter_lines(ITER_LINES_CHUNKSIZE)
--> 257         for line in lines:
    258             if line:
    259                 data = json.loads(line.decode("utf-8"))

/Users/benjamin.chodroff/anaconda3/anaconda/lib/python3.6/site-packages/requests/models.py in iter_lines(self, chunk_size, decode_unicode, delimiter)
    745         pending = None
    746 
--> 747         for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode):
    748 
    749             if pending is not None:

/Users/benjamin.chodroff/anaconda3/anaconda/lib/python3.6/site-packages/requests/models.py in generate()
    701             if hasattr(self.raw, 'stream'):
    702                 try:
--> 703                     for chunk in self.raw.stream(chunk_size, decode_content=True):
    704                         yield chunk
    705                 except ProtocolError as e:

/Users/benjamin.chodroff/anaconda3/anaconda/lib/python3.6/site-packages/requests/packages/urllib3/response.py in stream(self, amt, decode_content)
    426         """
    427         if self.chunked and self.supports_chunked_reads():
--> 428             for line in self.read_chunked(amt, decode_content=decode_content):
    429                 yield line
    430         else:

/Users/benjamin.chodroff/anaconda3/anaconda/lib/python3.6/site-packages/requests/packages/urllib3/response.py in read_chunked(self, amt, decode_content)
    588         with self._error_catcher():
    589             while True:
--> 590                 self._update_chunk_length()
    591                 if self.chunk_left == 0:
    592                     break

/Users/benjamin.chodroff/anaconda3/anaconda/lib/python3.6/site-packages/requests/packages/urllib3/response.py in _update_chunk_length(self)
    530         if self.chunk_left is not None:
    531             return
--> 532         line = self._fp.fp.readline()
    533         line = line.split(b';', 1)[0]
    534         try:

/Users/benjamin.chodroff/anaconda3/anaconda/lib/python3.6/socket.py in readinto(self, b)
    584         while True:
    585             try:
--> 586                 return self._sock.recv_into(b)
    587             except timeout:
    588                 self._timeout_occurred = True

/Users/benjamin.chodroff/anaconda3/anaconda/lib/python3.6/site-packages/requests/packages/urllib3/contrib/pyopenssl.py in recv_into(self, *args, **kwargs)
    254     def recv_into(self, *args, **kwargs):
    255         try:
--> 256             return self.connection.recv_into(*args, **kwargs)
    257         except OpenSSL.SSL.SysCallError as e:
    258             if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):

/Users/benjamin.chodroff/anaconda3/anaconda/lib/python3.6/site-packages/OpenSSL/SSL.py in recv_into(self, buffer, nbytes, flags)
   1332             result = _lib.SSL_peek(self._ssl, buf, nbytes)
   1333         else:
-> 1334             result = _lib.SSL_read(self._ssl, buf, nbytes)
   1335         self._raise_ssl_error(self._ssl, result)
   1336 

KeyboardInterrupt: 

In [ ]: