mirror of
https://github.com/wassname/catalyst.git
synced 2026-06-28 09:27:19 +08:00
Revert "ENH Allow order_percent to work with various market values"
This reverts commit dd37a49f2f as it lead to different algo behavior we have to investigate first.
This commit is contained in:
@@ -45,14 +45,11 @@ from zipline.test_algorithms import (
|
||||
TestOrderAlgorithm,
|
||||
TestOrderInstantAlgorithm,
|
||||
TestOrderPercentAlgorithm,
|
||||
TestOrderPercentAlgorithmPercentOf,
|
||||
TestOrderStyleForwardingAlgorithm,
|
||||
TestOrderValueAlgorithm,
|
||||
TestRegisterTransformAlgorithm,
|
||||
TestTargetAlgorithm,
|
||||
TestTargetAlgorithm_NonInt,
|
||||
TestTargetPercentAlgorithm,
|
||||
TestTargetPercentAlgorithmPercentOf,
|
||||
TestTargetValueAlgorithm,
|
||||
SetLongOnlyAlgorithm,
|
||||
SetMaxPositionSizeAlgorithm,
|
||||
@@ -306,9 +303,6 @@ class TestTransformAlgorithm(TestCase):
|
||||
self.panel_source, self.panel = \
|
||||
factory.create_test_panel_source(self.sim_params)
|
||||
|
||||
self.df_large = pd.concat([self.df] * 10, 1)
|
||||
self.df_large.columns = range(10)
|
||||
|
||||
def test_source_as_input(self):
|
||||
algo = TestRegisterTransformAlgorithm(
|
||||
sim_params=self.sim_params,
|
||||
@@ -380,7 +374,6 @@ class TestTransformAlgorithm(TestCase):
|
||||
AlgoClasses = [TestOrderAlgorithm,
|
||||
TestOrderValueAlgorithm,
|
||||
TestTargetAlgorithm,
|
||||
TestTargetAlgorithm_NonInt,
|
||||
TestOrderPercentAlgorithm,
|
||||
TestTargetPercentAlgorithm,
|
||||
TestTargetValueAlgorithm]
|
||||
@@ -391,16 +384,6 @@ class TestTransformAlgorithm(TestCase):
|
||||
)
|
||||
algo.run(self.df)
|
||||
|
||||
AlgoClasses2 = [
|
||||
TestOrderPercentAlgorithmPercentOf,
|
||||
TestTargetPercentAlgorithmPercentOf]
|
||||
|
||||
for AlgoClass in AlgoClasses2:
|
||||
algo = AlgoClass(
|
||||
sim_params=self.sim_params,
|
||||
)
|
||||
algo.run(self.df_large)
|
||||
|
||||
def test_order_method_style_forwarding(self):
|
||||
|
||||
method_names_to_test = ['order',
|
||||
|
||||
+19
-126
@@ -88,21 +88,6 @@ from zipline.history.history_container import HistoryContainer
|
||||
DEFAULT_CAPITAL_BASE = float("1.0e5")
|
||||
|
||||
|
||||
def round_if_near_integer(a, epsilon=1e-4):
|
||||
"""
|
||||
Round a to the nearest integer if that integer is within an epsilon
|
||||
of a.
|
||||
"""
|
||||
if abs(a - round(a)) <= epsilon:
|
||||
return round(a)
|
||||
else:
|
||||
return a
|
||||
|
||||
|
||||
def round_shares(shares):
|
||||
return int(round_if_near_integer(shares))
|
||||
|
||||
|
||||
class TradingAlgorithm(object):
|
||||
"""
|
||||
Base class for trading algorithms. Inherit and overload
|
||||
@@ -651,10 +636,20 @@ class TradingAlgorithm(object):
|
||||
Place an order using the specified parameters.
|
||||
"""
|
||||
|
||||
def round_if_near_integer(a, epsilon=1e-4):
|
||||
"""
|
||||
Round a to the nearest integer if that integer is within an epsilon
|
||||
of a.
|
||||
"""
|
||||
if abs(a - round(a)) <= epsilon:
|
||||
return round(a)
|
||||
else:
|
||||
return a
|
||||
|
||||
# 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
|
||||
amount = round_shares(amount)
|
||||
amount = int(round_if_near_integer(amount))
|
||||
|
||||
# Raises a ZiplineError if invalid parameters are detected.
|
||||
self.validate_order_params(sid,
|
||||
@@ -871,98 +866,16 @@ class TradingAlgorithm(object):
|
||||
assert value in ('daily', 'minute')
|
||||
self.sim_params.data_frequency = value
|
||||
|
||||
def get_market_value(self, mv_type=None, filter_fn=None):
|
||||
"""
|
||||
Returns the requested market value.
|
||||
|
||||
Options for mv_type are:
|
||||
portfolio [default]: net exposure or portfolio value
|
||||
net: net exposure or portfolio value
|
||||
gross: gross exposure
|
||||
cash: available cash
|
||||
ex_cash: net invested capital, ex-cash
|
||||
longs: long invested capital
|
||||
longs_cash: long invested capital plus cash
|
||||
shorts: short invested capital
|
||||
|
||||
Alternatively, a filter_fn can be supplied. The filter_fn
|
||||
should accept a Position and return True if that Position's
|
||||
market_value should be included in the total and False otherwise.
|
||||
"""
|
||||
|
||||
period = self.perf_tracker.cumulative_performance
|
||||
|
||||
# first try the filter_fn, if supplied
|
||||
if filter_fn is not None:
|
||||
positions = period.get_positions()
|
||||
mv = sum(
|
||||
p.amount * p.last_sale_price
|
||||
for p in positions.values()
|
||||
if filter_fn(p))
|
||||
|
||||
# net portfolio value
|
||||
elif mv_type is None or mv_type == 'portfolio' or mv_type == 'net':
|
||||
mv = period.ending_value + period.ending_cash
|
||||
|
||||
elif mv_type == 'gross':
|
||||
mv = period._gross_exposure()
|
||||
|
||||
# portfolio cash
|
||||
elif mv_type == 'cash':
|
||||
mv = period.ending_cash
|
||||
|
||||
# net invested capital
|
||||
elif mv_type == 'ex_cash':
|
||||
mv = period.ending_value
|
||||
|
||||
# long invested capital
|
||||
elif mv_type == 'longs':
|
||||
mv = period._long_exposure()
|
||||
|
||||
# long invested capital, plus cash
|
||||
elif mv_type == 'longs_cash':
|
||||
mv = period._long_exposure() + period.ending_cash
|
||||
|
||||
# short invested capital
|
||||
elif mv_type == 'shorts':
|
||||
mv = period._short_exposure()
|
||||
|
||||
else:
|
||||
raise ValueError(
|
||||
'Unrecognized value for "mv_type": {}'.format(mv_type))
|
||||
|
||||
return mv
|
||||
|
||||
@api_method
|
||||
def order_percent(self, sid, percent,
|
||||
limit_price=None,
|
||||
stop_price=None,
|
||||
style=None,
|
||||
percent_of=None,
|
||||
percent_of_fn=None):
|
||||
limit_price=None, stop_price=None, style=None):
|
||||
"""
|
||||
Place an order in the specified security corresponding to the given
|
||||
percent of some market value. If no market value is specified (via
|
||||
`percent_of`) then the net portfolio value is used.
|
||||
|
||||
Options for percent_of are:
|
||||
portfolio [default]: net exposure or portfolio value
|
||||
net: net exposure or portfolio value
|
||||
gross: gross exposure
|
||||
cash: available cash
|
||||
ex_cash: net invested capital, ex-cash
|
||||
longs: long invested capital
|
||||
longs_cash: long invested capital plus cash
|
||||
shorts: short invested capital
|
||||
|
||||
Alternatively, a percent_of_fn can be supplied. The percent_of_fn
|
||||
should accept a Position and return True if that Position's
|
||||
market_value should be included in the total and False otherwise.
|
||||
percent of the current portfolio value.
|
||||
|
||||
Note that percent must expressed as a decimal (0.50 means 50\%).
|
||||
"""
|
||||
mv = self.get_market_value(mv_type=percent_of, filter_fn=percent_of_fn)
|
||||
value = percent * mv
|
||||
value = self.portfolio.portfolio_value * percent
|
||||
return self.order_value(sid, value,
|
||||
limit_price=limit_price,
|
||||
stop_price=stop_price,
|
||||
@@ -980,7 +893,7 @@ class TradingAlgorithm(object):
|
||||
"""
|
||||
if sid in self.portfolio.positions:
|
||||
current_position = self.portfolio.positions[sid].amount
|
||||
req_shares = round_shares(target) - current_position
|
||||
req_shares = target - current_position
|
||||
return self.order(sid, req_shares,
|
||||
limit_price=limit_price,
|
||||
stop_price=stop_price,
|
||||
@@ -1016,37 +929,17 @@ class TradingAlgorithm(object):
|
||||
|
||||
@api_method
|
||||
def order_target_percent(self, sid, target,
|
||||
limit_price=None,
|
||||
stop_price=None,
|
||||
style=None,
|
||||
percent_of=None,
|
||||
percent_of_fn=None):
|
||||
limit_price=None, stop_price=None, style=None):
|
||||
"""
|
||||
Place an order to adjust a position to a target percent of some market
|
||||
value. If no market value is specified (via `percent_of`) then the net
|
||||
portfolio value is used. If the position doesn't already exist, this is
|
||||
Place an order to adjust a position to a target percent of the
|
||||
current portfolio value. If the position doesn't already exist, this is
|
||||
equivalent to placing a new order. If the position does exist, this is
|
||||
equivalent to placing an order for the difference between the target
|
||||
percent and the current percent.
|
||||
|
||||
Options for percent_of are:
|
||||
portfolio [default]: net exposure or portfolio value
|
||||
net: net exposure or portfolio value
|
||||
gross: gross exposure
|
||||
cash: available cash
|
||||
ex_cash: net invested capital, ex-cash
|
||||
longs: long invested capital
|
||||
longs_cash: long invested capital plus cash
|
||||
shorts: short invested capital
|
||||
|
||||
Alternatively, a percent_of_fn can be supplied. The percent_of_fn
|
||||
should accept a Position and return True if that Position's
|
||||
market_value should be included in the total and False otherwise.
|
||||
|
||||
Note that target must expressed as a decimal (0.50 means 50\%).
|
||||
"""
|
||||
mv = self.get_market_value(mv_type=percent_of, filter_fn=percent_of_fn)
|
||||
target_value = target * mv
|
||||
target_value = self.portfolio.portfolio_value * target
|
||||
return self.order_target_value(sid, target_value,
|
||||
limit_price=limit_price,
|
||||
stop_price=stop_price,
|
||||
|
||||
+4
-134
@@ -79,7 +79,7 @@ from nose.tools import assert_raises
|
||||
from six.moves import range
|
||||
from six import itervalues
|
||||
|
||||
from zipline.algorithm import TradingAlgorithm, round_shares
|
||||
from zipline.algorithm import TradingAlgorithm
|
||||
from zipline.api import FixedSlippage
|
||||
from zipline.errors import UnsupportedOrderParameters
|
||||
from zipline.finance.execution import (
|
||||
@@ -345,33 +345,6 @@ class TestTargetAlgorithm(TradingAlgorithm):
|
||||
self.order_target(0, self.target_shares)
|
||||
|
||||
|
||||
class TestTargetAlgorithm_NonInt(TradingAlgorithm):
|
||||
def initialize(self):
|
||||
self.target_shares = 0
|
||||
self.sale_price = None
|
||||
self.i = 0
|
||||
|
||||
def handle_data(self, data):
|
||||
if self.target_shares == 0:
|
||||
assert 0 not in self.portfolio.positions
|
||||
else:
|
||||
assert self.portfolio.positions[0]['amount'] == \
|
||||
self.target_shares, "Orders not filled correctly."
|
||||
assert self.portfolio.positions[0]['last_sale_price'] == \
|
||||
data[0].price, "Orders not filled at current price."
|
||||
|
||||
if self.i == 0:
|
||||
self.target_shares = 5
|
||||
self.order_target(0, 5.1)
|
||||
elif self.i == 1:
|
||||
self.target_shares = 10
|
||||
self.order_target(0, 10.1)
|
||||
elif self.i == 2:
|
||||
self.target_shares = 5
|
||||
self.order_target(0, 5.1)
|
||||
self.i += 1
|
||||
|
||||
|
||||
class TestOrderPercentAlgorithm(TradingAlgorithm):
|
||||
def initialize(self):
|
||||
self.target_shares = 0
|
||||
@@ -395,126 +368,23 @@ class TestOrderPercentAlgorithm(TradingAlgorithm):
|
||||
/ data[0].price)
|
||||
|
||||
|
||||
class TestOrderPercentAlgorithmPercentOf(TradingAlgorithm):
|
||||
def initialize(self):
|
||||
self.target_shares = dict([(i, 0) for i in range(8)])
|
||||
|
||||
def handle_data(self, data):
|
||||
for sid, target_shares in self.target_shares.items():
|
||||
position = self.portfolio.positions[sid]
|
||||
assert target_shares == position.amount
|
||||
|
||||
# reduce sizes due to volume limitiations
|
||||
full = 0.001
|
||||
half = 0.0005
|
||||
|
||||
pos_values = {}
|
||||
for i in range(len(data.keys())):
|
||||
pos_values[i] = (
|
||||
self.portfolio.positions[i].amount
|
||||
* self.portfolio.positions[i].last_sale_price)
|
||||
|
||||
port = self.portfolio.portfolio_value
|
||||
cash = self.portfolio.cash
|
||||
longs = sum(v for v in pos_values.values() if v > 0)
|
||||
shorts = sum(v for v in pos_values.values() if v < 0)
|
||||
gross = sum(abs(v) for v in pos_values.values())
|
||||
group = [0, 1, 2]
|
||||
group_val = sum(v for sid, v in pos_values.items() if sid in group)
|
||||
|
||||
def expected_shares(sid, value):
|
||||
return round_shares(value / data[sid].price)
|
||||
|
||||
self.order_percent(0, full, percent_of='portfolio')
|
||||
self.order_percent(1, -half, percent_of='cash')
|
||||
self.order_percent(2, half, percent_of='ex_cash')
|
||||
self.order_percent(3, half, percent_of='longs')
|
||||
self.order_percent(4, half, percent_of='longs_cash')
|
||||
self.order_percent(5, half, percent_of='shorts')
|
||||
self.order_percent(
|
||||
6, half, percent_of_fn=lambda p: p.sid in group)
|
||||
self.order_percent(7, full, percent_of='gross')
|
||||
|
||||
self.target_shares[0] += expected_shares(0, full * port)
|
||||
self.target_shares[1] += expected_shares(1, -half * cash)
|
||||
self.target_shares[2] += expected_shares(2, half * (port - cash))
|
||||
self.target_shares[3] += expected_shares(3, half * longs)
|
||||
self.target_shares[4] += expected_shares(4, half * (longs + cash))
|
||||
self.target_shares[5] += expected_shares(5, half * shorts)
|
||||
self.target_shares[6] += expected_shares(6, half * group_val)
|
||||
self.target_shares[7] += expected_shares(7, full * gross)
|
||||
|
||||
|
||||
class TestTargetPercentAlgorithm(TradingAlgorithm):
|
||||
def initialize(self):
|
||||
self.target_shares = 0
|
||||
self.sale_price = None
|
||||
self.exp_value = 0
|
||||
|
||||
def handle_data(self, data):
|
||||
if self.target_shares == 0:
|
||||
assert 0 not in self.portfolio.positions
|
||||
self.target_shares = 1
|
||||
else:
|
||||
value = self.portfolio.positions[0]['amount'] * self.sale_price
|
||||
assert abs(value - self.exp_value) <= self.sale_price, \
|
||||
assert np.round(self.portfolio.portfolio_value * 0.002) == \
|
||||
self.portfolio.positions[0]['amount'] * self.sale_price, \
|
||||
"Orders not filled correctly."
|
||||
assert self.portfolio.positions[0]['last_sale_price'] == \
|
||||
data[0].price, "Orders not filled at current price."
|
||||
self.sale_price = data[0].price
|
||||
self.order_target_percent(0, .002)
|
||||
self.exp_value = self.portfolio.portfolio_value * 0.002
|
||||
|
||||
|
||||
class TestTargetPercentAlgorithmPercentOf(TradingAlgorithm):
|
||||
def initialize(self):
|
||||
self.target_shares = dict([(i, 0) for i in range(8)])
|
||||
|
||||
def handle_data(self, data):
|
||||
|
||||
for sid, target_shares in self.target_shares.items():
|
||||
position = self.portfolio.positions[sid]
|
||||
assert target_shares == position.amount
|
||||
|
||||
# reduce sizes due to volume limitiations
|
||||
full = 0.001
|
||||
half = 0.0005
|
||||
|
||||
pos_values = {}
|
||||
for i in range(len(data.keys())):
|
||||
pos_values[i] = (
|
||||
self.portfolio.positions[i].amount
|
||||
* self.portfolio.positions[i].last_sale_price)
|
||||
|
||||
port = self.portfolio.portfolio_value
|
||||
cash = self.portfolio.cash
|
||||
longs = sum(v for v in pos_values.values() if v > 0)
|
||||
shorts = sum(v for v in pos_values.values() if v < 0)
|
||||
gross = sum(abs(v) for v in pos_values.values())
|
||||
group = [0, 1, 2]
|
||||
group_val = sum(v for sid, v in pos_values.items() if sid in group)
|
||||
|
||||
def expected_shares(sid, value):
|
||||
return round_shares(value / data[sid].price)
|
||||
|
||||
self.order_target_percent(0, full, percent_of='portfolio')
|
||||
self.order_target_percent(1, -half, percent_of='cash')
|
||||
self.order_target_percent(2, half, percent_of='ex_cash')
|
||||
self.order_target_percent(3, half, percent_of='longs')
|
||||
self.order_target_percent(4, half, percent_of='longs_cash')
|
||||
self.order_target_percent(5, half, percent_of='shorts')
|
||||
self.order_target_percent(
|
||||
6, half, percent_of_fn=lambda p: p.sid in group)
|
||||
self.order_target_percent(5, full, percent_of='gross')
|
||||
|
||||
self.target_shares[0] = expected_shares(0, full * port)
|
||||
self.target_shares[1] = expected_shares(1, -half * cash)
|
||||
self.target_shares[2] = expected_shares(2, half * (port - cash))
|
||||
self.target_shares[3] = expected_shares(3, half * longs)
|
||||
self.target_shares[4] = expected_shares(4, half * (longs + cash))
|
||||
self.target_shares[5] = expected_shares(5, half * shorts)
|
||||
self.target_shares[6] = expected_shares(6, half * group_val)
|
||||
self.target_shares[7] = expected_shares(7, full * gross)
|
||||
|
||||
|
||||
class TestTargetValueAlgorithm(TradingAlgorithm):
|
||||
@@ -536,7 +406,7 @@ class TestTargetValueAlgorithm(TradingAlgorithm):
|
||||
data[0].price, "Orders not filled at current price."
|
||||
|
||||
self.order_target_value(0, 20)
|
||||
self.target_shares = round_shares(20 / data[0].price)
|
||||
self.target_shares = np.round(20 / data[0].price)
|
||||
|
||||
|
||||
############################
|
||||
|
||||
Reference in New Issue
Block a user