Parallel order execution for Alpaca
Project description
blockpaca
Parallel execution system for Alpaca broker API
In-depth description of architecture here
Installation
pip install blockpaca
Pip installation requires Python version >= 3.10
Alternatively, you can clone the repository in a new or existing environment.
In terminal:
git clone https://github.com/henrysalkever/blockpaca.git
Repo structure
blockpaca/
├── LICENSE
├── pyproject.toml
├── README.md
└── src
└── blockpaca
├── __init__.py
├── core.py
├── live_display.py
└── tools.py
Running blockpaca
from blockpaca import run_trading
log_dir = "/Users/username/log_dir"
API_KEY = "custom_api_key" #API key obtained from your Alpaca account
SECRET_KEY = "custom_secret_key" #secret key obtained from your Alpaca account
TICKERS = ["AAPL", "TSLA", "NVDA"] #tickers to receive data for, up to 30 max for free accounts
def custom_strategy(context):
quotes, positions = context['get_current_data']()
#some custom trading logic
return order_dict
if __name__ == "__main__":
run_trading(
strategy_callable = custom_strategy,
trade_log_name = "custom_strat_name",
trade_log_path = log_dir,
api_key = API_KEY,
secret_key = SECRET_KEY,
tickers = TICKERS,
frequency = 10,
run_seconds = 60
eos_behavior = "hold",
warm_up_period = 10,
live_display = True,
display_refresh_rate = 0.05,
max_trade_updates = 20,
)
Parameters
strategy_callable : custom trading logic
trade_log_name : name for trade log file (make this something identifiable to the strategy you are running)
trade_log_path : path for trade logs
api_key : your api key from Alpaca
secret_key : your secret key from Alpaca
tickers : a list of the tickers you wish to receive quotes for (max 30 for free tier)
frequency : frequency at which the trading logic executes. Accepts integers, floats as well as strings of types "1m", "15m", "1h", "2h" *
run_seconds : total seconds to run the trading loop for. Accepts integers, floats, as well as strings of types "1m", "15m", "1h", "2h" * and "EOD" which will input the seconds until the market closes for the day minus 30 seconds.
eos_behavior : dictates the behavior at the end of the trading session. Options are "liquidate", "hold" and "custom". See the section on custom eos behavior for further details.
warm_up_period : time to wait for quotes before starting the trading loop (5 seconds recommended)
live_display : Boolean value to toggle on/off for the rich terminal display of positions and trade status
display_refresh_rate : time between each refresh of values in live display
max_trade_updates : controls how many trade updates are shown in the live display
*(numbers are adjustable, though will throw an error if you input a value for hours that will make your trading session terminate after 30 seconds before the market closes, for example "9h")
Creating Custom Trading Logic
Custom trading functions must be set up as follows:
def custom_function(context):
#trading logic
return orders
Your function must be set up to accept the context block as the only argument. The context block is a dictionary that is passed in that contains the methods for getting current quotes and positions, and submitting orders. The context block also contains the addresses for the shared memory, the result queue, and the command queue that are necessary for sharing information across Python processes.
context = {
"get_current_data": get_current_data, #callable
"submit_order": submit order, #callable
"shm_meta": shm_meta, #shared memory
"result_queue": result_q, #result queue
"command_queue": cmd_q, #command queue
}
Receiving Current Quotes and Positions
Within your trading function, run the following to get current quotes and positions:
quotes, positions = context['get_current_data']()
The schema for the quote data you recieve will be
{
"AAPL": {"bid": 150.25, "ask": 150.30},
"MSFT": {"bid": 299.50, "ask": 299.75},
"GOOG": {"bid": 2800.00, "ask": 2801.50}
}
and the schema for position data received will be
[
{"ticker": "AAPL", "quantity": 10, "bought_at": 145.00, "cost_basis": 1450.00},
{"ticker": "MSFT", "quantity": -5, "bought_at": 300.00, "cost_basis": -1500.00},
{"ticker": "GOOG", "quantity": 20, "bought_at": 2750.00, "cost_basis": 55000.00}
]
Persistent and Non-Persistent Data Between Trading Loops
If you would like to run strategies that depend on data collected over the course of the trading session (moving averages etc.), you should collect data in a structure outside of your custom trading function. For example, if you wanted to collect the quotes passed to the trading algorithm each time it was called, you would set up your code in the following way:
quote_collection = []
def trading_strat(context):
quotes, positions = context['get_current_data']()
quote_collection.append(quotes)
#trading logic...
return orders
if __name__ == "__main__":
run_trading(
strategy_callable = trading_strat,
...
)
If you were to instead initialize your list inside the trading function, it would be wiped every time your trading logic runs. You can also read in csv files or other past data and have it persist from execution to execution, it just needs to be read in outside of the strategy function itself.
Composing and Submitting Orders
Sides: "buy", "sell"
Actions: "buy_to_cover", "sell_long", "short_sell", "liquidate", "cover_short"
| Side | Action | Result |
|---|---|---|
| buy | none | open long |
| buy | buy_to_cover | close short |
| buy | cover_short | close short (used in eos) |
| sell | none/sell_long | close long |
| sell | short_sell | open short |
| sell | liquidate | close long (used in eos) |
the schema for individual orders is
{
"ticker": str, # e.g. "AAPL"
"quantity": float, # e.g. 10
"side": str, # "buy" or "sell"
"order_type": str, # "market" or "limit"
"action": str, # optional: "buy_to_cover", "short_sell", "sell_long", "liquidate", "cover_short"
"limit_price": float, # required if order_type is "limit"
}
to submit a block of orders, simply combine them into a list
orders = [
{"ticker": "AAPL", "quantity": 10, "side": "buy", "order_type": "market"},
{"ticker": "MSFT", "quantity": 5, "side": "buy", "order_type": "market"},
{"ticker": "GOOGL", "quantity": 3, "side": "sell", "order_type": "market"},
]
there are two options for submitting orders:
#Option 1
def trading_strat(context):
#retrieve quotes, positions, run trading logic to yield orders
context['submit_order'](orders)
return None
#Option 2
def trading_strat(context):
#retrieve quotes, positions, run trading logic to yield orders
return orders
Both of these options are different ways to access the same execution function.
Calling Protocol
It is important to call run_trading as follows
#global variables for initialization
def trading_function(context):
return orders
if __name__ == "__main__":
run_trading(
strategy_callable = trading_function,
#.....
)
because run_trading spawns child processes, and calling run_trading outside the main block may spawn extra child processes during import.
Custom EOS Behvaior
If you call run_trading with the eos_behavior parameter set to "custom", you must also provide a callable function to the custom_shutdown_action parameter. For example:
from blockpaca import run_trading
log_dir = "/Users/username/log_dir"
API_KEY = "custom_api_key" #API key obtained from your Alpaca account
SECRET_KEY = "custom_secret_key #secret key obtained from your Alpaca account
TICKERS = ["AAPL", "TSLA", "NVDA"] #tickers to receive data for, up to 30 max for free accounts
def custom_strategy(context):
quotes, positions = context['get_current_data]()
#some custom trading logic
return order_dict
def shutdown_strategy(context):
#custom shutdown logic
return order_dict
if __name__ == "__main__":
run_trading(
strategy_callable = custom_strategy,
trade_log_name = "custom_strat_name",
trade_log_path = log_dir,
api_key = API_KEY,
secret_key = SECRET_KEY,
tickers = TICKERS,
frequency = 10,
run_seconds = 60
eos_behavior = "custom",
custom_shutdown_action = shutdown_strategy,
warm_up_period = 10,
live_display = True,
display_refresh_rate = 0.05,
max_trade_updates = 20,
)
Rate Limiting and Retry Logic
Free tier customers of Alpaca are limited to 200 order submissions per minute. This system automatically handles overflow of orders per minute, and the orders will wait in the order they arrived until the rate replenishes. Future versions will include automatic toggle of this rate limit to a higher value for paying-tier Alpaca users, but for now if you have the paid tier you will need to go in and change the max trades per minute manually.
Additionally retry logic includes exponential backoff. For example if a POST request to Alpaca fails, the backoff time between retries will double after every fail, up to a maximum of three attempts. If you wish to allow more retries, it can be changed manually in the code (but not from the pip-installed package).
Trade Logs
Every order processed in a given trading session will be saved to "/Users/username/log_dir/trade_log_name.csv". These order updates come from the Alpaca trade update stream, and will include timestamps for pending_new, new, and filled for each order. The client_order_id field will also list the action as well as the execution worker that submitted the order. Trade logs will be saved as follows:
type,client_order_id,ticker,executed_price,executed_qty,status,side,action,timestamp,raw_status
execution,buy_long-7-2304b6b1,DIA,0.0,0.0,pending_new,buy,buy_long,1766515599.059126,OrderStatus.PENDING_NEW
execution,buy_long-7-2304b6b1,DIA,0.0,0.0,new,buy,buy_long,1766515599.067043,OrderStatus.NEW
execution,buy_long-7-2304b6b1,DIA,484.74,1.0,filled,buy,buy_long,1766515599.236655,OrderStatus.FILLED
In this case, execution worker 7 submitted this order.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file blockpaca-0.1.1.tar.gz.
File metadata
- Download URL: blockpaca-0.1.1.tar.gz
- Upload date:
- Size: 24.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67422595d4077a68a641f7348db5675bc1b5ba1baa08a0930ea65582dd5246b9
|
|
| MD5 |
a4e8d5ea64c5523a53d1a9a1aa5cfcee
|
|
| BLAKE2b-256 |
bea75263a02dc4c8aa953c9f8becf1dfac1e2a7dfbe65d9449f097f769045f5c
|
File details
Details for the file blockpaca-0.1.1-py3-none-any.whl.
File metadata
- Download URL: blockpaca-0.1.1-py3-none-any.whl
- Upload date:
- Size: 21.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
453b5c379263d4bf67992398cc85a67f528074dfe7c0bbf62100dd7a2fa394e1
|
|
| MD5 |
a4d8349c335fa31530caba655d0ab90d
|
|
| BLAKE2b-256 |
f37dd1996418a7b145ef194299759c95d8e041fb3ebbb2be651c677b4c265834
|