-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add auto-refresh sessions, test suite
- Loading branch information
Showing
19 changed files
with
1,025 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Contributions | ||
|
||
In order to run the test suite locally, you'll need to follow these steps to be able to authenticate to Tradestation. | ||
|
||
## Steps to follow to contribute | ||
|
||
1. Fork the repository to your personal Github account and make your proposed changes. | ||
2. Export your API key, secret key, refresh token, and account number to the following Github Actions repository secrets: `TS_API_KEY`, `TS_SECRET_KEY`, `TS_REFRESH`, and `TS_ACCOUNT`. The account should be a simulation account. | ||
3. Run `make install` to create the virtual environment, `make lint` to format your code, and `make test` to run the tests locally. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,44 @@ | ||
.. tradestation documentation master file, created by | ||
sphinx-quickstart on Mon Jan 13 22:34:26 2025. | ||
You can adapt this file completely to your liking, but it should at least | ||
contain the root `toctree` directive. | ||
.. image:: https://readthedocs.org/projects/tradestation/badge/?version=latest | ||
:target: https://tradestation.readthedocs.io/en/latest/?badge=latest | ||
:alt: Documentation Status | ||
|
||
tradestation documentation | ||
========================== | ||
.. image:: https://img.shields.io/pypi/v/tradestation | ||
:target: https://pypi.org/project/tradestation | ||
:alt: PyPI Package | ||
|
||
Add your content using ``reStructuredText`` syntax. See the | ||
`reStructuredText <https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html>`_ | ||
documentation for details. | ||
.. image:: https://static.pepy.tech/badge/tradestation | ||
:target: https://pepy.tech/project/tradestation | ||
:alt: PyPI Downloads | ||
|
||
.. image:: https://img.shields.io/github/v/release/tastyware/tradestation?label=release%20notes | ||
:target: https://github.com/tastyware/tradestation/releases | ||
:alt: Release | ||
|
||
TradeStation Python SDK | ||
======================= | ||
|
||
A simple, sync/async SDK for TradeStation built on their public API. This will allow you to create trading algorithms for whatever strategies you may have quickly and painlessly in Python. | ||
|
||
.. tip:: | ||
Do you use Tastytrade? Our `Tastytrade SDK <https://github.com/tastyware/tastytrade>`_ has many of the same features! | ||
|
||
.. toctree:: | ||
:maxdepth: 2 | ||
:caption: Contents: | ||
:caption: Documentation: | ||
|
||
installation | ||
sessions | ||
sync-async | ||
|
||
.. toctree:: | ||
:maxdepth: 2 | ||
:caption: SDK Reference: | ||
|
||
tradestation | ||
|
||
Indices and tables | ||
================== | ||
|
||
* :ref:`genindex` | ||
* :ref:`modindex` | ||
* :ref:`search` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
Installation | ||
============ | ||
|
||
Via pypi | ||
-------- | ||
|
||
The easiest way to install the SDK is using pip: | ||
|
||
:: | ||
|
||
$ pip install tradestation | ||
|
||
From source | ||
----------- | ||
|
||
You can also install from source. | ||
Make sure you have `uv <https://docs.astral.sh/uv/getting-started/installation/>`_ installed beforehand. | ||
|
||
:: | ||
|
||
$ git clone https://github.com/tastyware/tradestation.git | ||
$ cd tradestation | ||
$ make install | ||
|
||
If you're contributing, you'll want to run tests on your changes locally: | ||
|
||
:: | ||
|
||
$ make lint | ||
$ make test | ||
|
||
If you want to build the documentation (usually not necessary): | ||
|
||
:: | ||
|
||
$ make docs | ||
|
||
Windows | ||
------- | ||
|
||
If you want to install from source on Windows, you can't use the Makefile, so just run the commands individually. For example: | ||
|
||
:: | ||
|
||
$ git clone https://github.com/tastyware/tradestation.git | ||
$ cd tradestation | ||
$ uv sync | ||
$ uv pip install -e . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
Sessions | ||
======== | ||
|
||
Initial setup | ||
------------- | ||
|
||
Tradestation uses OAuth for secure authentication to the API. In order to obtain access tokens, you need to authenticate with OAuth 2's authorization code flow, which requires a local HTTP server running to handle the callback. Fortunately, the SDK makes doing this easy: | ||
|
||
.. code-block:: python | ||
from tradestation.oauth import login | ||
login() | ||
This will let you authenticate in your local browser. Fortunately, this only needs to be done once, as afterwards you can use the refresh token to obtain new access tokens indefinitely. | ||
|
||
Creating a session | ||
------------------ | ||
|
||
A session object is required to authenticate your requests to the Tradestation API. | ||
Create it by passing in the API key (client ID), secret key (client secret), and refresh token obtained in the initial setup. | ||
|
||
.. code-block:: python | ||
from tradestation import Session | ||
session = Session('api_key', 'secret_key', 'refresh_token') | ||
A simulated session can be used to test strategies or applications before using them in production: | ||
|
||
.. code-block:: python | ||
from tradestation import Session | ||
session = Session('api_key', 'secret_key', 'refresh_token', is_test=True) | ||
You can also use the legacy v2 API endpoints if desired: | ||
|
||
.. code-block:: python | ||
from tradestation import Session | ||
session = Session('api_key', 'secret_key', 'refresh_token', use_v2=True) | ||
Auto-refresh sessions | ||
--------------------- | ||
|
||
Since TradeStation access tokens only last 20 minutes by default, it can be annoying to have to remember to refresh them constantly. | ||
Fortunately, the SDK has a special class, `AutoRefreshSession`, that handles token refreshal (ok, ok, I know it's not a word!) for you! | ||
|
||
.. code-block:: python | ||
from tradestation import AutoRefreshSession | ||
session = await AutoRefreshSession('api_key', 'secret_key', 'refresh_token', is_test=True) | ||
# ... | ||
await session.close() # don't forget to cleanup the session when you're done! | ||
You can also create auto-refresh sessions using async context managers: | ||
|
||
.. code-block:: python | ||
async with AutoRefreshSession('api_key', 'secret_key', 'refresh_token') as session: | ||
# ... | ||
In this case, the context manager will handle the cleanup for you. | ||
Pretty easy, no? Other than initialization and cleanup, you can use auto-refresh sessions just like normal sessions. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
sync/async | ||
========== | ||
|
||
After creating a session (which is always initialized synchronously), the rest of the API endpoints implemented in the SDK have both sync and async implementations. | ||
|
||
Let's see how this looks: | ||
|
||
.. code-block:: python | ||
from tradestation import Account, Session | ||
session = Session('api_key', 'secret_key', 'refresh_token') | ||
accounts = Account.get_accounts(session) | ||
The async implementation is similar: | ||
|
||
.. code-block:: python | ||
session = Session('api_key', 'secret_key', 'refresh_token') | ||
# using async implementation | ||
accounts = await Account.a_get_accounts(session) | ||
That's it! All sync methods have a parallel async method that starts with `a_`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
tradestation | ||
============ | ||
|
||
Account | ||
------- | ||
.. automodule:: tradestation.account | ||
:members: | ||
:show-inheritance: | ||
|
||
Session | ||
------- | ||
.. automodule:: tradestation.session | ||
:members: | ||
:show-inheritance: | ||
|
||
Utils | ||
----- | ||
.. automodule:: tradestation.utils | ||
:members: | ||
:inherited-members: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from pytest import fixture | ||
|
||
from tradestation import Account, Session | ||
|
||
|
||
@fixture(scope="module") | ||
def accounts(session: Session) -> list[Account]: | ||
return Account.get_accounts(session) | ||
|
||
|
||
def test_get_accounts(accounts: list[Account]): | ||
assert accounts != [] | ||
|
||
|
||
async def test_get_accounts_async(session: Session): | ||
accounts = await Account.a_get_accounts(session) | ||
assert accounts != [] | ||
|
||
|
||
def test_get_balances(session: Session, accounts: list[Account]): | ||
balances = Account.get_balances(session, accounts) | ||
assert balances.balances != [] | ||
|
||
|
||
async def test_get_balances_async(session: Session, accounts: list[Account]): | ||
balances = await Account.a_get_balances(session, accounts) | ||
assert balances.balances != [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import pytest | ||
import signal | ||
|
||
from tradestation.oauth import ( | ||
Credentials, | ||
get_access_url, | ||
convert_auth_code, | ||
login, | ||
response_page, | ||
) | ||
|
||
|
||
def test_get_access_url(): | ||
credentials = Credentials(key="test") | ||
url = get_access_url(credentials) | ||
assert "test" in url | ||
|
||
|
||
def test_convert_auth_code(): | ||
with pytest.raises(Exception): | ||
convert_auth_code(Credentials(), "bogus") | ||
|
||
|
||
def test_response_page(): | ||
page = response_page("refresh", "access", {"key": "value"}) | ||
assert isinstance(page, bytes) | ||
|
||
|
||
def handler(signum, frame): | ||
raise TimeoutError | ||
|
||
|
||
def test_login(): | ||
signal.signal(signal.SIGALRM, handler) | ||
signal.alarm(3) | ||
with pytest.raises(TimeoutError): | ||
login() |
Oops, something went wrong.