Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support multiple frequency bars in barfeed #4

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ doc/_build
/notebooks

/testcases/credentials.py
*.log
*.sqlite
.DS_Store
.vscode/settings.json
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: python
python:
- "2.7"
- "3.6.8"

env:
global:
Expand All @@ -18,7 +18,7 @@ services:
- docker

before_install:
- docker pull gbecedillas/pyalgotrade:0.20-py27
- docker pull gbecedillas/pyalgotrade:0.20-py37
- cp travis/Dockerfile .
- docker build -t pyalgotrade_testcases .
- sudo pip install coveralls
Expand Down
26 changes: 19 additions & 7 deletions pyalgotrade/bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,28 @@ class Frequency(object):

"""Enum like class for bar frequencies. Valid values are:

* **Frequency.UNKNOWN**: The bar represents an unknown frequency trade.
* **Frequency.TRADE**: The bar represents a single trade.
* **Frequency.SECOND**: The bar summarizes the trading activity during 1 second.
* **Frequency.MINUTE**: The bar summarizes the trading activity during 1 minute.
* **Frequency.HOUR**: The bar summarizes the trading activity during 1 hour.
* **Frequency.HOUR_4**: The bar summarizes the trading activity during 4 hour.
* **Frequency.DAY**: The bar summarizes the trading activity during 1 day.
* **Frequency.WEEK**: The bar summarizes the trading activity during 1 week.
* **Frequency.MONTH**: The bar summarizes the trading activity during 1 month.
"""

# It is important for frequency values to get bigger for bigger windows.
TRADE = -1
SECOND = 1
MINUTE = 60
HOUR = 60*60
DAY = 24*60*60
WEEK = 24*60*60*7
MONTH = 24*60*60*31
UNKNOWN = -2
TRADE = -1
REALTIME = 0
SECOND = 1
MINUTE = 60
HOUR = 60*60
HOUR_4 = 60*60*4
DAY = 24*60*60
WEEK = 24*60*60*7
MONTH = 24*60*60*31


@six.add_metaclass(abc.ABCMeta)
Expand Down Expand Up @@ -260,20 +265,24 @@ def __init__(self, barDict):
# Check that bar datetimes are in sync
firstDateTime = None
firstInstrument = None
firstFreq = None
for instrument, currentBar in six.iteritems(barDict):
if firstDateTime is None:
firstDateTime = currentBar.getDateTime()
firstInstrument = instrument
firstFreq = currentBar.getFrequency()
elif currentBar.getDateTime() != firstDateTime:
raise Exception("Bar data times are not in sync. %s %s != %s %s" % (
instrument,
currentBar.getDateTime(),
firstInstrument,
firstDateTime
))
assert firstFreq == currentBar.getFrequency()

self.__barDict = barDict
self.__dateTime = firstDateTime
self.__frequency = firstFreq

def __getitem__(self, instrument):
"""Returns the :class:`pyalgotrade.bar.Bar` for the given instrument.
Expand Down Expand Up @@ -301,3 +310,6 @@ def getDateTime(self):
def getBar(self, instrument):
"""Returns the :class:`pyalgotrade.bar.Bar` for the given instrument or None if the instrument is not found."""
return self.__barDict.get(instrument, None)

def getBarsFrequency(self):
return self.__frequency
46 changes: 29 additions & 17 deletions pyalgotrade/barfeed/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,20 @@ class BaseBarFeed(feed.BaseFeed):
This is a base class and should not be used directly.
"""

def __init__(self, frequency, maxLen=None):
def __init__(self, frequencies, maxLen=None):
super(BaseBarFeed, self).__init__(maxLen)
self.__frequency = frequency
if not isinstance(frequencies, list):
raise Exception('only frequencies list is accepted')
self.__frequencies = frequencies
self.__useAdjustedValues = False
self.__defaultInstrument = None
self.__currentBars = None
self.__currentRealtimeBars = None
self.__lastBars = {}

def reset(self):
self.__currentBars = None
self.__currentRealtimeBars = None
self.__lastBars = {}
super(BaseBarFeed, self).reset()

Expand Down Expand Up @@ -92,30 +96,38 @@ def createDataSeries(self, key, maxLen):

def getNextValues(self):
dateTime = None
freq = None
bars = self.getNextBars()
if bars is not None:
freq = bars.getBarsFrequency()
dateTime = bars.getDateTime()

# Check that current bar datetimes are greater than the previous one.
if self.__currentBars is not None and self.__currentBars.getDateTime() >= dateTime:
raise Exception(
"Bar date times are not in order. Previous datetime was %s and current datetime is %s" % (
self.__currentBars.getDateTime(),
dateTime
if self.__currentBars is not None and self.__currentBars.getDateTime() > dateTime:
if freq == self.__currentBars.getBarsFrequency():
raise Exception(
"Bar date times are not in order. Previous datetime was %s and current datetime is %s" % (
self.__currentBars.getDateTime(),
dateTime
)
)
)

# Update self.__currentBars and self.__lastBars
self.__currentBars = bars
for instrument in bars.getInstruments():
self.__lastBars[instrument] = bars[instrument]
return (dateTime, bars)
return (dateTime, bars, freq)

def getFrequency(self):
return self.__frequency
def getAllFrequencies(self):
return self.__frequencies

def isIntraday(self):
return self.__frequency < bar.Frequency.DAY
for i in self.__frequencies:
if i < bar.Frequency.DAY:
return True

def getCurrentRealtimeBars(self):
return self.__currentRealtimeBars

def getCurrentBars(self):
"""Returns the current :class:`pyalgotrade.bar.Bars`."""
Expand All @@ -133,11 +145,11 @@ def getRegisteredInstruments(self):
"""Returns a list of registered intstrument names."""
return self.getKeys()

def registerInstrument(self, instrument):
def registerInstrument(self, instrument, freq):
self.__defaultInstrument = instrument
self.registerDataSeries(instrument)
self.registerDataSeries(instrument, freq)

def getDataSeries(self, instrument=None):
def getDataSeries(self, instrument=None, freq=None):
"""Returns the :class:`pyalgotrade.dataseries.bards.BarDataSeries` for a given instrument.

:param instrument: Instrument identifier. If None, the default instrument is returned.
Expand All @@ -146,7 +158,7 @@ def getDataSeries(self, instrument=None):
"""
if instrument is None:
instrument = self.__defaultInstrument
return self[instrument]
return self[instrument, freq] if freq is not None else self[instrument]

def getDispatchPriority(self):
return dispatchprio.BAR_FEED
Expand All @@ -158,7 +170,7 @@ class OptimizerBarFeed(BaseBarFeed):
def __init__(self, frequency, instruments, bars, maxLen=None):
super(OptimizerBarFeed, self).__init__(frequency, maxLen)
for instrument in instruments:
self.registerInstrument(instrument)
self.registerInstrument(instrument, frequency)
self.__bars = bars
self.__nextPos = 0
self.__currDateTime = None
Expand Down
3 changes: 2 additions & 1 deletion pyalgotrade/barfeed/csvfeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,9 @@ def addBarsFromCSV(self, instrument, path, timezone=None, skipMalformedBars=Fals
if timezone is None:
timezone = self.__timezone

assert len(self.getAllFrequencies()) == 1
rowParser = GenericRowParser(
self.__columnNames, self.__dateTimeFormat, self.getDailyBarTime(), self.getFrequency(),
self.__columnNames, self.__dateTimeFormat, self.getDailyBarTime(), self.getAllFrequencies()[0],
timezone, self.__barClass
)

Expand Down
5 changes: 3 additions & 2 deletions pyalgotrade/barfeed/dbfeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ def addBars(self, bars, frequency):
self.addBar(instrument, bar, frequency)

def addBarsFromFeed(self, feed):
for dateTime, bars in feed:
assert len(feed.getAllFrequencies()) == 1
for dateTime, bars, _ in feed:
if bars:
self.addBars(bars, feed.getFrequency())
self.addBars(bars, feed.getAllFrequencies()[0])

def addBar(self, instrument, bar, frequency):
raise NotImplementedError()
Expand Down
4 changes: 3 additions & 1 deletion pyalgotrade/barfeed/googlefeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ def addBarsFromCSV(self, instrument, path, timezone=None, skipMalformedBars=Fals

if timezone is None:
timezone = self.__timezone

assert len(self.getAllFrequencies()) == 1

rowParser = RowParser(self.getDailyBarTime(), self.getFrequency(), timezone, self.__sanitizeBars)
rowParser = RowParser(self.getDailyBarTime(), self.getAllFrequencies()[0], timezone, self.__sanitizeBars)
super(Feed, self).addBarsFromCSV(instrument, path, rowParser, skipMalformedBars=skipMalformedBars)
7 changes: 5 additions & 2 deletions pyalgotrade/barfeed/membf.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ def addBarsFromSequence(self, instrument, bars):
self.__bars[instrument].extend(bars)
self.__bars[instrument].sort(key=lambda b: b.getDateTime())

self.registerInstrument(instrument)
assert len(self.getAllFrequencies()) == 1
for i in bars:
assert i.getFrequency() == self.getAllFrequencies()[0]
self.registerInstrument(instrument, self.getAllFrequencies()[0])

def eof(self):
ret = True
Expand Down Expand Up @@ -115,5 +118,5 @@ def getNextBars(self):
return bar.Bars(ret)

def loadAll(self):
for dateTime, bars in self:
for dateTime, bars, freq in self:
pass
3 changes: 2 additions & 1 deletion pyalgotrade/barfeed/ninjatraderfeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,6 @@ def addBarsFromCSV(self, instrument, path, timezone=None):
if timezone is None:
timezone = self.__timezone

rowParser = RowParser(self.getFrequency(), self.getDailyBarTime(), timezone)
assert len(self.getAllFrequencies()) == 1
rowParser = RowParser(self.getAllFrequencies()[0], self.getDailyBarTime(), timezone)
super(Feed, self).addBarsFromCSV(instrument, path, rowParser)
11 changes: 6 additions & 5 deletions pyalgotrade/barfeed/resampled.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def __init__(self, barFeed, frequency, maxLen=None):

# Register the same instruments as in the underlying barfeed.
for instrument in barFeed.getRegisteredInstruments():
self.registerInstrument(instrument)
self.registerInstrument(instrument, frequency)

self.__values = []
self.__barFeed = barFeed
Expand All @@ -72,15 +72,16 @@ def __init__(self, barFeed, frequency, maxLen=None):
barFeed.getNewValuesEvent().subscribe(self.__onNewValues)

def __onNewValues(self, dateTime, value):
assert len(self.getAllFrequencies()) == 1
if self.__range is None:
self.__range = resamplebase.build_range(dateTime, self.getFrequency())
self.__grouper = BarsGrouper(self.__range.getBeginning(), value, self.getFrequency())
self.__range = resamplebase.build_range(dateTime, self.getAllFrequencies()[0])
self.__grouper = BarsGrouper(self.__range.getBeginning(), value, self.getAllFrequencies()[0])
elif self.__range.belongs(dateTime):
self.__grouper.addValue(value)
else:
self.__values.append(self.__grouper.getGrouped())
self.__range = resamplebase.build_range(dateTime, self.getFrequency())
self.__grouper = BarsGrouper(self.__range.getBeginning(), value, self.getFrequency())
self.__range = resamplebase.build_range(dateTime, self.getAllFrequencies()[0])
self.__grouper = BarsGrouper(self.__range.getBeginning(), value, self.getAllFrequencies()[0])

def getCurrentDateTime(self):
return self.__barFeed.getCurrentDateTime()
Expand Down
3 changes: 2 additions & 1 deletion pyalgotrade/barfeed/sqlitefeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,6 @@ def getDatabase(self):
return self.__db

def loadBars(self, instrument, timezone=None, fromDateTime=None, toDateTime=None):
bars = self.__db.getBars(instrument, self.getFrequency(), timezone, fromDateTime, toDateTime)
assert len(self.getAllFrequencies()) == 1
bars = self.__db.getBars(instrument, self.getAllFrequencies()[0], timezone, fromDateTime, toDateTime)
self.addBarsFromSequence(instrument, bars)
3 changes: 2 additions & 1 deletion pyalgotrade/barfeed/yahoofeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ def addBarsFromCSV(self, instrument, path, timezone=None):
if timezone is None:
timezone = self.__timezone

assert len(self.getAllFrequencies()) == 1
rowParser = RowParser(
self.getDailyBarTime(), self.getFrequency(), timezone, self.__sanitizeBars, self.__barClass
self.getDailyBarTime(), self.getAllFrequencies()[0], timezone, self.__sanitizeBars, self.__barClass
)
super(Feed, self).addBarsFromCSV(instrument, path, rowParser)
2 changes: 1 addition & 1 deletion pyalgotrade/bitstamp/livebroker.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ def stop(self):
self.__tradeMonitor.stop()

def join(self):
if self.__tradeMonitor.isAlive():
if self.__tradeMonitor.is_alive():
self.__tradeMonitor.join()

def eof(self):
Expand Down
2 changes: 1 addition & 1 deletion pyalgotrade/bitstamp/livefeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class LiveTradeFeed(barfeed.BaseBarFeed):
def __init__(self, maxLen=None):
super(LiveTradeFeed, self).__init__(bar.Frequency.TRADE, maxLen)
self.__barDicts = []
self.registerInstrument(common.btc_symbol)
self.registerInstrument(common.btc_symbol, bar.Frequency.TRADE)
self.__prevTradeDateTime = None
self.__thread = None
self.__wsClientConnected = False
Expand Down
3 changes: 2 additions & 1 deletion pyalgotrade/broker/backtesting.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,9 +423,10 @@ def __preProcessOrder(self, order, bar_):
def __postProcessOrder(self, order, bar_):
# For non-GTC orders and daily (or greater) bars we need to check if orders should expire right now
# before waiting for the next bar.
assert len(self.__barFeed.getAllFrequencies()) == 1
if not order.getGoodTillCanceled():
expired = False
if self.__barFeed.getFrequency() >= pyalgotrade.bar.Frequency.DAY:
if self.__barFeed.getAllFrequencies()[0] >= pyalgotrade.bar.Frequency.DAY:
expired = bar_.getDateTime().date() >= order.getAcceptedDateTime().date()

# Cancel the order if it will expire in the next bar.
Expand Down
3 changes: 2 additions & 1 deletion pyalgotrade/dataseries/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ def appendWithDateTime(self, dateTime, value):
"""

if dateTime is not None and len(self.__dateTimes) != 0 and self.__dateTimes[-1] >= dateTime:
raise Exception("Invalid datetime. It must be bigger than that last one")
raise Exception("Invalid datetime. "
"It must be bigger than that last one {0} {1}".format(self.__dateTimes[-1], dateTime))

assert(len(self.__values) == len(self.__dateTimes))
self.__dateTimes.append(dateTime)
Expand Down
7 changes: 4 additions & 3 deletions pyalgotrade/eventprofiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ def run(self, feed, useAdjustedCloseForReturns=True):
feed.getNewValuesEvent().unsubscribe(self.__onBars)


def build_plot(profilerResults):
def build_plot(profilerResults, alpha):
# Calculate each value.
x = []
mean = []
Expand All @@ -231,6 +231,7 @@ def build_plot(profilerResults):

# Cleanup
plt.clf()

# Plot a line with the mean cumulative returns.
plt.plot(x, mean, color='#0000FF')

Expand All @@ -255,12 +256,12 @@ def build_plot(profilerResults):
plt.ylabel('Cumulative returns')


def plot(profilerResults):
def plot(profilerResults, alpha=0):
"""Plots the result of the analysis.

:param profilerResults: The result of the analysis
:type profilerResults: :class:`Results`.
"""

build_plot(profilerResults)
build_plot(profilerResults, alpha)
plt.show()
Loading