ENH: Add method for computing current portfolio weights

This commit is contained in:
David Michalowicz
2017-05-30 13:38:22 -04:00
parent 731e3a8678
commit 94b1d5a40e
3 changed files with 137 additions and 3 deletions
+95 -2
View File
@@ -123,6 +123,7 @@ from zipline.test_algorithms import (
TestOrderPercentAlgorithm,
TestOrderStyleForwardingAlgorithm,
TestOrderValueAlgorithm,
TestPositionWeightsAlgorithm,
TestRegisterTransformAlgorithm,
TestTargetAlgorithm,
TestTargetPercentAlgorithm,
@@ -1095,10 +1096,62 @@ class TestPositions(WithLogger,
START_DATE = pd.Timestamp('2006-01-03', tz='utc')
END_DATE = pd.Timestamp('2006-01-06', tz='utc')
sids = ASSET_FINDER_EQUITY_SIDS = [1, 133]
ASSET_FINDER_EQUITY_SIDS = (1, 133)
@classmethod
def make_equity_daily_bar_data(cls):
frame = pd.DataFrame(
{
'open': [90, 95, 100, 105],
'high': [90, 95, 100, 105],
'low': [90, 95, 100, 105],
'close': [90, 95, 100, 105],
'volume': 100,
},
index=cls.equity_daily_bar_days,
)
return ((sid, frame) for sid in cls.asset_finder.equities_sids)
@classmethod
def make_futures_info(cls):
return pd.DataFrame.from_dict(
{
1000: {
'symbol': 'CLF06',
'root_symbol': 'CL',
'start_date': cls.START_DATE,
'end_date': cls.END_DATE,
'auto_close_date': cls.END_DATE + cls.trading_calendar.day,
'exchange': 'CME',
'multiplier': 100,
},
},
orient='index',
)
@classmethod
def make_future_minute_bar_data(cls):
trading_calendar = cls.trading_calendars[Future]
sids = cls.asset_finder.futures_sids
minutes = trading_calendar.minutes_for_sessions_in_range(
cls.future_minute_bar_days[0],
cls.future_minute_bar_days[-1],
)
frame = pd.DataFrame(
{
'open': 2.0,
'high': 2.0,
'low': 2.0,
'close': 2.0,
'volume': 100,
},
index=minutes,
)
return ((sid, frame) for sid in sids)
def test_empty_portfolio(self):
algo = EmptyPositionsAlgorithm(self.sids,
algo = EmptyPositionsAlgorithm(self.asset_finder.equities_sids,
sim_params=self.sim_params,
env=self.env)
daily_stats = algo.run(self.data_portal)
@@ -1124,6 +1177,46 @@ class TestPositions(WithLogger,
empty_positions = daily_stats.positions.map(lambda x: len(x) == 0)
self.assertTrue(empty_positions.all())
def test_position_weights(self):
sids = (1, 133, 1000)
equity_1, equity_133, future_1000 = \
self.asset_finder.retrieve_all(sids)
algo = TestPositionWeightsAlgorithm(
sids_and_amounts=zip(sids, [1, -1, 1]),
sim_params=self.sim_params,
env=self.env,
)
daily_stats = algo.run(self.data_portal)
expected_position_weights = [
# No positions held on the first day.
pd.Series({}),
# Each equity's weight is its price times the number of shares
# held. For example, we hold a long position in equity_1 so its
# weight is (+95.0 * 1) = 95. For a futures contract, its weight is
# the unit price times number of shares held times the multiplier.
# For future_1000, this is (2.0 * 1 * 100) = 200.
pd.Series({
equity_1: 95.0 / (95 + 95 + 200),
equity_133: -95.0 / (95 + 95 + 200),
future_1000: 200.0 / (95 + 95 + 200),
}),
pd.Series({
equity_1: 100.0 / (100 + 100 + 200),
equity_133: -100.0 / (100 + 100 + 200),
future_1000: 200.0 / (100 + 100 + 200),
}),
pd.Series({
equity_1: 105.0 / (105 + 105 + 200),
equity_133: -105.0 / (105 + 105 + 200),
future_1000: 200.0 / (105 + 105 + 200),
}),
]
for i, expected in enumerate(expected_position_weights):
assert_equal(daily_stats.iloc[i]['position_weights'], expected)
class TestBeforeTradingStart(WithDataPortal,
WithSimParams,
+25 -1
View File
@@ -16,7 +16,7 @@ from warnings import warn
import pandas as pd
from zipline.assets import Asset
from zipline.assets import Asset, Future
from zipline.utils.input_validation import expect_types
from .utils.enum import enum
from zipline._protocol import BarData # noqa
@@ -136,6 +136,10 @@ class Order(Event):
)
def asset_multiplier(asset):
return asset.multiplier if isinstance(asset, Future) else 1
class Portfolio(object):
def __init__(self):
@@ -169,6 +173,26 @@ class Portfolio(object):
},
)
@property
def current_portfolio_weights(self):
"""
Compute each asset's weight in the portfolio by calculating its held
value divided by the total value of all positions.
Each equity's value is its price times the number of shares held. Each
futures contract's value is its unit price times number of shares held
times the multiplier.
"""
position_values = pd.Series({
asset: (
position.last_sale_price *
position.amount *
asset_multiplier(asset)
)
for asset, position in self.positions.items()
})
return position_values / position_values.abs().sum()
class Account(object):
'''
+17
View File
@@ -691,6 +691,23 @@ class EmptyPositionsAlgorithm(TradingAlgorithm):
self.record(num_positions=len(self.portfolio.positions))
class TestPositionWeightsAlgorithm(TradingAlgorithm):
"""
An algorithm that records the weights of its portfolio holdings each day.
"""
def initialize(self, sids_and_amounts, *args, **kwargs):
self.ordered = False
self.sids_and_amounts = sids_and_amounts
def handle_data(self, data):
if not self.ordered:
for s, amount in self.sids_and_amounts:
self.order(self.sid(s), amount)
self.ordered = True
self.record(position_weights=self.portfolio.current_portfolio_weights)
class InvalidOrderAlgorithm(TradingAlgorithm):
"""
An algorithm that tries to make various invalid order calls, verifying that