mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-03 23:48:25 +08:00
@@ -453,10 +453,6 @@ class WithEstimatesTimeZero(WithEstimates):
|
||||
SID_FIELD_NAME: sid,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def init_class_fixtures(cls):
|
||||
super(WithEstimatesTimeZero, cls).init_class_fixtures()
|
||||
|
||||
def get_expected_estimate(self,
|
||||
q1_knowledge,
|
||||
q2_knowledge,
|
||||
|
||||
+123
-10
@@ -98,6 +98,7 @@ from zipline.testing import (
|
||||
to_utc,
|
||||
trades_by_sid_to_dfs,
|
||||
)
|
||||
from zipline.testing import RecordBatchBlotter
|
||||
from zipline.testing.fixtures import (
|
||||
WithDataPortal,
|
||||
WithLogger,
|
||||
@@ -123,6 +124,7 @@ from zipline.test_algorithms import (
|
||||
TestTargetAlgorithm,
|
||||
TestTargetPercentAlgorithm,
|
||||
TestTargetValueAlgorithm,
|
||||
TestBatchTargetPercentAlgorithm,
|
||||
SetLongOnlyAlgorithm,
|
||||
SetAssetDateBoundsAlgorithm,
|
||||
SetMaxPositionSizeAlgorithm,
|
||||
@@ -170,6 +172,7 @@ from zipline.test_algorithms import (
|
||||
set_benchmark_algo,
|
||||
no_handle_data,
|
||||
)
|
||||
from zipline.testing.predicates import assert_equal
|
||||
from zipline.utils.api_support import ZiplineAPI, set_algo_instance
|
||||
from zipline.utils.calendars import get_calendar, register_calendar
|
||||
from zipline.utils.context_tricks import CallbackManager
|
||||
@@ -905,14 +908,15 @@ def before_trading_start(context, data):
|
||||
self.assertEqual(algo.sim_params.data_frequency, 'minute')
|
||||
|
||||
@parameterized.expand([
|
||||
(TestOrderAlgorithm,),
|
||||
(TestOrderValueAlgorithm,),
|
||||
(TestTargetAlgorithm,),
|
||||
(TestOrderPercentAlgorithm,),
|
||||
(TestTargetPercentAlgorithm,),
|
||||
(TestTargetValueAlgorithm,),
|
||||
('order', TestOrderAlgorithm,),
|
||||
('order_value', TestOrderValueAlgorithm,),
|
||||
('order_target', TestTargetAlgorithm,),
|
||||
('order_percent', TestOrderPercentAlgorithm,),
|
||||
('order_target_percent', TestTargetPercentAlgorithm,),
|
||||
('order_target_value', TestTargetValueAlgorithm,),
|
||||
('batch_order_target_percent', TestBatchTargetPercentAlgorithm,),
|
||||
])
|
||||
def test_order_methods(self, algo_class):
|
||||
def test_order_methods(self, test_name, algo_class):
|
||||
algo = algo_class(
|
||||
sim_params=self.sim_params,
|
||||
env=self.env,
|
||||
@@ -935,7 +939,7 @@ def before_trading_start(context, data):
|
||||
sim_params=self.sim_params,
|
||||
env=self.env,
|
||||
)
|
||||
# Ensure that the environment's asset 0 is a Future
|
||||
# Ensure that the environment's asset 3 is a Future
|
||||
asset_to_test = algo.sid(3)
|
||||
self.assertIsInstance(asset_to_test, Future)
|
||||
|
||||
@@ -1015,7 +1019,7 @@ def before_trading_start(context, data):
|
||||
sim_params = SimulationParameters(
|
||||
start_session=start_session,
|
||||
end_session=period_end,
|
||||
capital_base=float("1.0e5"),
|
||||
capital_base=1.0e5,
|
||||
data_frequency='minute',
|
||||
trading_calendar=self.trading_calendar,
|
||||
)
|
||||
@@ -1735,6 +1739,115 @@ def handle_data(context, data):
|
||||
)
|
||||
test_algo.run(self.data_portal)
|
||||
|
||||
def test_batch_order_target_percent_matches_multi_order(self):
|
||||
weights = pd.Series([.3, .7])
|
||||
|
||||
multi_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY,
|
||||
self.asset_finder)
|
||||
multi_test_algo = TradingAlgorithm(
|
||||
script=dedent("""\
|
||||
from collections import OrderedDict
|
||||
from six import iteritems
|
||||
|
||||
from zipline.api import sid, order_target_percent
|
||||
|
||||
|
||||
def initialize(context):
|
||||
context.assets = [sid(0), sid(3)]
|
||||
context.placed = False
|
||||
|
||||
def handle_data(context, data):
|
||||
if not context.placed:
|
||||
for asset, weight in iteritems(OrderedDict(zip(
|
||||
context.assets, {weights}
|
||||
))):
|
||||
order_target_percent(asset, weight)
|
||||
|
||||
context.placed = True
|
||||
|
||||
""").format(weights=list(weights)),
|
||||
blotter=multi_blotter,
|
||||
env=self.env,
|
||||
)
|
||||
multi_stats = multi_test_algo.run(self.data_portal)
|
||||
self.assertFalse(multi_blotter.order_batch_called)
|
||||
|
||||
batch_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY,
|
||||
self.asset_finder)
|
||||
batch_test_algo = TradingAlgorithm(
|
||||
script=dedent("""\
|
||||
from collections import OrderedDict
|
||||
|
||||
from zipline.api import sid, batch_order_target_percent
|
||||
|
||||
|
||||
def initialize(context):
|
||||
context.assets = [sid(0), sid(3)]
|
||||
context.placed = False
|
||||
|
||||
def handle_data(context, data):
|
||||
if not context.placed:
|
||||
orders = batch_order_target_percent(OrderedDict(zip(
|
||||
context.assets, {weights}
|
||||
)))
|
||||
assert len(orders) == 2, \
|
||||
"len(orders) was %s but expected 2" % len(orders)
|
||||
for o in orders:
|
||||
assert o is not None, "An order is None"
|
||||
|
||||
context.placed = True
|
||||
|
||||
""").format(weights=list(weights)),
|
||||
blotter=batch_blotter,
|
||||
env=self.env,
|
||||
)
|
||||
batch_stats = batch_test_algo.run(self.data_portal)
|
||||
self.assertTrue(batch_blotter.order_batch_called)
|
||||
|
||||
for stats in (multi_stats, batch_stats):
|
||||
stats.orders = stats.orders.apply(
|
||||
lambda orders: [toolz.dissoc(o, 'id') for o in orders]
|
||||
)
|
||||
stats.transactions = stats.transactions.apply(
|
||||
lambda txns: [toolz.dissoc(txn, 'order_id') for txn in txns]
|
||||
)
|
||||
assert_equal(multi_stats, batch_stats)
|
||||
|
||||
def test_batch_order_target_percent_filters_null_orders(self):
|
||||
weights = pd.Series([1, 0])
|
||||
|
||||
batch_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY,
|
||||
self.asset_finder)
|
||||
batch_test_algo = TradingAlgorithm(
|
||||
script=dedent("""\
|
||||
from collections import OrderedDict
|
||||
|
||||
from zipline.api import sid, batch_order_target_percent
|
||||
|
||||
|
||||
def initialize(context):
|
||||
context.assets = [sid(0), sid(3)]
|
||||
context.placed = False
|
||||
|
||||
def handle_data(context, data):
|
||||
if not context.placed:
|
||||
orders = batch_order_target_percent(OrderedDict(zip(
|
||||
context.assets, {weights}
|
||||
)))
|
||||
assert len(orders) == 1, \
|
||||
"len(orders) was %s but expected 1" % len(orders)
|
||||
for o in orders:
|
||||
assert o is not None, "An order is None"
|
||||
|
||||
context.placed = True
|
||||
|
||||
""").format(weights=list(weights)),
|
||||
blotter=batch_blotter,
|
||||
env=self.env,
|
||||
)
|
||||
batch_test_algo.run(self.data_portal)
|
||||
self.assertTrue(batch_blotter.order_batch_called)
|
||||
|
||||
def test_order_dead_asset(self):
|
||||
# after asset 0 is dead
|
||||
params = SimulationParameters(
|
||||
@@ -3628,7 +3741,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase):
|
||||
cls.first_asset_expiration = cls.test_days[2]
|
||||
|
||||
def make_data(self, auto_close_delta, frequency,
|
||||
capital_base=float("1.0e5")):
|
||||
capital_base=1.0e5):
|
||||
|
||||
asset_info = make_jagged_equity_info(
|
||||
num_assets=3,
|
||||
|
||||
+87
-60
@@ -50,6 +50,12 @@ class BlotterTestCase(WithCreateBarData,
|
||||
END_DATE = pd.Timestamp('2006-01-06', tz='utc')
|
||||
ASSET_FINDER_EQUITY_SIDS = 24, 25
|
||||
|
||||
@classmethod
|
||||
def init_class_fixtures(cls):
|
||||
super(BlotterTestCase, cls).init_class_fixtures()
|
||||
cls.asset_24 = cls.asset_finder.retrieve_asset(24)
|
||||
cls.asset_25 = cls.asset_finder.retrieve_asset(25)
|
||||
|
||||
@classmethod
|
||||
def make_equity_daily_bar_data(cls):
|
||||
yield 24, pd.DataFrame(
|
||||
@@ -83,64 +89,59 @@ class BlotterTestCase(WithCreateBarData,
|
||||
(StopLimitOrder(10, 20), 10, 20)])
|
||||
def test_blotter_order_types(self, style_obj, expected_lmt, expected_stp):
|
||||
|
||||
blotter = Blotter('daily', self.env.asset_finder)
|
||||
blotter = Blotter('daily', self.asset_finder)
|
||||
|
||||
asset_24 = blotter.asset_finder.retrieve_asset(24)
|
||||
blotter.order(asset_24, 100, style_obj)
|
||||
result = blotter.open_orders[asset_24][0]
|
||||
blotter.order(self.asset_24, 100, style_obj)
|
||||
result = blotter.open_orders[self.asset_24][0]
|
||||
|
||||
self.assertEqual(result.limit, expected_lmt)
|
||||
self.assertEqual(result.stop, expected_stp)
|
||||
|
||||
def test_cancel(self):
|
||||
blotter = Blotter('daily', self.env.asset_finder)
|
||||
blotter = Blotter('daily', self.asset_finder)
|
||||
|
||||
asset_24 = blotter.asset_finder.retrieve_asset(24)
|
||||
asset_25 = blotter.asset_finder.retrieve_asset(25)
|
||||
|
||||
oid_1 = blotter.order(asset_24, 100, MarketOrder())
|
||||
oid_2 = blotter.order(asset_24, 200, MarketOrder())
|
||||
oid_3 = blotter.order(asset_24, 300, MarketOrder())
|
||||
oid_1 = blotter.order(self.asset_24, 100, MarketOrder())
|
||||
oid_2 = blotter.order(self.asset_24, 200, MarketOrder())
|
||||
oid_3 = blotter.order(self.asset_24, 300, MarketOrder())
|
||||
|
||||
# Create an order for another asset to verify that we don't remove it
|
||||
# when we do cancel_all on 24.
|
||||
blotter.order(asset_25, 150, MarketOrder())
|
||||
blotter.order(self.asset_25, 150, MarketOrder())
|
||||
|
||||
self.assertEqual(len(blotter.open_orders), 2)
|
||||
self.assertEqual(len(blotter.open_orders[asset_24]), 3)
|
||||
self.assertEqual(len(blotter.open_orders[self.asset_24]), 3)
|
||||
self.assertEqual(
|
||||
[o.amount for o in blotter.open_orders[asset_24]],
|
||||
[o.amount for o in blotter.open_orders[self.asset_24]],
|
||||
[100, 200, 300],
|
||||
)
|
||||
|
||||
blotter.cancel(oid_2)
|
||||
self.assertEqual(len(blotter.open_orders), 2)
|
||||
self.assertEqual(len(blotter.open_orders[asset_24]), 2)
|
||||
self.assertEqual(len(blotter.open_orders[self.asset_24]), 2)
|
||||
self.assertEqual(
|
||||
[o.amount for o in blotter.open_orders[asset_24]],
|
||||
[o.amount for o in blotter.open_orders[self.asset_24]],
|
||||
[100, 300],
|
||||
)
|
||||
self.assertEqual(
|
||||
[o.id for o in blotter.open_orders[asset_24]],
|
||||
[o.id for o in blotter.open_orders[self.asset_24]],
|
||||
[oid_1, oid_3],
|
||||
)
|
||||
|
||||
blotter.cancel_all_orders_for_asset(asset_24)
|
||||
blotter.cancel_all_orders_for_asset(self.asset_24)
|
||||
self.assertEqual(len(blotter.open_orders), 1)
|
||||
self.assertEqual(list(blotter.open_orders), [asset_25])
|
||||
self.assertEqual(list(blotter.open_orders), [self.asset_25])
|
||||
|
||||
def test_blotter_eod_cancellation(self):
|
||||
blotter = Blotter('minute', self.env.asset_finder,
|
||||
blotter = Blotter('minute', self.asset_finder,
|
||||
cancel_policy=EODCancel())
|
||||
asset_24 = blotter.asset_finder.retrieve_asset(24)
|
||||
|
||||
# Make two orders for the same sid, so we can test that we are not
|
||||
# mutating the orders list as we are cancelling orders
|
||||
blotter.order(asset_24, 100, MarketOrder())
|
||||
blotter.order(asset_24, -100, MarketOrder())
|
||||
blotter.order(self.asset_24, 100, MarketOrder())
|
||||
blotter.order(self.asset_24, -100, MarketOrder())
|
||||
|
||||
self.assertEqual(len(blotter.new_orders), 2)
|
||||
order_ids = [order.id for order in blotter.open_orders[asset_24]]
|
||||
order_ids = [order.id for order in blotter.open_orders[self.asset_24]]
|
||||
|
||||
self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN)
|
||||
self.assertEqual(blotter.new_orders[1].status, ORDER_STATUS.OPEN)
|
||||
@@ -155,11 +156,10 @@ class BlotterTestCase(WithCreateBarData,
|
||||
self.assertEqual(order.status, ORDER_STATUS.CANCELLED)
|
||||
|
||||
def test_blotter_never_cancel(self):
|
||||
blotter = Blotter('minute', self.env.asset_finder,
|
||||
blotter = Blotter('minute', self.asset_finder,
|
||||
cancel_policy=NeverCancel())
|
||||
|
||||
blotter.order(blotter.asset_finder.retrieve_asset(24), 100,
|
||||
MarketOrder())
|
||||
blotter.order(self.asset_24, 100, MarketOrder())
|
||||
|
||||
self.assertEqual(len(blotter.new_orders), 1)
|
||||
self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN)
|
||||
@@ -172,8 +172,7 @@ class BlotterTestCase(WithCreateBarData,
|
||||
|
||||
def test_order_rejection(self):
|
||||
blotter = Blotter(self.sim_params.data_frequency,
|
||||
self.env.asset_finder)
|
||||
asset_24 = blotter.asset_finder.retrieve_asset(24)
|
||||
self.asset_finder)
|
||||
|
||||
# Reject a nonexistent order -> no order appears in new_order,
|
||||
# no exceptions raised out
|
||||
@@ -181,10 +180,10 @@ class BlotterTestCase(WithCreateBarData,
|
||||
self.assertEqual(blotter.new_orders, [])
|
||||
|
||||
# Basic tests of open order behavior
|
||||
open_order_id = blotter.order(asset_24, 100, MarketOrder())
|
||||
second_order_id = blotter.order(asset_24, 50, MarketOrder())
|
||||
self.assertEqual(len(blotter.open_orders[asset_24]), 2)
|
||||
open_order = blotter.open_orders[asset_24][0]
|
||||
open_order_id = blotter.order(self.asset_24, 100, MarketOrder())
|
||||
second_order_id = blotter.order(self.asset_24, 50, MarketOrder())
|
||||
self.assertEqual(len(blotter.open_orders[self.asset_24]), 2)
|
||||
open_order = blotter.open_orders[self.asset_24][0]
|
||||
self.assertEqual(open_order.status, ORDER_STATUS.OPEN)
|
||||
self.assertEqual(open_order.id, open_order_id)
|
||||
self.assertIn(open_order, blotter.new_orders)
|
||||
@@ -192,7 +191,7 @@ class BlotterTestCase(WithCreateBarData,
|
||||
# Reject that order immediately (same bar, i.e. still in new_orders)
|
||||
blotter.reject(open_order_id)
|
||||
self.assertEqual(len(blotter.new_orders), 2)
|
||||
self.assertEqual(len(blotter.open_orders[asset_24]), 1)
|
||||
self.assertEqual(len(blotter.open_orders[self.asset_24]), 1)
|
||||
still_open_order = blotter.new_orders[0]
|
||||
self.assertEqual(still_open_order.id, second_order_id)
|
||||
self.assertEqual(still_open_order.status, ORDER_STATUS.OPEN)
|
||||
@@ -203,9 +202,9 @@ class BlotterTestCase(WithCreateBarData,
|
||||
# Do it again, but reject it at a later time (after tradesimulation
|
||||
# pulls it from new_orders)
|
||||
blotter = Blotter(self.sim_params.data_frequency,
|
||||
self.env.asset_finder)
|
||||
new_open_id = blotter.order(asset_24, 10, MarketOrder())
|
||||
new_open_order = blotter.open_orders[asset_24][0]
|
||||
self.asset_finder)
|
||||
new_open_id = blotter.order(self.asset_24, 10, MarketOrder())
|
||||
new_open_order = blotter.open_orders[self.asset_24][0]
|
||||
self.assertEqual(new_open_id, new_open_order.id)
|
||||
# Pretend that the trade simulation did this.
|
||||
blotter.new_orders = []
|
||||
@@ -220,9 +219,9 @@ class BlotterTestCase(WithCreateBarData,
|
||||
# You can't reject a filled order.
|
||||
# Reset for paranoia
|
||||
blotter = Blotter(self.sim_params.data_frequency,
|
||||
self.env.asset_finder)
|
||||
self.asset_finder)
|
||||
blotter.slippage_func = FixedSlippage()
|
||||
filled_id = blotter.order(asset_24, 100, MarketOrder())
|
||||
filled_id = blotter.order(self.asset_24, 100, MarketOrder())
|
||||
filled_order = None
|
||||
blotter.current_dt = self.sim_params.sessions[-1]
|
||||
bar_data = self.create_bardata(
|
||||
@@ -236,7 +235,7 @@ class BlotterTestCase(WithCreateBarData,
|
||||
self.assertEqual(filled_order.id, filled_id)
|
||||
self.assertIn(filled_order, blotter.new_orders)
|
||||
self.assertEqual(filled_order.status, ORDER_STATUS.FILLED)
|
||||
self.assertNotIn(filled_order, blotter.open_orders[asset_24])
|
||||
self.assertNotIn(filled_order, blotter.open_orders[self.asset_24])
|
||||
|
||||
blotter.reject(filled_id)
|
||||
updated_order = blotter.orders[filled_id]
|
||||
@@ -249,27 +248,25 @@ class BlotterTestCase(WithCreateBarData,
|
||||
status to OPEN/FILLED as necessary
|
||||
"""
|
||||
blotter = Blotter(self.sim_params.data_frequency,
|
||||
self.env.asset_finder)
|
||||
self.asset_finder)
|
||||
# Nothing happens on held of a non-existent order
|
||||
blotter.hold(56)
|
||||
self.assertEqual(blotter.new_orders, [])
|
||||
|
||||
asset_24 = blotter.asset_finder.retrieve_asset(24)
|
||||
|
||||
open_id = blotter.order(asset_24, 100, MarketOrder())
|
||||
open_order = blotter.open_orders[asset_24][0]
|
||||
open_id = blotter.order(self.asset_24, 100, MarketOrder())
|
||||
open_order = blotter.open_orders[self.asset_24][0]
|
||||
self.assertEqual(open_order.id, open_id)
|
||||
|
||||
blotter.hold(open_id)
|
||||
self.assertEqual(len(blotter.new_orders), 1)
|
||||
self.assertEqual(len(blotter.open_orders[asset_24]), 1)
|
||||
self.assertEqual(len(blotter.open_orders[self.asset_24]), 1)
|
||||
held_order = blotter.new_orders[0]
|
||||
self.assertEqual(held_order.status, ORDER_STATUS.HELD)
|
||||
self.assertEqual(held_order.reason, '')
|
||||
|
||||
blotter.cancel(held_order.id)
|
||||
self.assertEqual(len(blotter.new_orders), 1)
|
||||
self.assertEqual(len(blotter.open_orders[asset_24]), 0)
|
||||
self.assertEqual(len(blotter.open_orders[self.asset_24]), 0)
|
||||
cancelled_order = blotter.new_orders[0]
|
||||
self.assertEqual(cancelled_order.id, held_order.id)
|
||||
self.assertEqual(cancelled_order.status, ORDER_STATUS.CANCELLED)
|
||||
@@ -288,10 +285,9 @@ class BlotterTestCase(WithCreateBarData,
|
||||
ORDER_STATUS.FILLED
|
||||
|
||||
blotter = Blotter(self.sim_params.data_frequency,
|
||||
self.env.asset_finder)
|
||||
open_id = blotter.order(blotter.asset_finder.retrieve_asset(24),
|
||||
order_size, MarketOrder())
|
||||
open_order = blotter.open_orders[asset_24][0]
|
||||
self.asset_finder)
|
||||
open_id = blotter.order(self.asset_24, order_size, MarketOrder())
|
||||
open_order = blotter.open_orders[self.asset_24][0]
|
||||
self.assertEqual(open_id, open_order.id)
|
||||
blotter.hold(open_id)
|
||||
held_order = blotter.new_orders[0]
|
||||
@@ -312,27 +308,58 @@ class BlotterTestCase(WithCreateBarData,
|
||||
|
||||
def test_prune_orders(self):
|
||||
blotter = Blotter(self.sim_params.data_frequency,
|
||||
self.env.asset_finder)
|
||||
self.asset_finder)
|
||||
|
||||
asset_24 = blotter.asset_finder.retrieve_asset(24)
|
||||
asset_25 = blotter.asset_finder.retrieve_asset(25)
|
||||
|
||||
blotter.order(asset_24, 100, MarketOrder())
|
||||
open_order = blotter.open_orders[asset_24][0]
|
||||
blotter.order(self.asset_24, 100, MarketOrder())
|
||||
open_order = blotter.open_orders[self.asset_24][0]
|
||||
|
||||
blotter.prune_orders([])
|
||||
self.assertEqual(1, len(blotter.open_orders[asset_24]))
|
||||
self.assertEqual(1, len(blotter.open_orders[self.asset_24]))
|
||||
|
||||
blotter.prune_orders([open_order])
|
||||
self.assertEqual(0, len(blotter.open_orders[asset_24]))
|
||||
self.assertEqual(0, len(blotter.open_orders[self.asset_24]))
|
||||
|
||||
# prune an order that isn't in our our open orders list, make sure
|
||||
# nothing blows up
|
||||
|
||||
other_order = Order(
|
||||
dt=blotter.current_dt,
|
||||
sid=asset_25,
|
||||
sid=self.asset_25,
|
||||
amount=1
|
||||
)
|
||||
|
||||
blotter.prune_orders([other_order])
|
||||
|
||||
def test_batch_order_matches_multiple_orders(self):
|
||||
"""
|
||||
Ensure the effect of order_batch is the same as multiple calls to
|
||||
order.
|
||||
"""
|
||||
blotter1 = Blotter(self.sim_params.data_frequency,
|
||||
self.asset_finder)
|
||||
blotter2 = Blotter(self.sim_params.data_frequency,
|
||||
self.asset_finder)
|
||||
for i in range(1, 4):
|
||||
order_arg_lists = [
|
||||
(self.asset_24, i * 100, MarketOrder()),
|
||||
(self.asset_25, i * 100, LimitOrder(i * 100 + 1)),
|
||||
]
|
||||
|
||||
order_batch_ids = blotter1.batch_order(order_arg_lists)
|
||||
order_ids = []
|
||||
for order_args in order_arg_lists:
|
||||
order_ids.append(blotter2.order(*order_args))
|
||||
self.assertEqual(len(order_batch_ids), len(order_ids))
|
||||
|
||||
self.assertEqual(len(blotter1.open_orders),
|
||||
len(blotter2.open_orders))
|
||||
|
||||
for (asset, _, _), order_batch_id, order_id in zip(
|
||||
order_arg_lists, order_batch_ids, order_ids
|
||||
):
|
||||
self.assertEqual(len(blotter1.open_orders[asset]),
|
||||
len(blotter2.open_orders[asset]))
|
||||
self.assertEqual(order_batch_id,
|
||||
blotter1.open_orders[asset][i-1].id)
|
||||
self.assertEqual(order_id,
|
||||
blotter2.open_orders[asset][i-1].id)
|
||||
|
||||
+80
-27
@@ -13,6 +13,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
from collections import Iterable
|
||||
try:
|
||||
# optional cython based OrderedDict
|
||||
from cyordereddict import OrderedDict
|
||||
except ImportError:
|
||||
from collections import OrderedDict
|
||||
from copy import copy
|
||||
import operator as op
|
||||
import warnings
|
||||
@@ -33,6 +38,7 @@ from six import (
|
||||
itervalues,
|
||||
string_types,
|
||||
viewkeys,
|
||||
viewvalues,
|
||||
)
|
||||
|
||||
from zipline._protocol import handle_non_market_minutes
|
||||
@@ -1382,8 +1388,9 @@ class TradingAlgorithm(object):
|
||||
|
||||
Returns
|
||||
-------
|
||||
order_id : str
|
||||
The unique identifier for this order.
|
||||
order_id : str or None
|
||||
The unique identifier for this order, or None if no order was
|
||||
placed.
|
||||
|
||||
Notes
|
||||
-----
|
||||
@@ -1404,6 +1411,12 @@ class TradingAlgorithm(object):
|
||||
if not self._can_order_asset(asset):
|
||||
return None
|
||||
|
||||
amount, style = self._calculate_order(asset, amount,
|
||||
limit_price, stop_price, style)
|
||||
return self.blotter.order(asset, amount, style)
|
||||
|
||||
def _calculate_order(self, asset, amount,
|
||||
limit_price=None, stop_price=None, style=None):
|
||||
# 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
|
||||
@@ -1421,7 +1434,7 @@ class TradingAlgorithm(object):
|
||||
style = self.__convert_order_params_for_blotter(limit_price,
|
||||
stop_price,
|
||||
style)
|
||||
return self.blotter.order(asset, amount, style)
|
||||
return amount, style
|
||||
|
||||
def validate_order_params(self,
|
||||
asset,
|
||||
@@ -1744,11 +1757,15 @@ class TradingAlgorithm(object):
|
||||
if not self._can_order_asset(asset):
|
||||
return None
|
||||
|
||||
amount = self._calculate_order_percent_amount(asset, percent)
|
||||
return self.order(asset, amount,
|
||||
limit_price=limit_price,
|
||||
stop_price=stop_price,
|
||||
style=style)
|
||||
|
||||
def _calculate_order_percent_amount(self, asset, percent):
|
||||
value = self.portfolio.portfolio_value * percent
|
||||
return self.order_value(asset, value,
|
||||
limit_price=limit_price,
|
||||
stop_price=stop_price,
|
||||
style=style)
|
||||
return self._calculate_order_value_amount(asset, value)
|
||||
|
||||
@api_method
|
||||
@disallowed_in_before_trading_start(OrderInBeforeTradingStart())
|
||||
@@ -1810,18 +1827,18 @@ class TradingAlgorithm(object):
|
||||
if not self._can_order_asset(asset):
|
||||
return None
|
||||
|
||||
amount = self._calculate_order_target_amount(asset, target)
|
||||
return self.order(asset, amount,
|
||||
limit_price=limit_price,
|
||||
stop_price=stop_price,
|
||||
style=style)
|
||||
|
||||
def _calculate_order_target_amount(self, asset, target):
|
||||
if asset in self.portfolio.positions:
|
||||
current_position = self.portfolio.positions[asset].amount
|
||||
req_shares = target - current_position
|
||||
return self.order(asset, req_shares,
|
||||
limit_price=limit_price,
|
||||
stop_price=stop_price,
|
||||
style=style)
|
||||
else:
|
||||
return self.order(asset, target,
|
||||
limit_price=limit_price,
|
||||
stop_price=stop_price,
|
||||
style=style)
|
||||
target -= current_position
|
||||
|
||||
return target
|
||||
|
||||
@api_method
|
||||
@disallowed_in_before_trading_start(OrderInBeforeTradingStart())
|
||||
@@ -1885,10 +1902,11 @@ class TradingAlgorithm(object):
|
||||
return None
|
||||
|
||||
target_amount = self._calculate_order_value_amount(asset, target)
|
||||
return self.order_target(asset, target_amount,
|
||||
limit_price=limit_price,
|
||||
stop_price=stop_price,
|
||||
style=style)
|
||||
amount = self._calculate_order_target_amount(asset, target_amount)
|
||||
return self.order(asset, amount,
|
||||
limit_price=limit_price,
|
||||
stop_price=stop_price,
|
||||
style=style)
|
||||
|
||||
@api_method
|
||||
@disallowed_in_before_trading_start(OrderInBeforeTradingStart())
|
||||
@@ -1904,7 +1922,7 @@ class TradingAlgorithm(object):
|
||||
----------
|
||||
asset : Asset
|
||||
The asset that this order is for.
|
||||
percent : float
|
||||
target : float
|
||||
The desired percentage of the porfolio value to allocate to
|
||||
``asset``. This is specified as a decimal, for example:
|
||||
0.50 means 50%.
|
||||
@@ -1947,11 +1965,46 @@ class TradingAlgorithm(object):
|
||||
if not self._can_order_asset(asset):
|
||||
return None
|
||||
|
||||
target_value = self.portfolio.portfolio_value * target
|
||||
return self.order_target_value(asset, target_value,
|
||||
limit_price=limit_price,
|
||||
stop_price=stop_price,
|
||||
style=style)
|
||||
amount = self._calculate_order_target_percent_amount(asset, target)
|
||||
return self.order(asset, amount,
|
||||
limit_price=limit_price,
|
||||
stop_price=stop_price,
|
||||
style=style)
|
||||
|
||||
def _calculate_order_target_percent_amount(self, asset, target):
|
||||
target_amount = self._calculate_order_percent_amount(asset, target)
|
||||
return self._calculate_order_target_amount(asset, target_amount)
|
||||
|
||||
@api_method
|
||||
@disallowed_in_before_trading_start(OrderInBeforeTradingStart())
|
||||
def batch_order_target_percent(self, weights):
|
||||
"""Place orders towards a given portfolio of weights.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
weights : collections.Mapping[Asset -> float]
|
||||
|
||||
Returns
|
||||
-------
|
||||
order_ids : pd.Series[Asset -> str]
|
||||
The unique identifiers for the orders that were placed.
|
||||
|
||||
See Also
|
||||
--------
|
||||
:func:`zipline.api.order_target_percent`
|
||||
"""
|
||||
order_args = OrderedDict()
|
||||
for asset, target in iteritems(weights):
|
||||
if self._can_order_asset(asset):
|
||||
amount = self._calculate_order_target_percent_amount(
|
||||
asset, target,
|
||||
)
|
||||
amount, style = self._calculate_order(asset, amount)
|
||||
order_args[asset] = (asset, amount, style)
|
||||
|
||||
order_ids = self.blotter.batch_order(viewvalues(order_args))
|
||||
order_ids = pd.Series(data=order_ids, index=order_args)
|
||||
return order_ids[~order_ids.isnull()]
|
||||
|
||||
@error_keywords(sid='Keyword argument `sid` is no longer supported for '
|
||||
'get_open_orders. Use `asset` instead.')
|
||||
|
||||
@@ -75,12 +75,29 @@ class Blotter(object):
|
||||
self.current_dt = dt
|
||||
|
||||
def order(self, sid, amount, style, order_id=None):
|
||||
"""Place an order.
|
||||
|
||||
# something could be done with amount to further divide
|
||||
# between buy by share count OR buy shares up to a dollar amount
|
||||
# numeric == share count AND "$dollar.cents" == cost amount
|
||||
Parameters
|
||||
----------
|
||||
asset : zipline.assets.Asset
|
||||
The asset that this order is for.
|
||||
amount : int
|
||||
The amount of shares to order. If ``amount`` is positive, this is
|
||||
the number of shares to buy or cover. If ``amount`` is negative,
|
||||
this is the number of shares to sell or short.
|
||||
style : zipline.finance.execution.ExecutionStyle
|
||||
The execution style for the order.
|
||||
order_id : str, optional
|
||||
The unique identifier for this order.
|
||||
|
||||
"""
|
||||
Returns
|
||||
-------
|
||||
order_id : str or None
|
||||
The unique identifier for this order, or None if no order was
|
||||
placed.
|
||||
|
||||
Notes
|
||||
-----
|
||||
amount > 0 :: Buy/Cover
|
||||
amount < 0 :: Sell/Short
|
||||
Market order: order(sid, amount)
|
||||
@@ -89,9 +106,13 @@ class Blotter(object):
|
||||
StopLimit order: order(sid, amount, style=StopLimitOrder(limit_price,
|
||||
stop_price))
|
||||
"""
|
||||
# something could be done with amount to further divide
|
||||
# between buy by share count OR buy shares up to a dollar amount
|
||||
# numeric == share count AND "$dollar.cents" == cost amount
|
||||
|
||||
if amount == 0:
|
||||
# Don't bother placing orders for 0 shares.
|
||||
return
|
||||
return None
|
||||
elif amount > self.max_shares:
|
||||
# Arbitrary limit of 100 billion (US) shares will never be
|
||||
# exceeded except by a buggy algorithm.
|
||||
@@ -114,6 +135,27 @@ class Blotter(object):
|
||||
|
||||
return order.id
|
||||
|
||||
def batch_order(self, order_arg_lists):
|
||||
"""Place a batch of orders.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
order_arg_lists : iterable[tuple]
|
||||
Tuples of args that `order` expects.
|
||||
|
||||
Returns
|
||||
-------
|
||||
order_ids : list[str or None]
|
||||
The unique identifier (or None) for each of the orders placed
|
||||
(or not placed).
|
||||
|
||||
Notes
|
||||
-----
|
||||
This is required for `Blotter` subclasses to be able to place a batch
|
||||
of orders, instead of being passed the order requests one at a time.
|
||||
"""
|
||||
return [self.order(*order_args) for order_args in order_arg_lists]
|
||||
|
||||
def cancel(self, order_id, relay_status=True):
|
||||
if order_id not in self.orders:
|
||||
return
|
||||
|
||||
@@ -58,7 +58,7 @@ class Order(object):
|
||||
assert isinstance(sid, Asset)
|
||||
|
||||
# get a string representation of the uuid.
|
||||
self.id = id or self.make_id()
|
||||
self.id = self.make_id() if id is None else id
|
||||
self.dt = dt
|
||||
self.reason = None
|
||||
self.created = dt
|
||||
|
||||
@@ -411,7 +411,7 @@ class TestTargetPercentAlgorithm(TradingAlgorithm):
|
||||
|
||||
def handle_data(self, data):
|
||||
if not self.ordered:
|
||||
assert 0 not in self.portfolio.positions
|
||||
assert not self.portfolio.positions
|
||||
else:
|
||||
# Since you can't own fractional shares (at least in this
|
||||
# example), we want to make sure that our target amount is
|
||||
@@ -429,9 +429,17 @@ class TestTargetPercentAlgorithm(TradingAlgorithm):
|
||||
"Orders not filled at current price."
|
||||
|
||||
self.sale_price = data.current(sid(0), "price")
|
||||
self.order_target_percent(self.sid(0), .002)
|
||||
self._order(sid(0), .002)
|
||||
self.ordered = True
|
||||
|
||||
def _order(self, asset, target):
|
||||
return self.order_target_percent(asset, target)
|
||||
|
||||
|
||||
class TestBatchTargetPercentAlgorithm(TestTargetPercentAlgorithm):
|
||||
def _order(self, asset, target):
|
||||
return self.batch_order_target_percent({asset: target})
|
||||
|
||||
|
||||
class TestTargetValueAlgorithm(TradingAlgorithm):
|
||||
def initialize(self):
|
||||
|
||||
@@ -7,6 +7,7 @@ from .core import ( # noqa
|
||||
FetcherDataPortal,
|
||||
MockDailyBarReader,
|
||||
OpenPrice,
|
||||
RecordBatchBlotter,
|
||||
add_security_data,
|
||||
all_pairs_matching_predicate,
|
||||
all_subindices,
|
||||
|
||||
@@ -39,6 +39,7 @@ from zipline.data.us_equity_pricing import (
|
||||
BcolzDailyBarWriter,
|
||||
SQLiteAdjustmentWriter,
|
||||
)
|
||||
from zipline.finance.blotter import Blotter
|
||||
from zipline.finance.trading import TradingEnvironment
|
||||
from zipline.finance.order import ORDER_STATUS
|
||||
from zipline.lib.labelarray import LabelArray
|
||||
@@ -1502,6 +1503,20 @@ def ensure_doctest(f, name=None):
|
||||
return f
|
||||
|
||||
|
||||
class RecordBatchBlotter(Blotter):
|
||||
"""Blotter that tracks how its batch_order method was called.
|
||||
"""
|
||||
def __init__(self, data_frequency, asset_finder):
|
||||
super(RecordBatchBlotter, self).__init__(
|
||||
data_frequency, asset_finder,
|
||||
)
|
||||
self.order_batch_called = []
|
||||
|
||||
def batch_order(self, *args, **kwargs):
|
||||
self.order_batch_called.append((args, kwargs))
|
||||
return super(RecordBatchBlotter, self).batch_order(*args, **kwargs)
|
||||
|
||||
|
||||
####################################
|
||||
# Shared factors for pipeline tests.
|
||||
####################################
|
||||
|
||||
@@ -366,6 +366,12 @@ class WithAssetFinder(WithDefaultDateBounds):
|
||||
|
||||
@classmethod
|
||||
def make_asset_finder(cls):
|
||||
"""Returns a new AssetFinder
|
||||
|
||||
Returns
|
||||
-------
|
||||
asset_finder : zipline.assets.AssetFinder
|
||||
"""
|
||||
return cls.enter_class_context(tmp_asset_finder(
|
||||
url=cls.make_asset_finder_db_url(),
|
||||
equities=cls.make_equity_info(),
|
||||
|
||||
@@ -67,7 +67,7 @@ class TradingCalendarDispatcher(object):
|
||||
|
||||
Returns
|
||||
-------
|
||||
TradingCalendar
|
||||
calendar : zipline.utils.calendars.TradingCalendar
|
||||
The desired calendar.
|
||||
"""
|
||||
canonical_name = self.resolve_alias(name)
|
||||
|
||||
Reference in New Issue
Block a user