Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lumibot example_strategies stock_momentum.py #630

Open
pabloadmin opened this issue Nov 20, 2024 · 3 comments
Open

lumibot example_strategies stock_momentum.py #630

pabloadmin opened this issue Nov 20, 2024 · 3 comments

Comments

@pabloadmin
Copy link

Hi,

I tried this example and it didn't work initially, but I fixed it. I'm sharing it with you. Lumibot is a great tool.

Thanks!

from datetime import datetime

from lumibot.strategies.strategy import Strategy

"""
Strategy Description

Buys the best performing asset from self.symbols over self.period number of days.
For example, if SPY increased 2% yesterday, but VEU and AGG only increased 1% yesterday,
then we will buy SPY.
"""

class Momentum(Strategy):
# =====Overloading lifecycle methods=============

def initialize(self, symbols=None):
    # Setting the waiting period (in days)
    self.period = 2

    # The counter for the number of days we have been holding the current asset
    self.counter = 0

    # There is only one trading operation per day
    # No need to sleep between iterations
    self.sleeptime = 0

    # Set the symbols that we will be monitoring for momentum
    if symbols:
        self.symbols = symbols
    else:
        self.symbols = ["SPY", "VEU", "AGG"]

    # The asset that we want to buy/currently own, and the quantity
    self.asset = ""
    self.quantity = 0

def on_trading_iteration(self):
    # When the counter reaches the desired holding period,
    # re-evaluate which asset we should be holding
    momentums = []
    if self.counter == self.period or self.counter == 0:
        self.counter = 0
        momentums = self.get_assets_momentums()

        # Get the asset with the highest return in our period
        # (aka the highest momentum)
        momentums.sort(key=lambda x: x.get("return"))
        best_asset_data = momentums[-1]
        best_asset = best_asset_data["symbol"]
        best_asset_return = best_asset_data["return"]

        # Get the data for the currently held asset
        if self.asset:
            current_asset_data = [
                m for m in momentums if m["symbol"] == self.asset
            ][0]
            current_asset_return = current_asset_data["return"]

            # If the returns are equals, keep the current asset
            if current_asset_return >= best_asset_return:
                best_asset = self.asset
                best_asset_data = current_asset_data

        self.log_message("%s best symbol." % best_asset)

        # If the asset with the highest momentum has changed, buy the new asset
        if best_asset != self.asset:
            # Sell the current asset that we own
            if self.asset:
                self.log_message("Swapping %s for %s." % (self.asset, best_asset))
                order = self.create_order(self.asset, self.quantity, "sell")
                self.submit_order(order)

            # Calculate the quantity and send the buy order for the new asset
            self.asset = best_asset
            best_asset_price = best_asset_data["price"]
            self.quantity = int(self.portfolio_value // best_asset_price)
            order = self.create_order(self.asset, self.quantity, "buy")
            self.submit_order(order)
        else:
            self.log_message("Keeping %d shares of %s" % (self.quantity, self.asset))

    self.counter += 1

    # Stop for the day, since we are looking at daily momentums
    self.await_market_to_close()

def on_abrupt_closing(self):
    # Sell all positions
    self.sell_all()

def trace_stats(self, context, snapshot_before):
    """
    Add additional stats to the CSV logfile
    """
    # Get the values of all our variables from the last iteration
    row = {
        "old_best_asset": snapshot_before.get("asset"),
        "old_asset_quantity": snapshot_before.get("quantity"),
        "old_cash": snapshot_before.get("cash"),
        "new_best_asset": self.asset,
        "new_asset_quantity": self.quantity,
    }

    # Get the momentums of all the assets from the context of on_trading_iteration
    # (notice that on_trading_iteration has a variable called momentums, this is what
    # we are reading here)
    momentums = context.get("momentums")
    if len(momentums) != 0:
        for item in momentums:
            symbol = item.get("symbol")
            for key in item:
                if key != "symbol":
                    row[f"{symbol}_{key}"] = item[key]

    # Add all of our values to the row in the CSV file. These automatically get
    # added to portfolio_value, cash and return
    return row

# =============Helper methods====================

def get_assets_momentums(self):
    """
    Gets the momentums (the percentage return) for all the assets we are tracking,
    over the time period set in self.period
    """
    momentums = []
    data = self.get_historical_prices_for_assets(self.symbols, self.period + 2, timestep="day")
    # data = self.get_bars(self.symbols, self.period + 2, timestep="day")
    for asset, bars_set in data.items():
        # Get the return for symbol over self.period days
        symbol = asset.symbol
        symbol_momentum = bars_set.get_momentum(end=self.period)
        self.log_message(
            "%s has a return value of %.2f%% over the last %d day(s)."
            % (symbol, 100 * symbol_momentum, self.period)
        )

        momentums.append(
            {
                "symbol": symbol,
                "price": bars_set.get_last_price(),
                "return": symbol_momentum,
            }
        )

    return momentums

if name == "main":
is_live = 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 = Momentum(broker=broker)

    trader.add_strategy(strategy)
    strategy_executors = trader.run_all()

else:
    from lumibot.backtesting import YahooDataBacktesting

    # Backtest this strategy
    backtesting_start = datetime(2023, 1, 1)
    backtesting_end = datetime(2024, 8, 1)

    results = Momentum.backtest(
        YahooDataBacktesting,
        backtesting_start,
        backtesting_end,
        benchmark_asset="SPY",
        logfile="./log.txt",
    )

#############################################################

requirements.txt

aiodns==3.2.0
aiohappyeyeballs==2.4.3
aiohttp==3.11.6
aiosignal==1.3.1
alpaca-py==0.33.0
alpha_vantage==3.0.0
annotated-types==0.7.0
appdirs==1.4.4
APScheduler==3.10.4
asttokens==2.4.1
async-timeout==5.0.1
attrs==24.2.0
bcrypt==4.2.1
beautifulsoup4==4.12.3
bidict==0.23.1
blinker==1.9.0
ccxt==4.2.85
certifi==2024.8.30
cffi==1.15.0
charset-normalizer==3.4.0
click==8.1.7
colorama==0.4.6
contourpy==1.3.0
cryptography==43.0.3
cycler==0.12.1
decorator==5.1.1
dnspython==2.7.0
duckdb==1.1.3
email_validator==2.2.0
exceptiongroup==1.2.2
exchange_calendars==4.5.6
executing==2.1.0
Flask==3.1.0
Flask-Login==0.6.3
flask-marshmallow==1.2.1
Flask-Principal==0.4.0
Flask-Security==5.5.2
Flask-SocketIO==5.4.1
Flask-SQLAlchemy==3.1.1
Flask-WTF==1.2.2
fonttools==4.55.0
frozendict==2.4.6
frozenlist==1.5.0
greenlet==3.1.1
h11==0.14.0
holidays==0.53
html5lib==1.1
ibapi==9.81.1.post1
idna==3.10
ijson==3.3.0
importlib_metadata==8.5.0
importlib_resources==6.4.5
inflection==0.5.1
iniconfig==2.0.0
ipython==8.29.0
itsdangerous==2.2.0
jedi==0.19.2
Jinja2==3.1.4
jsonpickle==4.0.0
kiwisolver==1.4.6
korean-lunar-calendar==0.3.1
lumibot==3.3.1
lumiwealth-tradier==0.1.12
lxml==5.3.0
MarkupSafe==3.0.2
marshmallow==3.23.1
marshmallow-sqlalchemy==1.1.0
matplotlib==3.9.2
matplotlib-inline==0.1.7
more-itertools==10.5.0
msgpack==1.1.0
multidict==6.1.0
multitasking==0.0.11
numpy==1.26.4
packaging==24.2
pandas==2.2.2
pandas-datareader==0.10.0
pandas_market_calendars==4.4.2
parso==0.8.4
passlib==1.7.4
peewee==3.17.8
pillow==10.4.0
platformdirs==4.3.6
plotly==5.24.1
pluggy==1.5.0
polygon-api-client==1.14.2
prompt_toolkit==3.0.48
propcache==0.2.0
psutil==6.1.0
psycopg2-binary==2.9.10
pure_eval==0.2.3
pyarrow==18.0.0
pycares==4.4.0
pycparser==2.22
pydantic==2.0.3
pydantic_core==2.3.0
Pygments==2.18.0
pyluach==2.2.0
pyOpenSSL==22.0.0
pyopenssl-sdk==0.0.0.post0
pyparsing==3.2.0
pytest==8.3.3
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
python-engineio==4.10.1
python-socketio==5.11.4
pytz==2024.2
Quandl==3.7.0
quantstats-lumi==0.3.3
requests==2.32.3
scipy==1.10.1
seaborn==0.13.2
simple-websocket==1.1.0
six==1.16.0
soupsieve==2.6
SQLAlchemy==2.0.36
sseclient-py==1.8.0
stack-data==0.6.3
tabulate==0.9.0
tenacity==9.0.0
termcolor==2.5.0
thetadata==0.9.11
tomli==2.1.0
toolz==1.0.0
tqdm==4.67.0
traitlets==5.14.3
typing_extensions==4.12.2
tzdata==2024.2
tzlocal==5.2
urllib3==2.2.3
uuid==1.30
wcwidth==0.2.13
webencodings==0.5.1
websocket-client==1.8.0
websockets==12.0
Werkzeug==3.1.3
wget==3.2
wsproto==1.2.0
WTForms==3.2.1
yarl==1.17.2
yfinance==0.2.50
zipp==3.21.0

@grzesir
Copy link
Contributor

grzesir commented Nov 20, 2024

hey thanks @pabloadmin !
so what exactly was the issue? and how were you running it to get the problem? it works fine in render and on my local computer

@pabloadmin
Copy link
Author

Hi, i used the requirements.txt that appear in root github repository to install all libraries, but the code didn't work.
Maybe was a differents versions of libraries, different functions parameters, at this line made a change in the nema of parameter:

symbol_momentum = bars_set.get_momentum(num_periods=self.period)

symbol_momentum = bars_set.get_momentum(end=self.period)

So that i shared the requirements.txt that i used.

thanks to response!

@brettelliot
Copy link
Collaborator

brettelliot commented Nov 22, 2024

This is the correct (new) way to call get_momentum:

symbol_momentum = bars_set.get_momentum(num_periods=self.period)

If that doesn't work but using end does, then you are using an older version of Lumibot. The signature of get_momentum changed in 3.8.2.

Can you confirm what version you tried?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants