Skip to content

Commit

Permalink
Merge pull request #323 from Lumiwealth/yahoo-last-price-fix
Browse files Browse the repository at this point in the history
bug fix for yahoo get last price (it was getting the price from the d…
  • Loading branch information
davidlatte authored Nov 20, 2023
2 parents ed061db + 1e53517 commit fc0ed9f
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 52 deletions.
48 changes: 17 additions & 31 deletions lumibot/data_sources/yahoo_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from decimal import Decimal

import numpy

from lumibot.data_sources import DataSourceBacktesting
from lumibot.entities import Asset, Bars
from lumibot.tools import YahooHelper
Expand Down Expand Up @@ -54,24 +53,15 @@ def _append_data(self, asset, data):
return data

def _pull_source_symbol_bars(
self,
asset,
length,
timestep=MIN_TIMESTEP,
timeshift=None,
quote=None,
exchange=None,
include_after_hours=True
self, asset, length, timestep=MIN_TIMESTEP, timeshift=None, quote=None, exchange=None, include_after_hours=True
):
if exchange is not None:
logging.warning(
f"the exchange parameter is not implemented for YahooData, but {exchange} was passed as the exchange"
)

if quote is not None:
logging.warning(
f"quote is not implemented for YahooData, but {quote} was passed as the quote"
)
logging.warning(f"quote is not implemented for YahooData, but {quote} was passed as the quote")

self._parse_source_timestep(timestep, reverse=True)
if asset in self._data_store:
Expand Down Expand Up @@ -111,14 +101,10 @@ def _pull_source_bars(
"""pull broker bars for a list assets"""

if quote is not None:
logging.warning(
f"quote is not implemented for YahooData, but {quote} was passed as the quote"
)
logging.warning(f"quote is not implemented for YahooData, but {quote} was passed as the quote")

self._parse_source_timestep(timestep, reverse=True)
missing_assets = [
asset.symbol for asset in assets if asset not in self._data_store
]
missing_assets = [asset.symbol for asset in assets if asset not in self._data_store]

if missing_assets:
dfs = YahooHelper.get_symbols_data(missing_assets, auto_adjust=self.auto_adjust)
Expand All @@ -127,16 +113,12 @@ def _pull_source_bars(

result = {}
for asset in assets:
result[asset] = self._pull_source_symbol_bars(
asset, length, timestep=timestep, timeshift=timeshift
)
result[asset] = self._pull_source_symbol_bars(asset, length, timestep=timestep, timeshift=timeshift)
return result

def _parse_source_symbol_bars(self, response, asset, quote=None, length=None):
if quote is not None:
logging.warning(
f"quote is not implemented for YahooData, but {quote} was passed as the quote"
)
logging.warning(f"quote is not implemented for YahooData, but {quote} was passed as the quote")

bars = Bars(response, self.SOURCE, asset, raw=response)
return bars
Expand All @@ -146,9 +128,9 @@ def get_last_price(self, asset, timestep=None, quote=None, exchange=None, **kwar
if timestep is None:
timestep = self.get_timestep()

bars = self.get_historical_prices(
asset, 1, timestep=timestep, quote=quote # , timeshift=timedelta(days=-1)
)
# Use -1 timeshift to get the price for the current bar (otherwise gets yesterdays prices)
bars = self.get_historical_prices(asset, 1, timestep=timestep, quote=quote, timeshift=timedelta(days=-1))

if isinstance(bars, float):
return bars
elif bars is None:
Expand All @@ -171,9 +153,13 @@ def get_chains(self, asset):
>>> expirations = spy.options
>>> chain_data = spy.option_chain()
"""
raise NotImplementedError("Lumibot YahooData does not support historical options data. If you need this "
"feature, please use a different data source.")
raise NotImplementedError(
"Lumibot YahooData does not support historical options data. If you need this "
"feature, please use a different data source."
)

def get_strikes(self, asset):
raise NotImplementedError("Lumibot YahooData does not support historical options data. If you need this "
"feature, please use a different data source.")
raise NotImplementedError(
"Lumibot YahooData does not support historical options data. If you need this "
"feature, please use a different data source."
)
1 change: 1 addition & 0 deletions lumibot/example_strategies/stock_buy_and_hold.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def on_trading_iteration(self):

if is_live:
from credentials import ALPACA_CONFIG

from lumibot.brokers import Alpaca
from lumibot.traders import Trader

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="lumibot",
version="2.9.2",
version="2.9.3",
author="Robert Grzesik",
author_email="[email protected]",
description="Backtesting and Trading Library, Made by Lumiwealth",
Expand Down
39 changes: 19 additions & 20 deletions tests/backtest/test_example_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
from lumibot.backtesting import YahooDataBacktesting
from lumibot.example_strategies.stock_bracket import StockBracket
from lumibot.example_strategies.stock_buy_and_hold import BuyAndHold
from lumibot.example_strategies.stock_diversified_leverage import \
DiversifiedLeverage
from lumibot.example_strategies.stock_limit_and_trailing_stops import \
LimitAndTrailingStop
from lumibot.example_strategies.stock_diversified_leverage import DiversifiedLeverage
from lumibot.example_strategies.stock_limit_and_trailing_stops import LimitAndTrailingStop
from lumibot.example_strategies.stock_oco import StockOco

# Global parameters
Expand Down Expand Up @@ -106,9 +104,9 @@ def test_stock_buy_and_hold(self):
assert isinstance(strat_obj, BuyAndHold)

# Check that the results are correct
assert round(results["cagr"] * 100, 1) == 155.7
assert round(results["volatility"] * 100, 1) == 7.0
assert round(results["total_return"] * 100, 1) == 0.5
assert round(results["cagr"] * 100, 1) == 2857.5
assert round(results["volatility"] * 100, 1) == 11.2
assert round(results["total_return"] * 100, 1) == 1.9
assert round(results["max_drawdown"]["drawdown"] * 100, 1) == 0.0

def test_stock_diversified_leverage(self):
Expand All @@ -135,9 +133,9 @@ def test_stock_diversified_leverage(self):
assert isinstance(strat_obj, DiversifiedLeverage)

# Check that the results are correct
assert round(results["cagr"] * 100, 1) == 2907.9
assert round(results["volatility"] * 100, 0) == 25
assert round(results["total_return"] * 100, 1) == 1.9
assert round(results["cagr"] * 100, 1) == 1235709.3
assert round(results["volatility"] * 100, 0) == 20.0
assert round(results["total_return"] * 100, 1) == 5.3
assert round(results["max_drawdown"]["drawdown"] * 100, 1) == 0.0

def test_limit_and_trailing_stops(self):
Expand Down Expand Up @@ -179,25 +177,26 @@ def test_limit_and_trailing_stops(self):
assert filled_limit_orders.iloc[1]["filled_quantity"] == 100

# Get all the filled trailing stop orders
filled_trailing_stop_orders = trades_df[(trades_df["status"] == "fill")
& (trades_df["type"] == "trailing_stop")]
filled_trailing_stop_orders = trades_df[
(trades_df["status"] == "fill") & (trades_df["type"] == "trailing_stop")
]

# Check if we have an order with a rounded price of 2 decimals of 400.45 and a quantity of 50
order1 = filled_trailing_stop_orders[
(round(filled_trailing_stop_orders["price"], 2) == 400.45) & (
filled_trailing_stop_orders["filled_quantity"] == 50)
(round(filled_trailing_stop_orders["price"], 2) == 400.45)
& (filled_trailing_stop_orders["filled_quantity"] == 50)
]
assert len(order1) == 1

# Check if we have an order with a price of 399.30 and a quantity of 100
order2 = filled_trailing_stop_orders[
(round(filled_trailing_stop_orders["price"], 2) == 399.30) & (
filled_trailing_stop_orders["filled_quantity"] == 100)
(round(filled_trailing_stop_orders["price"], 2) == 399.30)
& (filled_trailing_stop_orders["filled_quantity"] == 100)
]
assert len(order2) == 1

# Check that the results are correct
assert round(results["cagr"] * 100, 1) == 75
assert round(results["volatility"] * 100, 1) == 11.3
assert round(results["total_return"] * 100, 1) == 0.9
assert round(results["max_drawdown"]["drawdown"] * 100, 1) == 0.7
assert round(results["cagr"] * 100, 1) == 54.8
assert round(results["volatility"] * 100, 1) == 6.2
assert round(results["total_return"] * 100, 1) == 0.7
assert round(results["max_drawdown"]["drawdown"] * 100, 1) == 0.2
62 changes: 62 additions & 0 deletions tests/backtest/test_yahoo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import datetime

from lumibot.backtesting import BacktestingBroker, YahooDataBacktesting
from lumibot.strategies import Strategy
from lumibot.traders import Trader


class YahooPriceTest(Strategy):
parameters = {
"symbol": "SPY", # The symbol to trade
}

def initialize(self):
# There is only one trading operation per day
self.sleeptime = "1D"

def on_trading_iteration(self):
# Get the parameters
symbol = self.parameters["symbol"]

# Get the datetime
self.dt = self.get_datetime()

# Get the last price
self.last_price = self.get_last_price(symbol)


class TestYahooBacktestFull:
def test_yahoo_last_price(self):
"""
Test the YahooDataBacktesting class by running a backtest and checking that the strategy object is returned
along with the correct results
"""
# Parameters: True = Live Trading | False = Backtest
# trade_live = False
backtesting_start = datetime.datetime(2023, 11, 1)
backtesting_end = datetime.datetime(2023, 11, 2)

data_source = YahooDataBacktesting(
datetime_start=backtesting_start,
datetime_end=backtesting_end,
)

broker = BacktestingBroker(data_source=data_source)

poly_strat_obj = YahooPriceTest(
broker=broker,
backtesting_start=backtesting_start,
backtesting_end=backtesting_end,
)

trader = Trader(logfile="", backtest=True)
trader.add_strategy(poly_strat_obj)
results = trader.run_all(show_plot=False, show_tearsheet=False, save_tearsheet=False)

assert results

last_price = poly_strat_obj.last_price
# Round to 2 decimal places
last_price = round(last_price, 2)

assert last_price == 419.20 # This is the correct price for 2023-11-01 (the open price)

0 comments on commit fc0ed9f

Please sign in to comment.