mirror of
https://github.com/wassname/optlib.git
synced 2026-07-04 17:20:32 +08:00
Add optlib/classes.py, wrap API response with them
This commit is contained in:
@@ -0,0 +1 @@
|
||||
TDA_API_KEY=
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
__pycache__/
|
||||
build
|
||||
dist
|
||||
*.egg-info
|
||||
.eggs
|
||||
.pypirc
|
||||
build/
|
||||
dist/
|
||||
*.egg-info/
|
||||
|
||||
.env
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user