mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-01 16:52:26 +08:00
added a simple test for the transaction simulator, heavily revised the simulator as a result.
This commit is contained in:
+69
-37
@@ -249,9 +249,8 @@ class TransactionSimulator(qmsg.BaseTransform):
|
||||
self.open_orders = {}
|
||||
self.order_count = 0
|
||||
self.txn_count = 0
|
||||
self.trade_window = datetime.timedelta(seconds=30)
|
||||
self.trade_window = datetime.timedelta(seconds=30)
|
||||
self.orderTTL = datetime.timedelta(days=1)
|
||||
self.volume_share = 0.05
|
||||
self.commission = 0.03
|
||||
|
||||
def transform(self, event):
|
||||
@@ -267,9 +266,12 @@ class TransactionSimulator(qmsg.BaseTransform):
|
||||
self.state['value'] = txn
|
||||
else:
|
||||
self.state['value'] = None
|
||||
qutil.LOGGER.info("unexpected event type in transform: {etype}".format(etype=event.type))
|
||||
log = "unexpected event type in transform: {etype}".format(
|
||||
etype=event.type
|
||||
)
|
||||
qutil.LOGGER.info(log)
|
||||
|
||||
#TODO: what to do if we get another kind of datasource event.type?
|
||||
|
||||
return self.state
|
||||
|
||||
def add_open_order(self, event):
|
||||
@@ -279,13 +281,19 @@ class TransactionSimulator(qmsg.BaseTransform):
|
||||
"""
|
||||
event.amount = int(event.amount)
|
||||
if event.amount == 0:
|
||||
qutil.LOGGER.debug("requested to trade zero shares of {sid}".format(sid=event.sid))
|
||||
log = "requested to trade zero shares of {sid}".format(
|
||||
sid=event.sid
|
||||
)
|
||||
qutil.LOGGER.debug(log)
|
||||
return
|
||||
|
||||
self.order_count += 1
|
||||
|
||||
if(not self.open_orders.has_key(event.sid)):
|
||||
self.open_orders[event.sid] = []
|
||||
|
||||
# set the filled property to zero
|
||||
event.filled = 0
|
||||
self.open_orders[event.sid].append(event)
|
||||
|
||||
def apply_trade_to_open_orders(self, event):
|
||||
@@ -293,47 +301,71 @@ class TransactionSimulator(qmsg.BaseTransform):
|
||||
if(event.volume == 0):
|
||||
#there are zero volume events bc some stocks trade
|
||||
#less frequently than once per minute.
|
||||
return self.create_dummy_txn(event.dt)
|
||||
return None
|
||||
|
||||
if self.open_orders.has_key(event.sid):
|
||||
orders = self.open_orders[event.sid]
|
||||
orders = sorted(orders, key=lambda o: o.dt)
|
||||
else:
|
||||
return None
|
||||
|
||||
remaining_orders = []
|
||||
|
||||
total_order = 0
|
||||
dt = event.dt
|
||||
|
||||
expired = []
|
||||
total_order = 0
|
||||
simulated_amount = 0
|
||||
simulated_impact = 0.0
|
||||
direction = 1.0
|
||||
for order in orders:
|
||||
#we're using minute bars, so allow orders within
|
||||
#30 seconds of the trade
|
||||
if((order.dt - event.dt) < self.trade_window):
|
||||
total_order += order.amount
|
||||
if(order.dt > dt):
|
||||
dt = order.dt
|
||||
#if the order still has time to live (TTL) keep track
|
||||
elif((self.algo_time - order.dt) < self.orderTTL):
|
||||
remaining_orders.append(order)
|
||||
|
||||
self.open_orders[event.sid] = remaining_orders
|
||||
|
||||
if(total_order != 0):
|
||||
direction = total_order / math.fabs(total_order)
|
||||
else:
|
||||
direction = 1
|
||||
|
||||
volume_share = (direction * total_order) / event.volume
|
||||
if volume_share > .25:
|
||||
volume_share = .25
|
||||
amount = volume_share * event.volume * direction
|
||||
impact = (volume_share)**2 * .1 * direction * event.price
|
||||
return self.create_transaction(
|
||||
event.sid,
|
||||
amount,
|
||||
event.price + impact,
|
||||
dt.replace(tzinfo = pytz.utc),
|
||||
direction
|
||||
)
|
||||
if(order.dt <= event.dt):
|
||||
|
||||
# orders are only good on the day they are issued
|
||||
if order.dt.day < event.dt.day:
|
||||
continue
|
||||
|
||||
open_amount = order.amount - order.filled
|
||||
|
||||
if(open_amount != 0):
|
||||
direction = open_amount / math.fabs(open_amount)
|
||||
else:
|
||||
direction = 1
|
||||
|
||||
desired_order = total_order + open_amount
|
||||
|
||||
volume_share = direction * (desired_order) / event.volume
|
||||
if volume_share > .25:
|
||||
volume_share = .25
|
||||
simulated_amount = volume_share * event.volume * direction
|
||||
simulated_impact = (volume_share)**2 * .1 * direction * event.price
|
||||
|
||||
order.filled += (simulated_amount - total_order)
|
||||
total_order = simulated_amount
|
||||
|
||||
# we cap the volume share at 25% of a trade
|
||||
if volume_share == .25:
|
||||
break
|
||||
|
||||
if simulated_amount == 0:
|
||||
warning = "Calculated a zero volume transation on trade: {event}"
|
||||
warning = warning.format(event=str(event))
|
||||
qutil.LOGGER.warn(warning)
|
||||
|
||||
orders = [ x for x in orders if x.amount - x.filled > 0 and x.dt.day >= event.dt.day]
|
||||
|
||||
self.open_orders[event.sid] = orders
|
||||
|
||||
|
||||
if simulated_amount > 0:
|
||||
return self.create_transaction(
|
||||
event.sid,
|
||||
simulated_amount,
|
||||
event.price + simulated_impact,
|
||||
dt.replace(tzinfo = pytz.utc),
|
||||
direction
|
||||
)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def create_transaction(self, sid, amount, price, dt, direction):
|
||||
|
||||
@@ -21,6 +21,7 @@ TradeSimulationClient, TradingEnvironment
|
||||
from zipline.simulator import AddressAllocator, Simulator
|
||||
from zipline.monitor import Controller
|
||||
from zipline.lines import SimulatedTrading
|
||||
from zipline.protocol_utils import namedict
|
||||
|
||||
DEFAULT_TIMEOUT = 15 # seconds
|
||||
|
||||
@@ -204,7 +205,9 @@ class FinanceTestCase(TestCase):
|
||||
self.zipline_test_config['trade_count'] = 200
|
||||
self.zipline_test_config['algorithm'] = test_algo
|
||||
|
||||
zipline = SimulatedTrading.create_test_zipline(**self.zipline_test_config)
|
||||
zipline = SimulatedTrading.create_test_zipline(
|
||||
**self.zipline_test_config
|
||||
)
|
||||
|
||||
zipline.simulate(blocking=True)
|
||||
#check that the algorithm received no events
|
||||
@@ -214,8 +217,87 @@ class FinanceTestCase(TestCase):
|
||||
"The algorithm should not receive any events due to filtering."
|
||||
)
|
||||
|
||||
|
||||
@timed(DEFAULT_TIMEOUT)
|
||||
def test_transaction_sim(self):
|
||||
|
||||
trade_count = 40
|
||||
trading_environment = factory.create_trading_environment()
|
||||
trade_sim = TransactionSimulator()
|
||||
price = [10.1] * trade_count
|
||||
volume = [100] * trade_count
|
||||
start_date = trading_environment.first_open
|
||||
one_day = timedelta(days=1)
|
||||
one_hour = timedelta(hours=1)
|
||||
sid = 1
|
||||
|
||||
generated_trades = factory.create_trade_history(
|
||||
sid,
|
||||
price,
|
||||
volume,
|
||||
one_hour,
|
||||
trading_environment
|
||||
)
|
||||
|
||||
trade_1 = generated_trades.pop()
|
||||
trade_sim.transform(trade_1)
|
||||
|
||||
order_amount = 100
|
||||
order_count = 2
|
||||
|
||||
for i in range(order_count):
|
||||
order = namedict(
|
||||
{
|
||||
'sid':sid,
|
||||
'amount':order_amount,
|
||||
'type':zp.DATASOURCE_TYPE.ORDER,
|
||||
'dt' : start_date + i * one_day
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
sim_state = trade_sim.transform(order)
|
||||
|
||||
# there should not be a new transaction from an order.
|
||||
self.assertTrue(sim_state['name'] == trade_sim.get_id)
|
||||
self.assertTrue(sim_state['value'] == None)
|
||||
|
||||
# there should now be one open order in the sid
|
||||
|
||||
oo = trade_sim.open_orders
|
||||
self.assertTrue(oo.has_key(sid))
|
||||
order_list = oo[sid]
|
||||
self.assertEqual(order_count, len(order_list))
|
||||
|
||||
for order in order_list:
|
||||
self.assertEqual(order.sid, sid)
|
||||
self.assertEqual(order.amount, order_amount)
|
||||
|
||||
transactions = []
|
||||
for trade in generated_trades:
|
||||
sim_state = trade_sim.transform(trade)
|
||||
|
||||
self.assertEqual(sim_state['name'], trade_sim.get_id)
|
||||
|
||||
if sim_state['value']:
|
||||
transactions.append(sim_state['value'])
|
||||
|
||||
|
||||
if len(trade_sim.open_orders[sid]) == 0:
|
||||
break
|
||||
|
||||
total_volume = 0
|
||||
for txn in transactions:
|
||||
total_volume += txn.amount
|
||||
|
||||
self.assertEqual(total_volume, order_count * order_amount)
|
||||
|
||||
# because we placed an order for 100 shares, and the volume
|
||||
# of each trade is 100, the simulator should spread the order
|
||||
# into 4 trades of 25 shares per order.
|
||||
self.assertEqual(len(transactions), 4 * order_count)
|
||||
|
||||
# the open orders should now be empty
|
||||
oo = trade_sim.open_orders
|
||||
self.assertTrue(oo.has_key(sid))
|
||||
order_list = oo[sid]
|
||||
self.assertEqual(0, len(order_list))
|
||||
|
||||
Reference in New Issue
Block a user