diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index eeb42b95..fbb802d7 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -33,6 +33,7 @@ import zipline.utils.math_utils as zp_math from zipline.gens.composites import date_sorted_sources from zipline.finance.trading import SimulationParameters from zipline.finance.blotter import Order +from zipline.finance.commission import PerShare, PerTrade, PerDollar from zipline.finance import trading from zipline.protocol import DATASOURCE_TYPE from zipline.utils.factory import create_random_simulation_parameters @@ -172,6 +173,29 @@ class TestCommissionEvents(unittest.TestCase): self.sim_params ) + # Test commission models and validate result + # Expected commission amounts: + # PerShare commission: 1.00, 1.00, 1.50 = $3.50 + # PerTrade commission: 5.00, 5.00, 5.00 = $15.00 + # PerDollar commission: 1.50, 3.00, 4.50 = $9.00 + # Total commission = $3.50 + $15.00 + $9.00 = $27.50 + + # Create 3 transactions: 50, 100, 150 shares traded @ $20 + transactions = [create_txn(events[0], 20, i) + for i in [50, 100, 150]] + + # Create commission models + models = [PerShare(cost=0.01, min_trade_cost=1.00), + PerTrade(cost=5.00), + PerDollar(cost=0.0015)] + + # Aggregate commission amounts + total_commission = 0 + for model in models: + for trade in transactions: + total_commission += model.calculate(trade)[1] + self.assertEqual(total_commission, 27.5) + cash_adj_dt = self.sim_params.first_open \ + datetime.timedelta(hours=3) cash_adjustment = factory.create_commission(1, 300.0, diff --git a/zipline/finance/commission.py b/zipline/finance/commission.py index f86b8b02..2512279b 100644 --- a/zipline/finance/commission.py +++ b/zipline/finance/commission.py @@ -17,28 +17,38 @@ class PerShare(object): """ Calculates a commission for a transaction based on a per - share cost. + share cost with an optional minimum cost per trade. """ - def __init__(self, cost=0.03): + def __init__(self, cost=0.03, min_trade_cost=None): """ Cost parameter is the cost of a trade per-share. $0.03 means three cents per share, which is a very conservative (quite high) for per share costs. + min_trade_cost parameter is the minimum trade cost + regardless of the number of shares traded (e.g. $1.00). """ self.cost = float(cost) + self.min_trade_cost = None if min_trade_cost is None\ + else float(min_trade_cost) def __repr__(self): - return "{class_name}(cost={cost})".format( - class_name=self.__class__.__name__, - cost=self.cost) + return "{class_name}(cost={cost}, min trade cost={min_trade_cost})"\ + .format(class_name=self.__class__.__name__, + cost=self.cost, + min_trade_cost=self.min_trade_cost) def calculate(self, transaction): """ returns a tuple of: (per share commission, total transaction commission) """ - return self.cost, abs(transaction.amount * self.cost) + commission = abs(transaction.amount * self.cost) + if self.min_trade_cost is None: + return self.cost, commission + else: + commission = max(commission, self.min_trade_cost) + return abs(commission / transaction.amount), commission class PerTrade(object):