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

Added tests for ccxt data. Standardized signature of get_last_price #693

Merged
merged 7 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lumibot/backtesting/polygon_backtesting.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import traceback
from collections import OrderedDict, defaultdict
from datetime import date, timedelta
from decimal import Decimal
from typing import Union

from polygon.exceptions import BadResponse
from termcolor import colored
Expand Down Expand Up @@ -216,7 +218,7 @@ def get_historical_prices_between_dates(
bars = self._parse_source_symbol_bars(response, asset, quote=quote)
return bars

def get_last_price(self, asset, timestep="minute", quote=None, exchange=None, **kwargs):
def get_last_price(self, asset, timestep="minute", quote=None, exchange=None, **kwargs) -> Union[float, Decimal, None]:
try:
dt = self.get_datetime()
self._update_pandas_data(asset, quote, 1, timestep, dt)
Expand Down
5 changes: 4 additions & 1 deletion lumibot/backtesting/thetadata_backtesting.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import logging
import re
import traceback
from decimal import Decimal
from typing import Union

import pandas as pd
import subprocess
from datetime import date, datetime, timedelta
Expand Down Expand Up @@ -247,7 +250,7 @@ def get_historical_prices_between_dates(
bars = self._parse_source_symbol_bars(response, asset, quote=quote)
return bars

def get_last_price(self, asset, timestep="minute", quote=None, exchange=None, **kwargs):
def get_last_price(self, asset, timestep="minute", quote=None, exchange=None, **kwargs) -> Union[float, Decimal, None]:
try:
dt = self.get_datetime()
self._update_pandas_data(asset, quote, 1, timestep, dt)
Expand Down
4 changes: 2 additions & 2 deletions lumibot/brokers/broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ def sync_positions(self, strategy):

# =========Market functions=======================

def get_last_price(self, asset: Asset, quote=None, exchange=None) -> float:
def get_last_price(self, asset: Asset, quote=None, exchange=None) -> Union[float, Decimal, None]:
"""
Takes an asset and returns the last known price

Expand All @@ -319,7 +319,7 @@ def get_last_price(self, asset: Asset, quote=None, exchange=None) -> float:

Returns
-------
float
float or Decimal or None
The last known price of the asset.
"""
if self.option_source and asset.asset_type == "option":
Expand Down
4 changes: 3 additions & 1 deletion lumibot/data_sources/alpaca_data.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import logging
from datetime import datetime, timedelta, timezone
from decimal import Decimal
from typing import Union

import pandas as pd
from alpaca.data.historical import CryptoHistoricalDataClient, StockHistoricalDataClient
Expand Down Expand Up @@ -141,7 +143,7 @@ def get_chains(self, asset: Asset, quote=None, exchange: str = None):
"feature, please use a different data source."
)

def get_last_price(self, asset, quote=None, exchange=None, **kwargs):
def get_last_price(self, asset, quote=None, exchange=None, **kwargs) -> Union[float, Decimal, None]:
if quote is not None:
# If the quote is not None, we use it even if the asset is a tuple
if type(asset) == Asset and asset.asset_type == "stock":
Expand Down
32 changes: 19 additions & 13 deletions lumibot/data_sources/ccxt_backtesting_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
from lumibot.tools import CcxtCacheDB
from pandas import DataFrame

from typing import Union,Any
from typing import Union, Any, Dict


class CcxtBacktestingData(DataSourceBacktesting):
"""Use CcxtCacheDB to download and cache data.
Expand Down Expand Up @@ -107,7 +108,7 @@ def _pull_source_symbol_bars(
data = self._data_store[symbol_timestep]
else:
data = self._pull_source_bars([asset],length,timestep,timeshift,quote,include_after_hours)
if data is None or len(data) == 0:
if data is None or data[symbol] is None or data[symbol].empty:
message = f"{self.SOURCE} did not return data for asset {symbol}. Make sure this symbol is valid."
logging.error(message)
return None
Expand All @@ -125,10 +126,15 @@ def _pull_source_symbol_bars(

return result_data.tail(length)


def _pull_source_bars(self, assets:tuple[Asset,Asset], length:int, timestep:str=MIN_TIMESTEP,
timeshift:int=None, quote:Asset=None, include_after_hours:bool=False
)->DataFrame:
def _pull_source_bars(
self,
assets: tuple[Asset,Asset],
length: int,
timestep: str = MIN_TIMESTEP,
timeshift: int = None,
quote: Asset = None,
include_after_hours: bool = False
) -> Dict:
"""pull broker bars for a list assets"""
parsed_timestep = self._parse_source_timestep(timestep, reverse=True)

Expand All @@ -150,17 +156,18 @@ def _pull_source_bars(self, assets:tuple[Asset,Asset], length:int, timestep:str=
else:
start_dt = start_dt - timedelta(minutes=self._download_start_dt_prebuffer)

data = self.cache_db.download_ohlcv(
symbol,parsed_timestep,
start_dt,
end_dt
)

data = self.cache_db.download_ohlcv(symbol,parsed_timestep,
start_dt,end_dt)

data.index = data.index.tz_localize("UTC")
data.index = data.index.tz_localize("UTC")
data.index = data.index.tz_convert(LUMIBOT_DEFAULT_PYTZ)
result[symbol] = data

return result


def get_historical_prices(self, asset:tuple[Asset,Asset], length:int, timestep:str=None,
timeshift:int=None, quote:Asset=None, exchange:Any=None, include_after_hours:bool=True
)->Bars:
Expand Down Expand Up @@ -231,8 +238,7 @@ def _parse_source_symbol_bars(self, response:DataFrame, asset:tuple[Asset,Asset]
bars = Bars(response, self.SOURCE, asset, quote=quote, raw=response)
return bars


def get_last_price(self, asset, timestep=None, quote=None, exchange=None, **kwargs):
def get_last_price(self, asset, timestep=None, quote=None, exchange=None, **kwargs) -> Union[float, Decimal, None]:
"""Takes an asset and returns the last known price of close"""
if timestep is None:
timestep = self.get_timestep()
Expand Down
4 changes: 3 additions & 1 deletion lumibot/data_sources/ccxt_data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import datetime
import logging
import time
from decimal import Decimal
from typing import Union

import ccxt
import pandas as pd
Expand Down Expand Up @@ -209,7 +211,7 @@ def _parse_source_symbol_bars(self, response, asset, quote=None, length=None):
bars = Bars(response, self.SOURCE, asset, quote=quote, raw=response)
return bars

def get_last_price(self, asset, quote=None, exchange=None, **kwargs):
def get_last_price(self, asset, quote=None, exchange=None, **kwargs) -> Union[float, Decimal, None]:
if quote is not None:
symbol = f"{asset.symbol}/{quote.symbol}"
else:
Expand Down
6 changes: 4 additions & 2 deletions lumibot/data_sources/data_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from datetime import datetime, timedelta
import traceback
import time
from decimal import Decimal
from typing import Union

import pandas as pd

Expand Down Expand Up @@ -101,7 +103,7 @@ def get_historical_prices(
pass

@abstractmethod
def get_last_price(self, asset, quote=None, exchange=None) -> float:
def get_last_price(self, asset, quote=None, exchange=None) -> Union[float, Decimal, None]:
"""
Takes an asset and returns the last known price

Expand All @@ -116,7 +118,7 @@ def get_last_price(self, asset, quote=None, exchange=None) -> float:

Returns
-------
float
float or Decimal or None
The last known price of the asset.
"""
pass
Expand Down
5 changes: 4 additions & 1 deletion lumibot/data_sources/example_broker_data.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import logging
from decimal import Decimal
from typing import Union

from termcolor import colored
from lumibot.entities import Asset, Bars
from lumibot.data_sources import DataSource
Expand All @@ -25,6 +28,6 @@ def get_historical_prices(
logging.error(colored("Method 'get_historical_prices' is not yet implemented.", "red"))
return None # Return None as a placeholder

def get_last_price(self, asset, quote=None, exchange=None) -> float:
def get_last_price(self, asset, quote=None, exchange=None) -> Union[float, Decimal, None]:
logging.error(colored("Method 'get_last_price' is not yet implemented.", "red"))
return 0.0 # Return 0.0 as a placeholder
4 changes: 3 additions & 1 deletion lumibot/data_sources/interactive_brokers_data.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import datetime
import math
from decimal import Decimal
from typing import Union

import pandas as pd

Expand Down Expand Up @@ -340,7 +342,7 @@ def get_historical_prices(
bars = self._parse_source_symbol_bars(response, asset, quote=quote, length=length)
return bars

def get_last_price(self, asset, timestep=None, quote=None, exchange=None, **kwargs):
def get_last_price(self, asset, timestep=None, quote=None, exchange=None, **kwargs) -> Union[float, Decimal, None]:
if exchange is None:
exchange = "SMART"

Expand Down
5 changes: 4 additions & 1 deletion lumibot/data_sources/interactive_brokers_rest_data.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import logging
from decimal import Decimal
from typing import Union

from termcolor import colored

from lumibot import LUMIBOT_DEFAULT_PYTZ
Expand Down Expand Up @@ -842,7 +845,7 @@ def get_historical_prices(

return bars

def get_last_price(self, asset, quote=None, exchange=None) -> float | None:
def get_last_price(self, asset, quote=None, exchange=None) -> Union[float, Decimal, None]:
field = "last_price"
response = self.get_market_snapshot(asset, [field]) # TODO add exchange

Expand Down
4 changes: 3 additions & 1 deletion lumibot/data_sources/pandas_data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import logging
from collections import defaultdict, OrderedDict
from datetime import timedelta
from decimal import Decimal
from typing import Union

import pandas as pd
from lumibot.data_sources import DataSourceBacktesting
Expand Down Expand Up @@ -178,7 +180,7 @@ def update_date_index(self):

return dt_index

def get_last_price(self, asset, quote=None, exchange=None):
def get_last_price(self, asset, quote=None, exchange=None) -> Union[float, Decimal, None]:
# Takes an asset and returns the last known price
tuple_to_find = self.find_asset_in_data_store(asset, quote)

Expand Down
6 changes: 4 additions & 2 deletions lumibot/data_sources/tradier_data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import logging
from collections import defaultdict
from datetime import datetime, date, timedelta
from decimal import Decimal
from typing import Union

import pandas as pd

Expand Down Expand Up @@ -255,7 +257,7 @@ def get_historical_prices(

return bars

def get_last_price(self, asset, quote=None, exchange=None):
def get_last_price(self, asset, quote=None, exchange=None) -> Union[float, Decimal, None]:
"""
This function returns the last price of an asset.
Parameters
Expand All @@ -269,7 +271,7 @@ def get_last_price(self, asset, quote=None, exchange=None):

Returns
-------
float
float or Decimal or none
Price of the asset
"""

Expand Down
3 changes: 2 additions & 1 deletion lumibot/data_sources/yahoo_data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from datetime import timedelta
from decimal import Decimal
from typing import Union

import numpy

Expand Down Expand Up @@ -131,7 +132,7 @@ def _parse_source_symbol_bars(self, response, asset, quote=None, length=None):
bars = Bars(response, self.SOURCE, asset, raw=response)
return bars

def get_last_price(self, asset, timestep=None, quote=None, exchange=None, **kwargs):
def get_last_price(self, asset, timestep=None, quote=None, exchange=None, **kwargs) -> Union[float, Decimal, None]:
"""Takes an asset and returns the last known price"""
if timestep is None:
timestep = self.get_timestep()
Expand Down
6 changes: 4 additions & 2 deletions lumibot/entities/bars.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import logging
from datetime import datetime
from decimal import Decimal
from typing import Union

import pandas as pd

Expand Down Expand Up @@ -168,7 +170,7 @@ def split(self):

return result

def get_last_price(self):
def get_last_price(self) -> Union[float, Decimal, None]:
"""Return the last price of the last bar

Parameters
Expand All @@ -177,7 +179,7 @@ def get_last_price(self):

Returns
-------
float
float, Decimal or None

"""
return self.df["close"].iloc[-1]
Expand Down
6 changes: 4 additions & 2 deletions lumibot/entities/data.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import datetime
import logging
import re
from decimal import Decimal
from typing import Union

import pandas as pd
from lumibot import LUMIBOT_DEFAULT_PYTZ as DEFAULT_PYTZ
Expand Down Expand Up @@ -383,7 +385,7 @@ def checker(self, *args, **kwargs):
return checker

@check_data
def get_last_price(self, dt, length=1, timeshift=0):
def get_last_price(self, dt, length=1, timeshift=0) -> Union[float, Decimal, None]:
"""Returns the last known price of the data.

Parameters
Expand All @@ -399,7 +401,7 @@ def get_last_price(self, dt, length=1, timeshift=0):

Returns
-------
float
float or Decimal or None
"""
iter_count = self.get_iter_count(dt)
open_price = self.datalines["open"].dataline[iter_count]
Expand Down
2 changes: 1 addition & 1 deletion lumibot/example_strategies/ccxt_backtesting_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def _position_sizing(self):
return cash, last_price, quantity

def _get_historical_prices(self):
return self.get_historical_prices(asset=self.asset,length=None,
return self.get_historical_prices(asset=self.asset,length=self.window,
timestep="day",quote=self.quote).df

def _get_bbands(self,history_df:DataFrame):
Expand Down
2 changes: 1 addition & 1 deletion lumibot/strategies/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -1651,7 +1651,7 @@ def sell_all(self, cancel_open_orders: bool = True, is_multileg: bool = False):
"""
self.broker.sell_all(self.name, cancel_open_orders=cancel_open_orders, strategy=self, is_multileg=is_multileg)

def get_last_price(self, asset: Union[Asset, str], quote=None, exchange=None):
def get_last_price(self, asset: Union[Asset, str], quote=None, exchange=None) -> Union[float, Decimal, None]:
"""Takes an asset and returns the last known price

Makes an active call to the market to retrieve the last price.
Expand Down
2 changes: 0 additions & 2 deletions tests/backtest/test_example_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,6 @@ def test_options_hold_to_expiry(self):
assert round(cash_settled_orders.iloc[0]["price"], 0) == 0
assert cash_settled_orders.iloc[0]["filled_quantity"] == 10

@pytest.mark.skip(reason="This test is skipped because the CCXTBackTesting data source is not currently "
"returning data for this date range.")
def test_ccxt_backtesting(self):
"""
Test the example strategy StockBracket by running a backtest and checking that the strategy object is returned
Expand Down
Loading
Loading