mirror of
https://github.com/wassname/optlib.git
synced 2026-07-05 17:30:23 +08:00
Update optlib. Add functionality, call API from instrument classes
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
from .instruments import Historical
|
||||
from .instruments import OptionChain
|
||||
|
||||
+8
-3
@@ -1,7 +1,10 @@
|
||||
from subprocess import check_output
|
||||
import json
|
||||
|
||||
from optlib.classes import Historical, OptionChain
|
||||
import logging
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ------------------------------
|
||||
# This class defines the URLs to the implemented endpoints.
|
||||
@@ -28,6 +31,8 @@ def _test_input(*args, **kwargs):
|
||||
def _get(endpoint, *args, **kwargs):
|
||||
|
||||
url = "?".join([endpoint, "&".join(f"{k}={v}" for k, v in kwargs.items())])
|
||||
logger.debug("GET", url)
|
||||
|
||||
if (resp := json.loads(check_output(["curl", "-gs", url]))).get("error"):
|
||||
raise API_InputError("{0}".format(resp["error"]))
|
||||
|
||||
@@ -61,7 +66,7 @@ def get_chain(*args, **kwargs):
|
||||
_test_input(*args, **kwargs)
|
||||
|
||||
endpoint = Endpoint.CHAIN
|
||||
return OptionChain.parse(_get(endpoint, *args, **kwargs))
|
||||
return _get(endpoint, *args, **kwargs)
|
||||
|
||||
def get_historical(*args, **kwargs):
|
||||
"""Request historical price data from TDAmeritrade's API.
|
||||
@@ -83,7 +88,7 @@ def get_historical(*args, **kwargs):
|
||||
_test_input(*args, **kwargs)
|
||||
|
||||
endpoint = Endpoint.HISTORY.format(kwargs["symbol"])
|
||||
return Historical.parse(_get(endpoint, *args, **kwargs))
|
||||
return _get(endpoint, *args, **kwargs)
|
||||
|
||||
def get_fundamental(*args, **kwargs):
|
||||
"""Retrieve fundamental data.
|
||||
|
||||
+54
-57
@@ -12,15 +12,12 @@ from __future__ import division
|
||||
|
||||
# import necessary libaries
|
||||
import math
|
||||
import logging
|
||||
import numpy as np
|
||||
from scipy.stats import mvn, norm
|
||||
|
||||
logging.basicConfig(
|
||||
format="[%(asctime)s %(levelname)s] %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
level=logging.INFO
|
||||
)
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# This class contains the limits on inputs for GBS models
|
||||
@@ -146,7 +143,7 @@ def _gbs_test_inputs(option_type, fs, x, t, r, b, v):
|
||||
# b = cost of carry, v = implied volatility
|
||||
# Outputs: value, delta, gamma, theta, vega, rho
|
||||
def _gbs(option_type, fs, x, t, r, b, v):
|
||||
logging.debug("Debugging Information: _gbs()")
|
||||
logger.debug("Debugging Information: _gbs()")
|
||||
# -----------
|
||||
# Test Inputs (throwing an exception on failure)
|
||||
_gbs_test_inputs(option_type, fs, x, t, r, b, v)
|
||||
@@ -159,7 +156,7 @@ def _gbs(option_type, fs, x, t, r, b, v):
|
||||
|
||||
if option_type == "c":
|
||||
# it's a call
|
||||
logging.debug(" Call Option")
|
||||
logger.debug(" Call Option")
|
||||
value = fs * math.exp((b - r) * t) * norm.cdf(d1) - x * math.exp(-r * t) * norm.cdf(d2)
|
||||
delta = math.exp((b - r) * t) * norm.cdf(d1)
|
||||
gamma = math.exp((b - r) * t) * norm.pdf(d1) / (fs * v * t__sqrt)
|
||||
@@ -169,7 +166,7 @@ def _gbs(option_type, fs, x, t, r, b, v):
|
||||
rho = x * t * math.exp(-r * t) * norm.cdf(d2)
|
||||
else:
|
||||
# it's a put
|
||||
logging.debug(" Put Option")
|
||||
logger.debug(" Put Option")
|
||||
value = x * math.exp(-r * t) * norm.cdf(-d2) - (fs * math.exp((b - r) * t) * norm.cdf(-d1))
|
||||
delta = -math.exp((b - r) * t) * norm.cdf(-d1)
|
||||
gamma = math.exp((b - r) * t) * norm.pdf(d1) / (fs * v * t__sqrt)
|
||||
@@ -178,8 +175,8 @@ def _gbs(option_type, fs, x, t, r, b, v):
|
||||
vega = math.exp((b - r) * t) * fs * t__sqrt * norm.pdf(d1)
|
||||
rho = -x * t * math.exp(-r * t) * norm.cdf(-d2)
|
||||
|
||||
logging.debug(" d1= {0}\n d2 = {1}".format(d1, d2))
|
||||
logging.debug(" delta = {0}\n gamma = {1}\n theta = {2}\n vega = {3}\n rho={4}".format(delta, gamma,
|
||||
logger.debug(" d1= {0}\n d2 = {1}".format(d1, d2))
|
||||
logger.debug(" delta = {0}\n gamma = {1}\n theta = {2}\n vega = {3}\n rho={4}".format(delta, gamma,
|
||||
theta, vega,
|
||||
rho))
|
||||
|
||||
@@ -196,17 +193,17 @@ def _gbs(option_type, fs, x, t, r, b, v):
|
||||
def _american_option(option_type, fs, x, t, r, b, v):
|
||||
# -----------
|
||||
# Test Inputs (throwing an exception on failure)
|
||||
logging.debug("Debugging Information: _american_option()")
|
||||
logger.debug("Debugging Information: _american_option()")
|
||||
_gbs_test_inputs(option_type, fs, x, t, r, b, v)
|
||||
|
||||
# -----------
|
||||
if option_type == "c":
|
||||
# Call Option
|
||||
logging.debug(" Call Option")
|
||||
logger.debug(" Call Option")
|
||||
return _bjerksund_stensland_2002(fs, x, t, r, b, v)
|
||||
else:
|
||||
# Put Option
|
||||
logging.debug(" Put Option")
|
||||
logger.debug(" Put Option")
|
||||
|
||||
# Using the put-call transformation: P(X, FS, T, r, b, V) = C(FS, X, T, -b, r-b, V)
|
||||
# WARNING - When reconciling this code back to the B&S paper, the order of variables is different
|
||||
@@ -237,13 +234,13 @@ def _bjerksund_stensland_1993(fs, x, t, r, b, v):
|
||||
rho = my_output[5]
|
||||
|
||||
# debugging for calculations
|
||||
logging.debug("-----")
|
||||
logging.debug("Debug Information: _Bjerksund_Stensland_1993())")
|
||||
logger.debug("-----")
|
||||
logger.debug("Debug Information: _Bjerksund_Stensland_1993())")
|
||||
|
||||
# if b >= r, it is never optimal to exercise before maturity
|
||||
# so we can return the GBS value
|
||||
if b >= r:
|
||||
logging.debug(" b >= r, early exercise never optimal, returning GBS value")
|
||||
logger.debug(" b >= r, early exercise never optimal, returning GBS value")
|
||||
return e_value, delta, gamma, theta, vega, rho
|
||||
|
||||
# Intermediate Calculations
|
||||
@@ -259,21 +256,21 @@ def _bjerksund_stensland_1993(fs, x, t, r, b, v):
|
||||
alpha = (i - x) * (i ** (-beta))
|
||||
|
||||
# debugging for calculations
|
||||
logging.debug(" b = {0}".format(b))
|
||||
logging.debug(" v2 = {0}".format(v2))
|
||||
logging.debug(" beta = {0}".format(beta))
|
||||
logging.debug(" b_infinity = {0}".format(b_infinity))
|
||||
logging.debug(" b_zero = {0}".format(b_zero))
|
||||
logging.debug(" h1 = {0}".format(h1))
|
||||
logging.debug(" i = {0}".format(i))
|
||||
logging.debug(" alpha = {0}".format(alpha))
|
||||
logger.debug(" b = {0}".format(b))
|
||||
logger.debug(" v2 = {0}".format(v2))
|
||||
logger.debug(" beta = {0}".format(beta))
|
||||
logger.debug(" b_infinity = {0}".format(b_infinity))
|
||||
logger.debug(" b_zero = {0}".format(b_zero))
|
||||
logger.debug(" h1 = {0}".format(h1))
|
||||
logger.debug(" i = {0}".format(i))
|
||||
logger.debug(" alpha = {0}".format(alpha))
|
||||
|
||||
# Check for immediate exercise
|
||||
if fs >= i:
|
||||
logging.debug(" Immediate Exercise")
|
||||
logger.debug(" Immediate Exercise")
|
||||
value = fs - x
|
||||
else:
|
||||
logging.debug(" American Exercise")
|
||||
logger.debug(" American Exercise")
|
||||
value = (alpha * (fs ** beta)
|
||||
- alpha * _phi(fs, t, beta, i, i, r, b, v)
|
||||
+ _phi(fs, t, 1, i, i, r, b, v)
|
||||
@@ -303,13 +300,13 @@ def _bjerksund_stensland_2002(fs, x, t, r, b, v):
|
||||
rho = my_output[5]
|
||||
|
||||
# debugging for calculations
|
||||
logging.debug("-----")
|
||||
logging.debug("Debug Information: _Bjerksund_Stensland_2002())")
|
||||
logger.debug("-----")
|
||||
logger.debug("Debug Information: _Bjerksund_Stensland_2002())")
|
||||
|
||||
# If b >= r, it is never optimal to exercise before maturity
|
||||
# so we can return the GBS value
|
||||
if b >= r:
|
||||
logging.debug(" Returning GBS value")
|
||||
logger.debug(" Returning GBS value")
|
||||
return e_value, delta, gamma, theta, vega, rho
|
||||
|
||||
# -----------
|
||||
@@ -335,16 +332,16 @@ def _bjerksund_stensland_2002(fs, x, t, r, b, v):
|
||||
alpha2 = (i2 - x) * (i2 ** (-beta))
|
||||
|
||||
# debugging for calculations
|
||||
logging.debug(" t1 = {0}".format(t1))
|
||||
logging.debug(" beta = {0}".format(beta))
|
||||
logging.debug(" b_infinity = {0}".format(b_infinity))
|
||||
logging.debug(" b_zero = {0}".format(b_zero))
|
||||
logging.debug(" h1 = {0}".format(h1))
|
||||
logging.debug(" h2 = {0}".format(h2))
|
||||
logging.debug(" i1 = {0}".format(i1))
|
||||
logging.debug(" i2 = {0}".format(i2))
|
||||
logging.debug(" alpha1 = {0}".format(alpha1))
|
||||
logging.debug(" alpha2 = {0}".format(alpha2))
|
||||
logger.debug(" t1 = {0}".format(t1))
|
||||
logger.debug(" beta = {0}".format(beta))
|
||||
logger.debug(" b_infinity = {0}".format(b_infinity))
|
||||
logger.debug(" b_zero = {0}".format(b_zero))
|
||||
logger.debug(" h1 = {0}".format(h1))
|
||||
logger.debug(" h2 = {0}".format(h2))
|
||||
logger.debug(" i1 = {0}".format(i1))
|
||||
logger.debug(" i2 = {0}".format(i2))
|
||||
logger.debug(" alpha1 = {0}".format(alpha1))
|
||||
logger.debug(" alpha2 = {0}".format(alpha2))
|
||||
|
||||
# check for immediate exercise
|
||||
if fs >= i2:
|
||||
@@ -418,13 +415,13 @@ def _phi(fs, t, gamma, h, i, r, b, v):
|
||||
|
||||
phi = math.exp(lambda1 * t) * (fs ** gamma) * (norm.cdf(d1) - ((i / fs) ** kappa) * norm.cdf(d2))
|
||||
|
||||
logging.debug("-----")
|
||||
logging.debug("Debug info for: _phi()")
|
||||
logging.debug(" d1={0}".format(d1))
|
||||
logging.debug(" d2={0}".format(d2))
|
||||
logging.debug(" lambda={0}".format(lambda1))
|
||||
logging.debug(" kappa={0}".format(kappa))
|
||||
logging.debug(" phi={0}".format(phi))
|
||||
logger.debug("-----")
|
||||
logger.debug("Debug info for: _phi()")
|
||||
logger.debug(" d1={0}".format(d1))
|
||||
logger.debug(" d2={0}".format(d2))
|
||||
logger.debug(" lambda={0}".format(lambda1))
|
||||
logger.debug(" kappa={0}".format(kappa))
|
||||
logger.debug(" phi={0}".format(phi))
|
||||
return phi
|
||||
|
||||
|
||||
@@ -519,9 +516,9 @@ def _newton_implied_vol(val_fn, option_type, x, fs, t, b, r, cp, precision=.0000
|
||||
value, delta, gamma, theta, vega, rho = val_fn(option_type, fs, x, t, r, b, v)
|
||||
min_diff = abs(cp - value)
|
||||
|
||||
logging.debug("-----")
|
||||
logging.debug("Debug info for: _Newton_ImpliedVol()")
|
||||
logging.debug(" Vinitial={0}".format(v))
|
||||
logger.debug("-----")
|
||||
logger.debug("Debug info for: _Newton_ImpliedVol()")
|
||||
logger.debug(" Vinitial={0}".format(v))
|
||||
|
||||
# Newton-Raphson Search
|
||||
countr = 0
|
||||
@@ -529,7 +526,7 @@ def _newton_implied_vol(val_fn, option_type, x, fs, t, b, r, cp, precision=.0000
|
||||
|
||||
v = v - (value - cp) / vega
|
||||
if (v > _GBS_Limits.MAX_V) or (v < _GBS_Limits.MIN_V):
|
||||
logging.debug(" Volatility out of bounds")
|
||||
logger.debug(" Volatility out of bounds")
|
||||
break
|
||||
|
||||
value, delta, gamma, theta, vega, rho = val_fn(option_type, fs, x, t, r, b, v)
|
||||
@@ -537,7 +534,7 @@ def _newton_implied_vol(val_fn, option_type, x, fs, t, b, r, cp, precision=.0000
|
||||
|
||||
# keep track of how many loops
|
||||
countr += 1
|
||||
logging.debug(" IVOL STEP {0}. v={1}".format(countr, v))
|
||||
logger.debug(" IVOL STEP {0}. v={1}".format(countr, v))
|
||||
|
||||
|
||||
# check if function converged and return a value
|
||||
@@ -552,8 +549,8 @@ def _newton_implied_vol(val_fn, option_type, x, fs, t, b, r, cp, precision=.0000
|
||||
# ----------
|
||||
# Find the Implied Volatility using a Bisection search
|
||||
def _bisection_implied_vol(val_fn, option_type, fs, x, t, r, b, cp, precision=.00001, max_steps=100):
|
||||
logging.debug("-----")
|
||||
logging.debug("Debug info for: _bisection_implied_vol()")
|
||||
logger.debug("-----")
|
||||
logger.debug("Debug info for: _bisection_implied_vol()")
|
||||
|
||||
# Estimate Upper and Lower bounds on volatility
|
||||
# Assume American Implied vol is within +/- 50% of the GBS Implied Vol
|
||||
@@ -576,8 +573,8 @@ def _bisection_implied_vol(val_fn, option_type, fs, x, t, r, b, cp, precision=.0
|
||||
current_step = 0
|
||||
diff = abs(cp - cp_mid)
|
||||
|
||||
logging.debug(" American IVOL starting conditions: CP={0} cp_mid={1}".format(cp, cp_mid))
|
||||
logging.debug(" IVOL {0}. V[{1},{2},{3}]".format(current_step, v_low, v_mid, v_high))
|
||||
logger.debug(" American IVOL starting conditions: CP={0} cp_mid={1}".format(cp, cp_mid))
|
||||
logger.debug(" IVOL {0}. V[{1},{2},{3}]".format(current_step, v_low, v_mid, v_high))
|
||||
|
||||
# Keep bisection volatility until correct price is found
|
||||
while (diff > precision) and (current_step < max_steps):
|
||||
@@ -599,7 +596,7 @@ def _bisection_implied_vol(val_fn, option_type, fs, x, t, r, b, cp, precision=.0
|
||||
cp_mid = val_fn(option_type, fs, x, t, r, b, v_mid)[0]
|
||||
diff = abs(cp - cp_mid)
|
||||
|
||||
logging.debug(" IVOL {0}. V[{1},{2},{3}]".format(current_step, v_low, v_mid, v_high))
|
||||
logger.debug(" IVOL {0}. V[{1},{2},{3}]".format(current_step, v_low, v_mid, v_high))
|
||||
|
||||
# return output
|
||||
if abs(cp - cp_mid) < precision:
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
from optlib.api import get_chain, get_historical
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import pandas as pd
|
||||
import json
|
||||
|
||||
|
||||
class Historical:
|
||||
|
||||
def __init__(self, data):
|
||||
@@ -17,6 +21,10 @@ class Historical:
|
||||
def parse(cls, resp):
|
||||
return Historical(resp)
|
||||
|
||||
@classmethod
|
||||
def get(cls, *args, **kwargs):
|
||||
return cls.parse(get_historical(*args, **kwargs))
|
||||
|
||||
def to_dataframe(self):
|
||||
return pd.DataFrame([c for c in self.candles])
|
||||
|
||||
@@ -85,6 +93,10 @@ class OptionChain:
|
||||
putExpDateMap=resp.get("putExpDateMap", dict())
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get(cls, *args, **kwargs):
|
||||
return cls.parse(get_chain(*args, **kwargs))
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, filepath):
|
||||
|
||||
@@ -97,11 +109,15 @@ class OptionChain:
|
||||
def options(self):
|
||||
return list(self)
|
||||
|
||||
@property
|
||||
def expiration_dates(self):
|
||||
return list({opt.expirationDate for opt in self.options})
|
||||
|
||||
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 _, strikes in self.expDateMap:
|
||||
for _, data in strikes.items():
|
||||
for r in data:
|
||||
yield Option(r)
|
||||
@@ -2,11 +2,11 @@ from setuptools import setup
|
||||
|
||||
setup(
|
||||
name="optlib",
|
||||
version="0.4.0",
|
||||
version="0.5.0",
|
||||
description="A library for financial options pricing written in Python.",
|
||||
url="http://github.com/bartolomed/optlib",
|
||||
author="Davis Edwards & Daniel Rojas",
|
||||
packages=["optlib"],
|
||||
install_requires=["numpy", "scipy"],
|
||||
install_requires=["numpy", "scipy", "pandas"],
|
||||
zip_safe=False
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user