mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 20:42:26 +00:00
Discordbot/expiryreactions++ (#1102)
* added load extension for options menu * options menu: oi, iv, unusual options * Update opt_menu.py * Update unu.py if no num requested set default to 10 * Update opt_menu.py + !oi ticker expiry request added without inputting date format. accepts 0-9 up to 10 weeks + added option volume plot (works same as oi) * Create vol.py * Create reaction_helper.py * Cleaned up, added historical options Cleaned up + added historical options data + added expiry reaction helper function * Create hist.py * cleanup * added min/max strike price * cleanup * cleanup, added min/max strike price * Update opt_menu.py * added yfinance if no tradier token * Update vol.py * Update opt_menu.py * Create oi_tradier.py * Update oi.py * Update opt_menu.py * Update oi.py * Update opt_menu.py * Update oi.py * Update iv.py * Update unu.py * Update oi_tradier.py * Update hist.py * Update opt_menu.py * Update reaction_helper.py * Create options_menu.py * Update run_discordbot.py * Update vol.py * Rename discordbot/options_menu.py to discordbot/stocks/options/options_menu.py * Delete opt_menu.py * Update oi.py + min strike/max strike * Delete oi_tradier.py * Update options_menu.py * Update hist.py * Update vol.py * Update oi.py * Update options_menu.py * Linting/Pre-Commit Fixes * linting * Linting * linting * pylint fixes * Update iv.py * Update options_menu.py * Update reaction_helper.py * Improved Code - removed 148 lines same function * Update run_discordbot.py * Update reaction_helper.py
This commit is contained in:
126
discordbot/reaction_helper.py
Normal file
126
discordbot/reaction_helper.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import asyncio
|
||||
import discord
|
||||
|
||||
from gamestonk_terminal.config_terminal import TRADIER_TOKEN
|
||||
from gamestonk_terminal.stocks.options import tradier_model, yfinance_model
|
||||
from discordbot.run_discordbot import gst_bot
|
||||
import discordbot.config_discordbot as cfg
|
||||
|
||||
|
||||
async def expiry_dates_reaction(ctx, ticker, expiry, func_cmd, call_arg: tuple = None):
|
||||
if TRADIER_TOKEN == "REPLACE_ME":
|
||||
dates = yfinance_model.option_expirations(ticker)
|
||||
else:
|
||||
dates = tradier_model.option_expirations(ticker)
|
||||
|
||||
index_dates = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
|
||||
if expiry is not None:
|
||||
try:
|
||||
if expiry not in dates:
|
||||
exp = int(expiry.replace("-", ""))
|
||||
|
||||
if (expiry not in dates) and (exp not in index_dates):
|
||||
raise Exception("Enter a valid expiration date.")
|
||||
|
||||
if expiry in dates:
|
||||
if call_arg is None:
|
||||
await func_cmd(ctx, ticker, expiry)
|
||||
else:
|
||||
await func_cmd(ctx, ticker, expiry, *call_arg)
|
||||
return
|
||||
if exp in index_dates:
|
||||
expiry = dates[int(expiry)]
|
||||
if call_arg is None:
|
||||
await func_cmd(ctx, ticker, expiry)
|
||||
else:
|
||||
await func_cmd(ctx, ticker, expiry, *call_arg)
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
embed = discord.Embed(
|
||||
title="ERROR Options: Expiry Date",
|
||||
colour=cfg.COLOR,
|
||||
description=e,
|
||||
)
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
await ctx.send(embed=embed)
|
||||
return
|
||||
|
||||
if not dates:
|
||||
embed = discord.Embed(
|
||||
title="ERROR Options",
|
||||
colour=cfg.COLOR,
|
||||
description="Enter a valid stock ticker",
|
||||
)
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
return
|
||||
|
||||
text = (
|
||||
"```0️⃣ " + dates[0] + "\n"
|
||||
"1️⃣ " + dates[1] + "\n"
|
||||
"2️⃣ " + dates[2] + "\n"
|
||||
"3️⃣ " + dates[3] + "\n"
|
||||
"4️⃣ " + dates[4] + "\n"
|
||||
"5️⃣ " + dates[5] + "\n"
|
||||
"6️⃣ " + dates[6] + "\n"
|
||||
"7️⃣ " + dates[7] + "\n"
|
||||
"8️⃣ " + dates[8] + "\n"
|
||||
"9️⃣ " + dates[9] + "```"
|
||||
)
|
||||
|
||||
title = " " + ticker.upper() + " Options: Expiry Date"
|
||||
embed = discord.Embed(title=title, description=text, colour=cfg.COLOR)
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
|
||||
msg = await ctx.send(embed=embed)
|
||||
|
||||
emoji_list = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"]
|
||||
|
||||
for emoji in emoji_list:
|
||||
await msg.add_reaction(emoji)
|
||||
|
||||
def check(reaction, user):
|
||||
return user == ctx.message.author and str(reaction.emoji) in emoji_list
|
||||
|
||||
try:
|
||||
reaction, _ = await gst_bot.wait_for(
|
||||
"reaction_add", timeout=cfg.MENU_TIMEOUT, check=check
|
||||
)
|
||||
for N in range(0, 10):
|
||||
if reaction.emoji == emoji_list[N]:
|
||||
if cfg.DEBUG:
|
||||
print(f"Reaction selected: {N}")
|
||||
expiry = dates[N]
|
||||
if call_arg is None:
|
||||
await func_cmd(ctx, ticker, expiry)
|
||||
else:
|
||||
await func_cmd(ctx, ticker, expiry, *call_arg)
|
||||
|
||||
for emoji in emoji_list:
|
||||
await msg.remove_reaction(emoji, ctx.bot.user)
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
for emoji in emoji_list:
|
||||
await msg.remove_reaction(emoji, ctx.bot.user)
|
||||
embed = discord.Embed(
|
||||
description="Error timeout - you snooze you lose! 😋",
|
||||
colour=cfg.COLOR,
|
||||
title="TIMEOUT " + ticker.upper() + " Options: Expiry Date",
|
||||
)
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
await ctx.send(embed=embed)
|
||||
103
discordbot/stocks/options/hist.py
Normal file
103
discordbot/stocks/options/hist.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import os
|
||||
import discord
|
||||
import mplfinance as mpf
|
||||
import discordbot.config_discordbot as cfg
|
||||
from gamestonk_terminal.stocks.options import tradier_model
|
||||
|
||||
|
||||
async def hist_command(
|
||||
ctx, ticker: str = None, expiry: str = "", strike: float = None, put: bool = False
|
||||
):
|
||||
"""Plot historical option prices
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ticker: str
|
||||
Stock ticker
|
||||
expiry: str
|
||||
accepts 0-9
|
||||
0 being weeklies
|
||||
1+ for weeks out
|
||||
prompts reaction helper if empty
|
||||
strike: float
|
||||
Option strike price
|
||||
put: bool
|
||||
c for call
|
||||
p for put
|
||||
"""
|
||||
|
||||
try:
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
print(f"!stocks.opt.hist {ticker} {strike} {put} {expiry}")
|
||||
|
||||
# Check for argument
|
||||
if ticker is None:
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
if strike is None or put == "":
|
||||
raise Exception(
|
||||
'A strike and c/p is required\n```bash\n"!hist {ticker} {strike} {c/p}"```'
|
||||
)
|
||||
|
||||
chain_id = None
|
||||
|
||||
df_hist = tradier_model.get_historical_options(
|
||||
ticker, expiry, strike, put, chain_id
|
||||
)
|
||||
|
||||
op_type = ["call", "put"][put]
|
||||
|
||||
mc = mpf.make_marketcolors(
|
||||
up="green", down="red", edge="black", wick="black", volume="in", ohlc="i"
|
||||
)
|
||||
|
||||
s = mpf.make_mpf_style(
|
||||
base_mpl_style="seaborn", marketcolors=mc, gridstyle=":", y_on_right=True
|
||||
)
|
||||
|
||||
mpf.plot(
|
||||
df_hist,
|
||||
type="candle",
|
||||
volume=True,
|
||||
title=f"\n{ticker.upper()} {strike} {op_type} expiring {expiry} Historical",
|
||||
tight_layout=True,
|
||||
style=s,
|
||||
figratio=(10, 7),
|
||||
figscale=1.10,
|
||||
figsize=(12, 5),
|
||||
update_width_config=dict(
|
||||
candle_linewidth=1.0, candle_width=0.8, volume_linewidth=1.0
|
||||
),
|
||||
savefig="opt_hist.png",
|
||||
)
|
||||
|
||||
imagefile = "opt_hist.png"
|
||||
image = discord.File(imagefile)
|
||||
|
||||
if cfg.DEBUG:
|
||||
print(f"Image: {imagefile}")
|
||||
title = " " + ticker.upper() + " Options: History"
|
||||
embed = discord.Embed(title=title, colour=cfg.COLOR)
|
||||
embed.set_image(url="attachment://opt_hist.png")
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
os.remove("opt_hist.png")
|
||||
|
||||
await ctx.send(embed=embed, file=image)
|
||||
|
||||
except Exception as e:
|
||||
embed = discord.Embed(
|
||||
title="ERROR Options: History",
|
||||
colour=cfg.COLOR,
|
||||
description=e,
|
||||
)
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
48
discordbot/stocks/options/iv.py
Normal file
48
discordbot/stocks/options/iv.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import discord
|
||||
from tabulate import tabulate
|
||||
|
||||
import discordbot.config_discordbot as cfg
|
||||
from gamestonk_terminal.stocks.options import barchart_model
|
||||
|
||||
|
||||
async def iv_command(ctx, ticker: str = None):
|
||||
"""Options IV"""
|
||||
|
||||
try:
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
print(f"!stocks.opt.iv {ticker}")
|
||||
|
||||
# Check for argument
|
||||
if ticker is None:
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
df = barchart_model.get_options_info(ticker)
|
||||
tickerr = ticker.upper()
|
||||
|
||||
report = "```" + tabulate(df, tablefmt="fancy_grid", showindex=False) + "```"
|
||||
embed = discord.Embed(
|
||||
title=" " + tickerr.__str__() + " Options: IV",
|
||||
description=report,
|
||||
colour=cfg.COLOR,
|
||||
)
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
except Exception as e:
|
||||
embed = discord.Embed(
|
||||
title="ERROR Options: IV",
|
||||
colour=cfg.COLOR,
|
||||
description=e,
|
||||
)
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
@@ -3,31 +3,52 @@ import os
|
||||
import discord
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
import yfinance as yf
|
||||
|
||||
import discordbot.config_discordbot as cfg
|
||||
from discordbot.run_discordbot import gst_imgur
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal.stocks.options import op_helpers
|
||||
from gamestonk_terminal.stocks.options import op_helpers, yfinance_model
|
||||
|
||||
|
||||
async def oi_command(ctx, ticker: str = "", expiration_date: str = ""):
|
||||
"""Show open interest for expiration and ticker"""
|
||||
async def oi_command(
|
||||
ctx,
|
||||
ticker: str = None,
|
||||
expiry: str = "",
|
||||
min_sp: float = None,
|
||||
max_sp: float = None,
|
||||
):
|
||||
"""Options OI"""
|
||||
|
||||
try:
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
print(f"!stocks.opt.oi {ticker.upper()} {expiration_date}")
|
||||
print(f"!stocks.opt.oi {ticker} {expiry} {min_sp} {max_sp}")
|
||||
|
||||
# Check for argument
|
||||
if ticker == "":
|
||||
# Check for argument
|
||||
if ticker is None:
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
dates = yfinance_model.option_expirations(ticker)
|
||||
|
||||
options = yf.Ticker(ticker).option_chain(expiration_date)
|
||||
if not dates:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
options = yfinance_model.get_option_chain(ticker, expiry)
|
||||
calls = options.calls
|
||||
puts = options.puts
|
||||
current_price = yfinance_model.get_price(ticker)
|
||||
|
||||
if min_sp is None:
|
||||
min_strike = 0.75 * current_price
|
||||
else:
|
||||
min_strike = min_sp
|
||||
|
||||
if max_sp is None:
|
||||
max_strike = 1.90 * current_price
|
||||
else:
|
||||
max_strike = max_sp
|
||||
|
||||
call_oi = calls.set_index("strike")["openInterest"] / 1000
|
||||
put_oi = puts.set_index("strike")["openInterest"] / 1000
|
||||
|
||||
@@ -36,22 +57,10 @@ async def oi_command(ctx, ticker: str = "", expiration_date: str = ""):
|
||||
columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"}
|
||||
)
|
||||
|
||||
df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True).fillna(0)
|
||||
df_opt = df_opt.rename(
|
||||
columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"}
|
||||
)
|
||||
max_pain = op_helpers.calculate_max_pain(df_opt)
|
||||
current_price = float(yf.Ticker(ticker).info["regularMarketPrice"])
|
||||
plt.style.use("seaborn")
|
||||
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
|
||||
call_oi.plot(
|
||||
x="strike",
|
||||
y="openInterest",
|
||||
label="Calls",
|
||||
ax=ax,
|
||||
marker="o",
|
||||
ls="-",
|
||||
c="g",
|
||||
)
|
||||
put_oi.plot(
|
||||
x="strike",
|
||||
y="openInterest",
|
||||
@@ -61,37 +70,48 @@ async def oi_command(ctx, ticker: str = "", expiration_date: str = ""):
|
||||
ls="-",
|
||||
c="r",
|
||||
)
|
||||
|
||||
ax.set_xlabel("Strike Price")
|
||||
ax.set_ylabel("Open Interest (1k)")
|
||||
call_oi.plot(
|
||||
x="strike",
|
||||
y="openInterest",
|
||||
label="Calls",
|
||||
ax=ax,
|
||||
marker="o",
|
||||
ls="-",
|
||||
c="g",
|
||||
)
|
||||
ax.axvline(
|
||||
current_price, lw=2, c="k", ls="--", label="Current Price", alpha=0.4
|
||||
)
|
||||
ax.axvline(max_pain, lw=3, c="k", label=f"Max Pain: {max_pain}", alpha=0.4)
|
||||
ax.set_title(f"Open Interest for {ticker.upper()} on {expiration_date}")
|
||||
|
||||
ax.grid("on")
|
||||
ax.legend()
|
||||
ax.set_xlabel("Strike Price")
|
||||
ax.set_ylabel("Open Interest (1k) ")
|
||||
ax.set_xlim(min_strike, max_strike)
|
||||
|
||||
ax.set_title(f"Open Interest for {ticker.upper()} expiring {expiry}")
|
||||
plt.legend(loc=0)
|
||||
fig.tight_layout()
|
||||
plt.savefig("OI.png")
|
||||
uploaded_image = gst_imgur.upload_image("OI.png", title="something")
|
||||
image_link = uploaded_image.link
|
||||
|
||||
imagefile = "opt_oi.png"
|
||||
plt.savefig("opt_oi.png")
|
||||
image = discord.File(imagefile)
|
||||
|
||||
if cfg.DEBUG:
|
||||
print(f"Image URL: {image_link}")
|
||||
title = f"Open Interest for {ticker} on {expiration_date}"
|
||||
print(f"Image {imagefile}")
|
||||
title = " " + ticker.upper() + " Options: Open Interest"
|
||||
embed = discord.Embed(title=title, colour=cfg.COLOR)
|
||||
embed.set_image(url="attachment://opt_oi.png")
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
embed.set_image(url=image_link)
|
||||
os.remove("OI.png")
|
||||
os.remove("opt_oi.png")
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
await ctx.send(embed=embed, file=image)
|
||||
|
||||
except Exception as e:
|
||||
embed = discord.Embed(
|
||||
title="ERROR Stock-Options: Open Interest",
|
||||
title="ERROR Options: Open Interest",
|
||||
colour=cfg.COLOR,
|
||||
description=e,
|
||||
)
|
||||
|
||||
@@ -1,21 +1,58 @@
|
||||
import asyncio
|
||||
|
||||
import discord
|
||||
|
||||
import discordbot.config_discordbot as cfg
|
||||
from discordbot.run_discordbot import gst_bot
|
||||
from discordbot.reaction_helper import expiry_dates_reaction
|
||||
|
||||
from discordbot.stocks.options.calls import calls_command
|
||||
from discordbot.stocks.options.expirations import expirations_command
|
||||
from discordbot.stocks.options.hist import hist_command
|
||||
from discordbot.stocks.options.iv import iv_command
|
||||
from discordbot.stocks.options.oi import oi_command
|
||||
from discordbot.stocks.options.puts import puts_command
|
||||
from discordbot.stocks.options.unu import unu_command
|
||||
from discordbot.stocks.options.vol import vol_command
|
||||
|
||||
|
||||
class OptionsCommands(discord.ext.commands.Cog):
|
||||
"""Dark Pool Shorts menu"""
|
||||
"""Options menu."""
|
||||
|
||||
def __init__(self, bot: discord.ext.commands.Bot):
|
||||
self.bot = bot
|
||||
|
||||
@discord.ext.commands.command(name="stocks.opt.iv", usage="[ticker]")
|
||||
async def iv(self, ctx: discord.ext.commands.Context, ticker: str = None):
|
||||
"""Displays ticker options IV [Barchart]
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
ticker: str
|
||||
ticker,
|
||||
"""
|
||||
async with ctx.typing():
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
await iv_command(ctx, ticker)
|
||||
|
||||
@discord.ext.commands.command(name="stocks.opt.unu", usage="[ticker]")
|
||||
async def unu(
|
||||
self,
|
||||
ctx: discord.ext.commands.Context,
|
||||
num: int = None,
|
||||
):
|
||||
"""Unusual Options
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
ticker: str
|
||||
ticker
|
||||
"""
|
||||
async with ctx.typing():
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
await unu_command(ctx, num)
|
||||
|
||||
@discord.ext.commands.command(name="stocks.opt.exp", usage="[ticker]")
|
||||
async def expirations(self, ctx: discord.ext.commands.Context, ticker=""):
|
||||
"""Get available expirations [yfinance]
|
||||
@@ -28,10 +65,11 @@ class OptionsCommands(discord.ext.commands.Cog):
|
||||
await expirations_command(ctx, ticker)
|
||||
|
||||
@discord.ext.commands.command(
|
||||
name="stocks.opt.calls", usage="[ticker] [expiration]"
|
||||
name="stocks.opt.calls",
|
||||
usage="[ticker] [expiration 0 - 9 (weeks out) or YYYY-MM-DD]",
|
||||
)
|
||||
async def calls(
|
||||
self, ctx: discord.ext.commands.Context, ticker="", expiration: str = ""
|
||||
self, ctx: discord.ext.commands.Context, ticker: str = None, expiry: str = None
|
||||
):
|
||||
"""Get call options for ticker and given expiration
|
||||
|
||||
@@ -39,14 +77,27 @@ class OptionsCommands(discord.ext.commands.Cog):
|
||||
----------
|
||||
ticker: str
|
||||
Stock ticker
|
||||
expiration: str
|
||||
Expiration date
|
||||
expiry: str
|
||||
accepts 0-9 or YYYY-MM-DD
|
||||
0 weeklies, 1+ for weeks out
|
||||
prompts reaction helper if empty
|
||||
"""
|
||||
await calls_command(ctx, ticker, expiration)
|
||||
async with ctx.typing():
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
@discord.ext.commands.command(name="stocks.opt.puts", usage="[ticker] [expiration]")
|
||||
if cfg.DEBUG:
|
||||
print(f"!stocks.opt.calls {ticker} {expiry}")
|
||||
|
||||
func_cmd = calls_command
|
||||
|
||||
await expiry_dates_reaction(ctx, ticker, expiry, func_cmd)
|
||||
|
||||
@discord.ext.commands.command(
|
||||
name="stocks.opt.puts",
|
||||
usage="[ticker] [expiration 0 - 9 (weeks out) or YYYY-MM-DD]",
|
||||
)
|
||||
async def puts(
|
||||
self, ctx: discord.ext.commands.Context, ticker="", expiration: str = ""
|
||||
self, ctx: discord.ext.commands.Context, ticker: str = None, expiry: str = None
|
||||
):
|
||||
"""Get put options for ticker and given expiration
|
||||
|
||||
@@ -54,25 +105,160 @@ class OptionsCommands(discord.ext.commands.Cog):
|
||||
----------
|
||||
ticker: str
|
||||
Stock ticker
|
||||
expiration: str
|
||||
Expiration date
|
||||
expiry: str
|
||||
accepts 0-9 or YYYY-MM-DD
|
||||
0 weeklies, 1+ for weeks out
|
||||
prompts reaction helper if empty
|
||||
"""
|
||||
await puts_command(ctx, ticker, expiration)
|
||||
async with ctx.typing():
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
@discord.ext.commands.command(name="stocks.opt.oi", usage="[ticker] [expiration]")
|
||||
if cfg.DEBUG:
|
||||
print(f"!stocks.opt.puts {ticker} {expiry}")
|
||||
|
||||
func_cmd = puts_command
|
||||
|
||||
await expiry_dates_reaction(ctx, ticker, expiry, func_cmd)
|
||||
|
||||
@discord.ext.commands.command(
|
||||
name="stocks.opt.oi",
|
||||
usage="[ticker] [expiration 0 - 9 (weeks out) or YYYY-MM-DD] [min strike] [max strike]",
|
||||
)
|
||||
async def oi(
|
||||
self, ctx: discord.ext.commands.Context, ticker="", expiration: str = ""
|
||||
self,
|
||||
ctx: discord.ext.commands.Context,
|
||||
ticker: str = None,
|
||||
expiry: str = None,
|
||||
min_sp: float = None,
|
||||
max_sp: float = None,
|
||||
):
|
||||
"""Get put options for ticker and given expiration
|
||||
"""Display options open interest for ticker and given expiration [Max Pain]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
-----------
|
||||
ticker: str
|
||||
Stock ticker
|
||||
expiration: str
|
||||
Expiration date
|
||||
ticker
|
||||
strike: float
|
||||
strike
|
||||
expiry: str
|
||||
accepts 0-9 or YYYY-MM-DD
|
||||
min_sp: float
|
||||
min strike price
|
||||
max_sp:float
|
||||
max strike price
|
||||
|
||||
Sends a message to the discord user with the expiry dates if empty.
|
||||
The user can then select a reaction to trigger the selected date.
|
||||
"""
|
||||
await oi_command(ctx, ticker, expiration)
|
||||
async with ctx.typing():
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
if cfg.DEBUG:
|
||||
print(f"!stocks.opt.oi {ticker} {expiry} {min_sp} {max_sp}")
|
||||
|
||||
call_arg = (min_sp, max_sp)
|
||||
func_cmd = oi_command
|
||||
|
||||
await expiry_dates_reaction(ctx, ticker, expiry, func_cmd, call_arg)
|
||||
|
||||
@discord.ext.commands.command(
|
||||
name="stocks.opt.hist",
|
||||
usage="[ticker] [strike] [c or p (call/put)] [expiration 0 - 9 (weeks out) or YYYY-MM-DD]",
|
||||
)
|
||||
async def hist(
|
||||
self,
|
||||
ctx: discord.ext.commands.Context,
|
||||
ticker: str = None,
|
||||
strike: float = None,
|
||||
put="",
|
||||
expiry: str = None,
|
||||
):
|
||||
"""Display chart of given option historical price [Tradier]
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
ticker: str
|
||||
ticker
|
||||
strike: float
|
||||
strike
|
||||
put: bool
|
||||
c for call
|
||||
p for put
|
||||
expiry: str
|
||||
accepts 0-9 or YYYY-MM-DD
|
||||
|
||||
Sends a message to the discord user with the expiry dates if empty.
|
||||
The user can then select a reaction to trigger the selected date.
|
||||
"""
|
||||
async with ctx.typing():
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
if cfg.DEBUG:
|
||||
print(f"!stocks.opt.hist {ticker} {strike} {put} {expiry}")
|
||||
|
||||
if strike is None or put == "":
|
||||
embed = discord.Embed(
|
||||
title="ERROR Options: History",
|
||||
colour=cfg.COLOR,
|
||||
description='A strike and c/p is required\n```bash\n"!stocks.opt.hist {ticker} {strike} {c/p}"```',
|
||||
)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
return
|
||||
|
||||
if put == "p":
|
||||
put = bool(True)
|
||||
if put == "c":
|
||||
put = bool(False)
|
||||
|
||||
call_arg = (
|
||||
strike,
|
||||
put,
|
||||
)
|
||||
func_cmd = hist_command
|
||||
|
||||
await expiry_dates_reaction(ctx, ticker, expiry, func_cmd, call_arg)
|
||||
|
||||
@discord.ext.commands.command(
|
||||
name="stocks.opt.vol",
|
||||
usage="[ticker] [expiration 0 - 9 (weeks out) or YYYY-MM-DD] [min strike] [max strike]",
|
||||
)
|
||||
async def vol(
|
||||
self,
|
||||
ctx: discord.ext.commands.Context,
|
||||
ticker: str = None,
|
||||
expiry: str = None,
|
||||
min_sp: float = None,
|
||||
max_sp: float = None,
|
||||
):
|
||||
"""Display options volume for ticker and given expiration
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
ticker: str
|
||||
ticker
|
||||
strike: float
|
||||
strike
|
||||
expiry: str
|
||||
accepts 0-9 or YYYY-MM-DD
|
||||
min_sp: float
|
||||
min strike price
|
||||
max_sp:float
|
||||
max strike price
|
||||
|
||||
Sends a message to the discord user with the expiry dates if empty.
|
||||
The user can then select a reaction to trigger the selected date.
|
||||
"""
|
||||
async with ctx.typing():
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
if cfg.DEBUG:
|
||||
print(f"!stocks.opt.vol {ticker} {expiry} {min_sp} {max_sp}")
|
||||
|
||||
call_arg = (min_sp, max_sp)
|
||||
func_cmd = vol_command
|
||||
|
||||
await expiry_dates_reaction(ctx, ticker, expiry, func_cmd, call_arg)
|
||||
|
||||
@discord.ext.commands.command(name="stocks.opt")
|
||||
async def opt(self, ctx: discord.ext.commands.Context, ticker="", expiration=""):
|
||||
@@ -161,7 +347,7 @@ class OptionsCommands(discord.ext.commands.Cog):
|
||||
embed = discord.Embed(
|
||||
description="Error timeout - you snooze you lose! 😋",
|
||||
colour=cfg.COLOR,
|
||||
title="TIMEOUT Stocks: Government (GOV) Menu",
|
||||
title="TIMEOUT Stocks: Options Menu",
|
||||
).set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
|
||||
109
discordbot/stocks/options/unu.py
Normal file
109
discordbot/stocks/options/unu.py
Normal file
@@ -0,0 +1,109 @@
|
||||
import discord
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import requests
|
||||
from tabulate import tabulate
|
||||
|
||||
import discordbot.config_discordbot as cfg
|
||||
from gamestonk_terminal.helper_funcs import get_user_agent
|
||||
|
||||
|
||||
async def unu_command(ctx, num: int = None):
|
||||
"""Unusual Options"""
|
||||
try:
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
print(f"!stocks.opt.unu {num}")
|
||||
|
||||
# Check for argument
|
||||
if num is None:
|
||||
num = 10
|
||||
|
||||
pages = np.arange(0, num // 20 + 1)
|
||||
data_list = []
|
||||
for page_num in pages:
|
||||
|
||||
r = requests.get(
|
||||
f"https://app.fdscanner.com/api2/unusualvolume?p=0&page_size=20&page={int(page_num)}",
|
||||
headers={"User-Agent": get_user_agent()},
|
||||
)
|
||||
|
||||
if r.status_code != 200:
|
||||
print("Error in fdscanner request")
|
||||
return pd.DataFrame(), "request error"
|
||||
|
||||
data_list.append(r.json())
|
||||
|
||||
ticker, expiry, option_strike, option_type, ask, bid, oi, vol, voi = (
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
)
|
||||
for data in data_list:
|
||||
for entry in data["data"]:
|
||||
ticker.append(entry["tk"])
|
||||
expiry.append(entry["expiry"])
|
||||
option_strike.append(float(entry["s"]))
|
||||
option_type.append("Put" if entry["t"] == "P" else "Call")
|
||||
ask.append(entry["a"])
|
||||
bid.append(entry["b"])
|
||||
oi.append(entry["oi"])
|
||||
vol.append(entry["v"])
|
||||
voi.append(entry["vol/oi"])
|
||||
|
||||
df = pd.DataFrame(
|
||||
{
|
||||
"Ticker": ticker,
|
||||
"Exp": expiry,
|
||||
"Strike": option_strike,
|
||||
"Type": option_type,
|
||||
"Vol/OI": voi,
|
||||
"Vol": vol,
|
||||
"OI": oi,
|
||||
}
|
||||
)
|
||||
|
||||
df = df.replace({"2021-", "2022-"}, "", regex=True)
|
||||
|
||||
report = (
|
||||
"```"
|
||||
+ tabulate(
|
||||
df,
|
||||
headers=["T", "Exp", "ST", "C/P", "V/O", "Vol", "OI"],
|
||||
tablefmt="fancy_grid",
|
||||
showindex=False,
|
||||
floatfmt=["", "", ".1f", "", ".1f", ".0f", ".0f", ".2f", ".2f"],
|
||||
)
|
||||
+ "```"
|
||||
)
|
||||
embed = discord.Embed(
|
||||
title="Unusual Options",
|
||||
description=report,
|
||||
colour=cfg.COLOR,
|
||||
)
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
except Exception as e:
|
||||
embed = discord.Embed(
|
||||
title="ERROR Unusual Options",
|
||||
colour=cfg.COLOR,
|
||||
description=e,
|
||||
)
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
113
discordbot/stocks/options/vol.py
Normal file
113
discordbot/stocks/options/vol.py
Normal file
@@ -0,0 +1,113 @@
|
||||
import os
|
||||
import discord
|
||||
import matplotlib.pyplot as plt
|
||||
import discordbot.config_discordbot as cfg
|
||||
|
||||
from gamestonk_terminal import config_plot as cfp
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal.stocks.options import yfinance_model
|
||||
|
||||
|
||||
async def vol_command(
|
||||
ctx,
|
||||
ticker: str = None,
|
||||
expiry: str = "",
|
||||
min_sp: float = None,
|
||||
max_sp: float = None,
|
||||
):
|
||||
"""Options VOL"""
|
||||
|
||||
try:
|
||||
|
||||
# Debug
|
||||
if cfg.DEBUG:
|
||||
print(f"!stocks.opt.vol {ticker} {expiry} {min_sp} {max_sp}")
|
||||
|
||||
# Check for argument
|
||||
if ticker is None:
|
||||
raise Exception("Stock ticker is required")
|
||||
|
||||
dates = yfinance_model.option_expirations(ticker)
|
||||
|
||||
if not dates:
|
||||
raise Exception("Stock ticker is invalid")
|
||||
|
||||
options = yfinance_model.get_option_chain(ticker, expiry)
|
||||
current_price = yfinance_model.get_price(ticker)
|
||||
|
||||
if min_sp is None:
|
||||
min_strike = 0.75 * current_price
|
||||
else:
|
||||
min_strike = min_sp
|
||||
|
||||
if max_sp is None:
|
||||
max_strike = 1.90 * current_price
|
||||
else:
|
||||
max_strike = max_sp
|
||||
|
||||
calls = options.calls
|
||||
puts = options.puts
|
||||
call_v = calls.set_index("strike")["volume"] / 1000
|
||||
put_v = puts.set_index("strike")["volume"] / 1000
|
||||
plt.style.use("seaborn")
|
||||
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI)
|
||||
|
||||
put_v.plot(
|
||||
x="strike",
|
||||
y="volume",
|
||||
label="Puts",
|
||||
ax=ax,
|
||||
marker="o",
|
||||
ls="-",
|
||||
c="r",
|
||||
)
|
||||
call_v.plot(
|
||||
x="strike",
|
||||
y="volume",
|
||||
label="Calls",
|
||||
ax=ax,
|
||||
marker="o",
|
||||
ls="-",
|
||||
c="g",
|
||||
)
|
||||
ax.axvline(
|
||||
current_price, lw=2, c="k", ls="--", label="Current Price", alpha=0.7
|
||||
)
|
||||
ax.grid("on")
|
||||
ax.set_xlabel("Strike Price")
|
||||
ax.set_ylabel("Volume (1k) ")
|
||||
ax.set_xlim(min_strike, max_strike)
|
||||
|
||||
ax.set_title(f"Volume for {ticker.upper()} expiring {expiry}")
|
||||
plt.legend(loc=0)
|
||||
fig.tight_layout(pad=1)
|
||||
|
||||
imagefile = "opt_vol.png"
|
||||
plt.savefig("opt_vol.png")
|
||||
image = discord.File(imagefile)
|
||||
|
||||
if cfg.DEBUG:
|
||||
print(f"Image: {imagefile}")
|
||||
title = " " + ticker.upper() + " Options: Volume"
|
||||
embed = discord.Embed(title=title, colour=cfg.COLOR)
|
||||
embed.set_image(url="attachment://opt_vol.png")
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
os.remove("opt_vol.png")
|
||||
|
||||
await ctx.send(embed=embed, file=image)
|
||||
|
||||
except Exception as e:
|
||||
embed = discord.Embed(
|
||||
title="ERROR Options: Volume",
|
||||
colour=cfg.COLOR,
|
||||
description=e,
|
||||
)
|
||||
embed.set_author(
|
||||
name=cfg.AUTHOR_NAME,
|
||||
icon_url=cfg.AUTHOR_ICON_URL,
|
||||
)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
Reference in New Issue
Block a user