Skip to content

Strategies

BuyAndHoldStrategy

BuyAndHoldStrategy

Bases: Strategy

A buy-and-hold rebalancing strategy that maintains a target portfolio weight.

This strategy monitors portfolio value and rebalances positions to maintain the target weight, with optional thresholds to avoid excessive trading.

Source code in alphaflow/strategies/buy_and_hold_strategy.py
class BuyAndHoldStrategy(Strategy):
    """A buy-and-hold rebalancing strategy that maintains a target portfolio weight.

    This strategy monitors portfolio value and rebalances positions to maintain
    the target weight, with optional thresholds to avoid excessive trading.
    """

    def __init__(
        self,
        symbol: str,
        target_weight: float,
        min_dollar_delta: float = 0,
        min_share_delta: float = 0,
        share_quantization: float | None = None,
    ) -> None:
        """Initialize the buy-and-hold strategy.

        Args:
            symbol: The ticker symbol to trade.
            target_weight: Target portfolio weight (0.0 to 1.0).
            min_dollar_delta: Minimum dollar difference to trigger rebalancing.
            min_share_delta: Minimum share difference to trigger rebalancing.
            share_quantization: Round shares to multiples of this value (e.g., 1 for whole shares).

        """
        self.symbol = symbol
        self.target_weight = target_weight
        self.min_dollar_delta = min_dollar_delta
        self.min_share_delta = min_share_delta
        self.share_quantization = share_quantization

    def topic_subscriptions(self) -> list[Topic]:
        """Return the topics this strategy subscribes to.

        Returns:
            List of topics this strategy listens to.

        """
        return [Topic.MARKET_DATA]

    def read_event(self, event: Event) -> None:
        """Process market data events and generate rebalancing orders.

        Args:
            event: The market data event containing price information.

        """
        # Type narrowing - we only get MarketDataEvent from Topic.MARKET_DATA
        if not isinstance(event, MarketDataEvent):
            return

        if event.symbol != self.symbol:
            return
        if self._alpha_flow.backtest_start_timestamp and event.timestamp < self._alpha_flow.backtest_start_timestamp:
            return
        if self._alpha_flow.backtest_end_timestamp and event.timestamp > self._alpha_flow.backtest_end_timestamp:
            return

        portfolio_value = self._alpha_flow.portfolio.get_portfolio_value(event.timestamp)
        position_value = self._alpha_flow.portfolio.get_position_value(self.symbol, event.timestamp)
        target_value = portfolio_value * self.target_weight
        purchase_value = target_value - position_value
        if abs(purchase_value) < self.min_dollar_delta:
            # Too small of a value difference
            return
        shares_needed = purchase_value / event.close

        if self.share_quantization:
            shares_needed = math.floor(shares_needed / self.share_quantization) * self.share_quantization

        if abs(shares_needed) < self.min_share_delta:
            # Too small of a share purchase
            return
        side = Side.BUY if shares_needed > 0 else Side.SELL
        self._alpha_flow.event_bus.publish(
            Topic.ORDER,
            OrderEvent(
                timestamp=event.timestamp,
                symbol=self.symbol,
                side=side,
                qty=abs(shares_needed),
                order_type=OrderType.MARKET,
            ),
        )

__init__(symbol, target_weight, min_dollar_delta=0, min_share_delta=0, share_quantization=None)

Initialize the buy-and-hold strategy.

Parameters:

Name Type Description Default
symbol str

The ticker symbol to trade.

required
target_weight float

Target portfolio weight (0.0 to 1.0).

required
min_dollar_delta float

Minimum dollar difference to trigger rebalancing.

0
min_share_delta float

Minimum share difference to trigger rebalancing.

0
share_quantization float | None

Round shares to multiples of this value (e.g., 1 for whole shares).

None
Source code in alphaflow/strategies/buy_and_hold_strategy.py
def __init__(
    self,
    symbol: str,
    target_weight: float,
    min_dollar_delta: float = 0,
    min_share_delta: float = 0,
    share_quantization: float | None = None,
) -> None:
    """Initialize the buy-and-hold strategy.

    Args:
        symbol: The ticker symbol to trade.
        target_weight: Target portfolio weight (0.0 to 1.0).
        min_dollar_delta: Minimum dollar difference to trigger rebalancing.
        min_share_delta: Minimum share difference to trigger rebalancing.
        share_quantization: Round shares to multiples of this value (e.g., 1 for whole shares).

    """
    self.symbol = symbol
    self.target_weight = target_weight
    self.min_dollar_delta = min_dollar_delta
    self.min_share_delta = min_share_delta
    self.share_quantization = share_quantization

topic_subscriptions()

Return the topics this strategy subscribes to.

Returns:

Type Description
list[Topic]

List of topics this strategy listens to.

Source code in alphaflow/strategies/buy_and_hold_strategy.py
def topic_subscriptions(self) -> list[Topic]:
    """Return the topics this strategy subscribes to.

    Returns:
        List of topics this strategy listens to.

    """
    return [Topic.MARKET_DATA]

read_event(event)

Process market data events and generate rebalancing orders.

Parameters:

Name Type Description Default
event Event

The market data event containing price information.

required
Source code in alphaflow/strategies/buy_and_hold_strategy.py
def read_event(self, event: Event) -> None:
    """Process market data events and generate rebalancing orders.

    Args:
        event: The market data event containing price information.

    """
    # Type narrowing - we only get MarketDataEvent from Topic.MARKET_DATA
    if not isinstance(event, MarketDataEvent):
        return

    if event.symbol != self.symbol:
        return
    if self._alpha_flow.backtest_start_timestamp and event.timestamp < self._alpha_flow.backtest_start_timestamp:
        return
    if self._alpha_flow.backtest_end_timestamp and event.timestamp > self._alpha_flow.backtest_end_timestamp:
        return

    portfolio_value = self._alpha_flow.portfolio.get_portfolio_value(event.timestamp)
    position_value = self._alpha_flow.portfolio.get_position_value(self.symbol, event.timestamp)
    target_value = portfolio_value * self.target_weight
    purchase_value = target_value - position_value
    if abs(purchase_value) < self.min_dollar_delta:
        # Too small of a value difference
        return
    shares_needed = purchase_value / event.close

    if self.share_quantization:
        shares_needed = math.floor(shares_needed / self.share_quantization) * self.share_quantization

    if abs(shares_needed) < self.min_share_delta:
        # Too small of a share purchase
        return
    side = Side.BUY if shares_needed > 0 else Side.SELL
    self._alpha_flow.event_bus.publish(
        Topic.ORDER,
        OrderEvent(
            timestamp=event.timestamp,
            symbol=self.symbol,
            side=side,
            qty=abs(shares_needed),
            order_type=OrderType.MARKET,
        ),
    )