-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b68587a
commit 22929d4
Showing
39 changed files
with
5,321 additions
and
0 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,4 @@ | ||
# Sphinx build info version 1 | ||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. | ||
config: cbfc8185f42158df9665e8f44e6319bc | ||
tags: 645f666f9bcd5a90fca523b33c5a78b7 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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,177 @@ | ||
================= | ||
Complete Examples | ||
================= | ||
|
||
Handling Keyboard Interupt | ||
-------------------------- | ||
|
||
Graceful exit on KeyboardInterupts | ||
|
||
.. code:: python | ||
from aio_trader.kite import KiteFeed | ||
from aio_trader import utils | ||
from typing import List | ||
import asyncio, sys | ||
# Define handlers | ||
def on_tick(tick: List, binary=False): | ||
print(tick) | ||
def on_connect(kws: KiteFeed): | ||
print("connected") | ||
# Subscribe to Aarti Industries | ||
asyncio.create_task(kws.subscribe_symbols([1793])) | ||
def on_order_update(data: dict): | ||
# Store order data in database or other operations | ||
pass | ||
async def cleanup(kws, kite): | ||
# perform cleanup operations here | ||
await kws.close() | ||
await kite.close() | ||
async def main(): | ||
await kite.authorize( | ||
request_token=request_token, | ||
api_key=api_key, | ||
secret=secret, | ||
) | ||
# Client session and logger instance are shared | ||
async with KiteFeed( | ||
user_id=user_id, | ||
enctoken=enctoken, | ||
session=kite.session | ||
logger=kite.log | ||
) as kws: | ||
kws.on_tick = on_tick | ||
kws.on_connect = on_connect | ||
kws.on_order_update = on_order_update | ||
# Handle KeyboardInterupt | ||
utils.add_signal_handlers(cleanup, kws, kite) | ||
# No code executes after this line | ||
await kws.connect() | ||
# Credentials loaded from a database or JSON file | ||
enctoken = "STORED_ENCTOKEN" | ||
user_id = "KITE_WEB_USER_ID" | ||
# Defined globally to allow access across the script | ||
kite = Kite(access_token=access_token) | ||
if "win" in sys.platform: | ||
# Required for windows users | ||
asyncio.set_event_loop_policy( | ||
asyncio.WindowsSelectorEventLoopPolicy(), | ||
) | ||
asyncio.run(main()) | ||
Downloading historical data | ||
--------------------------- | ||
|
||
When trying to download historical data synchronously, the typical approach would be: | ||
|
||
1. Request historical data | ||
2. Wait for a response from the server | ||
3. Process the data | ||
4. Repeat in a loop for the remaining symbols. | ||
|
||
The process is blocked while waiting for the server's response. No other task is executed, during this time. | ||
|
||
**With Async,** | ||
|
||
1. I am iterating over the symbols, creating a :py:obj:`Task` for each request. | ||
2. The :py:obj:`Task` does not execute the request immediately. Instead, it is scheduled for concurrent execution. | ||
3. Once the :py:obj:`Task` executes, it does not wait for the server response. It yields control back to the event loop to run other tasks. | ||
4. As the response returns, the :py:obj:`Task` is marked completed, and the result is available for processing. It eliminates a lot of time waiting on the network. | ||
|
||
Long CPU-intensive or IO-bound tasks can block the event loop. Use :py:obj:`loop.run_in_executor` to execute functions in separate threads or processes, without blocking the event loop. | ||
|
||
.. code:: python | ||
from aio_trader.kite import Kite | ||
from aio_trader import utils | ||
from pathlib import Path | ||
from typing import List | ||
import pandas as pd, asyncio | ||
async def main(): | ||
DIR = Path(__file__).parent | ||
instruments_file = DIR / "nse_instruments.csv" | ||
sym_list = ( | ||
"AARTIIND", | ||
"ASIANPAINT", | ||
"BATAINDIA", | ||
"HDFCLIFE", | ||
"BLUEDART", | ||
"BRITANNIA", | ||
"DEEPAKFERT", | ||
"DRREDDY", | ||
"EICHERMOT", | ||
"EIDPARRY", | ||
) | ||
columns = ("Date", "Open", "High", "Low", "Close", "Volume") | ||
async with Kite() as kite: | ||
await kite.authorize() | ||
if not instruments_file.exists(): | ||
instruments_file.write_bytes( | ||
await kite.instruments(exchange=kite.EXCHANGE_NSE) | ||
) | ||
df = pd.read_csv(instruments_file, index_col="tradingsymbol") | ||
tasks: List[asyncio.Task] = [] | ||
for sym in sym_list: | ||
# An async function returns a coroutine unless awaited | ||
coroutine = kite.historical_data( | ||
df.loc[sym, "instrument_token"], | ||
from_dt="2024-04-22", | ||
to_dt="2024-04-26", | ||
interval="day", | ||
) | ||
# Assign the coroutine to a task, to schedule it for execution | ||
# in the event loop. | ||
# Here the task name is assigned the symbol name. | ||
# We can use task.get_name to return this value | ||
task = asyncio.create_task(coroutine, name=sym) | ||
tasks.append(task) | ||
# asyncio.as_completed returns a coroutine with no reference to the task. | ||
# utils.as_completed returns the original task object from the previous | ||
# step. | ||
async for task in utils.as_completed(tasks): | ||
sym_name = task.get_name() | ||
result = task.result() | ||
candles = result["data"]["candles"] | ||
df = pd.DataFrame(candles, columns=columns, copy=False) | ||
df["Date"] = pd.to_datetime(df["Date"]) | ||
df.set_index("Date", drop=True, inplace=True) | ||
# save the result or perform further processing | ||
asyncio.run(main()) |
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,71 @@ | ||
====================== | ||
Package Implementation | ||
====================== | ||
|
||
.. code:: bash | ||
aio_trader/ | ||
├── kite | ||
│ ├── Kite.py | ||
│ ├── KiteFeed.py | ||
│ └── __init__.py | ||
├── AbstractBroker.py | ||
├── AbstractFeeder.py | ||
├── AsyncRequest.py | ||
├── __init__.py | ||
└── utils.py | ||
Each Stockbroker is packaged into a directory and can be imported independently of other brokers. | ||
|
||
:py:obj:`aio_trader.kite` is the only package available now. I will be adding more soon. | ||
|
||
.. code:: python | ||
from aio_trader.kite import Kite, KiteFeed | ||
Kite.py | ||
------- | ||
|
||
:py:obj:`Kite.py` is used to make and manage orders, access profiles, holdings, etc. | ||
|
||
The :py:obj:`Kite` class implements the :py:obj:`AbstractBroker` class. | ||
|
||
:py:obj:`AsyncRequest` is responsible for making HTTP requests, handling errors, and returning the response. | ||
|
||
:py:obj:`AsyncRequest` has a mechanism for retrying requests in case of errors. It uses [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff#Rate_limiting) to gradually increase the wait time between retries. | ||
|
||
It starts with a 3-second wait after the first try and incrementally increases to a max wait of 30 seconds. See the simple implementation below. It is similar to [pykiteconnect](https://github.com/zerodha/pykiteconnect) implementation. | ||
|
||
.. code:: python | ||
# incremental backoff | ||
base_wait = 3 | ||
max_wait = 30 | ||
retries = 0 | ||
for i in range(1, 5): | ||
wait = min(base_wait * (2**retries), max_wait) | ||
retries += 1 | ||
print(f"{retries} retry: {wait} seconds wait") | ||
In case of any network error or HTTP status codes other than 200, the request is retried. The only exception is a RuntimeError, which closes the connection and raises the error. | ||
|
||
The following status codes can trigger a RuntimeError: | ||
|
||
- **400**: Incorrect method or params | ||
- **429**: API Rate limit reached. | ||
- **403**: Forbidden | ||
|
||
|
||
KiteFeed.py | ||
----------- | ||
|
||
:py:obj:`KiteFeed.py` provides a WebSocket connection for receiving real-time market quotes. | ||
|
||
KiteFeed implements the :py:obj:`AbstractFeeder` class. It has a similar retry mechanism to :py:obj:`Kite`. If there is an error, the connection is re-attempted. A response code of 403 indicates the session has expired or is invalid. The connection is closed, with no attempts to retry. | ||
|
||
Utils.py | ||
-------- | ||
|
||
Utils.py contains helper functions. It will be covered later. |
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,46 @@ | ||
.. Aio-Trader documentation master file, created by | ||
sphinx-quickstart on Mon Apr 29 12:32:23 2024. | ||
You can adapt this file completely to your liking, but it should at least | ||
contain the root `toctree` directive. | ||
Welcome to Aio-Trader's documentation! | ||
====================================== | ||
|
||
**Aio-Trader** is an async library for accessing Indian stockbroker API and real-time market feeds. | ||
|
||
Python version: >= 3.8 | ||
|
||
.. toctree:: | ||
|
||
kite_usage | ||
examples | ||
implementation | ||
|
||
Installation | ||
------------ | ||
|
||
**1. Clone the repo** | ||
|
||
.. code:: console | ||
git clone https://github.com/BennyThadikaran/Aio-Trader.git | ||
cd aio_trader | ||
**2. Create a virtual env using venv and activate it.** | ||
|
||
.. code:: console | ||
py -m venv . | ||
source bin/Activate | ||
# On Windows, run the below to activate venv | ||
.\Scripts\Activate.ps1 | ||
**3. Install aio_trader** | ||
|
||
.. code:: console | ||
pip install . | ||
Oops, something went wrong.