diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py index 22fc4c50..ab275647 100644 --- a/tests/test_algorithm.py +++ b/tests/test_algorithm.py @@ -45,14 +45,11 @@ from zipline.test_algorithms import ( TestOrderAlgorithm, TestOrderInstantAlgorithm, TestOrderPercentAlgorithm, - TestOrderPercentAlgorithmPercentOf, TestOrderStyleForwardingAlgorithm, TestOrderValueAlgorithm, TestRegisterTransformAlgorithm, TestTargetAlgorithm, - TestTargetAlgorithm_NonInt, TestTargetPercentAlgorithm, - TestTargetPercentAlgorithmPercentOf, TestTargetValueAlgorithm, SetLongOnlyAlgorithm, SetMaxPositionSizeAlgorithm, @@ -306,9 +303,6 @@ class TestTransformAlgorithm(TestCase): self.panel_source, self.panel = \ factory.create_test_panel_source(self.sim_params) - self.df_large = pd.concat([self.df] * 10, 1) - self.df_large.columns = range(10) - def test_source_as_input(self): algo = TestRegisterTransformAlgorithm( sim_params=self.sim_params, @@ -380,7 +374,6 @@ class TestTransformAlgorithm(TestCase): AlgoClasses = [TestOrderAlgorithm, TestOrderValueAlgorithm, TestTargetAlgorithm, - TestTargetAlgorithm_NonInt, TestOrderPercentAlgorithm, TestTargetPercentAlgorithm, TestTargetValueAlgorithm] @@ -391,16 +384,6 @@ class TestTransformAlgorithm(TestCase): ) algo.run(self.df) - AlgoClasses2 = [ - TestOrderPercentAlgorithmPercentOf, - TestTargetPercentAlgorithmPercentOf] - - for AlgoClass in AlgoClasses2: - algo = AlgoClass( - sim_params=self.sim_params, - ) - algo.run(self.df_large) - def test_order_method_style_forwarding(self): method_names_to_test = ['order', diff --git a/zipline/algorithm.py b/zipline/algorithm.py index 2e75dcee..8a1435d7 100644 --- a/zipline/algorithm.py +++ b/zipline/algorithm.py @@ -88,21 +88,6 @@ from zipline.history.history_container import HistoryContainer DEFAULT_CAPITAL_BASE = float("1.0e5") -def round_if_near_integer(a, epsilon=1e-4): - """ - Round a to the nearest integer if that integer is within an epsilon - of a. - """ - if abs(a - round(a)) <= epsilon: - return round(a) - else: - return a - - -def round_shares(shares): - return int(round_if_near_integer(shares)) - - class TradingAlgorithm(object): """ Base class for trading algorithms. Inherit and overload @@ -651,10 +636,20 @@ class TradingAlgorithm(object): Place an order using the specified parameters. """ + def round_if_near_integer(a, epsilon=1e-4): + """ + Round a to the nearest integer if that integer is within an epsilon + of a. + """ + if abs(a - round(a)) <= epsilon: + return round(a) + else: + return a + # Truncate to the integer share count that's either within .0001 of # amount or closer to zero. # E.g. 3.9999 -> 4.0; 5.5 -> 5.0; -5.5 -> -5.0 - amount = round_shares(amount) + amount = int(round_if_near_integer(amount)) # Raises a ZiplineError if invalid parameters are detected. self.validate_order_params(sid, @@ -871,98 +866,16 @@ class TradingAlgorithm(object): assert value in ('daily', 'minute') self.sim_params.data_frequency = value - def get_market_value(self, mv_type=None, filter_fn=None): - """ - Returns the requested market value. - - Options for mv_type are: - portfolio [default]: net exposure or portfolio value - net: net exposure or portfolio value - gross: gross exposure - cash: available cash - ex_cash: net invested capital, ex-cash - longs: long invested capital - longs_cash: long invested capital plus cash - shorts: short invested capital - - Alternatively, a filter_fn can be supplied. The filter_fn - should accept a Position and return True if that Position's - market_value should be included in the total and False otherwise. - """ - - period = self.perf_tracker.cumulative_performance - - # first try the filter_fn, if supplied - if filter_fn is not None: - positions = period.get_positions() - mv = sum( - p.amount * p.last_sale_price - for p in positions.values() - if filter_fn(p)) - - # net portfolio value - elif mv_type is None or mv_type == 'portfolio' or mv_type == 'net': - mv = period.ending_value + period.ending_cash - - elif mv_type == 'gross': - mv = period._gross_exposure() - - # portfolio cash - elif mv_type == 'cash': - mv = period.ending_cash - - # net invested capital - elif mv_type == 'ex_cash': - mv = period.ending_value - - # long invested capital - elif mv_type == 'longs': - mv = period._long_exposure() - - # long invested capital, plus cash - elif mv_type == 'longs_cash': - mv = period._long_exposure() + period.ending_cash - - # short invested capital - elif mv_type == 'shorts': - mv = period._short_exposure() - - else: - raise ValueError( - 'Unrecognized value for "mv_type": {}'.format(mv_type)) - - return mv - @api_method def order_percent(self, sid, percent, - limit_price=None, - stop_price=None, - style=None, - percent_of=None, - percent_of_fn=None): + limit_price=None, stop_price=None, style=None): """ Place an order in the specified security corresponding to the given - percent of some market value. If no market value is specified (via - `percent_of`) then the net portfolio value is used. - - Options for percent_of are: - portfolio [default]: net exposure or portfolio value - net: net exposure or portfolio value - gross: gross exposure - cash: available cash - ex_cash: net invested capital, ex-cash - longs: long invested capital - longs_cash: long invested capital plus cash - shorts: short invested capital - - Alternatively, a percent_of_fn can be supplied. The percent_of_fn - should accept a Position and return True if that Position's - market_value should be included in the total and False otherwise. + percent of the current portfolio value. Note that percent must expressed as a decimal (0.50 means 50\%). """ - mv = self.get_market_value(mv_type=percent_of, filter_fn=percent_of_fn) - value = percent * mv + value = self.portfolio.portfolio_value * percent return self.order_value(sid, value, limit_price=limit_price, stop_price=stop_price, @@ -980,7 +893,7 @@ class TradingAlgorithm(object): """ if sid in self.portfolio.positions: current_position = self.portfolio.positions[sid].amount - req_shares = round_shares(target) - current_position + req_shares = target - current_position return self.order(sid, req_shares, limit_price=limit_price, stop_price=stop_price, @@ -1016,37 +929,17 @@ class TradingAlgorithm(object): @api_method def order_target_percent(self, sid, target, - limit_price=None, - stop_price=None, - style=None, - percent_of=None, - percent_of_fn=None): + limit_price=None, stop_price=None, style=None): """ - Place an order to adjust a position to a target percent of some market - value. If no market value is specified (via `percent_of`) then the net - portfolio value is used. If the position doesn't already exist, this is + Place an order to adjust a position to a target percent of the + current portfolio value. If the position doesn't already exist, this is equivalent to placing a new order. If the position does exist, this is equivalent to placing an order for the difference between the target percent and the current percent. - Options for percent_of are: - portfolio [default]: net exposure or portfolio value - net: net exposure or portfolio value - gross: gross exposure - cash: available cash - ex_cash: net invested capital, ex-cash - longs: long invested capital - longs_cash: long invested capital plus cash - shorts: short invested capital - - Alternatively, a percent_of_fn can be supplied. The percent_of_fn - should accept a Position and return True if that Position's - market_value should be included in the total and False otherwise. - Note that target must expressed as a decimal (0.50 means 50\%). """ - mv = self.get_market_value(mv_type=percent_of, filter_fn=percent_of_fn) - target_value = target * mv + target_value = self.portfolio.portfolio_value * target return self.order_target_value(sid, target_value, limit_price=limit_price, stop_price=stop_price, diff --git a/zipline/test_algorithms.py b/zipline/test_algorithms.py index ed77470c..132b6bb9 100644 --- a/zipline/test_algorithms.py +++ b/zipline/test_algorithms.py @@ -79,7 +79,7 @@ from nose.tools import assert_raises from six.moves import range from six import itervalues -from zipline.algorithm import TradingAlgorithm, round_shares +from zipline.algorithm import TradingAlgorithm from zipline.api import FixedSlippage from zipline.errors import UnsupportedOrderParameters from zipline.finance.execution import ( @@ -345,33 +345,6 @@ class TestTargetAlgorithm(TradingAlgorithm): self.order_target(0, self.target_shares) -class TestTargetAlgorithm_NonInt(TradingAlgorithm): - def initialize(self): - self.target_shares = 0 - self.sale_price = None - self.i = 0 - - def handle_data(self, data): - if self.target_shares == 0: - assert 0 not in self.portfolio.positions - else: - assert self.portfolio.positions[0]['amount'] == \ - self.target_shares, "Orders not filled correctly." - assert self.portfolio.positions[0]['last_sale_price'] == \ - data[0].price, "Orders not filled at current price." - - if self.i == 0: - self.target_shares = 5 - self.order_target(0, 5.1) - elif self.i == 1: - self.target_shares = 10 - self.order_target(0, 10.1) - elif self.i == 2: - self.target_shares = 5 - self.order_target(0, 5.1) - self.i += 1 - - class TestOrderPercentAlgorithm(TradingAlgorithm): def initialize(self): self.target_shares = 0 @@ -395,126 +368,23 @@ class TestOrderPercentAlgorithm(TradingAlgorithm): / data[0].price) -class TestOrderPercentAlgorithmPercentOf(TradingAlgorithm): - def initialize(self): - self.target_shares = dict([(i, 0) for i in range(8)]) - - def handle_data(self, data): - for sid, target_shares in self.target_shares.items(): - position = self.portfolio.positions[sid] - assert target_shares == position.amount - - # reduce sizes due to volume limitiations - full = 0.001 - half = 0.0005 - - pos_values = {} - for i in range(len(data.keys())): - pos_values[i] = ( - self.portfolio.positions[i].amount - * self.portfolio.positions[i].last_sale_price) - - port = self.portfolio.portfolio_value - cash = self.portfolio.cash - longs = sum(v for v in pos_values.values() if v > 0) - shorts = sum(v for v in pos_values.values() if v < 0) - gross = sum(abs(v) for v in pos_values.values()) - group = [0, 1, 2] - group_val = sum(v for sid, v in pos_values.items() if sid in group) - - def expected_shares(sid, value): - return round_shares(value / data[sid].price) - - self.order_percent(0, full, percent_of='portfolio') - self.order_percent(1, -half, percent_of='cash') - self.order_percent(2, half, percent_of='ex_cash') - self.order_percent(3, half, percent_of='longs') - self.order_percent(4, half, percent_of='longs_cash') - self.order_percent(5, half, percent_of='shorts') - self.order_percent( - 6, half, percent_of_fn=lambda p: p.sid in group) - self.order_percent(7, full, percent_of='gross') - - self.target_shares[0] += expected_shares(0, full * port) - self.target_shares[1] += expected_shares(1, -half * cash) - self.target_shares[2] += expected_shares(2, half * (port - cash)) - self.target_shares[3] += expected_shares(3, half * longs) - self.target_shares[4] += expected_shares(4, half * (longs + cash)) - self.target_shares[5] += expected_shares(5, half * shorts) - self.target_shares[6] += expected_shares(6, half * group_val) - self.target_shares[7] += expected_shares(7, full * gross) - - class TestTargetPercentAlgorithm(TradingAlgorithm): def initialize(self): self.target_shares = 0 self.sale_price = None - self.exp_value = 0 def handle_data(self, data): if self.target_shares == 0: assert 0 not in self.portfolio.positions self.target_shares = 1 else: - value = self.portfolio.positions[0]['amount'] * self.sale_price - assert abs(value - self.exp_value) <= self.sale_price, \ + assert np.round(self.portfolio.portfolio_value * 0.002) == \ + self.portfolio.positions[0]['amount'] * self.sale_price, \ "Orders not filled correctly." assert self.portfolio.positions[0]['last_sale_price'] == \ data[0].price, "Orders not filled at current price." self.sale_price = data[0].price self.order_target_percent(0, .002) - self.exp_value = self.portfolio.portfolio_value * 0.002 - - -class TestTargetPercentAlgorithmPercentOf(TradingAlgorithm): - def initialize(self): - self.target_shares = dict([(i, 0) for i in range(8)]) - - def handle_data(self, data): - - for sid, target_shares in self.target_shares.items(): - position = self.portfolio.positions[sid] - assert target_shares == position.amount - - # reduce sizes due to volume limitiations - full = 0.001 - half = 0.0005 - - pos_values = {} - for i in range(len(data.keys())): - pos_values[i] = ( - self.portfolio.positions[i].amount - * self.portfolio.positions[i].last_sale_price) - - port = self.portfolio.portfolio_value - cash = self.portfolio.cash - longs = sum(v for v in pos_values.values() if v > 0) - shorts = sum(v for v in pos_values.values() if v < 0) - gross = sum(abs(v) for v in pos_values.values()) - group = [0, 1, 2] - group_val = sum(v for sid, v in pos_values.items() if sid in group) - - def expected_shares(sid, value): - return round_shares(value / data[sid].price) - - self.order_target_percent(0, full, percent_of='portfolio') - self.order_target_percent(1, -half, percent_of='cash') - self.order_target_percent(2, half, percent_of='ex_cash') - self.order_target_percent(3, half, percent_of='longs') - self.order_target_percent(4, half, percent_of='longs_cash') - self.order_target_percent(5, half, percent_of='shorts') - self.order_target_percent( - 6, half, percent_of_fn=lambda p: p.sid in group) - self.order_target_percent(5, full, percent_of='gross') - - self.target_shares[0] = expected_shares(0, full * port) - self.target_shares[1] = expected_shares(1, -half * cash) - self.target_shares[2] = expected_shares(2, half * (port - cash)) - self.target_shares[3] = expected_shares(3, half * longs) - self.target_shares[4] = expected_shares(4, half * (longs + cash)) - self.target_shares[5] = expected_shares(5, half * shorts) - self.target_shares[6] = expected_shares(6, half * group_val) - self.target_shares[7] = expected_shares(7, full * gross) class TestTargetValueAlgorithm(TradingAlgorithm): @@ -536,7 +406,7 @@ class TestTargetValueAlgorithm(TradingAlgorithm): data[0].price, "Orders not filled at current price." self.order_target_value(0, 20) - self.target_shares = round_shares(20 / data[0].price) + self.target_shares = np.round(20 / data[0].price) ############################