Add optlib/classes.py, wrap API response with them

This commit is contained in:
dbrojas
2021-04-10 10:49:55 -05:00
parent 2553ff6b59
commit 6df338bf9b
6 changed files with 157 additions and 14 deletions
+1
View File
@@ -0,0 +1 @@
TDA_API_KEY=
+5 -5
View File
@@ -1,6 +1,6 @@
__pycache__/
build
dist
*.egg-info
.eggs
.pypirc
build/
dist/
*.egg-info/
.env
+1
View File
@@ -14,6 +14,7 @@ A library to fetch financial option chains and price options using closed-form s
* 7/21/2017 Davis Edwards, refactored all of the functions to match the parameter order to Haug's "The Complete Guide to Option Pricing Formulas".
* 08/31/2020 Daniel Rojas, Added functionality to fetch option chains using the TDAmeritrade API.
* 09/05/2020 Daniel Rojas, Added functionality to fetch historical data using the TDAmeritrade API.
* 04/10/2021 Daniel Rojas, Write classes to represent API responses
**TODO List**
+42 -8
View File
@@ -1,17 +1,21 @@
from subprocess import check_output
import json
from optlib.classes import Historical, OptionChain
# ------------------------------
# This class defines the URLs to the implemented endpoints.
class Endpoint:
CHAIN = "https://api.tdameritrade.com/v1/marketdata/chains"
HIST = "https://api.tdameritrade.com/v1/marketdata/{0}/pricehistory"
HISTORY = "https://api.tdameritrade.com/v1/marketdata/{0}/pricehistory"
INSTRUMENT = "https://api.tdameritrade.com/v1/instruments"
QUOTE = "https://api.tdameritrade.com/v1/marketdata/{0}/quotes"
# ------------------------------
# This class defines the Exception that gets thrown when the api input is bad.
class API_InputError(Exception):
def __init__(self, mismatch):
Exception.__init__(self, mismatch)
def __init__(self, msg):
Exception.__init__(self, msg)
# ------------------------------
# This function verifies that the required apikey and symbol arguments are provided.
@@ -25,7 +29,7 @@ def _get(endpoint, *args, **kwargs):
url = "?".join([endpoint, "&".join(f"{k}={v}" for k, v in kwargs.items())])
if (resp := json.loads(check_output(["curl", "-gs", url]))).get("error"):
raise API_InputError(f"{0}".format(resp["error"]))
raise API_InputError("{0}".format(resp["error"]))
return resp
@@ -52,12 +56,12 @@ def get_chain(*args, **kwargs):
optionType (str): Type of contracts to return. Default is ALL.
Returns:
resp (dict): API response with option chain.
chain (OptionChain): API response with option chain.
"""
_test_input(*args, **kwargs)
endpoint = Endpoint.CHAIN
return _get(endpoint, *args, **kwargs)
return OptionChain.parse(_get(endpoint, *args, **kwargs))
def get_historical(*args, **kwargs):
"""Request historical price data from TDAmeritrade's API.
@@ -67,7 +71,7 @@ def get_historical(*args, **kwargs):
symbol (str): Stock symbol to get the option chain for.
periodType (str): The type of period to show. Can be day, month, year, or ytd.
period (int): The number of periods to show.
frequencyType (str): The type of frequency with which a new candle is formed. Can be day, month, year, ytd.
frequencyType (str): The type of frequency with which a new candle is formed. Can be minute, daily, weekly, monthly.
frequency (str): The number of the frequencyType to be included in each candle.
startDate (int): Start date as milliseconds since epoch.
endDate (int): End date as milliseconds since epoch.
@@ -78,5 +82,35 @@ def get_historical(*args, **kwargs):
"""
_test_input(*args, **kwargs)
endpoint = Endpoint.HIST.format(kwargs["symbol"])
endpoint = Endpoint.HISTORY.format(kwargs["symbol"])
return Historical.parse(_get(endpoint, *args, **kwargs))
def get_fundamental(*args, **kwargs):
"""Retrieve fundamental data.
Args:
apikey (str): API key to api.tdameritrade.com.
symbol (str): Stock symbol to get the option chain for.
Returns:
resp (dict): API response with fundamentals data.
"""
_test_input(*args, **kwargs)
kwargs.update({"projection": "fundamental"})
return _get(Endpoint.INSTRUMENT, *args, **kwargs)
def get_quote(*args, **kwargs):
"""Get quote for a symbol.
Args:
apikey (str): API key to api.tdameritrade.com.
symbol (str): Stock symbol to get the option chain for.
Returns:
resp (dict): API response with price quote.
"""
_test_input(*args, **kwargs)
endpoint = Endpoint.QUOTE.format(kwargs["symbol"])
return _get(endpoint, *args, **kwargs)
+107
View File
@@ -0,0 +1,107 @@
from datetime import datetime
import pandas as pd
import json
class Historical:
def __init__(self, data):
self.symbol = data["symbol"]
self.empty = data["empty"]
self.candles = []
for c in data["candles"]:
c["datetime"] = datetime.utcfromtimestamp(c["datetime"] / 1000)
self.candles.append(c)
@classmethod
def parse(cls, resp):
return Historical(resp)
def to_dataframe(self):
return pd.DataFrame([c for c in self.candles])
def __iter__(self):
for c in self.candles:
yield c
class Option:
parseDateCols = (
"tradeTimeInLong",
"quoteTimeInLong",
"expirationDate",
"lastTradingDay",
)
def __init__(self, data):
self.dict = {}
for k, v in data.items():
if k in self.parseDateCols and v:
v = datetime.utcfromtimestamp(v / 1000)
self.dict.update({k: v})
setattr(self, k, v)
def to_dict(self):
return self.dict
class OptionChain:
def __init__(
self,
symbol,
isDelayed,
isIndex,
interestRate,
underlyingPrice,
volatility,
callExpDateMap,
putExpDateMap
):
self.symbol = symbol
self.isDelayed = isDelayed
self.isIndex = isIndex
self.interestRate = interestRate
self.underlyingPrice = underlyingPrice
self.volatility = volatility
self.expDateMap = tuple(callExpDateMap.items()) + tuple(putExpDateMap.items())
@classmethod
def parse(cls, resp):
if resp.get("status") != "SUCCESS":
raise ValueError("No successful response from chain API.")
if (strategy := resp.get("strategy")) != "SINGLE":
raise ValueError(f"Strategy ({strategy}) is not supported. Use strategy 'SINGLE'.")
return OptionChain(
symbol=resp["symbol"],
isDelayed=resp["isDelayed"],
isIndex=resp["isIndex"],
interestRate=resp["interestRate"],
underlyingPrice=resp["underlyingPrice"],
volatility=resp["volatility"],
callExpDateMap=resp.get("callExpDateMap", dict()),
putExpDateMap=resp.get("putExpDateMap", dict())
)
@classmethod
def from_json(cls, filepath):
with open(filepath, "r") as f:
resp = json.load(f)
return cls.parse(resp)
@property
def options(self):
return list(self)
def to_dataframe(self):
return pd.DataFrame([opt.to_dict() for opt in self.options])
def __iter__(self):
for expiryDate, strikes in self.expDateMap:
for strikePrice, data in strikes.items():
for r in data:
yield Option(r)
+1 -1
View File
@@ -2,7 +2,7 @@ from setuptools import setup
setup(
name="optlib",
version="0.3.0",
version="0.4.0",
description="A library for financial options pricing written in Python.",
url="http://github.com/bartolomed/optlib",
author="Davis Edwards & Daniel Rojas",