Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option base model #224

Open
wants to merge 68 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
ddc830b
create a model for an option instrument
jeffweng8 Apr 14, 2020
6bb7c2c
add options arg for endpoints, refactor get_instruments
jeffweng8 Apr 14, 2020
a1a2c47
streamline instrument getter functions
jeffweng8 Apr 14, 2020
97d965a
add popularity endpoint
jeffweng8 Apr 14, 2020
4c62b0a
remove deprecated
jeffweng8 Apr 15, 2020
b4b5f7d
add some basic unit tests for option
jeffweng8 Apr 15, 2020
0953fad
and news/docs
jeffweng8 Apr 15, 2020
4ab33a9
fix typos, requested changes
jeffweng8 Apr 15, 2020
cf6653e
fix indentation
jeffweng8 Apr 15, 2020
8be08b3
fix indentation
jeffweng8 Apr 15, 2020
b8d42ef
fix bugs, style
jeffweng8 Apr 15, 2020
346bc61
update known_third_party
jeffweng8 Apr 15, 2020
b6eae0c
fix OptionSchema init
jeffweng8 Apr 15, 2020
87909ae
fix formatting of data in option
jeffweng8 Apr 15, 2020
0facead
fix order of isinstance args
jeffweng8 Apr 15, 2020
c5538b0
fix attr name
jeffweng8 Apr 15, 2020
5e130c1
fix option model, tests
jeffweng8 Apr 18, 2020
2075429
remove redundant code
jeffweng8 Apr 18, 2020
cae9060
create a model for an option instrument
jeffweng8 Apr 14, 2020
dec0d73
streamline instrument getter functions
jeffweng8 Apr 14, 2020
b0b55fa
remove deprecated
jeffweng8 Apr 15, 2020
109ef49
add some basic unit tests for option
jeffweng8 Apr 15, 2020
fd583ef
and news/docs
jeffweng8 Apr 15, 2020
fa8d01f
fix typos, requested changes
jeffweng8 Apr 15, 2020
0e14bb2
fix indentation
jeffweng8 Apr 15, 2020
508a57d
fix indentation
jeffweng8 Apr 15, 2020
556fc99
fix bugs, style
jeffweng8 Apr 15, 2020
bea0f9d
update known_third_party
jeffweng8 Apr 15, 2020
24294c8
fix OptionSchema init
jeffweng8 Apr 15, 2020
18017fd
fix formatting of data in option
jeffweng8 Apr 15, 2020
f4e9044
fix order of isinstance args
jeffweng8 Apr 15, 2020
2c391de
fix attr name
jeffweng8 Apr 15, 2020
30898f4
fix option model, tests
jeffweng8 Apr 18, 2020
41313cd
remove redundant code
jeffweng8 Apr 18, 2020
229bce4
fix conflicts
jeffweng8 Apr 18, 2020
a632053
update endpoints to urls
jeffweng8 Apr 18, 2020
477bada
Merge branch 'master' into add_options_endpoints
jeffweng8 Apr 18, 2020
57d5e3f
create a model for an option instrument
jeffweng8 Apr 14, 2020
f39b124
add some basic unit tests for option
jeffweng8 Apr 15, 2020
802ab14
and news/docs
jeffweng8 Apr 15, 2020
01dedf0
fix typos, requested changes
jeffweng8 Apr 15, 2020
f1e4445
fix indentation
jeffweng8 Apr 15, 2020
8167931
fix indentation
jeffweng8 Apr 15, 2020
a78dd19
fix bugs, style
jeffweng8 Apr 15, 2020
845575c
update known_third_party
jeffweng8 Apr 15, 2020
fca8680
fix OptionSchema init
jeffweng8 Apr 15, 2020
5f94094
fix formatting of data in option
jeffweng8 Apr 15, 2020
ce4eb1b
fix order of isinstance args
jeffweng8 Apr 15, 2020
036b83b
fix attr name
jeffweng8 Apr 15, 2020
4e85da5
fix option model, tests
jeffweng8 Apr 18, 2020
3d1e943
remove redundant code
jeffweng8 Apr 18, 2020
44b7ee8
streamline instrument getter functions
jeffweng8 Apr 14, 2020
ea75509
remove deprecated
jeffweng8 Apr 15, 2020
734b2ac
fix indentation
jeffweng8 Apr 15, 2020
d42ba4f
fix indentation
jeffweng8 Apr 15, 2020
93641bd
fix bugs, style
jeffweng8 Apr 15, 2020
50a019e
fix option model, tests
jeffweng8 Apr 18, 2020
ea407fe
remove redundant code
jeffweng8 Apr 18, 2020
91a37b0
update endpoints to urls
jeffweng8 Apr 18, 2020
e6e88ea
fix merge conflicts
jeffweng8 Apr 20, 2020
99bc47f
Merge branch 'add_options_endpoints' of https://github.com/jeffweng8/…
jeffweng8 Apr 20, 2020
1cc267f
fix merge conflicts
jeffweng8 Apr 20, 2020
40dcae9
fix merge conflicts
jeffweng8 Apr 20, 2020
3f0185a
add chain model, basic tests
jeffweng8 Apr 21, 2020
9287b4f
Add option manager, paginator
jeffweng8 Apr 26, 2020
3c0d61d
add repr for option model
jeffweng8 May 3, 2020
1372cb4
merge from master, get_option_positions using model
jeffweng8 May 4, 2020
efcc31b
get option quote using model, update news
jeffweng8 May 4, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/stubs/pyrh.Robinhood.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pyrh.Robinhood
~Robinhood.get_account
~Robinhood.get_fundamentals
~Robinhood.get_historical_quotes
~Robinhood.get_instruments
~Robinhood.get_news
~Robinhood.get_open_orders
~Robinhood.get_option_chainid
Expand Down
1 change: 1 addition & 0 deletions newsfragments/201.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add option model, refactor endpoints
1 change: 1 addition & 0 deletions newsfragments/224.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add options related models and add new options related functions to robinhood.py.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ exclude = '''

[tool.isort]
known_first_party = 'robinhood'
known_third_party = ["certifi", "dateutil", "freezegun", "marshmallow", "pytest", "pytz", "requests", "requests_mock", "yarl"]
known_third_party = ["certifi", "dateutil", "freezegun", "marshmallow", "pytest", "pytz", "requests", "requests_mock", "six", "yarl"]
multi_line_output = 3
lines_after_imports = 2
force_grid_wrap = 0
Expand Down
20 changes: 20 additions & 0 deletions pyrh/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@
InstrumentSchema,
)
from .oauth import Challenge, ChallengeSchema, OAuth, OAuthSchema
from .option import (
Option,
OptionManager,
OptionPaginator,
OptionPaginatorSchema,
OptionPosition,
OptionPositionSchema,
OptionQuote,
OptionQuoteSchema,
OptionSchema,
)
from .portfolio import Portfolio, PortfolioSchema
from .sessionmanager import SessionManager, SessionManagerSchema

Expand All @@ -26,4 +37,13 @@
"InstrumentManager",
"InstrumentPaginator",
"InstrumentPaginatorSchema",
"Option",
"OptionSchema",
"OptionPosition",
"OptionPositionSchema",
"OptionQuote",
"OptionQuoteSchema",
"OptionManager",
"OptionPaginator",
"OptionPaginatorSchema",
]
27 changes: 27 additions & 0 deletions pyrh/models/chain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Option Chain data class."""

from marshmallow import fields

from .base import BaseModel, BaseSchema
from .option import MinTicksSchema


class Chain(BaseModel):
"""Chain data class. Represents an option chain."""

pass


class ChainSchema(BaseSchema):
"""Chain schema data loader."""

__model__ = Chain

id = fields.UUID()
symbol = fields.Str()
can_open_position = fields.Bool()
cash_component = fields.Float(allow_none=True)
expiration_dates = fields.List(fields.Str())
trade_value_multiplier = fields.Float()
underlying_instruments = fields.List(fields.Dict())
min_ticks = fields.Nested(MinTicksSchema)
291 changes: 291 additions & 0 deletions pyrh/models/option.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
"""Option data class."""
from typing import Any, Iterable, cast

from marshmallow import fields
from yarl import URL

from pyrh import exceptions, urls

from .base import (
BaseModel,
BasePaginator,
BasePaginatorSchema,
BaseSchema,
base_paginator,
)
from .sessionmanager import SessionManager


class MinTicks(BaseModel):
"""Min ticks data class. Describes min increments the option can be traded at."""

pass


class MinTicksSchema(BaseSchema):
"""Min ticks schema data loader."""

__model__ = MinTicks

above_tick = fields.Float()
below_tick = fields.Float()
cutoff_price = fields.Float()


class Option(BaseModel):
"""Robinhood Option data class. Represents an options instrument."""

def __repr__(self) -> str:
"""Return the object as a string.

Returns:
The string representation of the object.

"""
return (
f"Option<{self.chain_symbol}|{self.strike_price}|"
+ f"{self.type}|{self.expiration_date}>"
)


class OptionSchema(BaseSchema):
"""Robinhood Option schema data loader."""

__model__ = Option

chain_id = fields.UUID()
chain_symbol = fields.String()
created_at = fields.DateTime()
expiration_date = fields.Date()
id = fields.UUID()
issue_date = fields.Date()
min_ticks = fields.Nested(MinTicksSchema)
rhs_tradability = fields.String()
state = fields.String()
strike_price = fields.Float()
tradability = fields.String()
type = fields.String()
updated_at = fields.DateTime()
url = fields.URL()


class OptionPaginator(BasePaginator):
"""Thin wrapper around `self.results`, a list of `Option` objs."""

pass


class OptionPaginatorSchema(BasePaginatorSchema):
"""Schema class for the OptionPaginator.

The nested results are of type `Option`.

"""

__model__ = OptionPaginator

results = fields.List(fields.Nested(OptionSchema))


class OptionPosition(BaseModel):
"""Robinhood Option position data class. Represents an option position."""

def __repr__(self) -> str:
"""Return the object as a string.

Returns:
The string representation of the object.

"""
underlying = getattr(self, "underlying", self.option)
return f"OptionPosition<{underlying}|{self.quantity}|{self.type}>"


class OptionPositionSchema(BaseSchema):
"""Robinhood Option position schema data loader."""

__model__ = OptionPosition

account = (fields.URL(),)
average_price = (fields.Float(),)
chain_id = (fields.UUID(),)
chain_symbol = (fields.String(),)
id = (fields.UUID(),)
option = (fields.URL(),)
type = (fields.String(),) # should this be an enum?
pending_buy_quantity = (fields.Float(),)
pending_expired_quantity = (fields.Float(),)
pending_expiration_quantity = (fields.Float(),)
pending_exercise_quantity = (fields.Float(),)
pending_assignment_quantity = (fields.Float(),)
pending_sell_quantity = (fields.Float(),)
quantity = (fields.Float(),)
intraday_quantity = (fields.Float(),)
intraday_average_open_price = (fields.Float(),)
created_at = (fields.DateTime(),)
trade_value_multiplier = (fields.Float(),)
updated_at = (fields.DateTime(),)
url = (fields.URL(),)


class OptionQuote(BaseModel):
"""Robinhood Option quote data class. Represents an option quote."""

def __repr__(self) -> str:
"""Return the object as a string.

Returns:
The string representation of the object.

"""
return f"""OptionQuote<
Ask: {self.ask_size} x {self.ask_price}
Bid: {self.bid_size} x {self.bid_price}
Low: {self.low_price} | High: {self.high_price}
Volume: {self.volume} | Open Interest: {self.open_interest}
Implied Volatility: {self.implied_volatility}
Delta: {self.delta} | Gamma: {self.gamma} | Rho: {self.rho}
Theta: {self.theta} | Vega: {self.vega}
>"""


class OptionQuoteSchema(BaseSchema):
"""Robinhood Option quote schema data loader."""

__model__ = OptionQuote

adjusted_mark_price = (fields.Float(),)
ask_price = (fields.Float(),)
ask_size = (fields.Integer(),)
bid_price = (fields.Float(),)
bid_size = (fields.Integer(),)
break_even_price = (fields.Float(),)
high_price = (fields.Float(),)
instrument = (fields.URL(),)
last_trade_price = (fields.Float(),)
last_trade_size = (fields.Integer(),)
low_price = (fields.Float(),)
mark_price = (fields.Float(),)
open_interest = (fields.Integer(),)
previous_close_date = (fields.Date(),)
previous_close_price = (fields.Float(),)
volume = (fields.Integer(),)
chance_of_profit_long = (fields.Float(),)
chance_of_profit_short = (fields.Float(),)
delta = (fields.Float(),)
gamma = (fields.Float(),)
implied_volatility = (fields.Float(),)
rho = (fields.Float(),)
theta = (fields.Float(),)
vega = (fields.Float(),)
high_fill_rate_buy_price = (fields.Float(),)
high_fill_rate_sell_price = (fields.Float(),)
low_fill_rate_buy_price = (fields.Float(),)
low_fill_rate_sell_price = (fields.Float(),)


class OptionPositionPaginator(BasePaginator):
"""Thin wrapper around `self.results`, a list of `Option` objs."""

pass


class OptionPositionPaginatorSchema(BasePaginatorSchema):
"""Schema class for the OptionPaginator.

The nested results are of type `OptionPosition`.

"""

__model__ = OptionPositionPaginator

results = fields.List(fields.Nested(OptionPositionSchema))


class OptionManager(SessionManager):
"""Group together methods that manipulate an options."""

def _get_option_from_url(self, option_url: URL) -> Option:
"""Get option from option_url.

Args:
option_url: url to the option, used for getting the underlying option
for an options position.

Returns:
An Option object.
"""
option_data = self.get(option_url)
return cast(Option, OptionSchema().load(option_data))

def _get_option_quote(self, option_id: str) -> OptionQuote:
"""Get quote from option id.

Args:
option_id: underlying option id to get quote for

Returns:
An OptionQuote object.
"""
quote_data = self.get_url(
urls.MARKET_DATA_BASE.join(URL(f"options/{option_id}/"))
)
return cast(OptionQuote, OptionQuoteSchema().load(quote_data))

def _get_option_positions(self, open_pos: bool = True) -> Iterable[OptionPosition]:
# TODO figure out what /?nonzero=true is, returns quantity = 0...
url = urls.OPTIONS_BASE.join(URL("positions/?nonzero=true"))
positions = base_paginator(url, self, OptionPositionPaginatorSchema())
if open_pos:
positions = [p for p in positions if float(p.quantity) > 0]
for p in positions:
p.underlying = self._get_option_from_url(p.option)
return positions

def _get_option_id(
self, symbol: str, strike: str, expiry: str, otype: str, state: str = "active"
) -> str:
url = urls.OPTIONS_BASE.join(URL("instruments/"))
params = {
"chain_symbol": symbol,
"strike_price": strike,
"expiration_dates": expiry,
"type": otype,
"state": state,
}
results = self.get_url(url.with_query(**params)).get("results")
if not results:
e = """
Couldn't find option with symbol={}, strike={}, expiry={}, type={}, state={}
""".format(
symbol, strike, expiry, otype, state
)
raise exceptions.InvalidOptionId(e)
return str(results[0]["id"])

def get_options(self, **kwargs: Any) -> Iterable[Option]:
"""Get a generator of options.

Args:
**kwargs: If the query argument is provided, the returned values will
be restricted to option instruments that match the query. Possible
query parameters: chain_symbol, chain_id, state (active),
tradability, type (call vs put), expiration_dates, strike_price

Returns:
A generator of Options.
"""
valid_params = frozenset(
[
"chain_symbol",
"state",
"tradability",
"type",
"expiration_dates",
"strike_price",
"chain_id",
]
)
query = {k: v for k, v in kwargs.items() if k in valid_params}
url = urls.OPTIONS_INSTRUMENTS_BASE.with_query(**query)
return base_paginator(url, self, OptionPaginatorSchema())
Loading