Skip to content

Commit

Permalink
move driftRebalancer to examples; change example to be more lumibot l…
Browse files Browse the repository at this point in the history
…ike; remove logger in strategy
  • Loading branch information
brettelliot committed Nov 7, 2024
1 parent 5dad969 commit a4f70cf
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 51 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ git fetch origin
git merge origin/dev
git checkout my-feature
git rebase dev
git checkout my-feature
git push --force-with-lease origin my-feature
```

Expand Down
49 changes: 22 additions & 27 deletions lumibot/example_strategies/classic_60_40.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,40 @@
from datetime import datetime

from lumibot.strategies.drift_rebalancer import DriftRebalancer
from lumibot.credentials import IS_BACKTESTING
from lumibot.example_strategies.drift_rebalancer import DriftRebalancer

"""
Strategy Description
This strategy rebalances a portfolio of assets to a target weight every time the asset drifts
This strategy demonstrates the DriftRebalancer by rebalancing to a classic 60% stocks, 40% bonds portfolio.
It rebalances a portfolio of assets to a target weight every time the asset drifts
by a certain threshold. The strategy will sell the assets that has drifted the most and buy the
assets that has drifted the least to bring the portfolio back to the target weights.
"""


if __name__ == "__main__":
is_live = False

parameters = {
"market": "NYSE",
"sleeptime": "1D",
"drift_threshold": "0.15",
"acceptable_slippage": "0.005", # 50 BPS
"fill_sleeptime": 15,
"target_weights": {
"SPY": "0.60",
"TLT": "0.40"
},
"shorting": False
}

if is_live:
from credentials import ALPACA_CONFIG
from lumibot.brokers import Alpaca
from lumibot.traders import Trader

trader = Trader()
broker = Alpaca(ALPACA_CONFIG)
strategy = DriftRebalancer(broker=broker, parameters=parameters)
trader.add_strategy(strategy)
strategy_executors = trader.run_all()

if not IS_BACKTESTING:
print("This strategy is not meant to be run live. Please set IS_BACKTESTING to True.")
exit()
else:

parameters = {
"market": "NYSE",
"sleeptime": "1D",
"drift_threshold": "0.05",
"acceptable_slippage": "0.005", # 50 BPS
"fill_sleeptime": 15,
"target_weights": {
"SPY": "0.60",
"TLT": "0.40"
},
"shorting": False
}

from lumibot.backtesting import YahooDataBacktesting

backtesting_start = datetime(2023, 1, 2)
backtesting_end = datetime(2024, 10, 31)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@
from typing import Dict, Any
from decimal import Decimal, ROUND_DOWN
import time
import logging

from lumibot.strategies.strategy import Strategy

logger = logging.getLogger(__name__)
# print_full_pandas_dataframes()
# set_pandas_float_precision(precision=15)

"""
The DriftRebalancer strategy is designed to maintain a portfolio's target asset allocation by
rebalancing assets based on their drift from target weights. The strategy calculates the
Expand Down Expand Up @@ -88,7 +83,7 @@ def initialize(self, parameters: Any = None) -> None:
raise ValueError("drift_threshold must be less than 1.0")
for key, target_weight in self.target_weights.items():
if self.drift_threshold >= target_weight:
logger.warning(
self.logger.warning(
f"drift_threshold of {self.drift_threshold} is "
f">= target_weight of {key}: {target_weight}. Drift in this asset will never trigger a rebalance."
)
Expand All @@ -97,12 +92,12 @@ def initialize(self, parameters: Any = None) -> None:
def on_trading_iteration(self) -> None:
dt = self.get_datetime()
msg = f"{dt} on_trading_iteration called"
logger.info(msg)
self.logger.info(msg)
self.log_message(msg, broadcast=True)
self.cancel_open_orders()

if self.cash < 0:
logger.error(f"Negative cash: {self.cash} but DriftRebalancer does not support short sales or margin yet.")
self.logger.error(f"Negative cash: {self.cash} but DriftRebalancer does not support short sales or margin yet.")

drift_calculator = DriftCalculationLogic(target_weights=self.target_weights)

Expand Down Expand Up @@ -138,12 +133,12 @@ def on_trading_iteration(self) -> None:
msg += (
f" Absolute drift exceeds threshold of {self.drift_threshold:.2%}. Rebalance needed."
)
logger.info(msg)
self.logger.info(msg)
self.log_message(msg, broadcast=True)

if rebalance_needed:
msg = f"Rebalancing portfolio."
logger.info(msg)
self.logger.info(msg)
self.log_message(msg, broadcast=True)
rebalance_logic = LimitOrderRebalanceLogic(
strategy=self,
Expand All @@ -156,13 +151,13 @@ def on_trading_iteration(self) -> None:

def on_abrupt_closing(self):
dt = self.get_datetime()
logger.info(f"{dt} on_abrupt_closing called")
self.logger.info(f"{dt} on_abrupt_closing called")
self.log_message("On abrupt closing called.", broadcast=True)
self.cancel_open_orders()

def on_bot_crash(self, error):
dt = self.get_datetime()
logger.info(f"{dt} on_bot_crash called")
self.logger.info(f"{dt} on_bot_crash called")
self.log_message(f"Bot crashed with error: {error}", broadcast=True)
self.cancel_open_orders()

Expand Down Expand Up @@ -251,6 +246,7 @@ def __init__(
def rebalance(self) -> None:
# Execute sells first
sell_orders = []
buy_orders = []
for index, row in self.df.iterrows():
if row["drift"] == -1:
# Sell everything
Expand Down Expand Up @@ -282,14 +278,14 @@ def rebalance(self) -> None:
sell_orders.append(order)

for order in sell_orders:
logger.info(f"Submitted sell order: {order}")
self.strategy.logger.info(f"Submitted sell order: {order}")

if not self.strategy.is_backtesting:
# Sleep to allow sell orders to fill
time.sleep(self.fill_sleeptime)
orders = self.strategy.broker._pull_all_orders(self.strategy.name, self.strategy)
for order in orders:
logger.info(f"Order at broker: {order}")
self.strategy.logger.info(f"Order at broker: {order}")

# Get current cash position from the broker
cash_position = self.get_current_cash_position()
Expand All @@ -303,10 +299,21 @@ def rebalance(self) -> None:
order_value = row["target_value"] - row["current_value"]
quantity = (min(order_value, cash_position) / limit_price).quantize(Decimal('1'), rounding=ROUND_DOWN)
if quantity > 0:
self.place_limit_order(symbol=symbol, quantity=quantity, limit_price=limit_price, side="buy")
order = self.place_limit_order(symbol=symbol, quantity=quantity, limit_price=limit_price, side="buy")
buy_orders.append(order)
cash_position -= min(order_value, cash_position)
else:
logger.info(f"Ran out of cash to buy {symbol}. Cash: {cash_position} and limit_price: {limit_price:.2f}")
self.strategy.logger.info(f"Ran out of cash to buy {symbol}. Cash: {cash_position} and limit_price: {limit_price:.2f}")

for order in buy_orders:
self.strategy.logger.info(f"Submitted buy order: {order}")

if not self.strategy.is_backtesting:
# Sleep to allow orders to fill
time.sleep(self.fill_sleeptime)
orders = self.strategy.broker._pull_all_orders(self.strategy.name, self.strategy)
for order in orders:
self.strategy.logger.info(f"Order at broker: {order}")

def calculate_limit_price(self, *, last_price: Decimal, side: str) -> Decimal:
if side == "sell":
Expand Down
12 changes: 4 additions & 8 deletions tests/test_drift_rebalancer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,16 @@
from typing import Any
import datetime
import logging
import pytest

import pandas as pd
import numpy as np

from lumibot.strategies.drift_rebalancer import DriftCalculationLogic
from lumibot.strategies.drift_rebalancer import LimitOrderRebalanceLogic, Strategy
from lumibot.entities import Asset, Order
from lumibot.example_strategies.drift_rebalancer import DriftCalculationLogic, LimitOrderRebalanceLogic, DriftRebalancer
from lumibot.backtesting import BacktestingBroker, YahooDataBacktesting, PandasDataBacktesting
from lumibot.strategies.drift_rebalancer import DriftRebalancer
from lumibot.strategies.strategy import Strategy
from tests.fixtures import pandas_data_fixture
from lumibot.tools import print_full_pandas_dataframes, set_pandas_float_precision

logger = logging.getLogger(__name__)
print_full_pandas_dataframes()
set_pandas_float_precision(precision=5)

Expand Down Expand Up @@ -319,7 +315,7 @@ def test_calculate_drift_when_we_want_short_something(self):
)

df = self.calculator.calculate()
print(f"\n{df}")
# print(f"\n{df}")

assert df["current_weight"].tolist() == [Decimal("0.0"), Decimal("1.0")]
assert df["target_value"].tolist() == [Decimal("-500"), Decimal("500")]
Expand Down Expand Up @@ -583,7 +579,7 @@ def test_classic_60_60(self, pandas_data_fixture):
show_indicators=False,
save_logfile=False,
show_progress_bar=False,
quiet_logs=False,
# quiet_logs=False,
)

assert results is not None
Expand Down

0 comments on commit a4f70cf

Please sign in to comment.