ENH: Filter out empty positions from portfolio container.

To help prevent algorithms from operating on positions that are
not in the existing universe of stocks.

Formerly, iterating over positions would return positions for stocks
which had zero shares held. (Where an explicit check in algorithm
code for `pos.amount != 0` could prevent from using a non-existent
position.)
This commit is contained in:
Eddie Hebert
2014-01-10 14:30:29 -05:00
parent 15dd1f9c0e
commit 51e8b3244e
3 changed files with 81 additions and 2 deletions
+42 -1
View File
@@ -27,7 +27,8 @@ from zipline.test_algorithms import (TestRegisterTransformAlgorithm,
TestTargetAlgorithm,
TestOrderPercentAlgorithm,
TestTargetPercentAlgorithm,
TestTargetValueAlgorithm)
TestTargetValueAlgorithm,
EmptyPositionsAlgorithm)
from zipline.sources import (SpecificEquityTrades,
DataFrameSource,
@@ -194,3 +195,43 @@ class TestTransformAlgorithm(TestCase):
instant_fill=True)
algo.run(self.df)
class TestPositions(TestCase):
def setUp(self):
setup_logger(self)
self.sim_params = factory.create_simulation_parameters(num_days=4)
setup_logger(self)
trade_history = factory.create_trade_history(
1,
[10.0, 10.0, 11.0, 11.0],
[100, 100, 100, 300],
timedelta(days=1),
self.sim_params
)
self.source = SpecificEquityTrades(event_list=trade_history)
self.df_source, self.df = \
factory.create_test_df_source(self.sim_params)
self.panel_source, self.panel = \
factory.create_test_panel_source(self.sim_params)
def test_empty_portfolio(self):
algo = EmptyPositionsAlgorithm(sim_params=self.sim_params,
data_frequency='daily')
daily_stats = algo.run(self.df)
expected_position_count = [
0, # Before entering the first position
1, # After entering, exiting on this date
0, # After exiting
0,
]
for i, expected in enumerate(expected_position_count):
self.assertEqual(daily_stats.ix[i]['num_positions'],
expected)
+8 -1
View File
@@ -355,13 +355,20 @@ class PerformancePeriod(object):
positions = self._positions_store
for sid, pos in iteritems(self.positions):
if sid not in positions:
if pos.amount != 0 and sid not in positions:
positions[sid] = zp.Position(sid)
position = positions[sid]
position.amount = pos.amount
position.cost_basis = pos.cost_basis
position.last_sale_price = pos.last_sale_price
# Remove positions with no amounts from portfolio container
# that is passed to the algorithm.
# These positions are still stored internally for use with
# dividends etc.
if pos.amount == 0 and sid in positions:
del positions[sid]
return positions
def get_positions_list(self):
+31
View File
@@ -633,3 +633,34 @@ class TALIBAlgorithm(TradingAlgorithm):
else:
result = (np.nan,) * len(t.talib_fn.output_names)
self.talib_results[t].append(result)
class EmptyPositionsAlgorithm(TradingAlgorithm):
"""
An algorithm that ensures that 'phantom' positions do not appear
portfolio.positions in the case that a position has been entered
and fully exited.
"""
def initialize(self, *args, **kwargs):
self.ordered = False
self.exited = False
def handle_data(self, data):
if not self.ordered:
for s in data:
self.order(s, 100)
self.ordered = True
if not self.exited:
amounts = [pos.amount for pos
in self.portfolio.positions.itervalues()]
if (
all([(amount == 100) for amount in amounts]) and
(len(amounts) == len(data.keys()))
):
for stock in self.portfolio.positions:
self.order(stock, -100)
self.exited = True
# Should be 0 when all positions are exited.
self.record(num_positions=len(self.portfolio.positions))