"""Alpha model rebalancing.
Based on the new alpha model weights, rebalance the existing portfolio.
"""
import logging
from _decimal import Decimal
from typing import List, Dict
from tradeexecutor.state.portfolio import Portfolio
from tradeexecutor.state.trade import TradeExecution
from tradeexecutor.state.types import USDollarAmount
from tradeexecutor.strategy.alpha_model import AlphaModel
from tradeexecutor.strategy.pandas_trader.position_manager import PositionManager
from tradeexecutor.strategy.weighting import check_normalised_weights
logger = logging.getLogger(__name__)
[docs]def get_existing_portfolio_weights(portfolio: Portfolio) -> Dict[int, float]:
"""Calculate the existing portfolio weights.
Cash is not included in the weighting.
"""
total = portfolio.get_position_equity_and_loan_nav()
result = {}
for position in portfolio.open_positions.values():
result[position.pair.internal_id] = position.get_value() / total
return result
[docs]def get_weight_diffs(
existing_weights: Dict[int, float],
new_weights: Dict[int, float],
) -> Dict[int, float]:
"""Get the weight diffs.
The diffs include one entry for each token in existing or new portfolio.
"""
# Check that both inputs are sane
check_normalised_weights(new_weights)
check_normalised_weights(existing_weights)
diffs = {}
for id, new_weight in new_weights.items():
diffs[id] = new_weight - existing_weights.get(id, 0)
# Refill gaps of old assets that did not appear
# in the new portfolio
for id, old_weight in existing_weights.items():
if id not in diffs:
# Sell all
diffs[id] = -old_weight
return diffs
[docs]def rebalance_portfolio_old(
position_manager: PositionManager,
new_weights: Dict[int, float],
portfolio_total_value: USDollarAmount,
min_trade_threshold: USDollarAmount = 10.0,
) -> List[TradeExecution]:
"""Rebalance a portfolio based on alpha model weights.
.. warning ::
This is old deprecated method. Do not use anymore.
This will generate
- Sells for the existing assets
- Buys for new assetes or assets where we want to increase our position
:param portfolio:
Portfolio of our existing holdings
:param weights:
Each weight tells how much a certain trading pair
we should hold in our portfolio.
Pair id -> weight mappings.
Each weight must be normalised in the range of 0...1
and the total sum of the weights must be 1.
:param portfolio_total_value:
Target portfolio value in USD
:param min_trade_threshold:
If the notional value of a rebalance trade is smaller than this
USD amount don't make a trade.
:return:
List of trades we need to execute to reach the target portfolio.
The sells are sorted always before buys.
"""
portfolio = position_manager.state.portfolio
assert isinstance(portfolio, Portfolio)
existing_weights = get_existing_portfolio_weights(portfolio)
diffs = get_weight_diffs(existing_weights, new_weights)
dollar_values = {pair_id: weight * portfolio_total_value for pair_id, weight in diffs.items()}
# Generate trades
trades: List[TradeExecution] = []
for pair_id, value in dollar_values.items():
pair = position_manager.get_trading_pair(pair_id)
weight = new_weights.get(pair.internal_id, 0)
dollar_diff = value
if dollar_diff < 0:
# Calculate token amount
quantity_diff = Decimal(position_manager.estimate_asset_quantity(pair, dollar_diff))
else:
quantity_diff = None
logger.info(
"Rebalancing %s, old weight: %s, new weight: %s, diff: %s USD %s %s",
pair,
existing_weights.get(pair_id, 0),
weight,
dollar_diff,
quantity_diff,
pair.base.token_symbol,
)
if abs(dollar_diff) < min_trade_threshold:
logger.info("Not doing anything, value %f below trade threshold %f", value, min_trade_threshold)
else:
position_rebalance_trades = position_manager.adjust_position(pair, dollar_diff, quantity_diff, weight)
assert len(position_rebalance_trades) == 1, "Assuming always on trade for rebalance"
trade_str = " ,".join(t.get_human_description() for t in position_rebalance_trades)
logger.info("Rebalancing token %s with trades %s", pair.base.token_symbol, trade_str)
trades += position_rebalance_trades
trades.sort(key=lambda t: t.get_execution_sort_position())
# Return all rebalance trades
return trades