hootnot / oandapyV20-examples

Examples demonstrating the use of oandapyV20 (oanda-api-v20)
MIT License
147 stars 65 forks source link

Question: PriceTable (simplebot) #15

Closed Pearce94 closed 6 years ago

Pearce94 commented 6 years ago

Is there a way to pull all candle data (Time, Open, High, Low , Close, Volume) using PriceTable? Or is the information limited to requesting only close and volume?

hootnot commented 6 years ago

As it is in the example, PriceTable only handles datetime and close . It is easy to extend the class with open/high/low/volume.

You say: "is there a way to pull" , you mean put I guess ? If you extend the pricetable you need to feed the addItem method also a full candle: datetime/O/H/L/C/volume

Does this help (I copied it from my code, and stripped a small piece, guess it should do the job)

class PriceTable(object):

    def __init__(self, instrument, reserve=10000):
        self.instrument = instrument
        self._dt = [None] * reserve  # allocate space for datetime
        self._o = [None] * reserve   # allocate space for open values
        self._h = [None] * reserve   # allocate space for high values
        self._l = [None] * reserve   # allocate space for low values
        self._c = [None] * reserve   # allocate space for close/last values
        self._v = [None] * reserve   # allocate space for volume values
        self._events = {}            # registered events
        self.idx = 0

    def fireEvent(self, name, *args, **kwargs):
        if name in self._events:
            f = self._events[name]
            f(*args, **kwargs)

    def setHandler(self, name, f):
        if name not in self._events:
            self._events[name] = Event()
        self._events[name] += f

    def addItem(self, dt, o, h, l, c, v):
        self._dt[self.idx] = dt
        self._o[self.idx] = o
        self._h[self.idx] = h
        self._l[self.idx] = l
        self._c[self.idx] = c
        self._v[self.idx] = v
        self.idx += 1
        self.fireEvent('onAddItem', self.idx)

    def __len__(self):
        return self.idx

    def __getitem__(self, i):
        def rr(_i):
            if _i >= self.idx:  # do not go beyond idx in the reserved items
                raise IndexError("list assignment index out of range")
            if _i < 0:
                _i = self.idx + _i   # the actual end of the array
            return (self._dt[_i], self._o[_i],
                                  self._h[_i],
                                  self._l[_i],
                                  self._c[_i],
                                  self._v[_i])

        if isinstance(i, int):
            return rr(i)
        elif isinstance(i, slice):
            return [rr(j) for j in xrange(*i.indices(len(self)))]
        else:
            raise TypeError("Invalid argument")
Pearce94 commented 6 years ago

That works well thanks, and then is it necessary to add the same requests to PRecordFactory? Or is it better to just call PriceTable at n intervals?

Pearce94 commented 6 years ago

I had to add granularity to PriceTable otherwise it gave an error.

class PriceTable(object):

    def __init__(self, instrument, granularity, reserve=10000):
        self.instrument = instrument
        self.granularity = granularity
        self._dt = [None] * reserve  # allocate space for datetime
        self._o = [None] * reserve   # allocate space for open values
        self._h = [None] * reserve   # allocate space for high values
        self._l = [None] * reserve   # allocate space for low values
        self._c = [None] * reserve   # allocate space for close/last values
        self._v = [None] * reserve   # allocate space for volume values
        self._events = {}            # registered events
        self.idx = 0

Now it gives this error:

Traceback (most recent call last): File "C:\User\Documents\Trading\OandaTrading\OandaV20\Testbot.py", line 648, in units=clargs.units, clargs=clargs) File"C:\User\Documents\Trading\OandaTrading\OandaV20\Testbot.py",, line 520, in init int(crecord['volume'])) TypeError: addItem() takes exactly 7 arguments (4 given)

hootnot commented 6 years ago

you need to feed addItem also the other values that were added to the table, so: DATETIME/OPEN/HIGH/LOW/CLSE/VOLUME

Regarding the initialization part in the simplebot.py:

        for crecord in rv['candles']:
            if crecord['complete'] is True:
                self.pt.addItem(crecord['time'],
# add the others here
                                float(crecord['mid']['c']),
                                int(crecord['volume']))

(or a quick test you could supply the close value for all the other values . Then you know how your code responds)

The PRecordFactory in the example does not provide these values. So you need to change this too. Options:

Pearce94 commented 6 years ago

I've attempted to adapt the example as suggested, I'm able to request the data without error- but it gets returned as "None" for Open, High and Low. I'm assuming we're missing this input under def ParseTick(self, t):

    if t["type"] == "PRICE":
        #Need to add something here
        self.data["c"] = (float(t['closeoutBid']) +
                          float(t['closeoutAsk'])) / 2.0
        self.data["v"] += 1
    return rec
hootnot commented 6 years ago

The example has two parts: initialization and after that adding prices from the stream. The init thing is easy to adapt: just add the missing fields as I've pointed.

The prices from the stream is something else. A quick hack to get it work with OPEN/HIGH/LOW added just being a copy of the last:


        if t["type"] == "PRICE":
            self.data["c"] = (float(t['closeoutBid']) +
                              float(t['closeoutAsk'])) / 2.0
            self.data["o"] = self.data["c"]
            self.data["h"] = self.data["c"]
            self.data["l"] = self.data["c"]
            self.data["v"] += 1

But when you want the actual high and low for the timeframe you need to add additional logic to determine the values.

The simplebot .py was ment as an example. You can extend the example to your needs. If you need the OHLC records, please choose one of the options I gave you.

Further assistance to work to a specific solution is beyond the scope of the example, therefore I will close this issue now.

Pearce94 commented 6 years ago

I have found another way to combat this, by bypassing PRecordFactory and calling PriceTable every minute (for M1 data):

    starttime=time.time()
    while True:
        params = {"granularity": granularity,
              "count": self.clargs.longMA}
        r = instruments.InstrumentsCandles(instrument=instrument,
                                           params=params)
        rv = self.client.request(r)
        for crecord in rv['candles']:
                self.pt.addItem(crecord['time'],
                                float(crecord['mid']['o']),
                                float(crecord['mid']['h']),
                                float(crecord['mid']['l']),
                                float(crecord['mid']['c']),
                                int(crecord['volume']))
        time.sleep(60.0 - ((time.time() - starttime) % 60.0))
hootnot commented 6 years ago

you choose the 'retrieve candles from OANDA' option, which is fine. You probably get time drifting. Take a look at https://github.com/agronholm/apscheduler to tackle that if you need.