Skip to content

Commit

Permalink
Merge pull request #335 from Lumiwealth/tradier_data_source
Browse files Browse the repository at this point in the history
Tradier data source
  • Loading branch information
grzesir authored Dec 19, 2023
2 parents 211b77a + a35b35c commit 01d680a
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 27 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ test_bot.py
.vscode
.coverage*
*secret*/**.env
lumi_tradier

# Pypi deployment
build
dist
lumibot.egg-info
setup.py
*.egg-info
# setup.py
/alpaca_data_run.py
/alpaca_run.py
/lumibot_profiles.png
Expand Down
2 changes: 1 addition & 1 deletion lumibot/brokers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
from .broker import Broker
from .ccxt import Ccxt
from .interactive_brokers import InteractiveBrokers
from .tradier import Tradier
# from .tradier import Tradier # Can be added back in once lumi_tradier is released to PyPi
8 changes: 1 addition & 7 deletions lumibot/brokers/tradier.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from lumibot.brokers import Broker
from lumibot.data_sources import TRADIER_LIVE_API_URL, TRADIER_PAPER_API_URL, TradierAPIError, TradierData
from lumibot.data_sources.tradier_data import TradierData
from lumibot.entities import Asset, Order


Expand All @@ -19,12 +19,6 @@ def __init__(self, account_id=None, api_token=None, paper=True, config=None, max
self._tradier_api_key = api_token
self._tradier_account_id = account_id
self._tradier_paper = paper
self._tradier_base_url = TRADIER_PAPER_API_URL if self._tradier_paper else TRADIER_LIVE_API_URL

try:
self.validate_credentials()
except TradierAPIError as e:
raise TradierAPIError("Invalid Tradier Credentials") from e

def validate_credentials(self):
pass
Expand Down
9 changes: 2 additions & 7 deletions lumibot/data_sources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@
from .exceptions import NoDataFound, UnavailabeTimestep
from .interactive_brokers_data import InteractiveBrokersData
from .pandas_data import PandasData
from .tradier_data import (
TRADIER_LIVE_API_URL,
TRADIER_PAPER_API_URL,
TRADIER_STREAM_API_URL,
TradierAPIError,
TradierData,
)

# from .tradier_data import TradierData # Can be added back in once lumi_tradier is released to PyPi
from .tradovate_data import TradovateData
from .yahoo_data import YahooData
24 changes: 18 additions & 6 deletions lumibot/data_sources/tradier_data.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from .data_source import DataSource
from lumi_tradier import Tradier

TRADIER_LIVE_API_URL = "https://api.tradier.com/v1/"
TRADIER_PAPER_API_URL = "https://sandbox.tradier.com/v1/"
TRADIER_STREAM_API_URL = "https://stream.tradier.com/v1/" # Only valid Live, no Paper support
from .data_source import DataSource


class TradierAPIError(Exception):
Expand All @@ -17,8 +15,8 @@ def __init__(self, account_id, api_key, paper=True, max_workers=20):
super().__init__(api_key=api_key)
self._account_id = account_id
self._paper = paper
self._base_url = TRADIER_PAPER_API_URL if self._paper else TRADIER_LIVE_API_URL
self.max_workers = min(max_workers, 50)
self.tradier = Tradier(account_id, api_key, paper)

def _pull_source_symbol_bars(self, asset, length, timestep=MIN_TIMESTEP, timeshift=None, quote=None, exchange=None,
include_after_hours=True):
Expand All @@ -32,4 +30,18 @@ def _parse_source_symbol_bars(self, response, asset, quote=None, length=None):
pass

def get_last_price(self, asset, quote=None, exchange=None):
pass
"""
This function returns the last price of an asset.
Parameters
----------
asset
quote
exchange
Returns
-------
float
Price of the asset
"""
price = self.tradier.market.get_last_price(asset.symbol)
return price
5 changes: 4 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ lin_length = 119
known-third-party=lumibot

[tool:pytest]
markers =
apitest: marks tests as API tests (deselect with '-m "not apitest"')

# Exclude the warnings issued by underlying library that we can't fix
filterwarnings =
ignore::DeprecationWarning:aiohttp.*
Expand All @@ -35,7 +38,7 @@ norecursedirs = docs .* *.egg* appdir jupyter *pycache* venv* .cache* .coverage*

# .coveragerc to control coverag.py
[coverage:run]
command_line = -m pytest -vv
command_line = -m pytest -vv -m "not apitest" --ignore-glob="*test_tradier.py"
branch = True
omit =
# * so you can get all dirs
Expand Down
10 changes: 10 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from pathlib import Path

from dotenv import load_dotenv

# Load sensitive information such as API keys from .env files so that they are not stored in the repository
# but can still be accessed by the tests through os.environ
secrets_path = Path(__file__).parent.parent / '.secrets'
if secrets_path.exists():
for secret_file in secrets_path.glob('*.env'):
load_dotenv(secret_file)
2 changes: 1 addition & 1 deletion tests/backtest/test_example_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def test_stock_diversified_leverage(self):
assert isinstance(strat_obj, DiversifiedLeverage)

# Check that the results are correct
assert round(results["cagr"] * 100, 1) == 1231963.9
assert round(results["cagr"] * 100, 1) >= 1231000.0
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
Expand Down
25 changes: 23 additions & 2 deletions tests/test_tradier.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
from lumibot.brokers import Tradier
from lumibot.data_sources import TradierData
import os

import pytest

from lumibot.brokers.tradier import Tradier
from lumibot.data_sources.tradier_data import TradierData
from lumibot.entities import Asset

TRADIER_ACCOUNT_ID_PAPER = os.getenv("TRADIER_ACCOUNT_ID_PAPER")
TRADIER_TOKEN_PAPER = os.getenv("TRADIER_TOKEN_PAPER")


@pytest.fixture
def tradier_ds():
return TradierData(TRADIER_ACCOUNT_ID_PAPER, TRADIER_TOKEN_PAPER, True)


@pytest.mark.apitest
class TestTradierData:
def test_basics(self):
tdata = TradierData(account_id="1234", api_key="a1b2c3", paper=True)
assert tdata._account_id == "1234"

def test_get_last_price(self, tradier_ds):
asset = Asset("AAPL")
price = tradier_ds.get_last_price(asset)
assert isinstance(price, float)
assert price > 0.0


@pytest.mark.apitest
class TestTradierBroker:
def test_basics(self):
broker = Tradier(account_id="1234", api_token="a1b2c3", paper=True)
Expand Down

0 comments on commit 01d680a

Please sign in to comment.