Skip to content

Commit

Permalink
related westonplatter#10. create and submit Put Credit spread.
Browse files Browse the repository at this point in the history
  • Loading branch information
westonplatter committed Sep 30, 2018
1 parent 0995701 commit 1e63034
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 104 deletions.
89 changes: 80 additions & 9 deletions examples/option_order_place_vertical.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import configparser
from fast_arrow import (
Client,
OptionOrder
Option,
OptionChain,
OptionOrder,
SpreadsVertical,
Stock
)
import math


#
Expand All @@ -21,15 +26,81 @@
client = Client(username=username, password=password)
client.authenticate()

#
# fetch spy options
#
symbol = "SPY"
stock = Stock.fetch(client, symbol)
stock = Stock.mergein_marketdata_list(client, [stock])[0]

oc = OptionChain.fetch(client, stock["id"], symbol)
ed = oc['expiration_dates'][3]
ops = Option.in_chain(client, oc["id"], expiration_dates=[ed])

#
# enrich options with market data
#
ops = Option.mergein_marketdata_list(client, ops)

#
# genrate vertical spread table
#
width = 1
df = SpreadsVertical.gen_df(ops, width, "put", "sell")

#
# select the 4th row (should be a deep OTM put, credit spread)
#
vertical = df.iloc[[4]]

#
# create the order
#
direction = "credit"

legs = [
{ "side": "sell",
"option": vertical["instrument"].values[0],
"position_effect": "open",
"ratio_quantity": 1
},
{ "side": "buy",
"option": vertical["instrument_shifted"].values[0],
"position_effect": "open",
"ratio_quantity": 1
}
]

#
# configure order details
# for a Put Credit spread, set limit price at 1.0 less the full margin
#
my_bid_price = (vertical["margin"] * 100.0) - 1.00
my_bid_price_rounded = (math.floor(my_bid_price * 100.0))/100.0
x = my_bid_price_rounded / 100.0
my_bid_price_formatted = str(x)
price = my_bid_price_formatted

quantity = 1
time_in_force = "gfd"
trigger = "immediate"
order_type = "limit"

# @TODO create intuitive print statement
# print("Buying the {} {} {} for {} (dollar cost = ${})".format(
# symbol,
# strike_price,
# desired_type,
# my_bid_price_rounded,
# my_bid_price_rounded*100.0)
# )

oo = OptionOrder.submit(client, direction, legs, price, quantity, time_in_force, trigger, order_type)

print("Order submitted ... ref_id = {}".format(oo["ref_id"]))

#
# @TODO
# - fetch spy options
# - find $100 debit spread to buy in next 10-15 days
# - set price at 0.01
# - send order
# - cancel order
# cancel the order
#
#
print("Canceling order = {}".format(oo["ref_id"]))
result = OptionOrder.cancel(client, oo['cancel_url'])
print("Order canceled result = {}".format(result))
2 changes: 1 addition & 1 deletion fast_arrow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from fast_arrow.resources.option import Option

# spreads
from fast_arrow.spreads.vertical import Spread
from fast_arrow.spreads.vertical import Vertical as SpreadsVertical

# stocks
from fast_arrow.resources.stock_order import StockOrder
Expand Down
6 changes: 4 additions & 2 deletions fast_arrow/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def get(self, url=None, params=None, retry=True):
return res.json()
except requests.exceptions.RequestException as e:
attempts += 1
if retry:
if retry and res.status_code in [403]:
self.relogin_oauth2()


Expand All @@ -67,6 +67,8 @@ def post(self, url=None, payload=None, retry=True):
attempts = 1
while attempts <= HTTP_ATTEMPTS_MAX:
try:
# if url == "https://api.robinhood.com/options/orders/":
# import pdb; pdb.set_trace()
res = requests.post(url, headers=headers, data=payload, timeout=15, verify=self.certs)
res.raise_for_status()
if res.headers['Content-Length'] == '0':
Expand All @@ -75,7 +77,7 @@ def post(self, url=None, payload=None, retry=True):
return res.json()
except requests.exceptions.RequestException as e:
attempts += 1
if retry:
if retry and res.status_code in [403]:
self.relogin_oauth2()


Expand Down
7 changes: 2 additions & 5 deletions fast_arrow/resources/option_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,7 @@ def submit(cls, client, direction, legs, price, quantity, time_in_force, trigger

if run_validations:
assert(direction in ["debit", "credit"])

# @TODO - research this.
# might be formatted as decimal w/ 1/100th percision. eg, 1.23
# assert(type(price) is str)

assert(type(price) is str)
assert(type(quantity) is int)
assert(time_in_force in ["gfd", "gtc"])
assert(trigger in ["immediate"])
Expand All @@ -79,6 +75,7 @@ def submit(cls, client, direction, legs, price, quantity, time_in_force, trigger
})

request_url = "https://api.robinhood.com/options/orders/"
import pdb; pdb.set_trace()
data = client.post(request_url, payload=payload)
return data

Expand Down
Empty file added fast_arrow/spreads/__init__.py
Empty file.
125 changes: 39 additions & 86 deletions fast_arrow/spreads/vertical.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,90 +34,43 @@ def fetch_options_for_symbol(cls, symbol):


@classmethod
def gen_table(cls, options, width):
return []
def gen_df(cls, options, width, spread_type="call", spread_kind="buy"):
"""
Generate Pandas Dataframe of Vertical
:param options: python dict of options.
:param width: offset for spread. Must be integer.
:param spread_type: call or put. defaults to "call".
:param spread_kind: buy or sell. defaults to "buy".
"""
assert type(width) is int
assert spread_type in ["call", "put"]
assert spread_kind in ["buy", "sell"]

# get CALLs or PUTs
options = list(filter(lambda x: x["type"] == spread_type, options))

coef = (-1 if spread_kind == "buy" else 1)
shift = (coef * width)

df = pd.DataFrame.from_dict(options)
df['expiration_date'] = pd.to_datetime(df['expiration_date'], format="%Y-%m-%d")
df['adjusted_mark_price'] = pd.to_numeric(df['adjusted_mark_price'])
df['strike_price'] = pd.to_numeric(df['strike_price'])

df.sort_values(["expiration_date", "strike_price"], inplace=True)

for k,v in df.groupby("expiration_date"):
sdf = v.shift(shift)

df.loc[v.index, "strike_price_shifted"] = sdf["strike_price"]
df.loc[v.index, "instrument_shifted"] = sdf["instrument"]

if spread_kind == "sell":
df.loc[v.index, "margin"] = v["strike_price"] - sdf["strike_price"]
else:
df.loc[v.index, "margin"] = 0.0

df.loc[v.index, "premium"] = (v["adjusted_mark_price"] - sdf["adjusted_mark_price"])

# self.kind = (dd["kind"] if "kind" in dd else self.kind)
# assert self.kind is not None

# self.type = (dd["type"] if "type" in dd else self.type)
# assert self.type is not None

# self.width = (abs(dd["width"]) if "width" in dd else self.width)

# self.dte = (dd["dte"] if "dte" in dd else self.dte)
# assert self.dte is not None

# # create the resulting/specific df (sdf is in a row on the keyboard)
# self.sdf = self.df.sort_values(["expiration_date", "strike_price"])
#
# #
# # filter by dte param
# #
# self.dte_min = (datetime.datetime.now() + datetime.timedelta(days=self.dte[0]))
# self.dte_max = (datetime.datetime.now() + datetime.timedelta(days=self.dte[1]))
# self.sdf = self.sdf[ self.df["expiration_date"] >= self.dte_min ]
# self.sdf = self.sdf[ self.df["expiration_date"] <= self.dte_max ]
#
# self.sdf = self.sdf[self.sdf["type"] == self.type]
#
# coef = (1 if self.kind == "sell" else -1)
#
# for k,v in self.sdf.groupby("expiration_date"):
#
# sps = np.sort(v.strike_price.values)
# shift = self._calc_shift(sps, self.width)
#
# if shift is None:
# continue
#
# shift = int(shift) * coef
# shiftdf = v.shift(shift)
#
# v["strike_price_shifted"] = (shiftdf["strike_price"])
#
# if self.kind == "sell":
# v["margin"] = (v["strike_price"] - shiftdf["strike_price"])
# else:
# v["margin"] = 0.0
#
# v["premium"] = (v["adjusted_mark_price"] - shiftdf["adjusted_mark_price"])
# v["delta_spread"] = (v["delta"] - shiftdf["delta"])
# v["theta_spread"] = (v["theta"] - shiftdf["theta"])
#
# merge_cols = ["strike_price_shifted", "margin", "premium", "delta_spread", "theta_spread"]
#
# for col in merge_cols:
# self.sdf.loc[v.index, col] = v[col]
#
# return self.sdf
#
#
# def _calc_shift(self, sps, width):
# '''
# calc spread step to achieve desird margin
# @todo clean this up
# '''
# total = len(sps)
# median = int(np.floor(total/2))
#
# if np.allclose((abs(sps[median] - 0.50) % 1.0), 0.0):
# median += 1
#
# min_tick = (sps[median] - sps[median-1]) * 100.0
# if (min_tick - self.width) > 0.0:
# return None
#
# shift = 0
# diff = 1
# searching = True
# marign = 0
#
# while searching:
# margin = (sps[median] - sps[median-diff]) * 100
# if np.allclose(margin, self.width):
# shift = diff
# searching = False
# diff += 1
#
# return shift
return df
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def run_tests(self):
'click',
'pathlib2',
'requests',
'pandas>=0.23.2',
'numpy',
'yarl']


Expand All @@ -53,7 +55,7 @@ def run_tests(self):
url='https://github.com/westonplatter/fast_arrow/',
license='MIT License',
python_requires=">=3.5",
packages=['fast_arrow', 'fast_arrow.resources'],
packages=['fast_arrow', 'fast_arrow.resources', 'fast_arrow.spreads'],
package_data={'fast_arrow': ['ssl_certs/certs.pem']},
install_requires=deps,
tests_require=test_deps,
Expand Down

0 comments on commit 1e63034

Please sign in to comment.