Skip to content

Commit

Permalink
Merge branch 'feature/pull_83_set_shares' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
gbeced committed Oct 23, 2017
2 parents b2667ac + c4bf2cb commit d1d28ec
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 14 deletions.
46 changes: 37 additions & 9 deletions pyalgotrade/broker/backtesting.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ def __init__(self, cash, barFeed, commission=None):
else:
self.__commission = commission
self.__shares = {}
self.__instrumentPrice = {} # Used by setShares
self.__activeOrders = {}
self.__useAdjustedValues = False
self.__fillStrategy = fillstrategy.DefaultStrategy()
Expand All @@ -200,6 +201,7 @@ def __init__(self, cash, barFeed, commission=None):
self.__barFeed = barFeed
self.__allowNegativeCash = False
self.__nextOrderId = 1
self.__started = False

def _getNextOrderId(self):
ret = self.__nextOrderId
Expand Down Expand Up @@ -234,7 +236,7 @@ def getCash(self, includeShort=True):
bars = self.__barFeed.getCurrentBars()
for instrument, shares in self.__shares.iteritems():
if shares < 0:
instrumentPrice = self._getBar(bars, instrument).getClose(self.getUseAdjustedValues())
instrumentPrice = self._getBar(bars, instrument).getPrice()
ret += instrumentPrice * shares
return ret

Expand Down Expand Up @@ -290,23 +292,48 @@ def getInstrumentTraits(self, instrument):
def getShares(self, instrument):
return self.__shares.get(instrument, 0)

def setShares(self, instrument, quantity, price):
"""
Set existing shares before the strategy starts executing.
:param instrument: Instrument identifier.
:param quantity: The number of shares for the given instrument.
:param price: The price for each share.
"""

assert not self.__started, "Can't setShares once the strategy started executing"
self.__shares[instrument] = quantity
self.__instrumentPrice[instrument] = price

def getPositions(self):
return self.__shares

def getActiveInstruments(self):
return [instrument for instrument, shares in self.__shares.iteritems() if shares != 0]

def __getEquityWithBars(self, bars):
ret = self.getCash()
if bars is not None:
for instrument, shares in self.__shares.iteritems():
instrumentPrice = self._getBar(bars, instrument).getClose(self.getUseAdjustedValues())
ret += instrumentPrice * shares
def _getPriceForInstrument(self, instrument):
ret = None

# Try gettting the price from the last bar first.
lastBar = self.__barFeed.getLastBar(instrument)
if lastBar is not None:
ret = lastBar.getPrice()
else:
# Try using the instrument price set by setShares if its available.
ret = self.__instrumentPrice.get(instrument)

return ret

def getEquity(self):
"""Returns the portfolio value (cash + shares)."""
return self.__getEquityWithBars(self.__barFeed.getCurrentBars())
"""Returns the portfolio value (cash + shares * price)."""

ret = self.getCash()
for instrument, shares in self.__shares.iteritems():
instrumentPrice = self._getPriceForInstrument(instrument)
assert instrumentPrice is not None, "Price for %s is missing" % instrument
ret += instrumentPrice * shares
return ret


# Tries to commit an order execution.
def commitOrderExecution(self, order, dateTime, fillInfo):
Expand Down Expand Up @@ -453,6 +480,7 @@ def onBars(self, dateTime, bars):

def start(self):
super(Broker, self).start()
self.__started = True

def stop(self):
pass
Expand Down
2 changes: 1 addition & 1 deletion testcases/bitstamp_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def testNonceGenerator(self):
prevNonce = nonce


class TestStrategy(test_strategy.BaseTestStrategy):
class TestStrategy(test_strategy.BaseStrategy):
def __init__(self, feed, brk):
super(TestStrategy, self).__init__(feed, brk)
self.bid = None
Expand Down
7 changes: 7 additions & 0 deletions testcases/broker_backtesting_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,13 @@ def testGetActiveOrders(self):
self.assertEqual(len(brk.getActiveOrders("ins2")), 1)
self.assertEqual(len(brk.getActiveOrders("ins3")), 0)

def testSetShares(self):
barFeed = self.buildBarFeed(BaseTestCase.TestInstrument, bar.Frequency.MINUTE)
brk = self.buildBroker(1000, barFeed)
brk.setShares("btc", 100, 50)
self.assertEqual(brk.getShares("btc"), 100)
self.assertEqual(brk.getEquity(), 1000 + 100*50)


class MarketOrderTestCase(BaseTestCase):
def testGetPositions(self):
Expand Down
109 changes: 109 additions & 0 deletions testcases/returns_analyzer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import common
import strategy_test
import position_test
from testcases import test_strategy

from pyalgotrade.barfeed import yahoofeed
from pyalgotrade.barfeed import csvfeed
Expand Down Expand Up @@ -454,3 +455,111 @@ def testFirstBar(self):
for i in [0, -1]:
self.assertEqual(stratAnalyzer.getReturns().getDateTimes()[i], datetimes[i])
self.assertEqual(stratAnalyzer.getCumulativeReturns().getDateTimes()[i], datetimes[i])

def testSetSharesForUnusedInstrument(self):
barFeed = yahoofeed.Feed()
barFeed.addBarsFromCSV(AnalyzerTestCase.TestInstrument, common.get_data_file_path("orcl-2001-yahoofinance.csv"))

initialCash = 1000
strat = test_strategy.BacktestingStrategy(barFeed, initialCash)

unkSharesSet = 10
unkSharePrice = 10.4
strat.getBroker().setShares("UNK", unkSharesSet, unkSharePrice)

# 33.06
strat.scheduleCall(
datetime.datetime(2001, 1, 12),
lambda: strat.marketOrder(AnalyzerTestCase.TestInstrument, 1)
)
# 14.32
strat.scheduleCall(
datetime.datetime(2001, 11, 27),
lambda: strat.marketOrder(AnalyzerTestCase.TestInstrument, -1)
)

stratAnalyzer = returns.Returns()
strat.attachAnalyzer(stratAnalyzer)
initialEquity = strat.getBroker().getEquity()
strat.run()
finalEquity = strat.getBroker().getEquity()

# The unknown instrument should have no impact cash or returns.
finalCash = strat.getBroker().getCash()
self.assertEqual(
round(finalCash, 2),
round(initialCash + (14.32 - 33.06), 2)
)
self.assertEqual(
round(stratAnalyzer.getCumulativeReturns()[-1], 4),
round((finalEquity - initialEquity) / initialEquity, 4)
)
self.assertEqual(stratAnalyzer.getReturns()[-1], 0)
self.assertEqual(strat.getBroker().getShares(AnalyzerTestCase.TestInstrument), 0)
self.assertEqual(strat.getBroker().getShares("UNK"), unkSharesSet)
self.assertEqual(strat.getBroker().getEquity(), finalCash + unkSharesSet * unkSharePrice)

def testSetSharesWithNoStartingCash(self):
barFeed = yahoofeed.Feed()
barFeed.addBarsFromCSV(AnalyzerTestCase.TestInstrument, common.get_data_file_path("orcl-2001-yahoofinance.csv"))

strat = test_strategy.BacktestingStrategy(barFeed, 0)

initialShares = 2
initialPrice = 16.25
strat.getBroker().setShares(AnalyzerTestCase.TestInstrument, initialShares, initialPrice)

# Close initial position
closingPrice = 32.50
strat.scheduleCall(
datetime.datetime(2001, 1, 4),
lambda: strat.marketOrder(AnalyzerTestCase.TestInstrument, -initialShares)
)

stratAnalyzer = returns.Returns()
strat.attachAnalyzer(stratAnalyzer)
strat.run()

self.assertEqual(strat.getBroker().getShares(AnalyzerTestCase.TestInstrument), 0)
self.assertEqual(strat.getBroker().getCash(), closingPrice * initialShares)

# Check period returns.
expectedPeriodReturns = [
(26.37 - initialPrice) / initialPrice,
(32.00 - 26.37) / 26.37,
(32.56 - 32.00) / 32.00,
(32.50 - 32.56) / 32.56,
0,
# Nothing should change moving forward
0,
0,
]
for i, expectedReturn in enumerate(expectedPeriodReturns):
self.assertEqual(
stratAnalyzer.getReturns()[i],
expectedReturn
)
self.assertEqual(
stratAnalyzer.getReturns()[-1],
expectedPeriodReturns[-1]
)

# Check cumulative returns.
expectedCumulativeReturns = [
(26.37 - initialPrice) / initialPrice,
(32.00 - initialPrice) / initialPrice,
(32.56 - initialPrice) / initialPrice,
(32.50 - initialPrice) / initialPrice,
# Nothing should change moving forward
(32.50 - initialPrice) / initialPrice,
(32.50 - initialPrice) / initialPrice,
]
for i, expectedReturn in enumerate(expectedCumulativeReturns):
self.assertEqual(
round(stratAnalyzer.getCumulativeReturns()[i], 4),
round(expectedReturn, 4)
)
self.assertEqual(
round(stratAnalyzer.getCumulativeReturns()[-1], 4),
round(expectedCumulativeReturns[-1], 4)
)
30 changes: 26 additions & 4 deletions testcases/test_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,22 @@
from pyalgotrade import strategy


class BaseTestStrategy(strategy.BaseStrategy):
def __init__(self, barFeed, broker, maxMinutes=5):
super(BaseTestStrategy, self).__init__(barFeed, broker)
class TestStrategyMixin(object):
def __init__(self, *args, **kwargs):
super(TestStrategyMixin, self).__init__(*args, **kwargs)
self.posExecutionInfo = []
self.ordersUpdated = []
self.orderExecutionInfo = []
self.begin = datetime.datetime.now()
self.deadline = self.begin + datetime.timedelta(minutes=maxMinutes)
self.deadline = None
self._scheduled = {}
self.setStopAfter(5)

def setStopAfter(self, minutes):
self.deadline = datetime.datetime.now() + datetime.timedelta(minutes=minutes)

def scheduleCall(self, dateTime, callable):
self._scheduled.setdefault(dateTime, []).append(callable)

def onOrderUpdated(self, order):
self.ordersUpdated.append(order)
Expand All @@ -51,3 +59,17 @@ def onExitCanceled(self, position):
def onIdle(self):
if datetime.datetime.now() >= self.deadline:
self.stop()

def onBars(self, bars):
for callable in self._scheduled.get(bars.getDateTime(), []):
callable()


class BaseStrategy(TestStrategyMixin, strategy.BaseStrategy):
def __init__(self, barFeed, broker):
super(BaseStrategy, self).__init__(barFeed, broker)


class BacktestingStrategy(TestStrategyMixin, strategy.BacktestingStrategy):
def __init__(self, barFeed, cash_or_brk=1000000):
super(BacktestingStrategy, self).__init__(barFeed, cash_or_brk)

0 comments on commit d1d28ec

Please sign in to comment.