mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-06 05:14:38 +08:00
ENH: Add support for asynchronous commission events.
This commit is contained in:
@@ -145,6 +145,44 @@ class TestSplitPerformance(unittest.TestCase):
|
||||
daily_perf['ending_cash'], 1))
|
||||
|
||||
|
||||
class TestCommissionEvents(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.sim_params, self.dt, self.end_dt = \
|
||||
create_random_simulation_parameters()
|
||||
|
||||
self.sim_params.capital_base = 10e3
|
||||
|
||||
self.benchmark_events = benchmark_events_in_range(self.sim_params)
|
||||
|
||||
def test_commission_event(self):
|
||||
with trading.TradingEnvironment():
|
||||
events = factory.create_trade_history(
|
||||
1,
|
||||
[10, 10, 10, 10, 10],
|
||||
[100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params
|
||||
)
|
||||
|
||||
cash_adj_dt = self.sim_params.period_start \
|
||||
+ datetime.timedelta(hours=3)
|
||||
cash_adjustment = factory.create_commission(1, 300.0,
|
||||
cash_adj_dt)
|
||||
|
||||
# Insert a purchase order.
|
||||
events.insert(0, create_txn(events[0], 20, 1))
|
||||
|
||||
events.insert(1, cash_adjustment)
|
||||
results = calculate_results(self, events)
|
||||
# Validate that we lost 320 dollars from our cash pool.
|
||||
self.assertEqual(results[-1]['cumulative_perf']['ending_cash'],
|
||||
9680)
|
||||
# Validate that the cost basis of our position changed.
|
||||
self.assertEqual(results[-1]['daily_perf']['positions']
|
||||
[0]['cost_basis'], 320.0)
|
||||
|
||||
|
||||
class TestDividendPerformance(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
@@ -196,6 +196,10 @@ class Blotter(object):
|
||||
return
|
||||
|
||||
for order, txn in self.transact(trade_event, current_orders):
|
||||
if txn.type == zp.DATASOURCE_TYPE.COMMISSION:
|
||||
yield txn, order
|
||||
continue
|
||||
|
||||
if txn.amount == 0:
|
||||
raise zipline.errors.TransactionWithNoAmount(txn=txn)
|
||||
if math.copysign(1, txn.amount) != order.direction:
|
||||
|
||||
@@ -130,6 +130,7 @@ omitted).
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
import logbook
|
||||
import math
|
||||
|
||||
@@ -312,6 +313,10 @@ class PerformanceTracker(object):
|
||||
for perf_period in self.perf_periods:
|
||||
perf_period.record_order(event)
|
||||
|
||||
elif event.type == zp.DATASOURCE_TYPE.COMMISSION:
|
||||
for perf_period in self.perf_periods:
|
||||
perf_period.handle_commission(event)
|
||||
|
||||
elif event.type == zp.DATASOURCE_TYPE.CUSTOM:
|
||||
pass
|
||||
elif event.type == zp.DATASOURCE_TYPE.BENCHMARK:
|
||||
@@ -558,7 +563,8 @@ class Position(object):
|
||||
|
||||
def update(self, txn):
|
||||
if(self.sid != txn.sid):
|
||||
raise NameError('updating position with txn for a different sid')
|
||||
raise Exception('updating position with txn for a '
|
||||
'different sid')
|
||||
|
||||
# we're covering a short or closing a position
|
||||
if(self.amount + txn.amount == 0):
|
||||
@@ -572,6 +578,26 @@ class Position(object):
|
||||
self.cost_basis = total_cost / total_shares
|
||||
self.amount = self.amount + txn.amount
|
||||
|
||||
def adjust_commission_cost_basis(self, commission):
|
||||
"""
|
||||
A note about cost-basis in zipline: all positions are considered
|
||||
to share a cost basis, even if they were executed in different
|
||||
transactions with different commission costs, different prices, etc.
|
||||
|
||||
Due to limitations about how zipline handles positions, zipline will
|
||||
currently spread an externally-delivered commission charge across
|
||||
all shares in a position.
|
||||
"""
|
||||
|
||||
if commission.sid != self.sid:
|
||||
raise Exception('Updating a commission for a different sid?')
|
||||
if commission.cost == 0.0:
|
||||
return
|
||||
|
||||
prev_cost = self.cost_basis * self.amount
|
||||
new_cost = prev_cost + commission.cost
|
||||
self.cost_basis = new_cost / self.amount
|
||||
|
||||
def __repr__(self):
|
||||
template = "sid: {sid}, amount: {amount}, cost_basis: {cost_basis}, \
|
||||
last_sale_price: {last_sale_price}"
|
||||
@@ -701,6 +727,15 @@ class PerformancePeriod(object):
|
||||
self.period_cash_flow += payment_amount
|
||||
self.cumulative_capital_used -= payment_amount
|
||||
|
||||
def handle_commission(self, commission):
|
||||
# Deduct from our total cash pool.
|
||||
negative_commission = math.copysign(commission.cost, -1.0)
|
||||
self.handle_cash_payment(negative_commission)
|
||||
# Adjust the cost basis of the stock if we own it
|
||||
if commission.sid in self.positions:
|
||||
self.positions[commission.sid].\
|
||||
adjust_commission_cost_basis(commission)
|
||||
|
||||
def calculate_performance(self):
|
||||
self.ending_value = self.calculate_positions_value()
|
||||
|
||||
|
||||
+2
-1
@@ -29,7 +29,8 @@ DATASOURCE_TYPE = Enum(
|
||||
'EMPTY',
|
||||
'DONE',
|
||||
'CUSTOM',
|
||||
'BENCHMARK'
|
||||
'BENCHMARK',
|
||||
'COMMISSION'
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -195,6 +195,16 @@ def create_txn(sid, price, amount, datetime):
|
||||
return txn
|
||||
|
||||
|
||||
def create_commission(sid, value, datetime):
|
||||
txn = Event({
|
||||
'dt': datetime,
|
||||
'type': DATASOURCE_TYPE.COMMISSION,
|
||||
'cost': value,
|
||||
'sid': sid
|
||||
})
|
||||
return txn
|
||||
|
||||
|
||||
def create_txn_history(sid, priceList, amtList, interval, sim_params):
|
||||
txns = []
|
||||
current = sim_params.first_open
|
||||
|
||||
Reference in New Issue
Block a user