Merge pull request #452 from quantopian/leverage

ENH: Adding Leverage to performance tracking
This commit is contained in:
fawce
2014-12-24 11:20:04 -05:00
3 changed files with 247 additions and 5 deletions
+209
View File
@@ -53,6 +53,41 @@ oneday = timedelta(days=1)
tradingday = timedelta(hours=6, minutes=30)
def check_account(account,
settled_cash,
equity_with_loan,
total_positions_value,
regt_equity,
available_funds,
excess_liquidity,
cushion,
leverage,
net_leverage,
net_liquidation):
# this is a long only portfolio that is only partially invested
# so net and gross leverage are equal.
np.testing.assert_allclose(settled_cash,
account['settled_cash'], rtol=1e-3)
np.testing.assert_allclose(equity_with_loan,
account['equity_with_loan'], rtol=1e-3)
np.testing.assert_allclose(total_positions_value,
account['total_positions_value'], rtol=1e-3)
np.testing.assert_allclose(regt_equity,
account['regt_equity'], rtol=1e-3)
np.testing.assert_allclose(available_funds,
account['available_funds'], rtol=1e-3)
np.testing.assert_allclose(excess_liquidity,
account['excess_liquidity'], rtol=1e-3)
np.testing.assert_allclose(cushion,
account['cushion'], rtol=1e-3)
np.testing.assert_allclose(leverage, account['leverage'], rtol=1e-3)
np.testing.assert_allclose(net_leverage,
account['net_leverage'], rtol=1e-3)
np.testing.assert_allclose(net_liquidation,
account['net_liquidation'], rtol=1e-3)
def create_txn(trade_event, price, amount):
"""
Create a fake transaction to be filled and processed prior to the execution
@@ -222,7 +257,10 @@ class TestSplitPerformance(unittest.TestCase):
# Validate that the account attributes were updated.
account = results[1]['account']
self.assertEqual(float('inf'), account['day_trades_remaining'])
# this is a long only portfolio that is only partially invested
# so net and gross leverage are equal.
np.testing.assert_allclose(0.198, account['leverage'], rtol=1e-3)
np.testing.assert_allclose(0.198, account['net_leverage'], rtol=1e-3)
np.testing.assert_allclose(8020, account['regt_equity'], rtol=1e-3)
self.assertEqual(float('inf'), account['regt_margin'])
np.testing.assert_allclose(8020, account['available_funds'], rtol=1e-3)
@@ -791,6 +829,136 @@ class TestPositionPerformance(unittest.TestCase):
self.benchmark_events = benchmark_events_in_range(self.sim_params)
def test_long_short_positions(self):
"""
start with $1000
buy 100 stock1 shares at $10
sell short 100 stock2 shares at $10
stock1 then goes down to $9
stock2 goes to $11
"""
trades_1 = factory.create_trade_history(
1,
[10, 10, 10, 9],
[100, 100, 100, 100],
onesec,
self.sim_params
)
trades_2 = factory.create_trade_history(
2,
[10, 10, 10, 11],
[100, 100, 100, 100],
onesec,
self.sim_params
)
txn1 = create_txn(trades_1[1], 10.0, 100)
txn2 = create_txn(trades_2[1], 10.0, -100)
pp = perf.PerformancePeriod(1000.0)
pp.execute_transaction(txn1)
pp.execute_transaction(txn2)
for trade in itertools.chain(trades_1[:-2], trades_2[:-2]):
pp.update_last_sale(trade)
pp.calculate_performance()
# Validate that the account attributes were updated.
account = pp.as_account()
check_account(account,
settled_cash=1000.0,
equity_with_loan=1000.0,
total_positions_value=0.0,
regt_equity=1000.0,
available_funds=1000.0,
excess_liquidity=1000.0,
cushion=1.0,
leverage=2.0,
net_leverage=0.0,
net_liquidation=1000.0)
# now simulate stock1 going to $9
pp.update_last_sale(trades_1[-1])
# and stock2 going to $11
pp.update_last_sale(trades_2[-1])
pp.calculate_performance()
# Validate that the account attributes were updated.
account = pp.as_account()
check_account(account,
settled_cash=1000.0,
equity_with_loan=800.0,
total_positions_value=-200.0,
regt_equity=1000.0,
available_funds=1000.0,
excess_liquidity=1000.0,
cushion=1.25,
leverage=2.5,
net_leverage=-0.25,
net_liquidation=800.0)
def test_levered_long_position(self):
"""
start with $1,000, then buy 1000 shares at $10.
price goes to $11
"""
# post some trades in the market
trades = factory.create_trade_history(
1,
[10, 10, 10, 11],
[100, 100, 100, 100],
onesec,
self.sim_params
)
txn = create_txn(trades[1], 10.0, 1000)
pp = perf.PerformancePeriod(1000.0)
pp.execute_transaction(txn)
for trade in trades[:-2]:
pp.update_last_sale(trade)
pp.calculate_performance()
# Validate that the account attributes were updated.
account = pp.as_account()
check_account(account,
settled_cash=-9000.0,
equity_with_loan=1000.0,
total_positions_value=10000.0,
regt_equity=-9000.0,
available_funds=-9000.0,
excess_liquidity=-9000.0,
cushion=-9.0,
leverage=10.0,
net_leverage=10.0,
net_liquidation=1000.0)
# now simulate a price jump to $11
pp.update_last_sale(trades[-1])
pp.calculate_performance()
# Validate that the account attributes were updated.
account = pp.as_account()
check_account(account,
settled_cash=-9000.0,
equity_with_loan=2000.0,
total_positions_value=11000.0,
regt_equity=-9000.0,
available_funds=-9000.0,
excess_liquidity=-9000.0,
cushion=-4.5,
leverage=5.5,
net_leverage=5.5,
net_liquidation=2000.0)
def test_long_position(self):
"""
verify that the performance period calculates properly for a
@@ -871,6 +1039,20 @@ class TestPositionPerformance(unittest.TestCase):
self.assertEqual(pp.pnl, 100, "gain of 1 on 100 shares should be 100")
# Validate that the account attributes were updated.
account = pp.as_account()
check_account(account,
settled_cash=0.0,
equity_with_loan=1100.0,
total_positions_value=1100.0,
regt_equity=0.0,
available_funds=0.0,
excess_liquidity=0.0,
cushion=0.0,
leverage=1.0,
net_leverage=1.0,
net_liquidation=1100.0)
def test_short_position(self):
"""verify that the performance period calculates properly for a \
single short-sale transaction"""
@@ -1060,6 +1242,20 @@ cost of sole txn in test"
"drop of 1 on -100 shares should be 100"
)
# Validate that the account attributes.
account = ppTotal.as_account()
check_account(account,
settled_cash=2000.0,
equity_with_loan=1100.0,
total_positions_value=-900.0,
regt_equity=2000.0,
available_funds=2000.0,
excess_liquidity=2000.0,
cushion=1.8181,
leverage=0.8181,
net_leverage=-0.8181,
net_liquidation=1100.0)
def test_covering_short(self):
"""verify performance where short is bought and covered, and shares \
trade after cover"""
@@ -1141,6 +1337,19 @@ shares in position"
"gain of 1 on 100 shares should be 300"
)
account = pp.as_account()
check_account(account,
settled_cash=1300.0,
equity_with_loan=1300.0,
total_positions_value=0.0,
regt_equity=1300.0,
available_funds=1300.0,
excess_liquidity=1300.0,
cushion=1.0,
leverage=0.0,
net_leverage=0.0,
net_liquidation=1300.0)
def test_cost_basis_calc(self):
history_args = (
1,
+37 -5
View File
@@ -320,6 +320,38 @@ class PerformancePeriod(object):
def calculate_positions_value(self):
return np.dot(self._position_amounts, self._position_last_sale_prices)
def _long_value(self):
pos_values = self._position_amounts * self._position_last_sale_prices
longs = pos_values[pos_values > 0]
return longs.sum()
def _short_value(self):
pos_values = self._position_amounts * self._position_last_sale_prices
shorts = pos_values[pos_values < 0]
return shorts.sum()
def _gross_exposure(self):
return self._long_value() + abs(self._short_value())
def _net_exposure(self):
return self.calculate_positions_value()
def _net_liquidation_value(self):
lv = self.ending_cash + self._long_value() + self._short_value()
return lv
def _gross_leverage(self):
if self._net_liquidation_value != 0:
return self._gross_exposure() / self._net_liquidation_value()
return pd.inf
def _net_leverage(self):
if self._net_liquidation_value != 0:
return self._net_exposure() / self._net_liquidation_value()
return pd.inf
def update_last_sale(self, event):
if event.sid not in self.positions:
return
@@ -345,7 +377,8 @@ class PerformancePeriod(object):
'pnl': self.pnl,
'returns': self.returns,
'period_open': self.period_open,
'period_close': self.period_close
'period_close': self.period_close,
'gross_leverage': self._gross_leverage()
}
return rval
@@ -451,11 +484,10 @@ class PerformancePeriod(object):
account.day_trades_remaining = \
getattr(self, 'day_trades_remaining', float('inf'))
account.leverage = \
getattr(self, 'leverage',
self.ending_value / (self.ending_value + self.ending_cash))
getattr(self, 'leverage', self._gross_leverage())
account.net_leverage = self._net_leverage()
account.net_liquidation = \
getattr(self, 'net_liquidation',
self.ending_cash + self.ending_value)
getattr(self, 'net_liquidation', self._net_liquidation_value())
return account
def get_positions(self):
+1
View File
@@ -160,6 +160,7 @@ class Account(object):
self.cushion = 0.0
self.day_trades_remaining = float('inf')
self.leverage = 0.0
self.net_leverage = 0.0
self.net_liquidation = 0.0
def __getitem__(self, key):