TradeStation API
Source: Notion | Last edited: 2023-10-05 | ID: fcf46a37-4d3...
The following outlines the current state of the TradeStation API development.
The API follows an Access Token and Refresh Token pattern. Both access and refresh tokens are generated via a browser-based redirect URL, with the help of API key and Secret issued by TradeStation. Once the Access Token is generated, it’s only valid for 20 minutes, at which point the Refresh tokens need to be used to re-generate Access tokens.
Since we already generated both Access and Refresh tokens, I don’t think we need to ever repeat the initial browser-based (localhost redirect URL) authentication step.
(general note: all futures prices are 10 minutes delayed)
The boilerplate API authentication in python looks like this:
# ConstantsTOKEN_ENDPOINT = 'https://signin.tradestation.com/oauth/token'BARS_ENDPOINT = "https://api.tradestation.com/v3/marketdata/barcharts/"
CLIENT_ID = 'API Key'CLIENT_SECRET = 'API Secret'ACCESS_TOKEN = ''REFRESH_TOKEN = ''
HEADERS = { "Authorization": f"Bearer {ACCESS_TOKEN}"}And the refreshing of the Access Token, once expired works like this:
def refresh_access_token(refresh_token): data = { 'grant_type': 'refresh_token', 'refresh_token': refresh_token, 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, } response = requests.post(TOKEN_ENDPOINT, data=data) response_data = response.json()
if response.status_code != 200: raise Exception("Failed to refresh token. {}".format(response_data.getca('error_description')))
return response_data['access_token']API documentation can be found here:
https://api.tradestation.com/docs/specification
The five contracts that we have so far been interested in (Corn, Soybeans, Wheat, Yen, Light Sweet Crude Oil) are called with the following base names: “C”, “S”, “W”, “JY”, “CL”
but are then appended with Month and Year information depending on the contract we’re interested in. The Month Schema is the following:
# Example contract month/years:SYMBOLS_DESCRIPTIONS = ['CU23', 'SU23', 'WU23', 'JYU23', 'CLU23']Additional information about Futures Names can be found here:
Example Script for continual fetching of 1-minute data for our 5 contracts:
import requestsimport datetimeimport pandas as pdimport sqlite3import time
# ConstantsTOKEN_ENDPOINT = 'https://signin.tradestation.com/oauth/token'BARS_ENDPOINT = "https://api.tradestation.com/v3/marketdata/barcharts/"SYMBOLS_DESCRIPTIONS = ['CU23', 'SU23', 'WU23', 'JYU23', 'CLU23']CLIENT_ID = 'API Key'CLIENT_SECRET = 'API Secret'ACCESS_TOKEN = ''REFRESH_TOKEN = ''
HEADERS = { "Authorization": f"Bearer {ACCESS_TOKEN}"}
# Initialize database connectionconn = sqlite3.connect('futures_data.db')
def refresh_access_token(refresh_token): data = { 'grant_type': 'refresh_token', 'refresh_token': refresh_token, 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, } response = requests.post(TOKEN_ENDPOINT, data=data) response_data = response.json()
if response.status_code != 200: raise Exception("Failed to refresh token. {}".format(response_data.get('error_description')))
return response_data['access_token']
def fetch_bars_for_symbol(symbol): # Updated bars_url to use barsback instead of firstdate and lastdate bars_url = f"{BARS_ENDPOINT}{symbol}?interval=1&unit=Minute&barsback=1"
response = requests.get(bars_url, headers=HEADERS)
if response.status_code == 401: print("Access token expired. Refreshing token for bars...") global ACCESS_TOKEN ACCESS_TOKEN = refresh_access_token(REFRESH_TOKEN) HEADERS['Authorization'] = f"Bearer {ACCESS_TOKEN}" response = requests.get(bars_url, headers=HEADERS) # Retry the request
if response.status_code == 200: bar_data = response.json() bars = bar_data.get('Bars', [])
# Convert bars to pandas DataFrame df = pd.DataFrame(bars)
# Save to database df.to_sql(symbol, conn, if_exists='append', index=False)
# Print the dataframe print(df) else: print(f"Couldn't fetch bars for {symbol}") print(f"Status Code: {response.status_code}") print(f"Response Content: {response.content.decode('utf-8')}")
def main(): while True: for symbol in SYMBOLS_DESCRIPTIONS: print(f"Fetching bars for {symbol}:") fetch_bars_for_symbol(symbol) time.sleep(60) # Wait for 1 minute before the next fetch
if __name__ == "__main__": main()The above code lacks the following:
-
Error handling like network outage etc.
-
Contract Month expiry and rollover (working on it…)
-
Security in terms of keeping Access Tokens unexposed? (This account belongs to Emil and has $250k in it, which we could trade with this API… so we have to be careful with security)
-
sometimes it fetches the same candle twice in a row, because I think that the script’s time is not synched with the exchange’s time, so the script’s timing moves around the minute, while the exchange times for new minute candles are set.
The format of the data is the following:
Contract Switching and Rollover
Section titled “Contract Switching and Rollover”Switching to a new contract could be handled like so, for example (this is not fully tested yet):
(not all futures have all months available. Some futures for example, only have certain months, or quarterly contracts). For more information on specific futures contracts, see below.
import calendar
def get_expiry_date(symbol): """ Get the expiration date for a given contract based on its symbol. """ base_contract = symbol[0] month_code = symbol[1] year = int("20" + symbol[2:4]) month = month_codes[month_code]
if base_contract == 'C' or base_contract == 'S' or base_contract == 'W': # Trading terminates on the business day prior to the 15th day of the contract month. return datetime.date(year, month, 14) elif base_contract == 'JY': # Trading terminates 2 business days prior to the third Wednesday of the contract month. third_wednesday = [week[calendar.WEDNESDAY] for week in calendar.monthcalendar(year, month) if week[calendar.WEDNESDAY] != 0][2] return datetime.date(year, month, third_wednesday) - datetime.timedelta(days=2) elif base_contract == 'CL': # Trading terminates 3 business days before the 25th calendar day of the month prior to the contract month. if month == 1: month = 12 year -= 1 else: month -= 1 if datetime.date(year, month, 25).weekday() >= 5: return datetime.date(year, month, 25) - datetime.timedelta(days=4) else: return datetime.date(year, month, 25) - datetime.timedelta(days=3)
def should_switch_contract(symbol): """ Determine if today is the day to switch to the next contract. Arbitrarily set at 1 day before expiry """ expiry_date = get_expiry_date(symbol) today = datetime.date.today() return today == (expiry_date - datetime.timedelta(days=1))
def get_next_contract(symbol): """ Construct the next contract symbol based on the current contract symbol. """ base_contract = symbol[0] month_code = symbol[1] year = int("20" + symbol[2:4]) month = month_codes[month_code]
if month == 12: next_month_code = 'F' year += 1 else: next_month_index = list(month_codes.values()).index(month) + 1 next_month_code = list(month_codes.keys())[next_month_index]
return f"{base_contract}{next_month_code}{str(year)[2:]}"
month_codes = { 'F': 1, 'G': 2, 'H': 3, 'J': 4, 'K': 5, 'M': 6, 'N': 7, 'Q': 8, 'U': 9, 'V': 10, 'X': 11, 'Z': 12}
# ...
def main(): while True: for symbol in SYMBOLS_DESCRIPTIONS: if should_switch_contract(symbol): print(f"Switching from {symbol} due to contract expiry.") symbol = get_next_contract(symbol) print(f"Fetching bars for new contract: {symbol}") else: print(f"Fetching bars for {symbol}:")
fetch_bars_for_symbol(symbol) time.sleep(60) # Wait for 1 minute before the next fetch- TimeStamp: The date and time marking the end of the period for the bar data.
- TotalVolume: The total number of contracts traded during the specified time period.
- DownTicks: The number of times the price moved downward during the specified time period.
- DownVolume: The volume of contracts traded during the periods of downward price movement.
- OpenInterest: The number of futures contracts that have not yet been exercised, expired, or fulfilled by delivery. Open interest is used as an indicator of the strength of the price trend.
- IsRealtime: Indicates whether the bar data is real-time or not. “TRUE” for real-time data and “FALSE” for delayed or historical data.
- IsEndOfHistory: Indicates if this bar data represents the last available data point in the series. Often useful for understanding if more data is to be expected or not.
- TotalTicks: The total number of price changes or ticks that occurred during the specified time period.
- UnchangedTicks: The number of times the price remained unchanged during the specified time period.
- UnchangedVolume: The volume of contracts traded during the periods when the price remained unchanged.
- UpTicks: The number of times the price moved upward during the specified time period.
- UpVolume: The volume of contracts traded during the periods of upward price movement.
- Epoch: Represents the time in a machine-readable format, typically seconds since 1970-01-01 (the Unix epoch). It’s useful for algorithms or systems that work with time in a numeric format rather than a human-readable one.
- BarStatus: Indicates the status of the bar. “Closed” means the time period for this data point has ended, while other potential statuses (not shown in your example) might be “Open” or “In-progress”, depending on the system providing the data.
Further Notes on Specific Futures Contracts
Section titled “Further Notes on Specific Futures Contracts”Oil / CL
Section titled “Oil / CL”Soybeans
Section titled “Soybeans”Japanese Yen
Section titled “Japanese Yen”Trading Hours
Section titled “Trading Hours”
Continued API work:
Section titled “Continued API work:”Here is a better script that continually fetches 1-minute bars for all commodities, plus BTC and ETH futures. It also handles monthly roll-over (although this might not be optimal), and saves everything to a database. Very minimal error-handling.
TODO:
- line up the script time with the exchange time, so that bars are fetched at the right time.
- perhaps fetch 2 or 3 months of each contract at all times, so that we have parallel data for several expiry months for each instrument Notes: continual fetching with TradeStation has some quirks: 1-minute bars are sometimes missing, which is due to the fact that no trades occurred during that time.
import requestsimport datetimeimport pandas as pdimport sqlite3import timeimport calendarimport re
# ConstantsTOKEN_ENDPOINT = 'https://signin.tradestation.com/oauth/token'BARS_ENDPOINT = "https://api.tradestation.com/v3/marketdata/barcharts/"SYMBOLS_DESCRIPTIONS = ['C', 'S', 'W', 'JY', 'CL', 'BTC', 'ETH']CLIENT_ID = 'A77K5Vk37dCJRdXQlaWALLRGIpP45mp0'CLIENT_SECRET = ''ACCESS_TOKEN = ''REFRESH_TOKEN = ''
HEADERS = { "Authorization": f"Bearer {ACCESS_TOKEN}"}
# Initialize database connectionconn = sqlite3.connect('futures_data.db')
# Initial month and year for each base symbolcurrent_contracts = { 'C': {'month': 'U', 'year': '23'}, 'S': {'month': 'U', 'year': '23'}, 'W': {'month': 'U', 'year': '23'}, 'JY': {'month': 'U', 'year': '23'}, 'CL': {'month': 'U', 'year': '23'}, 'BTC': {'month': 'U', 'year': '23'}, 'ETH': {'month': 'U', 'year': '23'}, # Add the initialization for TBT}
# Month Codes for Contract Symbolsmonth_codes = { 'F': 1, 'G': 2, 'H': 3, 'J': 4, 'K': 5, 'M': 6, 'N': 7, 'Q': 8, 'U': 9, 'V': 10, 'X': 11, 'Z': 12}
def refresh_access_token(refresh_token): data = { 'grant_type': 'refresh_token', 'refresh_token': refresh_token, 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, } response = requests.post(TOKEN_ENDPOINT, data=data) response_data = response.json()
if response.status_code != 200: raise Exception("Failed to refresh token. {}".format(response_data.get('error_description')))
return response_data['access_token']
def get_contract_symbol(base_symbol): """ Constructs the full contract symbol based on the base symbol and the current month/year mapping. """ return base_symbol + current_contracts[base_symbol]['month'] + current_contracts[base_symbol]['year']
def fetch_bars_for_symbol(symbol): # Updated bars_url to use barsback instead of firstdate and lastdate bars_url = f"{BARS_ENDPOINT}{symbol}?interval=1&unit=Minute&barsback=1"
response = requests.get(bars_url, headers=HEADERS)
if response.status_code == 401: print("Access token expired. Refreshing token for bars...") global ACCESS_TOKEN ACCESS_TOKEN = refresh_access_token(REFRESH_TOKEN) HEADERS['Authorization'] = f"Bearer {ACCESS_TOKEN}" response = requests.get(bars_url, headers=HEADERS) # Retry the request
if response.status_code == 200: bar_data = response.json() bars = bar_data.get('Bars', [])
# Convert bars to pandas DataFrame df = pd.DataFrame(bars)
# Save to database df.to_sql(symbol, conn, if_exists='append', index=False)
# Print the dataframe #print(df) else: print(f"Couldn't fetch bars for {symbol}") print(f"Status Code: {response.status_code}") print(f"Response Content: {response.content.decode('utf-8')}")
def get_expiry_date(symbol): """ Get the expiration date for a given contract based on its symbol. """ # Extract month code and year using regex match = re.search(r'([A-Z])(\d+)$', symbol) if not match: raise ValueError(f"Invalid symbol format: {symbol}")
month_code = match.group(1) year = int("20" + match.group(2))
# Base contract is the remaining part of the symbol after removing month code and year base_contract = symbol[:match.start()]
month = month_codes[month_code]
if base_contract in ['C', 'S', 'W', 'BTC', 'ETH']: #BTC and ETH might need separate rules # Trading terminates on the business day prior to the 15th day of the contract month. return datetime.date(year, month, 14) elif base_contract == 'JY': # Trading terminates 2 business days prior to the third Wednesday of the contract month. third_wednesday = [week[calendar.WEDNESDAY] for week in calendar.monthcalendar(year, month) if week[calendar.WEDNESDAY] != 0][2] return datetime.date(year, month, third_wednesday) - datetime.timedelta(days=2) elif base_contract == 'CL': # Trading terminates 3 business days before the 25th calendar day of the month prior to the contract month. if month == 1: month = 12 year -= 1 else: month -= 1 if datetime.date(year, month, 25).weekday() >= 5: return datetime.date(year, month, 25) - datetime.timedelta(days=4) else: return datetime.date(year, month, 25) - datetime.timedelta(days=3)
def should_switch_contract(base_symbol): """ Determine if today is the day to switch to the next contract. Arbitrarily set at 1 day before expiry. """ symbol = get_contract_symbol(base_symbol) expiry_date = get_expiry_date(symbol) today = datetime.date.today() return today == (expiry_date - datetime.timedelta(days=1))
def get_next_contract(base_symbol): """ Update the current month/year mapping for the base symbol to the next contract's month/year. """ current_month_code = current_contracts[base_symbol]['month'] current_year = int(current_contracts[base_symbol]['year'])
# Check if current_month_code is the last in the month_codes list if current_month_code == 'Z': next_month_code = 'F' # Reset to the first month code current_year += 1 else: next_month_index = list(month_codes.keys()).index(current_month_code) + 1 next_month_code = list(month_codes.keys())[next_month_index]
# Ensure that the year is always two digits year_str = str(current_year)[-2:]
current_contracts[base_symbol]['month'] = next_month_code current_contracts[base_symbol]['year'] = year_str
def main(): while True: for base_symbol in SYMBOLS_DESCRIPTIONS: contract_symbol = get_contract_symbol(base_symbol) if should_switch_contract(base_symbol): print(f"Switching from {contract_symbol} due to contract expiry.") get_next_contract(base_symbol) contract_symbol = get_contract_symbol(base_symbol) print(f"Fetching bars for new contract: {contract_symbol}") else: print(f"Fetching bars for {contract_symbol}:")
fetch_bars_for_symbol(contract_symbol) time.sleep(60) # Wait for 1 minute before the next fetch
if __name__ == "__main__": main()occasional server errors with this script:
Fetching bars for BTCU23:Couldn't fetch bars for BTCU23Status Code: 500Response Content: {"Error":"InternalServerError","Message":"ERROR - The operation has timed out."}
Fetching bars for ETHU23:Couldn't fetch bars for ETHU23Status Code: 500Response Content: {"Error":"InternalServerError","Message":"ERROR - The operation has timed out."}Getting Historical Data with Individual Contract Logic
Section titled “Getting Historical Data with Individual Contract Logic”The below script seems to work for fetching historical data, and allows for logic adjustment for each contract individually, if required (i.e. when the rollover date is, etc.). This script throws a lot of errors but I think that is when it tries to query weekends or holidays for which there is no data. It just continues to run.
TradeStation’s Symbology can be found here: https://www.tradestation.com/learn/market-basics/futures-options/symbology/futures-plus-symbology/
import requestsimport pandas as pdimport datetimeimport os
# ConstantsTOKEN_ENDPOINT = 'https://signin.tradestation.com/oauth/token'BARS_ENDPOINT = "https://api.tradestation.com/v3/marketdata/barcharts/"SYMBOLS_DESCRIPTIONS = ['C', 'S', 'W', 'JY', 'CL']CLIENT_ID = 'A77K5Vk37dCJRdXQlaWALLRGIpP45mp0'CLIENT_SECRET = ''ACCESS_TOKEN = ''REFRESH_TOKEN = ''
HEADERS = { "Authorization": f"Bearer {ACCESS_TOKEN}"}
month_codes = { 'F': 1, 'G': 2, 'H': 3, 'J': 4, 'K': 5, 'M': 6, 'N': 7, 'Q': 8, 'U': 9, 'V': 10, 'X': 11, 'Z': 12}
# Functions from the TradeStation scriptdef refresh_access_token(refresh_token): data = { 'grant_type': 'refresh_token', 'refresh_token': refresh_token, 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, } response = requests.post(TOKEN_ENDPOINT, data=data) response_data = response.json() if response.status_code != 200: raise Exception("Failed to refresh token. {}".format(response_data.get('error_description'))) return response_data['access_token']
def fetch_bars_for_symbol(base_symbol, symbol, start_date, end_date, contract_month): first_date_str = start_date.strftime('%Y-%m-%dT%H:%M:%SZ') last_date_str = end_date.strftime('%Y-%m-%dT%H:%M:%SZ') bars_url = f"{BARS_ENDPOINT}{symbol}?interval=5&unit=Minute&firstdate={first_date_str}&lastdate={last_date_str}&sessiontemplate=USEQPreAndPost" response = requests.get(bars_url, headers=HEADERS)
# Handle token expiration and refresh if response.status_code == 401: print("Access token expired. Refreshing token for bars...") global ACCESS_TOKEN ACCESS_TOKEN = refresh_access_token(REFRESH_TOKEN) HEADERS['Authorization'] = f"Bearer {ACCESS_TOKEN}" response = requests.get(bars_url, headers=HEADERS) # Retry the request
if response.status_code == 200: bar_data = response.json() bars = bar_data.get('Bars', []) df = pd.DataFrame(bars) df['ContractMonth'] = contract_month filename = f"{base_symbol}_continuous_contract_TS_2022_August_2023.csv" filepath = os.path.join("./TS2YearOutput_V3/", filename)
# Check if the file exists and has headers header = not os.path.exists(filepath)
df.to_csv(filepath, mode='a', header=header, index=False) else: print(f"Couldn't fetch bars for {symbol}") print(f"Status Code: {response.status_code}") print(f"Response Content: {response.content.decode('utf-8')}")
# Define the logic for each symbol# Individual functions for each symbol
#Japanese Yendef get_JY_contract(today): if today.month < 2 or (today.month == 2 and today.day <= 10): return 3, today.year elif today.month < 5 or (today.month == 5 and today.day <= 10): return 6, today.year elif today.month < 8 or (today.month == 8 and today.day <= 10): return 9, today.year else: return 12, today.year
# Corndef get_C_contract(today): return get_generic_contract(today, [3, 5, 7, 9, 12])
# Soybeansdef get_S_contract(today): return get_generic_contract(today, [3, 5, 7, 9, 12])
# Wheatdef get_W_contract(today): return get_generic_contract(today, [3, 5, 7, 9, 12])
# Oildef get_CL_contract(today): if today.day < 10: contract_month = (today.month % 12) + 1 contract_year = today.year if today.month != 12 else today.year + 1 else: contract_month = ((today.month + 1) % 12) + 1 contract_year = today.year if today.month != 11 else today.year + 1 return contract_month, contract_year
def get_generic_contract(today, traded_months): if today.day <= 10: contract_month = today.month else: contract_month = (today.month % 12) + 1
while contract_month not in traded_months: contract_month = (contract_month % 12) + 1
contract_year = today.year if contract_month < today.month: contract_year += 1
return contract_month, contract_year
# Main functiondef main(): current_date = START_DATE while current_date <= END_DATE: for base_symbol in SYMBOLS_DESCRIPTIONS: contract_month, contract_year = globals()[f"get_{base_symbol}_contract"](current_date)
month_code = [k for k, v in month_codes.items() if v == contract_month][0] contract_symbol = f"{base_symbol}{month_code}{str(contract_year)[-2:]}" fetch_bars_for_symbol(base_symbol, contract_symbol, current_date, current_date + datetime.timedelta(days=1), contract_month) current_date += datetime.timedelta(days=1)
# Main executionif __name__ == "__main__": main()Order Execution
Section titled “Order Execution”The below script implements a Class that handles account information and order execution. Importantly, one must be careful to set the correct base-url to be either SIM (paper) or Live trading. In the code below, we have the SIM trading url in place.
This class can:
-
get accounts available with the given API
-
get account balances
-
get positions
-
get orders (this fetches orders of the day, both executed and pending)
-
confirm orders: This estimates cost and price of an oder, without actually executing it.
-
place orders (market and limit implemented so far, stoplimit, and stopmarket not yet…)
-
replace orders (not yet implemented)
-
cancel orders
-
cancel all unfilled orders
see API specifications for more details
import requestsimport reimport time
class ExecutionClass: def __init__(self, client_id, client_secret, refresh_token): self.TOKEN_ENDPOINT = 'https://signin.tradestation.com/oauth/token' self.ACCOUNT_ENDPOINT = "https://sim-api.tradestation.com/v3/brokerage/accounts" self.EXECUTION_ENDPOINT = "https://sim-api.tradestation.com/v3/orderexecution" self.MARKETDATA_ENDPOINT = "https://sim-api.tradestation.com/v3/marketdata" self.CLIENT_ID = '' self.CLIENT_SECRET = '' self.ACCESS_TOKEN = '' self.REFRESH_TOKEN = '' self.ACCOUNTS = ['SIM1530338X', 'SIM1530337F', 'SIM1530336M'] # account types: Forex, Futures, Margin self.HEADERS = { "Authorization": f"Bearer {self.ACCESS_TOKEN}" } self.refresh_access_token()
def refresh_access_token(self): data = { 'grant_type': 'refresh_token', 'refresh_token': self.REFRESH_TOKEN, 'client_id': self.CLIENT_ID, 'client_secret': self.CLIENT_SECRET, } response = requests.post(self.TOKEN_ENDPOINT, data=data) response_data = response.json()
if response.status_code != 200: raise Exception("Failed to refresh token. {}".format(response_data.get('error_description')))
self.ACCESS_TOKEN = response_data['access_token'] self.HEADERS['Authorization'] = f"Bearer {self.ACCESS_TOKEN}"
#generic request function def make_request(self, method, url, json): response = requests.request(method, url, json=json, headers=self.HEADERS)
if response.status_code == 401: print("Access token expired. Refreshing token...") self.refresh_access_token() response = requests.request(method, url, json=json, headers=self.HEADERS)
if response.status_code == 200: return response.json() else: return None
def get_account(self): data = self.make_request("GET", self.ACCOUNT_ENDPOINT, None) if data: #print(data) return data
def get_balances(self, account): url = f"{self.ACCOUNT_ENDPOINT}/{account}/balances" data = self.make_request("GET", url, None) if data: #print(f"{account} Current Balances:") #print(data) return data
def get_positions(self, account): url = f"{self.ACCOUNT_ENDPOINT}/{account}/positions" data = self.make_request("GET", url, None) if data: #print(f"{account} Current Positions:") #print(data) return data
# Function to get the quantity for a given symbol from the positions_data delived by get_positions() def get_quantity_for_symbol(self, positions_data, target_symbol): for position in positions_data.get("Positions", []): if position["Symbol"] == target_symbol: return int(position["Quantity"]) print(f"No position found for symbol: {target_symbol}") return 0
def get_orders(self, account): url = f"{self.ACCOUNT_ENDPOINT}/{account}/orders" data = self.make_request("GET", url, None) if data: #print(f"{account} Today's Orders:") #print(data) return data
# Returns estimated cost and commission information for an order without the order actually being placed. def confirm_order(self, account, symbol, quantity, order_type, trade_action, duration, limit_price=None): url = f"{self.EXECUTION_ENDPOINT}/orderconfirm"
payload = { "AccountID": f"{account}", "Symbol": f"{symbol}", "Quantity": f"{quantity}", "OrderType": f"{order_type}", # Valid Types: Market, Limit, Stop Market, Stop Limit, Options, and Order Sends Order (OSO) "TradeAction": f"{trade_action}", "TimeInForce": {"Duration": f"{duration}"}, "Route": "Intelligent" }
# Include LimitPrice only if it is not None (i.e., for Limit orders) if limit_price is not None: payload["LimitPrice"] = f"{limit_price}"
headers = { "content-type": "application/json", "Authorization": "Bearer TOKEN" }
data = self.make_request("POST", url, payload) if data: #print(f"{account} Order Confirm:") #print(data) return data
## !!! Place and Order !!! (Make sure to check if you are in SIM or LIVE Environment!) def place_order(self, account, symbol, quantity, order_type, trade_action, duration, limit_price=None): url = f"{self.EXECUTION_ENDPOINT}/orders"
payload = { "AccountID": f"{account}", "Symbol": f"{symbol}", "Quantity": f"{quantity}", "OrderType": f"{order_type}", # Valid Types: Market, Limit, Stop Market, Stop Limit, Options, and Order Sends Order (OSO) "TradeAction": f"{trade_action}", #BUY, SELL, ... "TimeInForce": {"Duration": f"{duration}"}, # DAY, ... "Route": "Intelligent" }
# Include LimitPrice only if it is not None (i.e., for Limit orders) if limit_price is not None: payload["LimitPrice"] = f"{limit_price}"
headers = { "content-type": "application/json", "Authorization": "Bearer TOKEN" }
data = self.make_request("POST", url, payload) if data: print(f"{account} Order Execution (!!!):") print(data) return data
# replace order def replace_oder(self, order_ID): return
# cancel an order given its order ID and account def cancel_order(self, order_ID, account): url = f"{self.EXECUTION_ENDPOINT}/orders/{order_ID}" data = self.make_request("DELETE", url, None) if data: print(f"{account} Order Cancellation:") print(data) return data
def close_all_positions(self, account): # Get all positions positions_data = self.get_positions(account)
if not positions_data or 'Positions' not in positions_data: print(f"{account} No open positions to close.") return
# Loop through each position for pos in positions_data['Positions']: symbol = pos['Symbol'] quantity = abs(int(pos['Quantity'])) # Make sure the quantity is positive long_short = pos['LongShort']
# Determine the trade action (Buy if short, Sell if long) trade_action = "BUY" if long_short == "Short" else "SELL"
# Place order to close the position print(f"Attempting to close position: {symbol}, {quantity}, {trade_action}") order_response = self.place_order(account, symbol, quantity, "Market", trade_action, "DAY")
print("Order response:") print(order_response)
# Function to cancel all unfilled orders def cancel_unfilled_orders(self, orders, account): for order in orders.get('Orders', []): legs = order.get('Legs', []) for leg in legs: if int(leg['QuantityRemaining']) > 0: symbol = leg['Symbol'] order_ID = order['OrderID'] print(f"Cancelling unfilled order for {symbol} with Order ID: {order_ID}")
# Cancel the order self.cancel_order(order_ID, account)
## function that calculates and returns initial margin requirements ## there doesnt seem to be a direct API method for this so we have to calculate ourselves def calculate_initial_margin_requirements(self, symbol_full): # Define the multipliers for each contract multipliers = { "BTC": 5, "ETH": 50, "CL": 1000, "C": 5000, "S": 5000, "JY": 12500000/100, "W": 5000 }
# Extract the base symbol by removing the last three characters base_symbol = symbol_full[:-3]
# Get the multiplier based on the base symbol multiplier = multipliers.get(base_symbol, None) if multiplier is None: print(f"Unknown base symbol: {base_symbol}") return
# Make the API call to get the estimated price and cost order_data = self.confirm_order(account="SIM1530337F", symbol=symbol_full, quantity=1, order_type="Market", trade_action="BUY", duration="DAY")
estimated_price = float(order_data['Confirmations'][0]['EstimatedPrice']) estimated_cost = float(order_data['Confirmations'][0]['EstimatedCost'].replace(',', ''))
# Special handling for futures quoted with fractional prices and in cents if base_symbol in ["C", "S", "W"]: estimated_price = estimated_price / 100 # Convert from cents to dollars
# Calculate the initial margin requirement initial_margin_requirement = (estimated_cost / (estimated_price * multiplier)) print(f"Initial Margin Requiremnt for {symbol_full}: {initial_margin_requirement * 100}%") return initial_margin_requirement
#get quote snapshot, bid, ask, etc. def get_quote_snapshot(self,symbol_full): url = f"{self.MARKETDATA_ENDPOINT}/quotes/{symbol_full}" data = self.make_request("GET", url, None) if data: # Extract the required fields from the data last_price = float(data['Quotes'][0]['Last']) ask_price = float(data['Quotes'][0]['Ask']) ask_size = int(data['Quotes'][0]['AskSize']) bid_price = float(data['Quotes'][0]['Bid']) bid_size = int(data['Quotes'][0]['BidSize']) # Extract market flags is_delayed = data['Quotes'][0]['MarketFlags']['IsDelayed'] is_halted = data['Quotes'][0]['MarketFlags']['IsHalted']
return { 'Last': last_price, 'Ask': ask_price, 'AskSize': ask_size, 'Bid': bid_price, 'BidSize': bid_size, 'IsDelayed': is_delayed, 'IsHalted': is_halted # indictates if Market is Open or not }
#returns true if if market is currently open def is_market_open(self, symbol_full): quote_snapshot_data = self.get_quote_snapshot(symbol_full) #print(not quote_snapshot_data['IsHalted']) return not quote_snapshot_data['IsHalted']
def main(self): self.get_account() #for account in self.ACCOUNTS: #self.get_balances(account) #self.get_positions(account) #self.get_orders(account)
## Test all of the important methods of the Execution Class:def run_trade_simulation(account, symbol_full): # check account balance print("Getting Account Balances: ") execution.get_balances(account) print()
# check order history print("Check Order History: ") execution.get_orders(account) print()
# get last quotes print("Last Quotes:") quote_snapshot = execution.get_quote_snapshot(symbol_full) print()
# place limit order print("Placing Limit order at Ask Price") execution.place_order(account, symbol_full, 1, "Limit", "BUY", "DAY", limit_price=quote_snapshot["Ask"]) print()
print("Waiting for a few seconds so that the order can execute... ") time.sleep(2)
# check orders print("Checking Order History Again:") orders = execution.get_orders(account) print()
# get positions print("Get Positions") positions_data = execution.get_positions(account) print()
# get position quanity for our symbol print(f"Position Quantity for {symbol_full}:") quantity = execution.get_quantity_for_symbol(positions_data, symbol_full) print(quantity) print()
# if there is a position, sell it. if quantity > 0: print("Placing market sell order...") execution.place_order(account, symbol_full, quantity, "Market", "SELL", "DAY")
# check account balance print("Getting Account Balances Again: ") execution.get_balances(account) print()
time.sleep(2) # finally, if there are unfilled orders, cancel them: # check orders print("Checking Order History Again:") orders = execution.get_orders(account) print()
## cancel unfilled orders: print("cancelling unfilled orders...") execution.cancel_unfilled_orders(orders, account)
return
#testing the close all positions function#also testing is_market_open() functiondef run_trade_simulation_v2(account, symbol_list): for s in symbol_list: print(f"Market for {s} is open? {execution.is_market_open(s)}")
if execution.is_market_open(symbol_list[0]): execution.place_order(account, symbol_list[0], 1, "Market", "BUY", "DAY")
if execution.is_market_open(symbol_list[1]): execution.place_order(account, symbol_list[1], 2, "Market", "SELL", "DAY")
positions = execution.get_positions(account) #print(positions) orders = execution.get_orders(account) #print(f"Orders: {orders}")
print("cancelling unfilled orders...") execution.cancel_unfilled_orders(orders, account)
print("Attempting to close all positions..... ") execution.close_all_positions(account) positions = execution.get_positions(account) print(positions) print(execution.get_balances(account)) return
if __name__ == '__main__': execution = ExecutionClass('API Key', 'API Secret', 'Refresh Token') execution.main() #run_trade_simulation('SIM1530337F','BTCU23') run_trade_simulation_v2('SIM1530337F',['BTCU23', 'ETHU23'])
#execution.get_quote_snapshot("BTCU23") #execution.confirm_order('SIM1530337F', "BTCU23",1,"Market", "BUY", "DAY") #execution.calculate_initial_margin_requirements("BTCU23") #execution.calculate_initial_margin_requirements("ETHU23") #execution.calculate_initial_margin_requirements("JYU23") #execution.calculate_initial_margin_requirements("CLV23") #execution.calculate_initial_margin_requirements("CZ23") #execution.calculate_initial_margin_requirements("WZ23") #execution.calculate_initial_margin_requirements("SU23")