mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-02 16:29:14 +08:00
Merge pull request #1822 from quantopian/var-and-cvar
Add portfolio weights method to algorithms
This commit is contained in:
+100
-2
@@ -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
@@ -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):
|
||||
'''
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user