Merge pull request #1822 from quantopian/var-and-cvar

Add portfolio weights method to algorithms
This commit is contained in:
David Michalowicz
2017-06-07 16:35:04 -04:00
committed by GitHub
3 changed files with 147 additions and 4 deletions
+100 -2
View File
@@ -123,6 +123,7 @@ from zipline.test_algorithms import (
TestOrderPercentAlgorithm,
TestOrderStyleForwardingAlgorithm,
TestOrderValueAlgorithm,
TestPositionWeightsAlgorithm,
TestRegisterTransformAlgorithm,
TestTargetAlgorithm,
TestTargetPercentAlgorithm,
@@ -1094,11 +1095,64 @@ class TestPositions(WithLogger,
ZiplineTestCase):
START_DATE = pd.Timestamp('2006-01-03', tz='utc')
END_DATE = pd.Timestamp('2006-01-06', tz='utc')
SIM_PARAMS_CAPITAL_BASE = 1000
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 +1178,50 @@ 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, [2, -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 position value is its price times the number of
# shares held. In this example, we hold a long position in 2 shares
# of equity_1 so its weight is (95.0 * 2) = 190.0 divided by the
# total portfolio value. The total portfolio value is the sum of
# cash ($905.00) plus the value of all equity positions.
#
# 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.0 divided by total portfolio value.
pd.Series({
equity_1: 190.0 / (190.0 - 95.0 + 905.0),
equity_133: -95.0 / (190.0 - 95.0 + 905.0),
future_1000: 200.0 / (190.0 - 95.0 + 905.0),
}),
pd.Series({
equity_1: 200.0 / (200.0 - 100.0 + 905.0),
equity_133: -100.0 / (200.0 - 100.0 + 905.0),
future_1000: 200.0 / (200.0 - 100.0 + 905.0),
}),
pd.Series({
equity_1: 210.0 / (210.0 - 105.0 + 905.0),
equity_133: -105.0 / (210.0 - 105.0 + 905.0),
future_1000: 200.0 / (210.0 - 105.0 + 905.0),
}),
]
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 / self.portfolio_value
class Account(object):
'''
+22 -1
View File
@@ -88,7 +88,7 @@ from zipline.api import (
)
from zipline.errors import UnsupportedOrderParameters
from zipline.assets import Future, Equity
from zipline.finance.commission import PerShare
from zipline.finance.commission import PerShare, PerTrade
from zipline.finance.execution import (
LimitOrder,
MarketOrder,
@@ -691,6 +691,27 @@ 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
self.set_commission(us_equities=PerTrade(0), us_futures=PerTrade(0))
self.set_slippage(
us_equities=FixedSlippage(0), us_futures=FixedSlippage(0),
)
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