added a simple test for the transaction simulator, heavily revised the simulator as a result.

This commit is contained in:
fawce
2012-04-10 22:46:50 -04:00
parent db7a4d2f91
commit f759cac61b
2 changed files with 155 additions and 41 deletions
+69 -37
View File
@@ -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):
+86 -4
View File
@@ -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))