Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,6 @@ dmypy.json

# Cython debug symbols
cython_debug/

# macOS
.DS_Store
112 changes: 112 additions & 0 deletions onebot/plugins/stocks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""
================================================
:mod:`onebot.plugins.stocks`
================================================

This plugin allows to query stocks
"""

from typing import Self
from irc3 import plugin, event
import yfinance as yf
import re


NOT_FOUND_MSG = "Symbol not found"
UNSUPPORTED = "unsupported stock"


@plugin
class StocksPlugin(object):
"""Stocks Plugin
Shows the latest quote when `$<symbol>` is meantioned
Quotes pulled from Yahoo Finance
"""

requires = ["irc3.plugins.command"]

def __init__(self, bot):
"""Initialise the plugin"""
self.bot = bot
self.log = bot.log.getChild(__name__)
self.config = bot.config.get(__name__, {})

@event(
r"^:(?P<mask>\S+!\S+@\S+) (?P<event>PRIVMSG|NOTICE) "
r"(?P<target>#\S+) :(?P<data>.*\$\^?[A-Za-z]+\b)"
)
async def on_msg(self, mask, event, target, data):
"""Parses in put and prints"""
if mask.nick == self.bot.nick or not target.is_channel:
return
symbols = re.findall(r"\$(\^?[A-Za-z]+\b)", data)
for symbol in symbols:
message = await self.stocks_response(symbol)
self.bot.privmsg(target, message)

async def stocks_response(self, symbol):
"""gets stock information"""
comp = yf.Ticker(symbol)

name = comp.info.get("shortName")
symbol = comp.info.get("symbol")
if symbol is None:
return NOT_FOUND_MSG

qType = comp.info.get("quoteType")
if qType == "EQUITY":
price = comp.info.get("currentPrice")
init = comp.info.get("previousClose")
mod = None
elif qType == "MUTUALFUND":
price = comp.info.get("previousClose")
hist = comp.history(period="1mo")
init = hist["Close"][hist.index.min()]
mod = " past month"
elif qType == "INDEX" or "ETF":
price = comp.info.get("ask")
init = comp.info.get("previousClose")
mod = None
else:
return UNSUPPORTED

diff = price - init
pct = (diff / price) * 100
change = f"{diff:.2f} ({pct:.1f}%)"
if diff > 0:
day_change = f"\x033${price:.2f} ▲ {change}\x03" # green
elif diff < 0:
day_change = f"\x034${price:.2f} ▼ {change}\x03" # red
else:
day_change = f"{price} {change}"

if symbol == "TSLA":
symbol = "🚀"

response = f"\x02{name}\x02 (${symbol}) {day_change} "
if mod:
response += mod

high = comp.info.get("dayHigh")
low = comp.info.get("dayLow")
vol = _human(comp.info.get("volume"))
if None not in (high, low, vol):
vola = f"\x0314[\x03H:{high:.2f} \x0314|\x03 L:{low:.2f} \x0314|\x03 Vol:{vol}\x0314]\x03"
response += vola

return response

@classmethod
def reload(cls, old: Self) -> Self: # pragma: no cover
return cls(old.bot)


def _human(n):
units = ["", "", "K", "M", "B"]
if n is None:
return None
sn = len(str(n))
rm = sn % 3
unit = sn // 3 + (rm > 0)
val = str(n)[:3] if rm == 0 else str(n)[:rm]
return f"{val}{units[unit]}"
Loading