From fae93c67adc9015c1466712f9c8ffa35a8b70872 Mon Sep 17 00:00:00 2001 From: Colin Delahunty <72827203+colin99d@users.noreply.github.com> Date: Sun, 20 Feb 2022 10:36:59 -0500 Subject: [PATCH] Refactor Bot (#1326) * First commit * Renamed to bot * change from discordbot to bots imports * refractor cmds + autocomplete helpers + updated dps plots/candle + quote cmd * isort fix * Update helpers.py * Added first refactor * Added commands and refactor * Added description handler * Through grains * ta + image border helper * Added indices * merged * Finished econ * options refactor * dark_pool_shorts, candle, quote done * pagination/dropdown logic to helpers + fixes * dd/disc/insider done + add image logic to helpers * linting * removed plt style * Almost done with Gov * fixing conflicts * Finished gov (excpect ones mentioned) * screener done * Test bug refactor * Store changes to switch * Finished refactor * Fixed error * Delete ::40 * Fixed issue * Fixed black * finished * fixes Co-authored-by: teh_coderer Co-authored-by: didierlopes.eth --- {discordbot => bots}/CONTRIBUTING.md | 0 {discordbot => bots}/README.md | 0 {discordbot => bots}/__init__.py | 0 {discordbot => bots}/cmds/cmds.py | 4 +- {discordbot => bots}/cmds/dd_cmds.py | 74 +-- bots/cmds/dps_cmds.py | 156 +++++++ bots/cmds/economy_cmds.py | 149 ++++++ {discordbot => bots}/cmds/gov_cmds.py | 146 +++--- {discordbot => bots}/cmds/options_cmds.py | 192 +++----- {discordbot => bots}/cmds/screener_cmds.py | 196 ++------ {discordbot => bots}/cmds/ta_cmds.py | 252 +++++------ {discordbot => bots}/config_discordbot.py | 3 +- {discordbot => bots}/economy/__init__.py | 0 bots/economy/currencies.py | 67 +++ bots/economy/energy.py | 70 +++ bots/economy/feargreed.py | 57 +++ bots/economy/futures.py | 63 +++ bots/economy/glbonds.py | 69 +++ bots/economy/grains.py | 70 +++ bots/economy/indices.py | 67 +++ bots/economy/meats.py | 69 +++ bots/economy/metals.py | 69 +++ bots/economy/overview.py | 63 +++ bots/economy/performance.py | 120 +++++ bots/economy/softs.py | 71 +++ bots/economy/usbonds.py | 69 +++ bots/economy/valuation.py | 126 ++++++ {discordbot => bots}/files/bg-dark.png | Bin {discordbot => bots}/files/bg-dark2.png | Bin {discordbot => bots}/files/bg.png | Bin {discordbot => bots}/files/tickers.csv | 0 {discordbot => bots}/generic_commands.py | 2 +- bots/helpers.py | 271 +++++++++++ .../gst_logo_lockup_rGreen_with_letters.png | Bin {discordbot => bots}/menus/__init__.py | 0 {discordbot => bots}/menus/menu.py | 6 +- {discordbot => bots}/requirements.txt | 0 {discordbot => bots}/run_discordbot.py | 0 {discordbot => bots}/stocks/__init__.py | 0 bots/stocks/candle.py | 209 +++++++++ .../stocks/dark_pool_shorts/__init__.py | 0 bots/stocks/dark_pool_shorts/dpotc.py | 208 +++++++++ bots/stocks/dark_pool_shorts/ftd.py | 122 +++++ bots/stocks/dark_pool_shorts/hsi.py | 80 ++++ bots/stocks/dark_pool_shorts/pos.py | 113 +++++ bots/stocks/dark_pool_shorts/psi.py | 157 +++++++ bots/stocks/dark_pool_shorts/shorted.py | 81 ++++ bots/stocks/dark_pool_shorts/sidtc.py | 104 +++++ bots/stocks/dark_pool_shorts/spos.py | 124 +++++ bots/stocks/disc/ford.py | 56 +++ .../stocks/due_diligence/__init__.py | 0 bots/stocks/due_diligence/analyst.py | 55 +++ bots/stocks/due_diligence/arktrades.py | 117 +++++ bots/stocks/due_diligence/customer.py | 30 ++ bots/stocks/due_diligence/est.py | 151 +++++++ bots/stocks/due_diligence/pt.py | 75 ++++ bots/stocks/due_diligence/sec.py | 33 ++ bots/stocks/due_diligence/supplier.py | 29 ++ .../stocks/government/__init__.py | 0 bots/stocks/government/contracts.py | 52 +++ bots/stocks/government/gtrades.py | 115 +++++ bots/stocks/government/histcont.py | 60 +++ bots/stocks/government/lastcontracts.py | 79 ++++ bots/stocks/government/lasttrades.py | 181 ++++++++ bots/stocks/government/lobbying.py | 92 ++++ bots/stocks/government/qtrcontracts.py | 101 +++++ bots/stocks/government/topbuys.py | 111 +++++ bots/stocks/government/toplobbying.py | 43 ++ bots/stocks/government/topsells.py | 122 +++++ bots/stocks/insider/lins.py | 119 +++++ .../stocks/options/__init__.py | 0 bots/stocks/options/cc_hist.py | 122 +++++ bots/stocks/options/hist.py | 154 +++++++ bots/stocks/options/iv.py | 50 +++ bots/stocks/options/oi.py | 137 ++++++ bots/stocks/options/opt_chain.py | 151 +++++++ bots/stocks/options/overview.py | 380 ++++++++++++++++ bots/stocks/options/unu.py | 96 ++++ bots/stocks/options/vol.py | 119 +++++ bots/stocks/options/vsurf.py | 124 +++++ bots/stocks/quote.py | 40 ++ .../stocks/screener/__init__.py | 0 bots/stocks/screener/financial.py | 125 ++++++ bots/stocks/screener/historical.py | 137 ++++++ bots/stocks/screener/overview.py | 128 ++++++ bots/stocks/screener/ownership.py | 127 ++++++ bots/stocks/screener/performance.py | 148 ++++++ bots/stocks/screener/presets_custom.py | 29 ++ bots/stocks/screener/presets_default.py | 20 + .../stocks/screener/screener_options.py | 2 +- bots/stocks/screener/technical.py | 127 ++++++ bots/stocks/screener/valuation.py | 127 ++++++ .../stocks/technical_analysis/__init__.py | 0 bots/stocks/technical_analysis/ad.py | 150 +++++++ bots/stocks/technical_analysis/adosc.py | 145 ++++++ bots/stocks/technical_analysis/adx.py | 176 ++++++++ bots/stocks/technical_analysis/aroon.py | 160 +++++++ bots/stocks/technical_analysis/bbands.py | 169 +++++++ bots/stocks/technical_analysis/cci.py | 170 +++++++ bots/stocks/technical_analysis/cg.py | 86 ++++ bots/stocks/technical_analysis/donchian.py | 96 ++++ bots/stocks/technical_analysis/ema.py | 139 ++++++ bots/stocks/technical_analysis/fib.py | 93 ++++ bots/stocks/technical_analysis/fisher.py | 167 +++++++ bots/stocks/technical_analysis/hma.py | 134 ++++++ bots/stocks/technical_analysis/kc.py | 163 +++++++ bots/stocks/technical_analysis/macd.py | 152 +++++++ bots/stocks/technical_analysis/obv.py | 136 ++++++ bots/stocks/technical_analysis/recom.py | 26 ++ bots/stocks/technical_analysis/rsi.py | 171 +++++++ bots/stocks/technical_analysis/sma.py | 135 ++++++ bots/stocks/technical_analysis/stoch.py | 188 ++++++++ bots/stocks/technical_analysis/summary.py | 24 + bots/stocks/technical_analysis/view.py | 33 ++ bots/stocks/technical_analysis/wma.py | 138 ++++++ bots/stocks/technical_analysis/zlma.py | 138 ++++++ discordbot/cmds/dps_cmds.py | 206 --------- discordbot/cmds/economy_cmds.py | 195 -------- discordbot/economy/currencies.py | 99 ---- discordbot/economy/energy.py | 98 ---- discordbot/economy/feargreed.py | 88 ---- discordbot/economy/futures.py | 95 ---- discordbot/economy/glbonds.py | 102 ----- discordbot/economy/grains.py | 97 ---- discordbot/economy/indices.py | 96 ---- discordbot/economy/meats.py | 97 ---- discordbot/economy/metals.py | 102 ----- discordbot/economy/overview.py | 95 ---- discordbot/economy/performance.py | 151 ------- discordbot/economy/softs.py | 102 ----- discordbot/economy/usbonds.py | 100 ----- discordbot/economy/valuation.py | 158 ------- discordbot/helpers.py | 19 - discordbot/reaction_helper.py | 127 ------ discordbot/stocks/candle.py | 240 ---------- discordbot/stocks/dark_pool_shorts/dpotc.py | 165 ------- discordbot/stocks/dark_pool_shorts/ftd.py | 170 ------- discordbot/stocks/dark_pool_shorts/hsi.py | 82 ---- discordbot/stocks/dark_pool_shorts/pos.py | 113 ----- discordbot/stocks/dark_pool_shorts/psi.py | 148 ------ discordbot/stocks/dark_pool_shorts/shorted.py | 88 ---- discordbot/stocks/dark_pool_shorts/sidtc.py | 104 ----- discordbot/stocks/dark_pool_shorts/spos.py | 119 ----- discordbot/stocks/disc/ford.py | 92 ---- discordbot/stocks/due_diligence/analyst.py | 87 ---- discordbot/stocks/due_diligence/arktrades.py | 129 ------ discordbot/stocks/due_diligence/customer.py | 52 --- discordbot/stocks/due_diligence/est.py | 162 ------- discordbot/stocks/due_diligence/pt.py | 109 ----- discordbot/stocks/due_diligence/sec.py | 55 --- discordbot/stocks/due_diligence/supplier.py | 52 --- discordbot/stocks/government/contracts.py | 109 ----- discordbot/stocks/government/gtrades.py | 168 ------- discordbot/stocks/government/histcont.py | 107 ----- discordbot/stocks/government/lastcontracts.py | 87 ---- discordbot/stocks/government/lasttrades.py | 189 -------- discordbot/stocks/government/lobbying.py | 93 ---- discordbot/stocks/government/qtrcontracts.py | 158 ------- discordbot/stocks/government/topbuys.py | 161 ------- discordbot/stocks/government/toplobbying.py | 96 ---- discordbot/stocks/government/topsells.py | 173 ------- discordbot/stocks/insider/lins.py | 131 ------ discordbot/stocks/options/cc_hist.py | 174 ------- discordbot/stocks/options/hist.py | 208 --------- discordbot/stocks/options/iv.py | 74 --- discordbot/stocks/options/oi.py | 188 -------- discordbot/stocks/options/opt_chain.py | 162 ------- discordbot/stocks/options/overview.py | 424 ------------------ discordbot/stocks/options/unu.py | 120 ----- discordbot/stocks/options/vol.py | 167 ------- discordbot/stocks/options/vsurf.py | 184 -------- discordbot/stocks/screener/financial.py | 136 ------ discordbot/stocks/screener/historical.py | 188 -------- discordbot/stocks/screener/overview.py | 137 ------ discordbot/stocks/screener/ownership.py | 137 ------ discordbot/stocks/screener/performance.py | 158 ------- discordbot/stocks/screener/presets_custom.py | 52 --- discordbot/stocks/screener/presets_default.py | 42 -- discordbot/stocks/screener/technical.py | 137 ------ discordbot/stocks/screener/valuation.py | 137 ------ discordbot/stocks/technical_analysis/ad.py | 200 --------- discordbot/stocks/technical_analysis/adosc.py | 197 -------- discordbot/stocks/technical_analysis/adx.py | 228 ---------- discordbot/stocks/technical_analysis/aroon.py | 209 --------- .../stocks/technical_analysis/bbands.py | 219 --------- discordbot/stocks/technical_analysis/cci.py | 219 --------- discordbot/stocks/technical_analysis/cg.py | 132 ------ .../stocks/technical_analysis/donchian.py | 142 ------ discordbot/stocks/technical_analysis/ema.py | 186 -------- discordbot/stocks/technical_analysis/fib.py | 140 ------ .../stocks/technical_analysis/fisher.py | 215 --------- discordbot/stocks/technical_analysis/hma.py | 182 -------- discordbot/stocks/technical_analysis/kc.py | 211 --------- discordbot/stocks/technical_analysis/macd.py | 204 --------- discordbot/stocks/technical_analysis/obv.py | 187 -------- discordbot/stocks/technical_analysis/recom.py | 51 --- discordbot/stocks/technical_analysis/rsi.py | 221 --------- discordbot/stocks/technical_analysis/sma.py | 182 -------- discordbot/stocks/technical_analysis/stoch.py | 238 ---------- .../stocks/technical_analysis/summary.py | 47 -- discordbot/stocks/technical_analysis/view.py | 87 ---- discordbot/stocks/technical_analysis/wma.py | 185 -------- discordbot/stocks/technical_analysis/zlma.py | 185 -------- discordbot/ticker | Bin 45 -> 0 bytes 204 files changed, 10116 insertions(+), 12894 deletions(-) rename {discordbot => bots}/CONTRIBUTING.md (100%) rename {discordbot => bots}/README.md (100%) rename {discordbot => bots}/__init__.py (100%) rename {discordbot => bots}/cmds/cmds.py (98%) rename {discordbot => bots}/cmds/dd_cmds.py (54%) create mode 100644 bots/cmds/dps_cmds.py create mode 100644 bots/cmds/economy_cmds.py rename {discordbot => bots}/cmds/gov_cmds.py (61%) rename {discordbot => bots}/cmds/options_cmds.py (62%) rename {discordbot => bots}/cmds/screener_cmds.py (56%) rename {discordbot => bots}/cmds/ta_cmds.py (67%) rename {discordbot => bots}/config_discordbot.py (96%) rename {discordbot => bots}/economy/__init__.py (100%) create mode 100644 bots/economy/currencies.py create mode 100644 bots/economy/energy.py create mode 100644 bots/economy/feargreed.py create mode 100644 bots/economy/futures.py create mode 100644 bots/economy/glbonds.py create mode 100644 bots/economy/grains.py create mode 100644 bots/economy/indices.py create mode 100644 bots/economy/meats.py create mode 100644 bots/economy/metals.py create mode 100644 bots/economy/overview.py create mode 100644 bots/economy/performance.py create mode 100644 bots/economy/softs.py create mode 100644 bots/economy/usbonds.py create mode 100644 bots/economy/valuation.py rename {discordbot => bots}/files/bg-dark.png (100%) rename {discordbot => bots}/files/bg-dark2.png (100%) rename {discordbot => bots}/files/bg.png (100%) rename {discordbot => bots}/files/tickers.csv (100%) rename {discordbot => bots}/generic_commands.py (98%) create mode 100644 bots/helpers.py rename {discordbot => bots}/images/gst_logo_lockup_rGreen_with_letters.png (100%) rename {discordbot => bots}/menus/__init__.py (100%) rename {discordbot => bots}/menus/menu.py (97%) rename {discordbot => bots}/requirements.txt (100%) rename {discordbot => bots}/run_discordbot.py (100%) rename {discordbot => bots}/stocks/__init__.py (100%) create mode 100644 bots/stocks/candle.py rename {discordbot => bots}/stocks/dark_pool_shorts/__init__.py (100%) create mode 100644 bots/stocks/dark_pool_shorts/dpotc.py create mode 100644 bots/stocks/dark_pool_shorts/ftd.py create mode 100644 bots/stocks/dark_pool_shorts/hsi.py create mode 100644 bots/stocks/dark_pool_shorts/pos.py create mode 100644 bots/stocks/dark_pool_shorts/psi.py create mode 100644 bots/stocks/dark_pool_shorts/shorted.py create mode 100644 bots/stocks/dark_pool_shorts/sidtc.py create mode 100644 bots/stocks/dark_pool_shorts/spos.py create mode 100644 bots/stocks/disc/ford.py rename {discordbot => bots}/stocks/due_diligence/__init__.py (100%) create mode 100644 bots/stocks/due_diligence/analyst.py create mode 100644 bots/stocks/due_diligence/arktrades.py create mode 100644 bots/stocks/due_diligence/customer.py create mode 100644 bots/stocks/due_diligence/est.py create mode 100644 bots/stocks/due_diligence/pt.py create mode 100644 bots/stocks/due_diligence/sec.py create mode 100644 bots/stocks/due_diligence/supplier.py rename {discordbot => bots}/stocks/government/__init__.py (100%) create mode 100644 bots/stocks/government/contracts.py create mode 100644 bots/stocks/government/gtrades.py create mode 100644 bots/stocks/government/histcont.py create mode 100644 bots/stocks/government/lastcontracts.py create mode 100644 bots/stocks/government/lasttrades.py create mode 100644 bots/stocks/government/lobbying.py create mode 100644 bots/stocks/government/qtrcontracts.py create mode 100644 bots/stocks/government/topbuys.py create mode 100644 bots/stocks/government/toplobbying.py create mode 100644 bots/stocks/government/topsells.py create mode 100644 bots/stocks/insider/lins.py rename {discordbot => bots}/stocks/options/__init__.py (100%) create mode 100644 bots/stocks/options/cc_hist.py create mode 100644 bots/stocks/options/hist.py create mode 100644 bots/stocks/options/iv.py create mode 100644 bots/stocks/options/oi.py create mode 100644 bots/stocks/options/opt_chain.py create mode 100644 bots/stocks/options/overview.py create mode 100644 bots/stocks/options/unu.py create mode 100644 bots/stocks/options/vol.py create mode 100644 bots/stocks/options/vsurf.py create mode 100644 bots/stocks/quote.py rename {discordbot => bots}/stocks/screener/__init__.py (100%) create mode 100644 bots/stocks/screener/financial.py create mode 100644 bots/stocks/screener/historical.py create mode 100644 bots/stocks/screener/overview.py create mode 100644 bots/stocks/screener/ownership.py create mode 100644 bots/stocks/screener/performance.py create mode 100644 bots/stocks/screener/presets_custom.py create mode 100644 bots/stocks/screener/presets_default.py rename {discordbot => bots}/stocks/screener/screener_options.py (99%) create mode 100644 bots/stocks/screener/technical.py create mode 100644 bots/stocks/screener/valuation.py rename {discordbot => bots}/stocks/technical_analysis/__init__.py (100%) create mode 100644 bots/stocks/technical_analysis/ad.py create mode 100644 bots/stocks/technical_analysis/adosc.py create mode 100644 bots/stocks/technical_analysis/adx.py create mode 100644 bots/stocks/technical_analysis/aroon.py create mode 100644 bots/stocks/technical_analysis/bbands.py create mode 100644 bots/stocks/technical_analysis/cci.py create mode 100644 bots/stocks/technical_analysis/cg.py create mode 100644 bots/stocks/technical_analysis/donchian.py create mode 100644 bots/stocks/technical_analysis/ema.py create mode 100644 bots/stocks/technical_analysis/fib.py create mode 100644 bots/stocks/technical_analysis/fisher.py create mode 100644 bots/stocks/technical_analysis/hma.py create mode 100644 bots/stocks/technical_analysis/kc.py create mode 100644 bots/stocks/technical_analysis/macd.py create mode 100644 bots/stocks/technical_analysis/obv.py create mode 100644 bots/stocks/technical_analysis/recom.py create mode 100644 bots/stocks/technical_analysis/rsi.py create mode 100644 bots/stocks/technical_analysis/sma.py create mode 100644 bots/stocks/technical_analysis/stoch.py create mode 100644 bots/stocks/technical_analysis/summary.py create mode 100644 bots/stocks/technical_analysis/view.py create mode 100644 bots/stocks/technical_analysis/wma.py create mode 100644 bots/stocks/technical_analysis/zlma.py delete mode 100644 discordbot/cmds/dps_cmds.py delete mode 100644 discordbot/cmds/economy_cmds.py delete mode 100644 discordbot/economy/currencies.py delete mode 100644 discordbot/economy/energy.py delete mode 100644 discordbot/economy/feargreed.py delete mode 100644 discordbot/economy/futures.py delete mode 100644 discordbot/economy/glbonds.py delete mode 100644 discordbot/economy/grains.py delete mode 100644 discordbot/economy/indices.py delete mode 100644 discordbot/economy/meats.py delete mode 100644 discordbot/economy/metals.py delete mode 100644 discordbot/economy/overview.py delete mode 100644 discordbot/economy/performance.py delete mode 100644 discordbot/economy/softs.py delete mode 100644 discordbot/economy/usbonds.py delete mode 100644 discordbot/economy/valuation.py delete mode 100644 discordbot/helpers.py delete mode 100644 discordbot/reaction_helper.py delete mode 100644 discordbot/stocks/candle.py delete mode 100644 discordbot/stocks/dark_pool_shorts/dpotc.py delete mode 100644 discordbot/stocks/dark_pool_shorts/ftd.py delete mode 100644 discordbot/stocks/dark_pool_shorts/hsi.py delete mode 100644 discordbot/stocks/dark_pool_shorts/pos.py delete mode 100644 discordbot/stocks/dark_pool_shorts/psi.py delete mode 100644 discordbot/stocks/dark_pool_shorts/shorted.py delete mode 100644 discordbot/stocks/dark_pool_shorts/sidtc.py delete mode 100644 discordbot/stocks/dark_pool_shorts/spos.py delete mode 100644 discordbot/stocks/disc/ford.py delete mode 100644 discordbot/stocks/due_diligence/analyst.py delete mode 100644 discordbot/stocks/due_diligence/arktrades.py delete mode 100644 discordbot/stocks/due_diligence/customer.py delete mode 100644 discordbot/stocks/due_diligence/est.py delete mode 100644 discordbot/stocks/due_diligence/pt.py delete mode 100644 discordbot/stocks/due_diligence/sec.py delete mode 100644 discordbot/stocks/due_diligence/supplier.py delete mode 100644 discordbot/stocks/government/contracts.py delete mode 100644 discordbot/stocks/government/gtrades.py delete mode 100644 discordbot/stocks/government/histcont.py delete mode 100644 discordbot/stocks/government/lastcontracts.py delete mode 100644 discordbot/stocks/government/lasttrades.py delete mode 100644 discordbot/stocks/government/lobbying.py delete mode 100644 discordbot/stocks/government/qtrcontracts.py delete mode 100644 discordbot/stocks/government/topbuys.py delete mode 100644 discordbot/stocks/government/toplobbying.py delete mode 100644 discordbot/stocks/government/topsells.py delete mode 100644 discordbot/stocks/insider/lins.py delete mode 100644 discordbot/stocks/options/cc_hist.py delete mode 100644 discordbot/stocks/options/hist.py delete mode 100644 discordbot/stocks/options/iv.py delete mode 100644 discordbot/stocks/options/oi.py delete mode 100644 discordbot/stocks/options/opt_chain.py delete mode 100644 discordbot/stocks/options/overview.py delete mode 100644 discordbot/stocks/options/unu.py delete mode 100644 discordbot/stocks/options/vol.py delete mode 100644 discordbot/stocks/options/vsurf.py delete mode 100644 discordbot/stocks/screener/financial.py delete mode 100644 discordbot/stocks/screener/historical.py delete mode 100644 discordbot/stocks/screener/overview.py delete mode 100644 discordbot/stocks/screener/ownership.py delete mode 100644 discordbot/stocks/screener/performance.py delete mode 100644 discordbot/stocks/screener/presets_custom.py delete mode 100644 discordbot/stocks/screener/presets_default.py delete mode 100644 discordbot/stocks/screener/technical.py delete mode 100644 discordbot/stocks/screener/valuation.py delete mode 100644 discordbot/stocks/technical_analysis/ad.py delete mode 100644 discordbot/stocks/technical_analysis/adosc.py delete mode 100644 discordbot/stocks/technical_analysis/adx.py delete mode 100644 discordbot/stocks/technical_analysis/aroon.py delete mode 100644 discordbot/stocks/technical_analysis/bbands.py delete mode 100644 discordbot/stocks/technical_analysis/cci.py delete mode 100644 discordbot/stocks/technical_analysis/cg.py delete mode 100644 discordbot/stocks/technical_analysis/donchian.py delete mode 100644 discordbot/stocks/technical_analysis/ema.py delete mode 100644 discordbot/stocks/technical_analysis/fib.py delete mode 100644 discordbot/stocks/technical_analysis/fisher.py delete mode 100644 discordbot/stocks/technical_analysis/hma.py delete mode 100644 discordbot/stocks/technical_analysis/kc.py delete mode 100644 discordbot/stocks/technical_analysis/macd.py delete mode 100644 discordbot/stocks/technical_analysis/obv.py delete mode 100644 discordbot/stocks/technical_analysis/recom.py delete mode 100644 discordbot/stocks/technical_analysis/rsi.py delete mode 100644 discordbot/stocks/technical_analysis/sma.py delete mode 100644 discordbot/stocks/technical_analysis/stoch.py delete mode 100644 discordbot/stocks/technical_analysis/summary.py delete mode 100644 discordbot/stocks/technical_analysis/view.py delete mode 100644 discordbot/stocks/technical_analysis/wma.py delete mode 100644 discordbot/stocks/technical_analysis/zlma.py delete mode 100644 discordbot/ticker diff --git a/discordbot/CONTRIBUTING.md b/bots/CONTRIBUTING.md similarity index 100% rename from discordbot/CONTRIBUTING.md rename to bots/CONTRIBUTING.md diff --git a/discordbot/README.md b/bots/README.md similarity index 100% rename from discordbot/README.md rename to bots/README.md diff --git a/discordbot/__init__.py b/bots/__init__.py similarity index 100% rename from discordbot/__init__.py rename to bots/__init__.py diff --git a/discordbot/cmds/cmds.py b/bots/cmds/cmds.py similarity index 98% rename from discordbot/cmds/cmds.py rename to bots/cmds/cmds.py index d33306a53dd..e1c280e6684 100644 --- a/discordbot/cmds/cmds.py +++ b/bots/cmds/cmds.py @@ -1,9 +1,9 @@ # pylint: disable=W0612 import disnake from disnake.ext import commands -from menus.menu import Menu -import discordbot.config_discordbot as cfg +import bots.config_discordbot as cfg +from bots.menus.menu import Menu class CmdsCommands(commands.Cog): diff --git a/discordbot/cmds/dd_cmds.py b/bots/cmds/dd_cmds.py similarity index 54% rename from discordbot/cmds/dd_cmds.py rename to bots/cmds/dd_cmds.py index c520f939f00..9aad1d77514 100644 --- a/discordbot/cmds/dd_cmds.py +++ b/bots/cmds/dd_cmds.py @@ -1,32 +1,16 @@ from __future__ import annotations import disnake -import pandas as pd from disnake.ext import commands -from discordbot.config_discordbot import logger -from discordbot.stocks.due_diligence.analyst import analyst_command -from discordbot.stocks.due_diligence.arktrades import arktrades_command -from discordbot.stocks.due_diligence.customer import customer_command -from discordbot.stocks.due_diligence.est import est_command -from discordbot.stocks.due_diligence.pt import pt_command -from discordbot.stocks.due_diligence.sec import sec_command -from discordbot.stocks.due_diligence.supplier import supplier_command - - -def default_completion(inter: disnake.AppCmdInter) -> list[str]: - return ["Start Typing", "If you want", "to", "Live"] - - -def ticker_autocomp(inter: disnake.AppCmdInter, ticker: str): - if not ticker: - return default_completion(inter) - print(f"ticker_autocomp [ticker]: {ticker}") - tlow = ticker.lower() - col_list = ["Name"] - df = pd.read_csv("files/tickers.csv", usecols=col_list) - df = df["Name"] - return [ticker for ticker in df if ticker.lower().startswith(tlow)][:24] +from bots.helpers import ShowView, ticker_autocomp +from bots.stocks.due_diligence.analyst import analyst_command +from bots.stocks.due_diligence.arktrades import arktrades_command +from bots.stocks.due_diligence.customer import customer_command +from bots.stocks.due_diligence.est import est_command +from bots.stocks.due_diligence.pt import pt_command +from bots.stocks.due_diligence.sec import sec_command +from bots.stocks.due_diligence.supplier import supplier_command class DueDiligenceCommands(commands.Cog): @@ -37,7 +21,7 @@ class DueDiligenceCommands(commands.Cog): @commands.slash_command(name="dd-analyst") async def analyst( - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), ): """Displays analyst recommendations [Finviz] @@ -46,13 +30,11 @@ class DueDiligenceCommands(commands.Cog): ----------- ticker: Stock Ticker """ - await ctx.response.defer() - logger.info("dd-analyst") - await analyst_command(ctx, ticker) + await ShowView().discord(analyst_command, inter, "dd-analyst", ticker) @commands.slash_command(name="dd-pt") async def pt( - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), raw: bool = False, start="", @@ -65,13 +47,11 @@ class DueDiligenceCommands(commands.Cog): raw: True or false start: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("dd-pt") - await pt_command(ctx, ticker, raw, start) + await ShowView().discord(pt_command, inter, "dd-pt", ticker, raw, start) @commands.slash_command(name="dd-est") async def est( - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), ): """Displays earning estimates [Business Insider] @@ -80,13 +60,11 @@ class DueDiligenceCommands(commands.Cog): ----------- ticker: Stock Ticker """ - await ctx.response.defer() - logger.info("dd-est") - await est_command(ctx, ticker) + await ShowView().discord(est_command, inter, "dd-est", ticker) @commands.slash_command(name="dd-sec") async def sec( - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), ): """Displays sec filings [Market Watch] @@ -95,13 +73,11 @@ class DueDiligenceCommands(commands.Cog): ----------- ticker: Stock Ticker """ - await ctx.response.defer() - logger.info("dd-sec") - await sec_command(ctx, ticker) + await ShowView().discord(sec_command, inter, "dd-sec", ticker) @commands.slash_command(name="dd-supplier") async def supplier( - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), ): """Displays suppliers of the company [CSIMarket] @@ -110,13 +86,11 @@ class DueDiligenceCommands(commands.Cog): ----------- ticker: Stock Ticker """ - await ctx.response.defer() - logger.info("dd-supplier") - await supplier_command(ctx, ticker) + await ShowView().discord(supplier_command, inter, "dd-supplier", ticker) @commands.slash_command(name="dd-customer") async def customer( - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), ): """Displays customers of the company [CSIMarket] @@ -125,13 +99,11 @@ class DueDiligenceCommands(commands.Cog): ----------- ticker: Stock Ticker """ - await ctx.response.defer() - logger.info("dd-customer") - await customer_command(ctx, ticker) + await ShowView().discord(customer_command, inter, "dd-customer", ticker) @commands.slash_command(name="dd-arktrades") async def arktrades( - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), num: int = 10, ): @@ -142,9 +114,7 @@ class DueDiligenceCommands(commands.Cog): ticker: Stock Ticker num: number of rows displayed """ - await ctx.response.defer() - logger.info("dd-arktrades") - await arktrades_command(ctx, ticker, num) + await ShowView().discord(arktrades_command, inter, "dd-arktrades", ticker, num) def setup(bot: commands.Bot): diff --git a/bots/cmds/dps_cmds.py b/bots/cmds/dps_cmds.py new file mode 100644 index 00000000000..04f9988fa45 --- /dev/null +++ b/bots/cmds/dps_cmds.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +import disnake +from disnake.ext import commands + +from bots.helpers import ShowView, ticker_autocomp +from bots.stocks.dark_pool_shorts.dpotc import dpotc_command +from bots.stocks.dark_pool_shorts.ftd import ftd_command +from bots.stocks.dark_pool_shorts.hsi import hsi_command +from bots.stocks.dark_pool_shorts.pos import pos_command +from bots.stocks.dark_pool_shorts.psi import psi_command +from bots.stocks.dark_pool_shorts.shorted import shorted_command +from bots.stocks.dark_pool_shorts.sidtc import sidtc_command +from bots.stocks.dark_pool_shorts.spos import spos_command + +pos_choices = { + "Short Vol (1M)": "sv", + "Short Vol %": "sv_pct", + "Net Short Vol (1M)": "nsv", + "Net Short Vol ($100M)": "nsv_dollar", + "DP Position (1M)": "dpp", + "DP Position ($1B)": "dpp_dollar", +} + + +class DarkPoolShortsCommands(commands.Cog): + """Dark Pool Shorts menu""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + self.bot.help_command.cog = self + + def cog_unload(self): + self.bot.help_command = None + + @commands.slash_command(name="dps-shorted") + async def shorted(self, inter: disnake.AppCmdInter, num: int = 10): + """Show most shorted stocks [Yahoo Finance] + + Parameters + ----------- + num: Number of the most shorted stocks to retrieve + """ + await ShowView().discord(shorted_command, inter, "dps-shorted", num) + + @commands.slash_command(name="dps-hsi") + async def hsi(self, inter: disnake.AppCmdInter, num: int = 10): + """Show top high short interest stocks of over 20% ratio [shortinterest.com] + + Parameters + ----------- + num: Number of top stocks to print + """ + await ShowView().discord(hsi_command, inter, "dps-hsi", num) + + @commands.slash_command(name="dps-pos") + async def pos( + self, + inter: disnake.AppCmdInter, + sort: str = commands.Param(choices=pos_choices), + num: int = 10, + ): + """Dark pool short position [Stockgrid] + + Parameters + ----------- + sort: Field for which to sort. + num: Number of top tickers to show + """ + await ShowView().discord(pos_command, inter, "dps-pos", sort, num) + + @commands.slash_command(name="dps-sidtc") + async def sidtc( + self, + inter: disnake.AppCmdInter, + sort: str = commands.Param( + choices={ + "Float Short %": "float", + "Days to Cover": "dtc", + "Short Interest": "si", + } + ), + num: int = 10, + ): + """Short interest and days to cover [Stockgrid] + + Parameters + ----------- + sort: Field for which to sort. Possible are: `float`, `dtc`, `si`. + num: Number of top tickers to show + """ + await ShowView().discord(sidtc_command, inter, "dps-sidtc", sort, num) + + @commands.slash_command(name="dps-ftd") + async def ftd( + self, + inter: disnake.AppCmdInter, + ticker: str = commands.Param(autocomplete=ticker_autocomp), + start="", + end="", + ): + """Fails-to-deliver data [SEC] + + Parameters + ---------- + ticker: Stock Ticker + start: YYYY-MM-DD format + end: YYYY-MM-DD format + """ + await ShowView().discord(ftd_command, inter, "dps-ftd", ticker, start, end) + + @commands.slash_command(name="dps-dpotc") + async def dpotc( + self, + inter: disnake.AppCmdInter, + ticker: str = commands.Param(autocomplete=ticker_autocomp), + ): + """Dark pools (ATS) vs OTC data [FINRA] + + Parameters + ---------- + ticker: Stock Ticker + """ + await ShowView().discord(dpotc_command, inter, "dps-dpotc", ticker) + + @commands.slash_command(name="dps-spos") + async def spos( + self, + inter: disnake.AppCmdInter, + ticker: str = commands.Param(autocomplete=ticker_autocomp), + ): + """Net short vs position [Stockgrid] + + Parameters + ---------- + ticker: Stock Ticker + """ + await ShowView().discord(spos_command, inter, "dps-spos", ticker) + + @commands.slash_command(name="dps-psi") + async def psi( + self, + inter: disnake.AppCmdInter, + ticker: str = commands.Param(autocomplete=ticker_autocomp), + ): + """Price vs short interest volume [Stockgrid] + + Parameters + ---------- + ticker: Stock Ticker + """ + await ShowView().discord(psi_command, inter, "dps-psi", ticker) + + +def setup(bot: commands.Bot): + bot.add_cog(DarkPoolShortsCommands(bot)) diff --git a/bots/cmds/economy_cmds.py b/bots/cmds/economy_cmds.py new file mode 100644 index 00000000000..e0fbb70a322 --- /dev/null +++ b/bots/cmds/economy_cmds.py @@ -0,0 +1,149 @@ +import disnake +from disnake.ext import commands + +from bots.economy.currencies import currencies_command +from bots.economy.energy import energy_command +from bots.economy.feargreed import feargreed_command +from bots.economy.futures import futures_command +from bots.economy.glbonds import glbonds_command +from bots.economy.grains import grains_command +from bots.economy.indices import indices_command +from bots.economy.meats import meats_command +from bots.economy.metals import metals_command +from bots.economy.overview import overview_command +from bots.economy.performance import performance_command +from bots.economy.softs import softs_command +from bots.economy.usbonds import usbonds_command +from bots.economy.valuation import valuation_command +from bots.helpers import ShowView + +group = [ + "basic_materials", + "capitalization", + "communication_services", + "consumer_cyclical", + "consumer_defensive", + "country", + "energy", + "financial", + "healthcare", + "industry", + "industrials", + "real_estate", + "sector", + "technology", + "utilities", +] + +fgind = { + "Junk Bond Demand": "jbd", + "Market Volatility": "mv", + "Put and Call Options": "pco", + "Market Momentum": "mm", + "Stock Price Strength": "sps", + "Stock Price Breadth": "spb", + "Safe Heaven Demand": "shd", +} + + +class EconomyCommands(commands.Cog): + """Economy Commands menu""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.slash_command(name="econ-feargreed") + async def feargreed(self, inter: disnake.AppCmdInter): + """Feargreed command [CNN]""" + await ShowView().discord(feargreed_command, inter, "econ-feargreed") + + @commands.slash_command(name="econ-overview") + async def overview(self, inter: disnake.AppCmdInter): + """Market data overview [Wall St. Journal]""" + await ShowView().discord(overview_command, inter, "econ-overview") + + @commands.slash_command(name="econ-indices") + async def indices(self, inter: disnake.AppCmdInter): + """US indices overview [Wall St. Journal]""" + await ShowView().discord(indices_command, inter, "econ-indices") + + @commands.slash_command(name="econ-futures") + async def futures(self, inter: disnake.AppCmdInter): + """Futures and commodities overview [Wall St. Journal]""" + await ShowView().discord(futures_command, inter, "econ-futures") + + @commands.slash_command(name="econ-usbonds") + async def usbonds(self, inter: disnake.AppCmdInter): + """US bonds overview [Wall St. Journal]""" + await ShowView().discord(usbonds_command, inter, "econ-usbonds") + + @commands.slash_command(name="econ-glbonds") + async def glbonds(self, inter: disnake.AppCmdInter): + """Global bonds overview [Wall St. Journal]""" + await ShowView().discord(glbonds_command, inter, "econ-glbonds") + + @commands.slash_command(name="econ-energy") + async def energy(self, inter: disnake.AppCmdInter): + """Displays energy futures data [Finviz]""" + await ShowView().discord(energy_command, inter, "econ-energy") + + @commands.slash_command(name="econ-metals") + async def metals(self, inter: disnake.AppCmdInter): + """Displays metals futures data [Finviz]""" + await ShowView().discord(metals_command, inter, "econ-metals") + + @commands.slash_command(name="econ-meats") + async def meats(self, inter: disnake.AppCmdInter): + """Displays meats futures data [Finviz]""" + await ShowView().discord(meats_command, inter, "econ-meats") + + @commands.slash_command(name="econ-grains") + async def grains(self, inter: disnake.AppCmdInter): + """Displays grains futures data [Finviz]""" + await ShowView().discord(grains_command, inter, "econ-grains") + + @commands.slash_command(name="econ-softs") + async def softs(self, inter: disnake.AppCmdInter): + """Displays softs futures data [Finviz]""" + await ShowView().discord(softs_command, inter, "econ-softs") + + @commands.slash_command(name="econ-currencies") + async def currencies(self, inter: disnake.AppCmdInter): + """Currencies overview [Wall St. Journal]""" + await ShowView().discord(currencies_command, inter, "econ-currencies") + + @commands.slash_command(name="econ-valuation") + async def valuation( + self, + inter: disnake.AppCmdInter, + economy_group: str = commands.Param(choices=group), + ): + """Valuation of sectors, industry, country [Finviz] + + Parameters + ----------- + economy_group: Economy Group + """ + await ShowView().discord( + valuation_command, inter, "econ-valuation", economy_group + ) + + @commands.slash_command(name="econ-performance") + async def performance( + self, + inter: disnake.AppCmdInter, + economy_group: str = commands.Param(choices=group), + ): + """Performance of sectors, industry, country [Finviz] + + Parameters + ----------- + economy_group: Economy Group + """ + await ShowView().discord( + performance_command, inter, "econ-performance", economy_group + ) + + +def setup(bot: commands.Bot): + bot.add_cog(EconomyCommands(bot)) diff --git a/discordbot/cmds/gov_cmds.py b/bots/cmds/gov_cmds.py similarity index 61% rename from discordbot/cmds/gov_cmds.py rename to bots/cmds/gov_cmds.py index 3acd17cdb23..1d81467e3a8 100644 --- a/discordbot/cmds/gov_cmds.py +++ b/bots/cmds/gov_cmds.py @@ -1,35 +1,19 @@ from __future__ import annotations import disnake -import pandas as pd from disnake.ext import commands -from discordbot.config_discordbot import logger -from discordbot.stocks.government.contracts import contracts_command -from discordbot.stocks.government.gtrades import gtrades_command -from discordbot.stocks.government.histcont import histcont_command -from discordbot.stocks.government.lastcontracts import lastcontracts_command -from discordbot.stocks.government.lasttrades import lasttrades_command -from discordbot.stocks.government.lobbying import lobbying_command -from discordbot.stocks.government.qtrcontracts import qtrcontracts_command -from discordbot.stocks.government.topbuys import topbuys_command -from discordbot.stocks.government.toplobbying import toplobbying_command -from discordbot.stocks.government.topsells import topsells_command - - -def default_completion(inter: disnake.AppCmdInter) -> list[str]: - return ["Start Typing", "for a", "stock ticker"] - - -def ticker_autocomp(inter: disnake.AppCmdInter, ticker: str): - if not ticker: - return default_completion(inter) - print(f"ticker_autocomp [ticker]: {ticker}") - tlow = ticker.lower() - col_list = ["Name"] - df = pd.read_csv("files/tickers.csv", usecols=col_list) - df = df["Name"] - return [ticker for ticker in df if ticker.lower().startswith(tlow)][:24] +from bots.helpers import ShowView, ticker_autocomp +from bots.stocks.government.contracts import contracts_command +from bots.stocks.government.gtrades import gtrades_command +from bots.stocks.government.histcont import histcont_command +from bots.stocks.government.lastcontracts import lastcontracts_command +from bots.stocks.government.lasttrades import lasttrades_command +from bots.stocks.government.lobbying import lobbying_command +from bots.stocks.government.qtrcontracts import qtrcontracts_command +from bots.stocks.government.topbuys import topbuys_command +from bots.stocks.government.toplobbying import toplobbying_command +from bots.stocks.government.topsells import topsells_command class GovernmentCommands(commands.Cog): @@ -45,7 +29,7 @@ class GovernmentCommands(commands.Cog): @commands.slash_command(name="gov-lasttrades") async def lasttrades( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, gov_type: str = commands.Param(choices=["congress", "senate", "house"]), past_days: int = 5, representative="", @@ -58,14 +42,19 @@ class GovernmentCommands(commands.Cog): past_days: Positive number of past transaction days representative: Enter name of a representative """ - await ctx.response.defer() - logger.info("gov-lasttrades") - await lasttrades_command(ctx, gov_type, past_days, representative) + await ShowView().discord( + lasttrades_command, + inter, + "gov-lasttrades", + gov_type, + past_days, + representative, + ) @commands.slash_command(name="gov-topbuys") async def topbuys( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, gov_type: str = commands.Param(choices=["congress", "senate", "house"]), past_transactions_months: int = 5, num: int = 20, @@ -80,14 +69,20 @@ class GovernmentCommands(commands.Cog): num: Number of most sold stocks to retrieve raw: If raw data should be outputted """ - await ctx.response.defer() - logger.info("gov-topbuys") - await topbuys_command(ctx, gov_type, past_transactions_months, num, raw) + await ShowView().discord( + topbuys_command, + inter, + "gov-topbuys", + gov_type, + past_transactions_months, + num, + raw, + ) @commands.slash_command(name="gov-topsells") async def topsells( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, gov_type: str = commands.Param(choices=["congress", "senate", "house"]), past_transactions_months: int = 5, num: int = 10, @@ -102,13 +97,19 @@ class GovernmentCommands(commands.Cog): num: Number of most sold stocks to retrieve raw: If raw data should be outputted """ - await ctx.response.defer() - logger.info("gov-topsells") - await topsells_command(ctx, gov_type, past_transactions_months, num, raw) + await ShowView().discord( + topsells_command, + inter, + "gov-topsells", + gov_type, + past_transactions_months, + num, + raw, + ) @commands.slash_command(name="gov-lastcontracts") async def lastcontracts( - self, ctx: disnake.AppCmdInter, past_transactions_days: int = 2, num: int = 20 + self, inter: disnake.AppCmdInter, past_transactions_days: int = 2, num: int = 20 ): """Displays last government contracts [quiverquant.com] @@ -117,14 +118,18 @@ class GovernmentCommands(commands.Cog): past_transactions_days: Positive number of past transaction days num: Number of contracts """ - await ctx.response.defer() - logger.info("gov-lastcontracts") - await lastcontracts_command(ctx, past_transactions_days, num) + await ShowView().discord( + lastcontracts_command, + inter, + "gov-lastcontracts", + past_transactions_days, + num, + ) @commands.slash_command(name="gov-qtrcontracts") async def qtrcontracts( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, num: int = 20, analysis: str = commands.Param(choices=["total", "upmom", "downmom"]), ): @@ -135,13 +140,13 @@ class GovernmentCommands(commands.Cog): analysis: Possible arguments: total, upmom & downmom num: Number of contracts """ - await ctx.response.defer() - logger.info("gov-qtrcontracts") - await qtrcontracts_command(ctx, num, analysis) + await ShowView().discord( + qtrcontracts_command, inter, "gov-qtrcontracts", num, analysis + ) @commands.slash_command(name="gov-toplobbying") async def toplobbying( - self, ctx: disnake.AppCmdInter, num: int = 10, raw: bool = False + self, inter: disnake.AppCmdInter, num: int = 10, raw: bool = False ): """Displays top lobbying firms [quiverquant.com] @@ -150,14 +155,14 @@ class GovernmentCommands(commands.Cog): num: Number to show raw: If raw data should be outputted """ - await ctx.response.defer() - logger.info("gov-toplobbying") - await toplobbying_command(ctx, num, raw) + await ShowView().discord( + toplobbying_command, inter, "gov-toplobbying", num, raw + ) @commands.slash_command(name="gov-gtrades") async def gtrades( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), gov_type: str = commands.Param(choices=["congress", "senate", "house"]), past_transactions_months: int = 10, @@ -172,14 +177,20 @@ class GovernmentCommands(commands.Cog): past_transactions_months: Positive number of past transaction months raw: If raw data should be outputted """ - await ctx.response.defer() - logger.info("gov-gtrades") - await gtrades_command(ctx, ticker, gov_type, past_transactions_months, raw) + await ShowView().discord( + gtrades_command, + inter, + "gov-gtrades", + ticker, + gov_type, + past_transactions_months, + raw, + ) @commands.slash_command(name="gov-contracts") async def contracts( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), past_transaction_days: int = 10, raw: bool = False, @@ -192,14 +203,19 @@ class GovernmentCommands(commands.Cog): past_transaction_days: Number of past transaction months raw: If raw data should be outputted """ - await ctx.response.defer() - logger.info("gov-contracts") - await contracts_command(ctx, ticker, past_transaction_days, raw) + await ShowView().discord( + contracts_command, + inter, + "gov-contracts", + ticker, + past_transaction_days, + raw, + ) @commands.slash_command(name="gov-histcont") async def histcont( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), ): """Displays historical quarterly-contracts [quiverquant.com] @@ -208,14 +224,12 @@ class GovernmentCommands(commands.Cog): ----------- ticker: Stock Ticker """ - await ctx.response.defer() - logger.info("gov-histcont") - await histcont_command(ctx, ticker) + await ShowView().discord(histcont_command, inter, "gov-histcont", ticker) @commands.slash_command(name="gov-lobbying") async def lobbying( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), num: int = 10, ): @@ -226,9 +240,7 @@ class GovernmentCommands(commands.Cog): ticker: Stock Ticker num: Number of events """ - await ctx.response.defer() - logger.info("gov-lobbying") - await lobbying_command(ctx, ticker, num) + await ShowView().discord(lobbying_command, inter, "gov-lobbying", ticker, num) def setup(bot: commands.Bot): diff --git a/discordbot/cmds/options_cmds.py b/bots/cmds/options_cmds.py similarity index 62% rename from discordbot/cmds/options_cmds.py rename to bots/cmds/options_cmds.py index 5912075249e..95a36794881 100644 --- a/discordbot/cmds/options_cmds.py +++ b/bots/cmds/options_cmds.py @@ -1,74 +1,28 @@ from __future__ import annotations -import pickle -import time - import disnake import disnake.ext.commands as commands -import pandas as pd -from discordbot.config_discordbot import logger -from discordbot.stocks.candle import candle_command -from discordbot.stocks.disc.ford import ford_command -from discordbot.stocks.insider.lins import lins_command -from discordbot.stocks.options.cc_hist import cc_hist_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.opt_chain import chain_command -from discordbot.stocks.options.overview import overview_command -from discordbot.stocks.options.unu import unu_command -from discordbot.stocks.options.vol import vol_command -from discordbot.stocks.options.vsurf import vsurf_command -from gamestonk_terminal.stocks.options import yfinance_model +from bots.helpers import ShowView, expiry_autocomp, ticker_autocomp +from bots.stocks.candle import candle_command +from bots.stocks.disc.ford import ford_command +from bots.stocks.insider.lins import lins_command +from bots.stocks.options.cc_hist import cc_hist_command +from bots.stocks.options.hist import hist_command +from bots.stocks.options.iv import iv_command +from bots.stocks.options.oi import oi_command +from bots.stocks.options.opt_chain import chain_command +from bots.stocks.options.overview import overview_command +from bots.stocks.options.unu import unu_command +from bots.stocks.options.vol import vol_command +from bots.stocks.options.vsurf import vsurf_command +from bots.stocks.quote import quote_command -startTime = time.time() -data = [] - - -class Ticker: - def __init__(self, ticker): - super().__init__() - global tickerr - self._ticker = ticker - tickerr = ticker - - @property - def ticker(self): - return self._ticker - - def __repr__(self) -> str: - data.append(f"{self._ticker}") - file = open("ticker", "wb") - pickle.dump(data, file) - file.close() - return f"{self._ticker}" - - -def default_completion(inter: disnake.AppCmdInter) -> list[str]: - return ["Start Typing", "for a", "stock ticker"] - - -def ticker_autocomp(inter: disnake.AppCmdInter, ticker: str): - if not ticker: - return default_completion(inter) - if ticker: - ticker = str(Ticker(ticker)) - print(f"ticker_autocomp [ticker]: {ticker}") - tlow = ticker.lower() - col_list = ["Name"] - df = pd.read_csv("files/tickers.csv", usecols=col_list) - df = df["Name"] - return [ticker for ticker in df if ticker.lower().startswith(tlow)][:24] - - -def expiry_autocomp(inter: disnake.AppCmdInter, tickerr: str): - file = open("ticker", "rb") - data = pickle.load(file) - file.close() - print(data) - dates = yfinance_model.option_expirations(data[-1]) - return [dates for dates in dates][:24] +vsurf_choices = { + "Volatility": "IV", + "Open Interest": "OI", + "Last Price": "LP", +} class SlashCommands(commands.Cog): @@ -96,9 +50,9 @@ class SlashCommands(commands.Cog): min_sp: Minimum Strike Price max_sp: Maximum Strike Price """ - await inter.response.defer() - logger.info("opt-chain") - await chain_command(inter, ticker, expiry, opt_type, min_sp, max_sp) + await ShowView().discord( + chain_command, inter, "opt-chain", ticker, expiry, opt_type, min_sp, max_sp + ) @commands.slash_command(name="opt-oi") async def open_interest( @@ -118,9 +72,9 @@ class SlashCommands(commands.Cog): min_sp: Minimum Strike Price max_sp: Maximum Strike Price """ - await inter.response.defer() - logger.info("opt-oi") - await oi_command(inter, ticker, expiry, min_sp, max_sp) + await ShowView().discord( + oi_command, inter, "opt-oi", ticker, expiry, min_sp, max_sp + ) @commands.slash_command(name="opt-iv") async def iv( @@ -134,9 +88,21 @@ class SlashCommands(commands.Cog): ----------- ticker: Stock Ticker """ - await inter.response.defer() - logger.info("opt-iv") - await iv_command(inter, ticker) + await ShowView().discord(iv_command, inter, "opt-iv", ticker) + + @commands.slash_command(name="q") + async def quote( + self, + inter: disnake.AppCmdInter, + ticker: str = commands.Param(autocomplete=ticker_autocomp), + ): + """Displays ticker quote [yFinance] + + Parameters + ----------- + ticker: Stock Ticker + """ + await ShowView().discord(quote_command, inter, "quote", ticker) @commands.slash_command(name="disc-ford") async def ford(self, inter: disnake.AppCmdInter): @@ -146,16 +112,12 @@ class SlashCommands(commands.Cog): ----------- num: Number of stocks to display """ - await inter.response.defer() - logger.info("disc-ford") - await ford_command(inter) + await ShowView().discord(ford_command, inter, "disc-ford") @commands.slash_command(name="opt-unu") async def unu(self, inter: disnake.AppCmdInter): """Unusual Options""" - await inter.response.defer() - logger.info("disc-ford") - await unu_command(inter) + await ShowView().discord(unu_command, inter, "disc-ford") @commands.slash_command(name="ins-last") async def lins( @@ -171,9 +133,7 @@ class SlashCommands(commands.Cog): ticker : Stock Ticker num : Number of latest insider activity to display """ - await inter.response.defer() - logger.info("ins-last") - await lins_command(inter, ticker, num) + await lins_command(inter, "ins-last", ticker, num) @commands.slash_command(name="cc") async def cc( @@ -195,9 +155,9 @@ class SlashCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await inter.response.defer() - logger.info("cc") - await candle_command(inter, ticker, interval, past_days, start, end) + await ShowView().discord( + candle_command, inter, "cc", ticker, interval, past_days, start, end + ) @commands.slash_command(name="btc") async def btc( @@ -217,9 +177,9 @@ class SlashCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await inter.response.defer() - logger.info("btc") - await candle_command(inter, "btc-usd", interval, past_days, start, end) + await ShowView().discord( + candle_command, inter, "btc", "btc-usd", interval, past_days, start, end + ) @commands.slash_command(name="eth") async def eth( @@ -239,9 +199,9 @@ class SlashCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await inter.response.defer() - logger.info("eth") - await candle_command(inter, "eth-usd", interval, past_days, start, end) + await ShowView().discord( + candle_command, inter, "eth", "eth-usd", interval, past_days, start, end + ) @commands.slash_command(name="sol") async def sol( @@ -261,9 +221,9 @@ class SlashCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await inter.response.defer() - logger.info("sol") - await candle_command(inter, "sol-usd", interval, past_days, start, end) + await ShowView().discord( + candle_command, inter, "sol", "sol-usd", interval, past_days, start, end + ) @commands.slash_command(name="opt-overview") async def overview( @@ -283,9 +243,9 @@ class SlashCommands(commands.Cog): min_sp: Minimum Strike Price max_sp: Maximum Strike Price """ - await inter.response.defer() - logger.info("opt-overview") - await overview_command(inter, ticker, expiry, min_sp, max_sp) + await ShowView().discord( + overview_command, inter, "opt-overview", ticker, expiry, min_sp, max_sp + ) @commands.slash_command(name="opt-vol") async def volume( @@ -301,16 +261,14 @@ class SlashCommands(commands.Cog): ticker: Stock Ticker expiry: Expiration Date """ - await inter.response.defer() - logger.info("opt-vol") - await vol_command(inter, ticker, expiry) + await ShowView().discord(vol_command, inter, "opt-vol", ticker, expiry) @commands.slash_command(name="opt-vsurf") async def vsurf( self, inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), - z: str = commands.Param(choices=["Volatility", "Open Interest", "Last Price"]), + z: str = commands.Param(choices=vsurf_choices), ): """Display Volatility Surface @@ -319,17 +277,7 @@ class SlashCommands(commands.Cog): ticker: Stock Ticker z: The variable for the Z axis """ - await inter.response.defer() - logger.info("opt-vsurf") - - if z == "Volatility": - z = "IV" - if z == "Open Interest": - z = "OI" - if z == "Last Price": - z = "LP" - - await vsurf_command(inter, ticker, z) + await ShowView().discord(vsurf_command, inter, "opt-vsurf", ticker, z) @commands.slash_command(name="opt-hist") async def history( @@ -358,12 +306,12 @@ class SlashCommands(commands.Cog): ticker: Stock Ticker expiry: Expiration Date strike: Options Strike Price - type: Calls or Puts + opt_type: Calls or Puts greek: Greek variable to plot """ - await inter.response.defer() - logger.info("opt-hist") - await hist_command(inter, ticker, expiry, strike, opt_type, greek) + await ShowView().discord( + hist_command, inter, "opt-hist", ticker, expiry, strike, opt_type, greek + ) @commands.slash_command(name="opt-cc-hist") async def cc_history( @@ -381,14 +329,12 @@ class SlashCommands(commands.Cog): ticker: Stock Ticker expiry: Expiration Date strike: Options Strike Price - type: Calls or Puts + opt_type: Calls or Puts """ - await inter.response.defer() - logger.info("opt-cc-hist") - await cc_hist_command(inter, ticker, expiry, strike, opt_type) + await ShowView().discord( + cc_hist_command, inter, "opt-cc-hist", ticker, expiry, strike, opt_type + ) def setup(bot): bot.add_cog(SlashCommands(bot)) - executionTime = time.time() - startTime - print(f"> Extension {__name__} is ready: time in seconds: {str(executionTime)}\n") diff --git a/discordbot/cmds/screener_cmds.py b/bots/cmds/screener_cmds.py similarity index 56% rename from discordbot/cmds/screener_cmds.py rename to bots/cmds/screener_cmds.py index 72120815631..21f2ff0edad 100644 --- a/discordbot/cmds/screener_cmds.py +++ b/bots/cmds/screener_cmds.py @@ -1,96 +1,19 @@ from __future__ import annotations import disnake -import pandas as pd from disnake.ext import commands -from discordbot.config_discordbot import logger -from discordbot.stocks.screener.financial import financial_command -from discordbot.stocks.screener.historical import historical_command -from discordbot.stocks.screener.overview import overview_command -from discordbot.stocks.screener.ownership import ownership_command -from discordbot.stocks.screener.performance import performance_command -from discordbot.stocks.screener.presets_custom import presets_custom_command -from discordbot.stocks.screener.presets_default import presets_default_command -from discordbot.stocks.screener.technical import technical_command -from discordbot.stocks.screener.valuation import valuation_command +from bots.helpers import ShowView, presets_custom_autocomp, signals_autocomp +from bots.stocks.screener.financial import financial_command +from bots.stocks.screener.historical import historical_command +from bots.stocks.screener.overview import overview_command +from bots.stocks.screener.ownership import ownership_command +from bots.stocks.screener.performance import performance_command +from bots.stocks.screener.presets_custom import presets_custom_command +from bots.stocks.screener.presets_default import presets_default_command +from bots.stocks.screener.technical import technical_command +from bots.stocks.screener.valuation import valuation_command -presets_custom = [ - "potential_reversals", - "golden_cross_penny", - "rosenwald_gtfo", - "golden_cross", - "bull_runs_over_10pct", - "recent_growth_and_support", - "heavy_inst_ins", - "short_squeeze_scan", - "under_15dol_stocks", - "top_performers_healthcare", - "oversold_under_3dol", - "value_stocks", - "cheap_dividend", - "death_cross", - "top_performers_tech", - "unusual_volume", - "cheap_oversold", - "undervalue", - "high_vol_and_low_debt", - "simplistic_momentum_scanner_under_7dol", - "5pct_above_low", - "growth_stocks", - "cheap_bottom_dividend", - "analyst_strong_buy", - "oversold", - "rosenwald", - "weak_support_and_top_performers", - "channel_up_and_low_debt_and_sma_50and200", - "template", - "modified_neff", - "buffett_like", - "oversold_under_5dol", - "sexy_year", - "news_scanner", - "top_performers_all", - "stocks_strong_support_levels", - "continued_momentum_scan", - "modified_dreman", - "break_out_stocks", -] -signals = [ - "top_gainers", - "top_losers", - "new_high", - "new_low", - "most_volatile", - "most_active", - "unusual_volume", - "overbought", - "oversold", - "downgrades", - "upgrades", - "earnings_before", - "earnings_after", - "recent_insider_buying", - "recent_insider_selling", - "major_news", - "horizontal_sr", - "tl_resistance", - "tl_support", - "wedge_up", - "wedge_down", - "wedge", - "triangle_ascending", - "triangle_descending", - "channel_up", - "channel_down", - "channel", - "double_top", - "double_bottom", - "multiple_top", - "multiple_bottom", - "head_shoulders", - "head_shoulders_inverse", -] sort = { "overview": [ "Ticker", @@ -195,39 +118,6 @@ sort = { # pylint: disable=R0912 -def default_completion(inter: disnake.AppCmdInter) -> list[str]: - return ["Start Typing", "for a", "stock ticker"] - - -def presets_custom_autocomp(inter: disnake.AppCmdInter, preset: str): - df = presets_custom - if not preset: - return df[:24] - plow = preset.lower() - print(f"preset_custom_autocomp [preset]: {preset}") - return [preset for preset in df if preset.lower().startswith(plow)][:24] - - -def signals_autocomp(inter: disnake.AppCmdInter, signal: str): - df = signals - if not signal: - return df[:24] - print(f"signal_autocomp [signal]: {signal}") - slow = signal.lower() - return [signal for signal in df if signal.lower().startswith(slow)][:24] - - -def ticker_autocomp(inter: disnake.AppCmdInter, ticker: str): - if not ticker: - return default_completion(inter) - print(f"ticker_autocomp [ticker]: {ticker}") - tlow = ticker.lower() - col_list = ["Name"] - df = pd.read_csv("files/tickers.csv", usecols=col_list) - df = df["Name"] - return [ticker for ticker in df if ticker.lower().startswith(tlow)][:24] - - class ScreenerCommands(commands.Cog): """Screener menu""" @@ -235,21 +125,19 @@ class ScreenerCommands(commands.Cog): self.bot = bot @commands.slash_command(name="scr-presets_default") - async def presets_default(self, ctx: disnake.AppCmdInter): + async def presets_default(self, inter: disnake.AppCmdInter): """Displays every available preset""" - logger.info("scr-presets_default") - await presets_default_command(ctx) + await ShowView().discord(presets_default_command, inter, "scr-presets_default") @commands.slash_command(name="scr-presets_custom") - async def presets_custom(self, ctx: disnake.AppCmdInter): + async def presets_custom(self, inter: disnake.AppCmdInter): """Displays every available preset""" - logger.info("scr-presets_custom") - await presets_custom_command(ctx) + await ShowView().discord(presets_custom_command, inter, "scr-presets_custom") @commands.slash_command(name="scr-historical") async def historical( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, signal: str = commands.Param(autocomplete=signals_autocomp), start="", ): @@ -260,14 +148,14 @@ class ScreenerCommands(commands.Cog): signal: Signal. Default: most_volatile start: Starting date in YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("scr-historical") - await historical_command(ctx, signal, start) + await ShowView().discord( + historical_command, inter, "scr-historical", signal, start + ) @commands.slash_command(name="scr-overview") async def overview( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, preset: str = commands.Param(autocomplete=presets_custom_autocomp), sort: str = commands.Param(choices=sort["overview"]), limit: int = 5, @@ -282,14 +170,14 @@ class ScreenerCommands(commands.Cog): limit: number of stocks to display ascend: whether it's sorted by ascending order or not. Default: False """ - await ctx.response.defer() - logger.info("scr-overview") - await overview_command(ctx, preset, sort, limit, ascend) + await ShowView().discord( + overview_command, inter, "scr-overview", preset, sort, limit, ascend + ) @commands.slash_command(name="scr-valuation") async def valuation( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, preset: str = commands.Param(autocomplete=presets_custom_autocomp), sort: str = commands.Param(choices=sort["valuation"]), limit: int = 5, @@ -304,14 +192,14 @@ class ScreenerCommands(commands.Cog): limit: number of stocks to display ascend: whether it's sorted by ascending order or not. Default: False """ - await ctx.response.defer() - logger.info("scr-valuation") - await valuation_command(ctx, preset, sort, limit, ascend) + await ShowView().discord( + valuation_command, inter, "scr-valuation", preset, sort, limit, ascend + ) @commands.slash_command(name="scr-financial") async def financial( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, preset: str = commands.Param(autocomplete=presets_custom_autocomp), sort: str = commands.Param(choices=sort["financial"]), limit: int = 5, @@ -326,14 +214,14 @@ class ScreenerCommands(commands.Cog): limit: number of stocks to display ascend: whether it's sorted by ascending order or not. Default: False """ - await ctx.response.defer() - logger.info("scr-financial") - await financial_command(ctx, preset, sort, limit, ascend) + await ShowView().discord( + financial_command, inter, "scr-financial", preset, sort, limit, ascend + ) @commands.slash_command(name="scr-ownership") async def ownership( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, preset: str = commands.Param(autocomplete=presets_custom_autocomp), sort: str = commands.Param(choices=sort["ownership"]), limit: int = 5, @@ -348,14 +236,14 @@ class ScreenerCommands(commands.Cog): limit: number of stocks to display ascend: whether it's sorted by ascending order or not. Default: False """ - await ctx.response.defer() - logger.info("scr-ownership") - await ownership_command(ctx, preset, sort, limit, ascend) + await ShowView().discord( + ownership_command, inter, "scr-ownership", preset, sort, limit, ascend + ) @commands.slash_command(name="scr-performance") async def performance( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, preset: str = commands.Param(autocomplete=presets_custom_autocomp), sort: str = commands.Param(choices=sort["performance"]), limit: int = 5, @@ -370,14 +258,14 @@ class ScreenerCommands(commands.Cog): limit: number of stocks to display ascend: whether it's sorted by ascending order or not. Default: False """ - await ctx.response.defer() - logger.info("scr-performance") - await performance_command(ctx, preset, sort, limit, ascend) + await ShowView().discord( + performance_command, inter, "scr-performance", preset, sort, limit, ascend + ) @commands.slash_command(name="scr-technical") async def technical( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, preset: str = commands.Param(autocomplete=presets_custom_autocomp), sort: str = commands.Param(choices=sort["technical"]), limit: int = 5, @@ -392,9 +280,9 @@ class ScreenerCommands(commands.Cog): limit: number of stocks to display ascend: whether it's sorted by ascending order or not. Default: False """ - await ctx.response.defer() - logger.info("scr-technical") - await technical_command(ctx, preset, sort, limit, ascend) + await ShowView().discord( + technical_command, inter, "scr-technical", preset, sort, limit, ascend + ) def setup(bot: commands.Bot): diff --git a/discordbot/cmds/ta_cmds.py b/bots/cmds/ta_cmds.py similarity index 67% rename from discordbot/cmds/ta_cmds.py rename to bots/cmds/ta_cmds.py index 6e45750738a..ed96782d9b7 100644 --- a/discordbot/cmds/ta_cmds.py +++ b/bots/cmds/ta_cmds.py @@ -2,33 +2,32 @@ from __future__ import annotations import disnake -import pandas as pd from disnake.ext import commands -from discordbot.config_discordbot import logger -from discordbot.stocks.technical_analysis.ad import ad_command -from discordbot.stocks.technical_analysis.adosc import adosc_command -from discordbot.stocks.technical_analysis.adx import adx_command -from discordbot.stocks.technical_analysis.aroon import aroon_command -from discordbot.stocks.technical_analysis.bbands import bbands_command -from discordbot.stocks.technical_analysis.cci import cci_command -from discordbot.stocks.technical_analysis.cg import cg_command -from discordbot.stocks.technical_analysis.donchian import donchian_command -from discordbot.stocks.technical_analysis.ema import ema_command -from discordbot.stocks.technical_analysis.fib import fib_command -from discordbot.stocks.technical_analysis.fisher import fisher_command -from discordbot.stocks.technical_analysis.hma import hma_command -from discordbot.stocks.technical_analysis.kc import kc_command -from discordbot.stocks.technical_analysis.macd import macd_command -from discordbot.stocks.technical_analysis.obv import obv_command -from discordbot.stocks.technical_analysis.recom import recom_command -from discordbot.stocks.technical_analysis.rsi import rsi_command -from discordbot.stocks.technical_analysis.sma import sma_command -from discordbot.stocks.technical_analysis.stoch import stoch_command -from discordbot.stocks.technical_analysis.summary import summary_command -from discordbot.stocks.technical_analysis.view import view_command -from discordbot.stocks.technical_analysis.wma import wma_command -from discordbot.stocks.technical_analysis.zlma import zlma_command +from bots.helpers import ShowView, ticker_autocomp +from bots.stocks.technical_analysis.ad import ad_command +from bots.stocks.technical_analysis.adosc import adosc_command +from bots.stocks.technical_analysis.adx import adx_command +from bots.stocks.technical_analysis.aroon import aroon_command +from bots.stocks.technical_analysis.bbands import bbands_command +from bots.stocks.technical_analysis.cci import cci_command +from bots.stocks.technical_analysis.cg import cg_command +from bots.stocks.technical_analysis.donchian import donchian_command +from bots.stocks.technical_analysis.ema import ema_command +from bots.stocks.technical_analysis.fib import fib_command +from bots.stocks.technical_analysis.fisher import fisher_command +from bots.stocks.technical_analysis.hma import hma_command +from bots.stocks.technical_analysis.kc import kc_command +from bots.stocks.technical_analysis.macd import macd_command +from bots.stocks.technical_analysis.obv import obv_command +from bots.stocks.technical_analysis.recom import recom_command +from bots.stocks.technical_analysis.rsi import rsi_command +from bots.stocks.technical_analysis.sma import sma_command +from bots.stocks.technical_analysis.stoch import stoch_command +from bots.stocks.technical_analysis.summary import summary_command +from bots.stocks.technical_analysis.view import view_command +from bots.stocks.technical_analysis.wma import wma_command +from bots.stocks.technical_analysis.zlma import zlma_command possible_ma = [ "dema", @@ -51,21 +50,6 @@ possible_ma = [ ] -def default_completion(inter: disnake.AppCmdInter) -> list[str]: - return ["Start Typing", "for a", "stock ticker"] - - -def ticker_autocomp(inter: disnake.AppCmdInter, ticker: str): - if not ticker: - return default_completion(inter) - print(f"ticker_autocomp [ticker]: {ticker}") - tlow = ticker.lower() - col_list = ["Name"] - df = pd.read_csv("files/tickers.csv", usecols=col_list) - df = df["Name"] - return [ticker for ticker in df if ticker.lower().startswith(tlow)][:24] - - class TechnicalAnalysisCommands(commands.Cog): """Technical Analysis menu.""" @@ -75,7 +59,7 @@ class TechnicalAnalysisCommands(commands.Cog): @commands.slash_command(name="ta-ema") async def ema( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), window="", offset: int = 0, @@ -92,14 +76,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-ema") - await ema_command(ctx, ticker, window, offset, start, end) + await ShowView().discord( + ema_command, inter, "ta-ema", ticker, window, offset, start, end + ) @commands.slash_command(name="ta-sma") async def sma( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), window="", offset: int = 0, @@ -116,14 +100,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-sma") - await sma_command(ctx, ticker, window, offset, start, end) + await ShowView().discord( + sma_command, inter, "ta-sma", ticker, window, offset, start, end + ) @commands.slash_command(name="ta-wma") async def wma( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), window="", offset: int = 0, @@ -140,14 +124,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-wma") - await wma_command(ctx, ticker, window, offset, start, end) + await ShowView().discord( + wma_command, inter, "ta-wma", ticker, window, offset, start, end + ) @commands.slash_command(name="ta-hma") async def hma( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), window="", offset: int = 0, @@ -164,14 +148,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-hma") - await hma_command(ctx, ticker, window, offset, start, end) + await ShowView().discord( + hma_command, inter, "ta-hma", ticker, window, offset, start, end + ) @commands.slash_command(name="ta-zlma") async def zlma( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), window="", offset: int = 0, @@ -188,14 +172,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-zlma") - await zlma_command(ctx, ticker, window, offset, start, end) + await ShowView().discord( + zlma_command, inter, "ta-zlma", ticker, window, offset, start, end + ) @commands.slash_command(name="ta-cci") async def cci( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), length="14", scalar="0.015", @@ -212,14 +196,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-cci") - await cci_command(ctx, ticker, length, scalar, start, end) + await ShowView().discord( + cci_command, inter, "ta-cci", ticker, length, scalar, start, end + ) @commands.slash_command(name="ta-macd") async def macd( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), fast="12", slow="26", @@ -238,14 +222,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-macd") - await macd_command(ctx, ticker, fast, slow, signal, start, end) + await ShowView().discord( + macd_command, inter, "ta-macd", ticker, fast, slow, signal, start, end + ) @commands.slash_command(name="ta-rsi") async def rsi( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), length="14", scalar="100", @@ -264,14 +248,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-rsi") - await rsi_command(ctx, ticker, length, scalar, drift, start, end) + await ShowView().discord( + rsi_command, inter, "ta-rsi", ticker, length, scalar, drift, start, end + ) @commands.slash_command(name="ta-stoch") async def stoch( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), fast_k="14", slow_d="3", @@ -290,14 +274,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-stoch") - await stoch_command(ctx, ticker, fast_k, slow_d, slow_k, start, end) + await ShowView().discord( + stoch_command, inter, "ta-stoch", ticker, fast_k, slow_d, slow_k, start, end + ) @commands.slash_command(name="ta-fisher") async def fisher( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), length="14", start="", @@ -312,14 +296,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-fisher") - await fisher_command(ctx, ticker, length, start, end) + await ShowView().discord( + fisher_command, inter, "ta-fisher", ticker, length, start, end + ) @commands.slash_command(name="ta-cg") async def cg( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), length="14", start="", @@ -334,14 +318,12 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-cg") - await cg_command(ctx, ticker, length, start, end) + await ShowView().discord(cg_command, inter, "ta-cg", ticker, length, start, end) @commands.slash_command(name="ta-adx") async def adx( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), length="14", scalar="100", @@ -360,14 +342,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-adx") - await adx_command(ctx, ticker, length, scalar, drift, start, end) + await ShowView().discord( + adx_command, inter, "ta-adx", ticker, length, scalar, drift, start, end + ) @commands.slash_command(name="ta-aroon") async def aroon( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), length="25", scalar="100", @@ -384,14 +366,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-aroon") - await aroon_command(ctx, ticker, length, scalar, start, end) + await ShowView().discord( + aroon_command, inter, "ta-aroon", ticker, length, scalar, start, end + ) @commands.slash_command(name="ta-bbands") async def bbands( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), length="5", std="2", @@ -410,15 +392,15 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-bbands") ma_mode = str(ma_mode) - await bbands_command(ctx, ticker, length, std, ma_mode, start, end) + await ShowView().discord( + bbands_command, inter, "ta-bbands", ticker, length, std, ma_mode, start, end + ) @commands.slash_command(name="ta-donchian") async def donchian( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), upper_length="25", lower_length="100", @@ -435,15 +417,22 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-donchian") - await donchian_command(ctx, ticker, upper_length, lower_length, start, end) + await ShowView().discord( + donchian_command, + inter, + "ta-donchian", + ticker, + upper_length, + lower_length, + start, + end, + ) # pylint: disable=too-many-arguments @commands.slash_command(name="ta-kc") async def kc( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), length="20", scalar="2", @@ -464,15 +453,24 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-kc") ma_mode = str(ma_mode) - await kc_command(ctx, ticker, length, scalar, ma_mode, offset, start, end) + await ShowView().discord( + kc_command, + inter, + "ta-kc", + ticker, + length, + scalar, + ma_mode, + offset, + start, + end, + ) @commands.slash_command(name="ta-ad") async def ad( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), is_open="False", start="", @@ -487,14 +485,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-ad") - await ad_command(ctx, ticker, is_open, start, end) + await ShowView().discord( + ad_command, inter, "ta-ad", ticker, is_open, start, end + ) @commands.slash_command(name="ta-adosc") async def adosc( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), is_open="False", fast="3", @@ -513,14 +511,14 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-adosc") - await adosc_command(ctx, ticker, is_open, fast, slow, start, end) + await ShowView().discord( + adosc_command, inter, "ta-adosc", ticker, is_open, fast, slow, start, end + ) @commands.slash_command(name="ta-obv") async def obv( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), start="", end="", @@ -533,14 +531,12 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD end: YYYY-MM-DD """ - await ctx.response.defer() - logger.info("ta-obv") - await obv_command(ctx, ticker, start, end) + await ShowView().discord(obv_command, inter, "ta-obv", ticker, start, end) @commands.slash_command(name="ta-fib") async def fib( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), start="", end="", @@ -553,14 +549,12 @@ class TechnicalAnalysisCommands(commands.Cog): start: YYYY-MM-DD format end: YYYY-MM-DD format """ - await ctx.response.defer() - logger.info("ta-fib") - await fib_command(ctx, ticker, start, end) + await ShowView().discord(fib_command, inter, "ta-fib", ticker, start, end) @commands.slash_command(name="ta-view") async def view( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), ): """Displays image from Finviz [Finviz] @@ -569,14 +563,12 @@ class TechnicalAnalysisCommands(commands.Cog): ----------- ticker: Stock Ticker """ - await ctx.response.defer() - logger.info("ta-view") - await view_command(ctx, ticker) + await ShowView().discord(view_command, inter, "ta-view", ticker) @commands.slash_command(name="ta-summary") async def summary( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), ): """Displays text of a given stocks ta summary [FinBrain API] @@ -585,14 +577,12 @@ class TechnicalAnalysisCommands(commands.Cog): ----------- ticker: Stock Ticker """ - await ctx.response.defer() - logger.info("ta-summary") - await summary_command(ctx, ticker) + await ShowView().discord(summary_command, inter, "ta-summary", ticker) @commands.slash_command(name="ta-recom") async def recom( self, - ctx: disnake.AppCmdInter, + inter: disnake.AppCmdInter, ticker: str = commands.Param(autocomplete=ticker_autocomp), ): """Displays text of a given stocks recommendation based on ta [Tradingview API] @@ -601,9 +591,7 @@ class TechnicalAnalysisCommands(commands.Cog): ----------- ticker: Stock Ticker """ - await ctx.response.defer() - logger.info("ta-recom") - await recom_command(ctx, ticker) + await ShowView().discord(recom_command, inter, "ta-recom", ticker) def setup(bot: commands.Bot): diff --git a/discordbot/config_discordbot.py b/bots/config_discordbot.py similarity index 96% rename from discordbot/config_discordbot.py rename to bots/config_discordbot.py index 7bc5b3aa42b..a26e222468c 100644 --- a/discordbot/config_discordbot.py +++ b/bots/config_discordbot.py @@ -3,6 +3,7 @@ import logging import os import sys + import disnake import pyimgur @@ -31,7 +32,7 @@ DISCORD_BOT_TOKEN = os.getenv("GT_DISCORD_BOT_TOKEN") or "REPLACE_ME" IMGUR_CLIENT_ID = os.getenv("GT_IMGUR_CLIENT_ID") or "REPLACE_ME" # Settings -SLASH_TESTING_SERVERS: List[int] = [] # Test server IDs +SLASH_TESTING_SERVERS: List[int] = [936333632317046844] # Test server IDs COMMAND_PREFIX = "!" DATE_FORMAT = "%Y-%m-%d" COLOR = disnake.Color.from_rgb(255, 0, 0) diff --git a/discordbot/economy/__init__.py b/bots/economy/__init__.py similarity index 100% rename from discordbot/economy/__init__.py rename to bots/economy/__init__.py diff --git a/bots/economy/currencies.py b/bots/economy/currencies.py new file mode 100644 index 00000000000..23287fae4bd --- /dev/null +++ b/bots/economy/currencies.py @@ -0,0 +1,67 @@ +import df2img +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.economy import wsj_model + + +def currencies_command(): + """Currencies overview [Wall St. Journal]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("econ-currencies") + + # Retrieve data + df = wsj_model.global_currencies() + df = pd.DataFrame.from_dict(df) + + # Check for argument + if df.empty: + raise Exception("No available data found") + + df["Last"] = pd.to_numeric(df["Last"].astype(float)) + df["Chng"] = pd.to_numeric(df["Chng"].astype(float)) + df["%Chng"] = pd.to_numeric(df["%Chng"].astype(float)) + + formats = {"Last": "{:.2f}", "Chng": "{:.2f}", "%Chng": "{:.2f}%"} + for col, value in formats.items(): + df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 + + df = df.fillna("") + df.set_index(" ", inplace=True) + + # Debug user output + if cfg.DEBUG: + logger.debug(df.to_string()) + + df = df[ + [ + "Last", + "Chng", + "%Chng", + ] + ] + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(800, (40 + (40 * dindex))), + col_width=[8, 3, 3], + tbl_cells=dict( + align="left", + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("econ-currencies.png", fig) + return { + "title": "Economy: [WSJ] Currencies", + "imagefile": imagefile, + } diff --git a/bots/economy/energy.py b/bots/economy/energy.py new file mode 100644 index 00000000000..66caf3d575e --- /dev/null +++ b/bots/economy/energy.py @@ -0,0 +1,70 @@ +import df2img +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.economy import finviz_model + + +def energy_command(): + """Displays energy futures data [Finviz]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("econ-energy") + + # Retrieve data + d_futures = finviz_model.get_futures() + df = pd.DataFrame(d_futures["Energy"]) + + # Check for argument + if df.empty: + raise Exception("No available data found") + + formats = {"last": "${:.2f}", "prevClose": "${:.2f}"} + for col, value in formats.items(): + df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 + + df = df.sort_values(by="ticker", ascending=False) + + # Debug user output + if cfg.DEBUG: + logger.debug(df.to_string()) + + df = df.fillna("") + df.set_index("label", inplace=True) + + df = df[ + [ + "prevClose", + "last", + "change", + ] + ] + df.index.names = [""] + df = df.rename( + columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"} + ) + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(800, (40 + (40 * dindex))), + col_width=[8, 3, 2], + tbl_cells=dict( + align="left", + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + + imagefile = save_image("econ-energy.png", fig) + return { + "title": "[Finviz] Energy Futures", + "imagefile": imagefile, + } diff --git a/bots/economy/feargreed.py b/bots/economy/feargreed.py new file mode 100644 index 00000000000..484f23b0395 --- /dev/null +++ b/bots/economy/feargreed.py @@ -0,0 +1,57 @@ +import datetime +import os + +import matplotlib.pyplot as plt + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from gamestonk_terminal.economy import cnn_model, cnn_view + + +def feargreed_command(indicator=""): + """CNN Fear and Greed Index [CNN]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("econ-futures") + + # Check for argument + possible_indicators = ("", "jbd", "mv", "pco", "mm", "sps", "spb", "shd") + + if indicator not in possible_indicators: + raise Exception( + f"Select a valid indicator from {', '.join(possible_indicators)}" # nosec + ) + + # Retrieve data + fig = plt.figure(figsize=[1, 1], dpi=10) + + report, _ = cnn_model.get_feargreed_report(indicator, fig) + cnn_view.fear_and_greed_index(indicator=indicator, export="png") + plt.close("all") + + # Output data + now = datetime.datetime.now() + image_path = os.path.join( + cfg.GST_PATH, + "exports", + "economy", + f"feargreed_{now.strftime('%Y%m%d_%H%M%S')}.png", + ) + + i = 0 + while not os.path.exists(image_path) and i < 10: + now -= datetime.timedelta(seconds=1) + image_path = os.path.join( + cfg.GST_PATH, + "exports", + "economy", + f"feargreed_{now.strftime('%Y%m%d_%H%M%S')}.png", + ) + i += 1 + + return { + "title": "Economy: [CNN] Fear Geed Index", + "imagefile": image_path, + "description": report, + } diff --git a/bots/economy/futures.py b/bots/economy/futures.py new file mode 100644 index 00000000000..d8dcaf40bd0 --- /dev/null +++ b/bots/economy/futures.py @@ -0,0 +1,63 @@ +import df2img +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.economy import wsj_model + + +def futures_command(): + """Futures and commodities overview [Wall St. Journal]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("econ-futures") + + # Retrieve data + df = wsj_model.top_commodities() + + # Check for argument + if df.empty: + raise Exception("No available data found") + + df["Price"] = pd.to_numeric(df["Price"].astype(float)) + df["Chg"] = pd.to_numeric(df["Chg"].astype(float)) + df["%Chg"] = pd.to_numeric(df["%Chg"].astype(float)) + + # Debug user output + if cfg.DEBUG: + logger.debug(df.to_string()) + + formats = {"Price": "${:.2f}", "Chg": "${:.2f}", "%Chg": "{:.2f}%"} + for col, value in formats.items(): + df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 + + df = df.fillna("") + df.set_index(" ", inplace=True) + + df = df[ + [ + "Price", + "Chg", + "%Chg", + ] + ] + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(800, (40 + (40 * dindex))), + col_width=[8, 3, 3], + tbl_cells=dict( + align="left", + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("econ-futures.png", fig) + return {"title": "Economy: [WSJ] Futures/Commodities", "imagefile": imagefile} diff --git a/bots/economy/glbonds.py b/bots/economy/glbonds.py new file mode 100644 index 00000000000..c911c153297 --- /dev/null +++ b/bots/economy/glbonds.py @@ -0,0 +1,69 @@ +import df2img +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.economy import wsj_model + + +def glbonds_command(): + """Global bonds overview [Wall St. Journal]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("econ-glbonds") + + # Retrieve data + df = wsj_model.global_bonds() + + # Check for argument + if df.empty: + raise Exception("No available data found") + + df["Rate (%)"] = pd.to_numeric(df["Rate (%)"].astype(float)) + df["Yld (%)"] = pd.to_numeric(df["Yld (%)"].astype(float)) + df["Yld Chg (%)"] = pd.to_numeric(df["Yld Chg (%)"].astype(float)) + + formats = { + "Rate (%)": "{:.2f}%", + "Yld (%)": "{:.2f}%", + "Yld Chg (%)": "{:.2f}%", + } + for col, value in formats.items(): + df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 + + # Debug user output + if cfg.DEBUG: + logger.debug(df.to_string()) + + df = df.fillna("") + df = df.replace(("Government Bond", "10 Year"), ("Gov .Bond", "10yr"), regex=True) + df.set_index(" ", inplace=True) + df = df.set_axis( + [ + "Rate", + "Yld", + "Yld Chg", + ], + axis="columns", + ) + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(800, (40 + (40 * dindex))), + col_width=[8, 3, 3], + tbl_cells=dict( + align="left", + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("econ-glbonds.png", fig) + + return {"title": "Economy: [WSJ] Global Bonds", "imagefile": imagefile} diff --git a/bots/economy/grains.py b/bots/economy/grains.py new file mode 100644 index 00000000000..e715d4579aa --- /dev/null +++ b/bots/economy/grains.py @@ -0,0 +1,70 @@ +import df2img +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.economy import finviz_model + + +def grains_command(): + """Displays grains futures data [Finviz]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("econ-grains") + + # Retrieve data + d_futures = finviz_model.get_futures() + df = pd.DataFrame(d_futures["Grains"]) + + # Check for argument + if df.empty: + raise Exception("No available data found") + + formats = {"last": "${:.2f}", "prevClose": "${:.2f}"} + for col, value in formats.items(): + df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 + + df = df.fillna("") + df = df.set_index("label") + df = df.sort_values(by="ticker", ascending=False) + + # Debug user output + if cfg.DEBUG: + logger.debug(df) + + df = df[ + [ + "prevClose", + "last", + "change", + ] + ] + df.index.names = [""] + df = df.rename( + columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"} + ) + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(800, (40 + (40 * dindex))), + col_width=[6, 3, 3], + tbl_cells=dict( + align="left", + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + + imagefile = save_image("econ-grains.png", fig) + + return { + "title": "Economy: [Finviz] Grains Futures", + "imagefile": imagefile, + } diff --git a/bots/economy/indices.py b/bots/economy/indices.py new file mode 100644 index 00000000000..56e92bc978a --- /dev/null +++ b/bots/economy/indices.py @@ -0,0 +1,67 @@ +import df2img +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.economy import wsj_model + + +def indices_command(): + """US indices overview [Wall St. Journal]""" + # Debug user input + if cfg.DEBUG: + logger.debug("econ-indices") + + # Retrieve data + df = wsj_model.us_indices() + + # Check for argument + if df.empty: + raise Exception("No available data found") + + df["Price"] = pd.to_numeric(df["Price"].astype(float)) + df["Chg"] = pd.to_numeric(df["Chg"].astype(float)) + df["%Chg"] = pd.to_numeric(df["%Chg"].astype(float)) + + formats = {"Price": "${:.2f}", "Chg": "${:.2f}", "%Chg": "{:.2f}%"} + for col, value in formats.items(): + # pylint: disable=W0640 + df[col] = df[col].map(lambda x: value.format(x)) + + # Debug user output + if cfg.DEBUG: + logger.debug(df) + + df = df[ + [ + "Price", + "Chg", + "%Chg", + ] + ] + + df = df.fillna("") + df.set_index(" ", inplace=True) + + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(800, (40 + (40 * dindex))), + col_width=[8, 3, 3], + tbl_cells=dict( + align=["left", "center"], + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("econ-indices.png", fig) + return { + "title": "Economy: [WSJ] US Indices", + "imagefile": imagefile, + } diff --git a/bots/economy/meats.py b/bots/economy/meats.py new file mode 100644 index 00000000000..5afc56f5e40 --- /dev/null +++ b/bots/economy/meats.py @@ -0,0 +1,69 @@ +import df2img +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.economy import finviz_model + + +def meats_command(): + """Displays meats futures data [Finviz]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("econ-meats") + + # Retrieve data + d_futures = finviz_model.get_futures() + df = pd.DataFrame(d_futures["Meats"]) + + # Check for argument + if df.empty: + raise Exception("No available data found") + + df = df.fillna("") + df = df.set_index("label") + df = df.sort_values(by="ticker", ascending=False) + + formats = {"last": "${:.2f}", "prevClose": "${:.2f}"} + for col, value in formats.items(): + df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 + + # Debug user output + if cfg.DEBUG: + logger.debug(df) + + df = df[ + [ + "prevClose", + "last", + "change", + ] + ] + df.index.names = [""] + df = df.rename( + columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"} + ) + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(800, (40 + (40 * dindex))), + col_width=[6, 3, 3], + tbl_cells=dict( + align="left", + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + + imagefile = save_image("econ-meats.png", fig) + return { + "title": "Economy: [Finviz] Meats Futures", + "imagefile": imagefile, + } diff --git a/bots/economy/metals.py b/bots/economy/metals.py new file mode 100644 index 00000000000..ab3973236eb --- /dev/null +++ b/bots/economy/metals.py @@ -0,0 +1,69 @@ +import df2img +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.economy import finviz_model + + +def metals_command(): + """Displays metals futures data [Finviz]""" + # Debug user input + if cfg.DEBUG: + logger.debug("econ-metals") + + # Retrieve data + d_futures = finviz_model.get_futures() + df = pd.DataFrame(d_futures["Metals"]) + + # Check for argument + if df.empty: + raise Exception("No available data found") + + formats = {"last": "${:.2f}", "prevClose": "${:.2f}"} + for col, value in formats.items(): + df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 + + # Debug user output + if cfg.DEBUG: + logger.debug(df) + + df = df.sort_values(by="ticker", ascending=False) + df = df.fillna("") + df.set_index("label", inplace=True) + + df = df[ + [ + "prevClose", + "last", + "change", + ] + ] + + df.index.names = [""] + df = df.rename( + columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"} + ) + + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(800, (40 + (40 * dindex))), + col_width=[6, 3, 3], + tbl_cells=dict( + align="left", + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("econ-metals.png", fig) + return { + "title": "Economy: [WSJ] Metals Futures", + "imagefile": imagefile, + } diff --git a/bots/economy/overview.py b/bots/economy/overview.py new file mode 100644 index 00000000000..61aa8b08d64 --- /dev/null +++ b/bots/economy/overview.py @@ -0,0 +1,63 @@ +import df2img +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.economy import wsj_model + + +def overview_command(): + """Market data overview [Wall St. Journal]""" + # Debug user input + if cfg.DEBUG: + logger.debug("econ-overview") + + # Retrieve data + df = wsj_model.market_overview() + + # Check for argument + if df.empty: + raise Exception("No available data found") + + df["Price"] = pd.to_numeric(df["Price"].astype(float)) + df["Chg"] = pd.to_numeric(df["Chg"].astype(float)) + df["%Chg"] = pd.to_numeric(df["%Chg"].astype(float)) + + formats = {"Price": "${:.2f}", "Chg": "${:.2f}", "%Chg": "{:.2f}%"} + for col, v in formats.items(): + df[col] = df[col].map(lambda x: v.format(x)) # pylint: disable=W0640 + + df = df[ + [ + "Price", + "Chg", + "%Chg", + ] + ] + + df = df.fillna("") + df.set_index(" ", inplace=True) + + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(800, (40 + (40 * dindex))), + col_width=[8, 3, 3], + tbl_cells=dict( + align="left", + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + + imagefile = save_image("econ-indices.png", fig) + return { + "title": "Economy: [WSJ] Market Overview", + "imagefile": imagefile, + } diff --git a/bots/economy/performance.py b/bots/economy/performance.py new file mode 100644 index 00000000000..f4a6844c387 --- /dev/null +++ b/bots/economy/performance.py @@ -0,0 +1,120 @@ +import df2img +import numpy as np +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.economy import finviz_model + + +def performance_command(economy_group="sector"): + """Performance of sectors, industry, country [Finviz]""" + + d_economy_group = { + "sector": "Sector", + "industry": "Industry", + "basic_materials": "Industry (Basic Materials)", + "communication_services": "Industry (Communication Services)", + "consumer_cyclical": "Industry (Consumer Cyclical)", + "consumer_defensive": "Industry (Consumer Defensive)", + "energy": "Industry (Energy)", + "financial": "Industry (Financial)", + "healthcare": "Industry (Healthcare)", + "industrials": "Industry (Industrials)", + "real_estate": "Industry (Real Estate)", + "technology": "Industry (Technology)", + "utilities": "Industry (Utilities)", + "country": "Country (U.S. listed stocks only)", + "capitalization": "Capitalization", + } + + # Debug user input + if cfg.DEBUG: + logger.debug("econ-performance %s", economy_group) + + # Select default group + if not economy_group: + if cfg.DEBUG: + logger.debug("Use default economy_group: 'sector'") + economy_group = "sector" + + # Check for argument + possible_groups = list(d_economy_group.keys()) + + if economy_group not in possible_groups: + possible_group_list = ", ".join(possible_groups) + raise Exception(f"Select a valid group from {possible_group_list}") # nosec + + group = d_economy_group[economy_group] + + # Retrieve data + df_group = finviz_model.get_valuation_performance_data(group, "performance") + + # Check for argument + if df_group.empty: + raise Exception("No available data found") + + # Output data + df = pd.DataFrame(df_group) + df = df.replace(np.nan, 0) + + df["Volume"] = df["Volume"] / 1_000_000 + df["Avg Volume"] = df["Avg Volume"] / 1_000_000 + + formats = { + "Perf Month": "{:.2f}", + "Perf Quart": "{:.2f}", + "Perf Half": "{:.2f}", + "Perf Year": "{:.2f}", + "Perf YTD": "{:.2f}", + "Avg Volume": "{:.0f}M", + "Change": "{:.2f}", + "Volume": "{:.0f}M", + } + for col, value in formats.items(): + df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 + + df = df.set_axis( + [ + "Name", + "Week", + "Month", + "3Month", + "6Month", + "1Year", + "YTD", + "Recom", + "Avg Vol.", + "RelVolume", + "Change", + "Volume", + ], + axis="columns", + ) + + df = df.fillna("") + df.set_index("Name", inplace=True) + + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(1500, (40 + (50 * dindex))), + col_width=[10, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3.5], + tbl_cells=dict( + align=["left", "center"], + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + + imagefile = save_image("econ-performance.png", fig) + return { + "title": f"Economy: [WSJ] Performance {group}", + "imagefile": imagefile, + } diff --git a/bots/economy/softs.py b/bots/economy/softs.py new file mode 100644 index 00000000000..0ad6d052283 --- /dev/null +++ b/bots/economy/softs.py @@ -0,0 +1,71 @@ +import df2img +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.economy import finviz_model + + +def softs_command(): + """Displays softs futures data [Finviz]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("econ-softs") + + # Retrieve data + d_futures = finviz_model.get_futures() + df = pd.DataFrame(d_futures["Softs"]) + + # Check for argument + if df.empty: + raise Exception("No available data found") + + formats = {"last": "${:.2f}", "prevClose": "${:.2f}"} + for col, value in formats.items(): + df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 + + # Debug user output + if cfg.DEBUG: + logger.debug(df) + + df = df.sort_values(by="ticker", ascending=False) + df = df.fillna("") + df.set_index("label", inplace=True) + + df = df[ + [ + "prevClose", + "last", + "change", + ] + ] + + df.index.names = [""] + df = df.rename( + columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"} + ) + + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(800, (40 + (40 * dindex))), + col_width=[6, 3, 3], + tbl_cells=dict( + align="left", + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + + imagefile = save_image("econ-currencies.png", fig) + return { + "title": "Economy: [WSJ] Currencies", + "imagefile": imagefile, + } diff --git a/bots/economy/usbonds.py b/bots/economy/usbonds.py new file mode 100644 index 00000000000..f9c14401013 --- /dev/null +++ b/bots/economy/usbonds.py @@ -0,0 +1,69 @@ +import df2img +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.economy import wsj_model + + +def usbonds_command(): + """US bonds overview [Wall St. Journal]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("econ-usbonds") + + # Retrieve data + df = wsj_model.us_bonds() + + # Check for argument + if df.empty: + raise Exception("No available data found") + + df["Rate (%)"] = pd.to_numeric(df["Rate (%)"].astype(float)) + df["Yld (%)"] = pd.to_numeric(df["Yld (%)"].astype(float)) + df["Yld Chg (%)"] = pd.to_numeric(df["Yld Chg (%)"].astype(float)) + + formats = { + "Rate (%)": "{:.2f}%", + "Yld (%)": "{:.2f}%", + "Yld Chg (%)": "{:.2f}%", + } + for col, value in formats.items(): + df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 + + df = df.fillna("") + df.set_index(" ", inplace=True) + + df = df.set_axis( + [ + "Rate", + "Yld", + "Yld Chg", + ], + axis="columns", + ) + + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(800, (40 + (40 * dindex))), + col_width=[8, 3, 3], + tbl_cells=dict( + align="left", + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + + imagefile = save_image("econ-usbonds.png", fig) + return { + "title": "Economy: [WSJ] US Bonds", + "imagefile": imagefile, + } diff --git a/bots/economy/valuation.py b/bots/economy/valuation.py new file mode 100644 index 00000000000..e1d38d57452 --- /dev/null +++ b/bots/economy/valuation.py @@ -0,0 +1,126 @@ +import df2img +import numpy as np +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.economy import finviz_model + + +def valuation_command(economy_group="sector"): + """Valuation of sectors, industry, country [Finviz]""" + + d_economy_group = { + "sector": "Sector", + "industry": "Industry", + "basic_materials": "Industry (Basic Materials)", + "communication_services": "Industry (Communication Services)", + "consumer_cyclical": "Industry (Consumer Cyclical)", + "consumer_defensive": "Industry (Consumer Defensive)", + "energy": "Industry (Energy)", + "financial": "Industry (Financial)", + "healthcare": "Industry (Healthcare)", + "industrials": "Industry (Industrials)", + "real_estate": "Industry (Real Estate)", + "technology": "Industry (Technology)", + "utilities": "Industry (Utilities)", + "country": "Country (U.S. listed stocks only)", + "capitalization": "Capitalization", + } + + # Debug user input + if cfg.DEBUG: + logger.debug("econ-valuation %s", economy_group) + + # Select default group + if economy_group == "": + if cfg.DEBUG: + logger.debug("Use default economy_group: 'sector'") + economy_group = "sector" + + # Check for argument + possible_groups = list(d_economy_group.keys()) + + if economy_group not in possible_groups: + raise Exception( + f"Select a valid group from {', '.join(possible_groups)}" # nosec + ) + + group = d_economy_group[economy_group] + + # Retrieve data + df_group = finviz_model.get_valuation_performance_data(group, "valuation") + + # Check for argument + if df_group.empty: + raise Exception("No available data found") + + # Output data + df = pd.DataFrame(df_group) + df = df.replace(np.nan, 0) + + df = df.set_axis( + [ + "Name", + "MarketCap", + "P/E", + "FwdP/E", + "PEG", + "P/S", + "P/B", + "P/C", + "P/FCF", + "EPSpast5Y", + "EPSnext5Y", + "Salespast5Y", + "Change", + "Volume", + ], + axis="columns", + ) + + df["P/E"] = pd.to_numeric(df["P/E"].astype(float)) + df["FwdP/E"] = pd.to_numeric(df["FwdP/E"].astype(float)) + df["EPSpast5Y"] = pd.to_numeric(df["EPSpast5Y"].astype(float)) + df["EPSnext5Y"] = pd.to_numeric(df["EPSnext5Y"].astype(float)) + df["Salespast5Y"] = pd.to_numeric(df["Salespast5Y"].astype(float)) + df["Volume"] = pd.to_numeric(df["Volume"].astype(float)) + df["Volume"] = df["Volume"] / 1_000_000 + + formats = { + "P/E": "{:.2f}", + "FwdP/E": "{:.2f}", + "EPSpast5Y": "{:.2f}", + "EPSnext5Y": "{:.2f}", + "Salespast5Y": "{:.2f}", + "Change": "{:.2f}", + "Volume": "{:.0f}M", + } + for col, value in formats.items(): + df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 + + df = df.fillna("") + df.set_index("Name", inplace=True) + + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(1600, (40 + (50 * dindex))), + col_width=[12, 5, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 4, 4], + tbl_cells=dict( + align=["left", "center"], + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("econ-valuation.png", fig) + return { + "title": "Economy: [Finviz] Valuation", + "imagefile": imagefile, + } diff --git a/discordbot/files/bg-dark.png b/bots/files/bg-dark.png similarity index 100% rename from discordbot/files/bg-dark.png rename to bots/files/bg-dark.png diff --git a/discordbot/files/bg-dark2.png b/bots/files/bg-dark2.png similarity index 100% rename from discordbot/files/bg-dark2.png rename to bots/files/bg-dark2.png diff --git a/discordbot/files/bg.png b/bots/files/bg.png similarity index 100% rename from discordbot/files/bg.png rename to bots/files/bg.png diff --git a/discordbot/files/tickers.csv b/bots/files/tickers.csv similarity index 100% rename from discordbot/files/tickers.csv rename to bots/files/tickers.csv diff --git a/discordbot/generic_commands.py b/bots/generic_commands.py similarity index 98% rename from discordbot/generic_commands.py rename to bots/generic_commands.py index 0b2ab16b106..50c76c9f13f 100644 --- a/discordbot/generic_commands.py +++ b/bots/generic_commands.py @@ -1,5 +1,5 @@ import discord -import discordbot.config_discordbot as cfg +import bots.config_discordbot as cfg class GenericCommands(discord.ext.commands.Cog): diff --git a/bots/helpers.py b/bots/helpers.py new file mode 100644 index 00000000000..32cfd9a5ed2 --- /dev/null +++ b/bots/helpers.py @@ -0,0 +1,271 @@ +import os +import uuid + +import df2img +import disnake +import pandas as pd +import yfinance as yf +from numpy.core.fromnumeric import transpose +from PIL import Image + +import bots.config_discordbot as cfg + +presets_custom = [ + "potential_reversals", + "golden_cross_penny", + "rosenwald_gtfo", + "golden_cross", + "bull_runs_over_10pct", + "recent_growth_and_support", + "heavy_inst_ins", + "short_squeeze_scan", + "under_15dol_stocks", + "top_performers_healthcare", + "oversold_under_3dol", + "value_stocks", + "cheap_dividend", + "death_cross", + "top_performers_tech", + "unusual_volume", + "cheap_oversold", + "undervalue", + "high_vol_and_low_debt", + "simplistic_momentum_scanner_under_7dol", + "5pct_above_low", + "growth_stocks", + "cheap_bottom_dividend", + "analyst_strong_buy", + "oversold", + "rosenwald", + "weak_support_and_top_performers", + "channel_up_and_low_debt_and_sma_50and200", + "template", + "modified_neff", + "buffett_like", + "oversold_under_5dol", + "sexy_year", + "news_scanner", + "top_performers_all", + "stocks_strong_support_levels", + "continued_momentum_scan", + "modified_dreman", + "break_out_stocks", +] +signals = [ + "top_gainers", + "top_losers", + "new_high", + "new_low", + "most_volatile", + "most_active", + "unusual_volume", + "overbought", + "oversold", + "downgrades", + "upgrades", + "earnings_before", + "earnings_after", + "recent_insider_buying", + "recent_insider_selling", + "major_news", + "horizontal_sr", + "tl_resistance", + "tl_support", + "wedge_up", + "wedge_down", + "wedge", + "triangle_ascending", + "triangle_descending", + "channel_up", + "channel_down", + "channel", + "double_top", + "double_bottom", + "multiple_top", + "multiple_bottom", + "head_shoulders", + "head_shoulders_inverse", +] + + +def load(ticker, start_date): + df_stock_candidate = yf.download(ticker, start=start_date, progress=False) + df_stock_candidate.index.name = "date" + return df_stock_candidate + + +def quote(ticker): + ticker = yf.Ticker(ticker) + quote_df = pd.DataFrame( + [ + { + "Symbol": ticker.info["symbol"], + "Name": ticker.info["shortName"], + "Price": ticker.info["regularMarketPrice"], + "Open": ticker.info["regularMarketOpen"], + "High": ticker.info["dayHigh"], + "Low": ticker.info["dayLow"], + "Previous Close": ticker.info["previousClose"], + "Volume": ticker.info["volume"], + "52 Week High": ticker.info["fiftyTwoWeekHigh"], + "52 Week Low": ticker.info["fiftyTwoWeekLow"], + } + ] + ) + quote_df["Change"] = quote_df["Price"] - quote_df["Previous Close"] + quote_df["Change %"] = quote_df.apply( + lambda x: f'{((x["Change"] / x["Previous Close"]) * 100):.2f}%', + axis="columns", + ) + for c in [ + "Price", + "Open", + "High", + "Low", + "Previous Close", + "52 Week High", + "52 Week Low", + "Change", + ]: + quote_df[c] = quote_df[c].apply(lambda x: f"{x:.2f}") + quote_df["Volume"] = quote_df["Volume"].apply(lambda x: f"{x:,}") + + quote_df = quote_df.set_index("Symbol") + quote_data = transpose(quote_df) + return quote_data + + +def uuid_get(): + rand = str(uuid.uuid1()).replace("-", "") + return rand + + +def autocrop_image(image, border=0): + bbox = image.getbbox() + image = image.crop(bbox) + (width, height) = image.size + width += border * 2 + height += border * 2 + cropped_image = Image.new("RGBA", (width, height), (0, 0, 0, 0)) + cropped_image.paste(image, (border, border)) + return cropped_image + + +def ticker_autocomp(inter, ticker: str): # pylint: disable=W0613 + if not ticker: + return ["Start Typing", "for a", "stock ticker"] + print(f"ticker_autocomp [ticker]: {ticker}") + tlow = ticker.lower() + col_list = ["Name"] + df = pd.read_csv("files/tickers.csv", usecols=col_list) + df = df["Name"] + return [ticker for ticker in df if ticker.lower().startswith(tlow)][:24] + + +def expiry_autocomp(inter, ticker: str): # pylint: disable=W0613 + data = inter.filled_options["ticker"] + yf_ticker = yf.Ticker(data) + dates = list(yf_ticker.options) + return dates[:24] + + +def presets_custom_autocomp(inter, preset: str): # pylint: disable=W0613 + df = presets_custom + if not preset: + return df[:24] + plow = preset.lower() + print(f"preset_custom_autocomp [preset]: {preset}") + return [preset for preset in df if preset.lower().startswith(plow)][:24] + + +def signals_autocomp(inter, signal: str): # pylint: disable=W0613 + df = signals + if not signal: + return df[:24] + print(f"signal_autocomp [signal]: {signal}") + slow = signal.lower() + return [signal for signal in df if signal.lower().startswith(slow)][:24] + + +def save_image(file, fig): + imagefile = f"{file.replace('.png', '')}_{uuid_get()}.png" + df2img.save_dataframe(fig=fig, filename=imagefile) + image = Image.open(imagefile) + image = autocrop_image(image, 0) + image.save(imagefile, "PNG", quality=100) + return imagefile + + +def image_border(file, **kwargs): + imagefile = f"{file.replace('.png', '')}_{uuid_get()}.png" + if "fig" in kwargs: + fig = kwargs["fig"] + fig.write_image(imagefile) + img = Image.open(file) + im_bg = Image.open(cfg.IMG_BG) + h = img.height + 240 + w = img.width + 520 + + # Paste fig onto background img and autocrop background + img = img.resize((w, h), Image.ANTIALIAS) + x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) + y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) + x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) + y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) + img = img.convert("RGB") + im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) + im_bg.save(file, "PNG", quality=100) + image = Image.open(file) + image = autocrop_image(image, 0) + image.save(imagefile, "PNG", quality=100) + os.remove(file) + return imagefile + + +class ShowView: + async def run_discord(self, func, inter, *args, **kwargs): + data = func(*args, **kwargs) + + if "view" in data: + await inter.send( + embed=data["embed"][0], + view=data["view"](data["embed"], data["choices"]), + ) + + else: + title = data.get("title", "") + embed = disnake.Embed( + title=title, colour=cfg.COLOR, description=data.get("description", "") + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + if "imagefile" in data: + image = disnake.File(data["imagefile"]) + embed.set_image(url=f"attachment://{data['imagefile']}") + os.remove(data["imagefile"]) + await inter.send(embed=embed, file=image) + else: + await inter.send(embed=embed) + + async def discord(self, func, inter, name, *args, **kwargs): + await inter.response.defer() + cfg.logger.info(name) + if os.environ.get("DEBUG_MODE") == "true": + await self.run_discord(func, inter, *args, **kwargs) + else: + try: + await self.run_discord(func, inter, *args, **kwargs) + except Exception as e: + embed = disnake.Embed( + title=name, + colour=cfg.COLOR, + description=e, + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + await inter.send(embed=embed, delete_after=30.0) diff --git a/discordbot/images/gst_logo_lockup_rGreen_with_letters.png b/bots/images/gst_logo_lockup_rGreen_with_letters.png similarity index 100% rename from discordbot/images/gst_logo_lockup_rGreen_with_letters.png rename to bots/images/gst_logo_lockup_rGreen_with_letters.png diff --git a/discordbot/menus/__init__.py b/bots/menus/__init__.py similarity index 100% rename from discordbot/menus/__init__.py rename to bots/menus/__init__.py diff --git a/discordbot/menus/menu.py b/bots/menus/menu.py similarity index 97% rename from discordbot/menus/menu.py rename to bots/menus/menu.py index 2b27146e63a..9c7526b0962 100644 --- a/discordbot/menus/menu.py +++ b/bots/menus/menu.py @@ -1,11 +1,11 @@ import time from typing import List -import config_discordbot as cfg - import disnake from disnake.ext import commands +import bots.config_discordbot as cfg + bot = commands.Bot() startTime = time.time() @@ -40,7 +40,7 @@ class Menu(disnake.ui.View): def set_link_button(self) -> None: if not hasattr(self, "link_button"): - self.link_button = disnake.ui.Button( + self.link_button: disnake.ui.Button = disnake.ui.Button( style=disnake.ButtonStyle.url, url="https://github.com/GamestonkTerminal/GamestonkTerminal", label="Site", diff --git a/discordbot/requirements.txt b/bots/requirements.txt similarity index 100% rename from discordbot/requirements.txt rename to bots/requirements.txt diff --git a/discordbot/run_discordbot.py b/bots/run_discordbot.py similarity index 100% rename from discordbot/run_discordbot.py rename to bots/run_discordbot.py diff --git a/discordbot/stocks/__init__.py b/bots/stocks/__init__.py similarity index 100% rename from discordbot/stocks/__init__.py rename to bots/stocks/__init__.py diff --git a/bots/stocks/candle.py b/bots/stocks/candle.py new file mode 100644 index 00000000000..4d56023667d --- /dev/null +++ b/bots/stocks/candle.py @@ -0,0 +1,209 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go +import yfinance as yf +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import overlap_model + + +def candle_command( + ticker: str = "", + interval: int = 15, + past_days: int = 1, + start="", + end="", +): + """Shows candle plot of loaded ticker. [Source: Yahoo Finance, IEX Cloud or Alpha Vantage] + + Parameters + ---------- + ticker: str + Ticker name + interval: int + chart mins or daily + past_days: int + Display the past * days. Default: 1(Not for Daily) + start: str + start date format YYYY-MM-DD + end: str + end date format YYYY-MM-DD + """ + + logger.info("candle %s %s %s %s", ticker, interval, start, end) + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + futures = "=F" or "^" + crypto = "-USD" + if interval == 1440: + df_stock_candidate = yf.download( + ticker, + start=start, + end=end, + progress=False, + ) + + # Check that loading a stock was not successful + if df_stock_candidate.empty: + raise Exception(f"No data found for {ticker.upper()}") + + df_stock_candidate.index.name = "date" + else: + s_int = str(interval) + "m" + d_granularity = { + "1m": past_days, + "5m": past_days, + "15m": past_days, + "30m": past_days, + "60m": past_days, + } + s_start_dt = datetime.utcnow() - timedelta(days=d_granularity[s_int]) + s_date_start = s_start_dt.strftime("%Y-%m-%d") + + df_stock_candidate = yf.download( + ticker, + start=s_date_start if s_start_dt > start else start.strftime("%Y-%m-%d"), + progress=False, + interval=s_int, + prepost=True, + ) + + # Check that loading a stock was not successful + if df_stock_candidate.empty: + raise Exception(f"No data found for {ticker.upper()}.") + + df_stock_candidate.index = df_stock_candidate.index.tz_localize(None) + df_stock_candidate.index.name = "date" + df_stock = df_stock_candidate + price_df = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + + df_vwap = overlap_model.vwap(price_df, 0) + + plt_title = [f"{ticker.upper()} Intraday {interval}min", "Volume"] + title = f"Intraday {interval}min Chart for {ticker.upper()}" + if interval == 1440: + plt_title = [f"{ticker.upper()} Daily", "Volume"] + title = f"Daily Chart for {ticker.upper()}" + + fig = make_subplots( + shared_xaxes=True, + vertical_spacing=0.09, + subplot_titles=plt_title, + specs=[[{"secondary_y": True}]], + ) + fig.add_trace( + go.Candlestick( + x=df_stock.index, + open=df_stock.Open, + high=df_stock.High, + low=df_stock.Low, + close=df_stock.Close, + name="OHLC", + showlegend=False, + ), + secondary_y=True, + ) + fig.add_trace( + go.Bar( + x=df_stock.index, + y=df_stock.Volume, + name="Volume", + yaxis="y2", + marker_color="#d81aea", + opacity=0.4, + showlegend=False, + ), + secondary_y=False, + ) + if interval != 1440: + fig.add_trace( + go.Scatter( + name="VWAP", + x=df_stock.index, + y=df_vwap["VWAP_D"], + opacity=0.65, + line=dict(color="#fdc708", width=2), + showlegend=True, + ), + secondary_y=True, + ) + fig.update_xaxes(showspikes=True, spikesnap="cursor", spikemode="across") + fig.update_yaxes(showspikes=True, spikethickness=2) + fig.update_layout( + margin=dict(l=0, r=0, t=40, b=20), + template=cfg.PLT_CANDLE_STYLE_TEMPLATE, + yaxis2_title="Price ($)", + yaxis_title="Volume", + yaxis=dict( + showgrid=False, + fixedrange=False, + side="left", + titlefont=dict(color="#d81aea"), + tickfont=dict(color="#d81aea"), + nticks=20, + ), + yaxis2=dict( + side="right", + fixedrange=False, + anchor="x", + layer="above traces", + overlaying="y", + nticks=20, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + showspikes=True, + ), + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), + dragmode="pan", + hovermode="x unified", + spikedistance=1000, + hoverdistance=100, + ) + if futures in ticker.upper(): + fig.update_xaxes( + rangebreaks=[ + dict(bounds=["sat", "sun"]), + dict(bounds=[17, 17.50], pattern="hour"), + ], + ) + elif crypto not in ticker.upper() and interval != 1440: + fig.update_xaxes( + rangebreaks=[ + dict(bounds=["sat", "mon"]), + dict(bounds=[20, 9.50], pattern="hour"), + ], + ) + config = dict({"scrollZoom": True}) + imagefile = "candle.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/candle_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/candle_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": title, + "description": plt_link, + "imagefile": imagefile, + } diff --git a/discordbot/stocks/dark_pool_shorts/__init__.py b/bots/stocks/dark_pool_shorts/__init__.py similarity index 100% rename from discordbot/stocks/dark_pool_shorts/__init__.py rename to bots/stocks/dark_pool_shorts/__init__.py diff --git a/bots/stocks/dark_pool_shorts/dpotc.py b/bots/stocks/dark_pool_shorts/dpotc.py new file mode 100644 index 00000000000..02aaac73074 --- /dev/null +++ b/bots/stocks/dark_pool_shorts/dpotc.py @@ -0,0 +1,208 @@ +import plotly.graph_objects as go +import yfinance as yf +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.stocks.dark_pool_shorts import finra_model + + +def dpotc_command(ticker: str = ""): + """Dark pools (ATS) vs OTC data [FINRA]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("dps-dpotc %s", ticker) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + ticker = ticker.upper() + + stock = yf.download(ticker, progress=False) + if stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve data + ats, otc = finra_model.getTickerFINRAdata(ticker) + + # Debug user output + if cfg.DEBUG: + logger.debug(ats.to_string()) + logger.debug(otc.to_string()) + + # Output data + title = f"Stocks: [FINRA] Dark Pools (ATS) vs OTC {ticker}" + + if ats.empty and otc.empty: + raise Exception("Stock ticker is invalid") + + fig = make_subplots( + rows=2, + cols=1, + shared_xaxes=True, + vertical_spacing=0.07, + row_width=[0.4, 0.6], + specs=[[{"secondary_y": True}], [{"secondary_y": False}]], + ) + + if not ats.empty and not otc.empty: + fig.add_trace( + go.Bar( + x=ats.index, + y=(ats["totalWeeklyShareQuantity"] + otc["totalWeeklyShareQuantity"]), + name="ATS", + opacity=0.8, + ), + row=1, + col=1, + secondary_y=False, + ) + fig.add_trace( + go.Bar( + x=otc.index, + y=otc["totalWeeklyShareQuantity"], + name="OTC", + opacity=0.8, + yaxis="y2", + offset=0.0001, + ), + row=1, + col=1, + ) + + elif not ats.empty: + fig.add_trace( + go.Bar( + x=ats.index, + y=(ats["totalWeeklyShareQuantity"] + otc["totalWeeklyShareQuantity"]), + name="ATS", + opacity=0.8, + ), + row=1, + col=1, + secondary_y=False, + ) + + elif not otc.empty: + fig.add_trace( + go.Bar( + x=otc.index, + y=otc["totalWeeklyShareQuantity"], + name="OTC", + opacity=0.8, + yaxis="y2", + secondary_y=False, + ), + row=1, + col=1, + ) + + if not ats.empty: + fig.add_trace( + go.Scatter( + name="ATS", + x=ats.index, + y=ats["totalWeeklyShareQuantity"] / ats["totalWeeklyTradeCount"], + line=dict(color="#fdc708", width=2), + opacity=1, + showlegend=False, + yaxis="y2", + ), + row=2, + col=1, + ) + + if not otc.empty: + fig.add_trace( + go.Scatter( + name="OTC", + x=otc.index, + y=otc["totalWeeklyShareQuantity"] / otc["totalWeeklyTradeCount"], + line=dict(color="#d81aea", width=2), + opacity=1, + showlegend=False, + ), + row=2, + col=1, + ) + else: + fig.add_trace( + go.Scatter( + name="OTC", + x=otc.index, + y=otc["totalWeeklyShareQuantity"] / otc["totalWeeklyTradeCount"], + line=dict(color="#d81aea", width=2), + opacity=1, + showlegend=False, + ), + row=2, + col=1, + ) + + fig.update_xaxes(showspikes=True, spikesnap="cursor", spikemode="across") + fig.update_yaxes(showspikes=True, spikethickness=2) + fig.update_layout( + margin=dict(l=20, r=0, t=10, b=20), + template=cfg.PLT_CANDLE_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"Dark Pools (ATS) vs OTC (Non-ATS) Data for {ticker}", + title_x=0.5, + yaxis3_title="Shares per Trade", + yaxis_title="Total Weekly Shares", + xaxis2_title="Weeks", + yaxis=dict( + fixedrange=False, + side="left", + nticks=20, + ), + yaxis2=dict( + fixedrange=False, + showgrid=False, + overlaying="y", + anchor="x", + layer="above traces", + ), + yaxis3=dict( + fixedrange=False, + nticks=10, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + showspikes=True, + nticks=20, + ), + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), + barmode="group", + bargap=0.5, + bargroupgap=0, + dragmode="pan", + hovermode="x unified", + spikedistance=1000, + hoverdistance=100, + ) + config = dict({"scrollZoom": True}) + imagefile = "dps_dpotc.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/dpotc_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/dpotc_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": title, + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/dark_pool_shorts/ftd.py b/bots/stocks/dark_pool_shorts/ftd.py new file mode 100644 index 00000000000..0cfe52d4a6b --- /dev/null +++ b/bots/stocks/dark_pool_shorts/ftd.py @@ -0,0 +1,122 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.stocks.dark_pool_shorts import sec_model + + +def ftd_command(ticker: str = "", start="", end=""): + """Fails-to-deliver data [SEC]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("dps-ftd %s %s %s", ticker, start, end) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + ticker = ticker.upper() + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + # Retrieve data + ftds_data = sec_model.get_fails_to_deliver(ticker, start, end, 0) + + # Debug user output + if cfg.DEBUG: + logger.debug(ftds_data.to_string()) + + stock = helpers.load(ticker, start) + stock_ftd = stock[stock.index > start] + stock_ftd = stock_ftd[stock_ftd.index < end] + + # Output data + fig = make_subplots(shared_xaxes=True, specs=[[{"secondary_y": True}]]) + + fig.add_trace( + go.Scatter( + name=ticker, + x=stock_ftd.index, + y=stock_ftd["Adj Close"], + line=dict(color="#fdc708", width=2), + opacity=1, + showlegend=False, + ), + secondary_y=False, + ) + fig.add_trace( + go.Bar( + name="FTDs", + x=ftds_data["SETTLEMENT DATE"], + y=ftds_data["QUANTITY (FAILS)"] / 1000, + opacity=1, + ), + secondary_y=True, + ) + # Set y-axes titles + fig.update_yaxes(title_text="Shares [K]", secondary_y=True) + fig.update_layout( + margin=dict(l=0, r=20, t=30, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker}", + title_x=0.5, + yaxis_title="Stock Price ($)", + yaxis=dict( + side="right", + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + fixedrange=False, + ), + xaxis2=dict( + rangeslider=dict(visible=False), + type="date", + fixedrange=False, + ), + dragmode="pan", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), + yaxis2=dict( + side="left", + position=0.15, + fixedrange=False, + ), + hovermode="x unified", + ) + config = dict({"scrollZoom": True}) + imagefile = "dps_ftd.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/ftds_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/ftds_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: [SEC] Failure-to-deliver {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/dark_pool_shorts/hsi.py b/bots/stocks/dark_pool_shorts/hsi.py new file mode 100644 index 00000000000..0ec8b736ed3 --- /dev/null +++ b/bots/stocks/dark_pool_shorts/hsi.py @@ -0,0 +1,80 @@ +import disnake + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.menus.menu import Menu +from gamestonk_terminal.stocks.dark_pool_shorts import shortinterest_model + + +def hsi_command(num: int = 10): + """Show top high short interest stocks of over 20% ratio [shortinterest.com]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("dps-hsi %s", num) + + # Check for argument + if num < 0: + raise Exception("Number has to be above 0") + + # Retrieve data + df = shortinterest_model.get_high_short_interest() + df = df.iloc[1:].head(n=num) + + # Debug user output + if cfg.DEBUG: + logger.debug(df.to_string()) + + # Output data + title = "Stocks: [highshortinterest.com] Top High Short Interest" + + future_column_name = df["Ticker"] + df = df.transpose() + df.columns = future_column_name + df.drop("Ticker") + embeds = [] + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + initial_str = "Overview" + i = 1 + for col_name in df.columns.values: + menu = f"\nPage {i}: {col_name}" + initial_str += f"\nPage {i}: {col_name}" + choices.append( + disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), + ) + i += 1 + + reports = [f"{initial_str}"] + embeds.append( + disnake.Embed( + title=title, + description=initial_str, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + for column in df.columns.values: + description = "```" + df[column].fillna("").to_string() + "```" + embeds.append( + disnake.Embed( + title=title, + description=description, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{description}") + + return { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } diff --git a/bots/stocks/dark_pool_shorts/pos.py b/bots/stocks/dark_pool_shorts/pos.py new file mode 100644 index 00000000000..9758394e92c --- /dev/null +++ b/bots/stocks/dark_pool_shorts/pos.py @@ -0,0 +1,113 @@ +import disnake + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.menus.menu import Menu +from gamestonk_terminal.stocks.dark_pool_shorts import stockgrid_model + + +def pos_command(sort="dpp_dollar", num: int = 10): + """Dark pool short position [Stockgrid]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("dps-pos %s %s", sort, num) + + # Check for argument + possible_sorts = ("sv", "sv_pct", "nsv", "nsv_dollar", "dpp", "dpp_dollar") + + if sort not in possible_sorts: + raise Exception(f"The possible sorts are: {', '.join(possible_sorts)}") + + if num < 0: + raise Exception("Number has to be above 0") + + # Retrieve data + df = stockgrid_model.get_dark_pool_short_positions(sort, False) + df = df.iloc[:num] + + # Debug user output + if cfg.DEBUG: + logger.debug(df.to_string()) + + # Output data + title = "Stocks: [Stockgrid] Dark Pool Short Position" + + dp_date = df["Date"].values[0] + df = df.drop(columns=["Date"]) + df["Net Short Volume $"] = df["Net Short Volume $"] / 100_000_000 + df["Short Volume"] = df["Short Volume"] / 1_000_000 + df["Net Short Volume"] = df["Net Short Volume"] / 1_000_000 + df["Short Volume %"] = df["Short Volume %"] * 100 + df["Dark Pools Position $"] = df["Dark Pools Position $"] / (1_000_000_000) + df["Dark Pools Position"] = df["Dark Pools Position"] / 1_000_000 + df.columns = [ + "Ticker", + "Short Vol. (1M)", + "Short Vol. %", + "Net Short Vol. (1M)", + "Net Short Vol. ($100M)", + "DP Position (1M)", + "DP Position ($1B)", + ] + future_column_name = df["Ticker"] + df = df.transpose() + df.columns = future_column_name + df.drop("Ticker") + embeds = [] + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + initial_str = "Overview" + i = 1 + for col_name in df.columns.values: + menu = f"\nPage {i}: {col_name}" + initial_str += f"\nPage {i}: {col_name}" + if i < 19: + choices.append( + disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), + ) + if i == 20: + choices.append( + disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), + ) + i += 1 + + reports = [f"{initial_str}"] + embeds.append( + disnake.Embed( + title=title, + description=initial_str, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + for column in df.columns.values: + description = ( + "```The following data corresponds to the date: " + + dp_date + + "\n\n" + + df[column].fillna("").to_string() + + "```", + ) + embeds.append( + disnake.Embed( + title=title, + description=description, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{description}") + + return { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } diff --git a/bots/stocks/dark_pool_shorts/psi.py b/bots/stocks/dark_pool_shorts/psi.py new file mode 100644 index 00000000000..69224a17b2a --- /dev/null +++ b/bots/stocks/dark_pool_shorts/psi.py @@ -0,0 +1,157 @@ +import plotly.graph_objects as go +import yfinance as yf +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.stocks.dark_pool_shorts import stockgrid_model + + +def psi_command(ticker: str = ""): + """Price vs short interest volume [Stockgrid]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("dps-psi %s", ticker) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + ticker = ticker.upper() + + stock = yf.download(ticker, progress=False) + if stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve data + df, prices = stockgrid_model.get_short_interest_volume(ticker) + + # Debug user output + if cfg.DEBUG: + logger.debug(df.to_string()) + + # Output data + fig = make_subplots( + rows=2, + cols=1, + shared_xaxes=True, + vertical_spacing=0.06, + row_width=[0.3, 0.6], + specs=[[{"secondary_y": True}], [{"secondary_y": False}]], + ) + fig.add_trace( + go.Scatter( + name=ticker, + x=df["date"].values, + y=prices[len(prices) - len(df) :], + line=dict(color="#fdc708", width=2), + opacity=1, + showlegend=False, + ), + row=1, + col=1, + secondary_y=False, + ) + fig.add_trace( + go.Bar( + x=df["date"], + y=df["total_volume"] / 1_000_000, + name="Total Volume", + yaxis="y2", + ), + row=1, + col=1, + secondary_y=True, + ) + fig.add_trace( + go.Bar( + x=df["date"], + y=df["short_volume"] / 1_000_000, + name="Short Volume", + yaxis="y2", + ), + row=1, + col=1, + secondary_y=True, + ) + fig.add_trace( + go.Scatter( + name="Short Vol. %", + x=df["date"].values, + y=100 * df["short_volume%"], + line=dict(width=2), + opacity=1, + showlegend=False, + ), + row=2, + col=1, + secondary_y=False, + ) + fig.update_traces(hovertemplate="%{y:.2f}") + fig.update_xaxes(showspikes=True, spikesnap="cursor", spikemode="across") + fig.update_yaxes(showspikes=True, spikethickness=2) + fig.update_layout( + margin=dict(l=10, r=0, t=40, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"Price vs Short Volume Interest for {ticker}", + title_x=0.45, + yaxis_title="Stock Price ($)", + yaxis2_title="FINRA Volume [M]", + yaxis3_title="Short Vol. %", + yaxis=dict( + side="right", + fixedrange=False, + titlefont=dict(color="#fdc708"), + tickfont=dict(color="#fdc708"), + nticks=20, + ), + yaxis2=dict( + side="left", + fixedrange=False, + anchor="x", + overlaying="y", + titlefont=dict(color="#d81aea"), + tickfont=dict(color="#d81aea"), + nticks=20, + ), + yaxis3=dict( + fixedrange=False, + titlefont=dict(color="#9467bd"), + tickfont=dict(color="#9467bd"), + nticks=20, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), + hovermode="x unified", + spikedistance=1000, + hoverdistance=100, + ) + config = dict({"scrollZoom": True}) + imagefile = "dps_psi.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/psi_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/psi_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: [Stockgrid] Price vs Short Interest Volume {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/dark_pool_shorts/shorted.py b/bots/stocks/dark_pool_shorts/shorted.py new file mode 100644 index 00000000000..c3b1efe8650 --- /dev/null +++ b/bots/stocks/dark_pool_shorts/shorted.py @@ -0,0 +1,81 @@ +import disnake + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.menus.menu import Menu +from gamestonk_terminal.stocks.dark_pool_shorts import yahoofinance_model + + +def shorted_command(num: int = 10): + """Show most shorted stocks [Yahoo Finance]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("dps-shorted %s", num) + + # Check for argument + if num < 0: + raise Exception("Number has to be above 0") + + # Retrieve data + df = yahoofinance_model.get_most_shorted().head(num) + + # Debug user output + if cfg.DEBUG: + logger.debug(df.to_string()) + + # Output data + title = "Stocks: [Yahoo Finance] Most Shorted" + df.dropna(how="all", axis=1, inplace=True) + df = df.replace(float("NaN"), "") + future_column_name = df["Symbol"] + df = df.transpose() + df.columns = future_column_name + df.drop("Symbol") + embeds = [] + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + initial_str = "Overview" + i = 1 + for col_name in df.columns.values: + menu = f"\nPage {i}: {col_name}" + initial_str += f"\nPage {i}: {col_name}" + if i < 19: + choices.append( + disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), + ) + if i == 20: + choices.append( + disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), + ) + i += 1 + + reports = [f"{initial_str}"] + embeds.append( + disnake.Embed( + title=title, + description=initial_str, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + for column in df.columns.values: + description = "```" + df[column].fillna("").to_string() + "```" + embeds.append( + disnake.Embed(description=description, colour=cfg.COLOR,).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{description}") + + return { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } diff --git a/bots/stocks/dark_pool_shorts/sidtc.py b/bots/stocks/dark_pool_shorts/sidtc.py new file mode 100644 index 00000000000..95dd4e7048f --- /dev/null +++ b/bots/stocks/dark_pool_shorts/sidtc.py @@ -0,0 +1,104 @@ +import disnake + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.menus.menu import Menu +from gamestonk_terminal.stocks.dark_pool_shorts import stockgrid_model + + +def sidtc_command(sort="float", num: int = 10): + """Short interest and days to cover [Stockgrid]""" + + # Debug + if cfg.DEBUG: + logger.debug("dps-sidtc %s %s", sort, num) + + # Check for argument + possible_sorts = ("float", "dtc", "si") + + if sort not in possible_sorts: + raise Exception(f"The possible sorts are: {', '.join(possible_sorts)}") + + if num < 0: + raise Exception("Number has to be above 0") + + # Retrieve data + df = stockgrid_model.get_short_interest_days_to_cover(sort) + df = df.iloc[:num] + + # Debug user output + if cfg.DEBUG: + logger.debug(df.to_string()) + + # Output data + title = "Stocks: [Stockgrid] Short Interest and Days to Cover" + dp_date = df["Date"].values[0] + df = df.drop(columns=["Date"]) + df["Short Interest"] = df["Short Interest"] / 1_000_000 + df.head() + df.columns = [ + "Ticker", + "Float Short %", + "Days to Cover", + "Short Interest (1M)", + ] + future_column_name = df["Ticker"] + df = df.transpose() + df.columns = future_column_name + df.drop("Ticker") + embeds = [] + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + initial_str = "Overview" + i = 1 + for col_name in df.columns.values: + menu = f"\nPage {i}: {col_name}" + initial_str += f"\nPage {i}: {col_name}" + if i < 19: + choices.append( + disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), + ) + if i == 20: + choices.append( + disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), + ) + i += 1 + + reports = [f"{initial_str}"] + embeds.append( + disnake.Embed( + title="Dark Pool Shorts", description=initial_str, colour=cfg.COLOR + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + + for column in df.columns.values: + description = ( + "```The following data corresponds to the date: " + + dp_date + + "\n\n" + + df[column].fillna("").to_string() + + "```", + ) + embeds.append( + disnake.Embed( + title=title, + description=description, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{description}") + + return { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } diff --git a/bots/stocks/dark_pool_shorts/spos.py b/bots/stocks/dark_pool_shorts/spos.py new file mode 100644 index 00000000000..ec1149cb320 --- /dev/null +++ b/bots/stocks/dark_pool_shorts/spos.py @@ -0,0 +1,124 @@ +import plotly.graph_objects as go +import yfinance as yf +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.stocks.dark_pool_shorts import stockgrid_model + + +def spos_command(ticker: str = ""): + """Net short vs position [Stockgrid]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("dps-spos %s", ticker) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + ticker = ticker.upper() + + stock = yf.download(ticker, progress=False) + if stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve data + df = stockgrid_model.get_net_short_position(ticker) + + # Debug user output + if cfg.DEBUG: + logger.debug(df.to_string()) + + # Output data + title = f"Stocks: [Stockgrid] Net Short vs Position {ticker}" + + fig = make_subplots(shared_xaxes=True, specs=[[{"secondary_y": True}]]) + + fig.add_trace( + go.Scatter( + name="Position ($)", + x=df["dates"].values, + y=df["dollar_dp_position"] * 1_000, + line=dict(color="#fdc708", width=2), + opacity=1, + showlegend=False, + ), + secondary_y=True, + ) + fig.add_trace( + go.Bar( + name="Net Short Vol. ($)", + x=df["dates"], + y=df["dollar_net_volume"], + opacity=1, + showlegend=False, + ), + secondary_y=False, + ) + # Set y-axes titles + fig.update_xaxes(dtick="M1", tickformat="%b %d\n%Y") + fig.update_yaxes(title_text="Position ($)", secondary_y=True) + fig.update_traces(hovertemplate="%{y:.2s}") + fig.update_layout( + margin=dict(l=0, r=10, t=40, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"Net Short Vol. vs Position for {ticker}", + title_x=0.5, + yaxis_title="Net Short Vol. ($)", + yaxis=dict( + side="left", + showgrid=False, + fixedrange=False, + layer="above traces", + titlefont=dict(color="#d81aea"), + tickfont=dict(color="#d81aea"), + nticks=10, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + fixedrange=False, + ), + xaxis2=dict( + rangeslider=dict(visible=False), + type="date", + fixedrange=False, + ), + dragmode="pan", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), + yaxis2=dict( + side="right", + position=0.15, + fixedrange=False, + titlefont=dict(color="#fdc708"), + tickfont=dict(color="#fdc708"), + nticks=10, + ), + hovermode="x unified", + ) + config = dict({"scrollZoom": True}) + imagefile = "dps_spos.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/spos_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/spos_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": title, + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/disc/ford.py b/bots/stocks/disc/ford.py new file mode 100644 index 00000000000..b8723833a78 --- /dev/null +++ b/bots/stocks/disc/ford.py @@ -0,0 +1,56 @@ +import df2img + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.stocks.discovery import fidelity_model + + +def ford_command(): + """Display Orders by Fidelity Customers. [Source: Fidelity]""" + + # Debug + if cfg.DEBUG: + logger.debug("disc-ford") + order_header, df_orders = fidelity_model.get_orders() + + df_orders = df_orders.head(n=30).iloc[:, :-1] + df_orders = df_orders.applymap(str) + + font_color = ( + ["white"] * 2 + + [ + [ + "#ad0000" if boolv else "#0d5700" + for boolv in df_orders["Price Change"].str.contains("-") + ] + ] + + [["white"] * 3] + ) + + df_orders.set_index("Symbol", inplace=True) + df_orders = df_orders.apply(lambda x: x.str.slice(0, 20)) + + dindex = len(df_orders.index) + fig = df2img.plot_dataframe( + df_orders, + fig_size=(1500, (40 + (40 * dindex))), + col_width=[1, 3, 2.2, 3, 2, 2], + tbl_cells=dict( + align=["left", "center", "left", "left", "center"], + font=dict(color=font_color), + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("disc-ford.png", fig) + + return { + "title": "Fidelity Customer Orders", + "imagefile": imagefile, + } diff --git a/discordbot/stocks/due_diligence/__init__.py b/bots/stocks/due_diligence/__init__.py similarity index 100% rename from discordbot/stocks/due_diligence/__init__.py rename to bots/stocks/due_diligence/__init__.py diff --git a/bots/stocks/due_diligence/analyst.py b/bots/stocks/due_diligence/analyst.py new file mode 100644 index 00000000000..377ee4bae37 --- /dev/null +++ b/bots/stocks/due_diligence/analyst.py @@ -0,0 +1,55 @@ +import df2img +import numpy as np + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.stocks.due_diligence import finviz_model + + +def analyst_command(ticker=""): + """Displays analyst recommendations [Finviz]""" + + # Debug + if cfg.DEBUG: + logger.debug("dd-analyst %s", ticker) + + # Check for argument + if not ticker: + raise Exception("Stock ticker is required") + + df = finviz_model.get_analyst_data(ticker) + df = df.replace(np.nan, 0) + df.index.names = ["Date"] + df = df.rename( + columns={ + "category": "Category", + "analyst": "Analyst", + "rating": "Rating", + "target": "Target", + "target_from": "Target From", + "target_to": "Target To", + } + ) + + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(1500, (40 + (40 * dindex))), + col_width=[5, 5, 8, 14, 5, 5, 5], + tbl_cells=dict( + height=35, + ), + font=dict( + family="Consolas", + size=20, + ), + template="plotly_dark", + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("dd-analyst.png", fig) + + return { + "title": f"Stocks: [Finviz] Analyst Recommendations {ticker.upper()}", + "imagefile": imagefile, + } diff --git a/bots/stocks/due_diligence/arktrades.py b/bots/stocks/due_diligence/arktrades.py new file mode 100644 index 00000000000..adce688cee4 --- /dev/null +++ b/bots/stocks/due_diligence/arktrades.py @@ -0,0 +1,117 @@ +import os + +import df2img +import disnake +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import gst_imgur, logger +from bots.helpers import save_image +from bots.menus.menu import Menu +from gamestonk_terminal.stocks.due_diligence import ark_model + + +def arktrades_command(ticker: str = "", num: int = 10): + """Displays trades made by ark [cathiesark.com]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("dd-arktrades %s", ticker) + + if ticker: + ark_holdings = ark_model.get_ark_trades_by_ticker(ticker) + + if ark_holdings.empty: + raise Exception( + "Issue getting data from cathiesark.com. Likely no trades found.\n" + ) + + ark_holdings["Total"] = ark_holdings["Total"] / 1_000_000 + ark_holdings.rename(columns={"direction": "B/S", "weight": "F %"}, inplace=True) + ark_holdings = ark_holdings.drop( + columns=["ticker", "everything.profile.companyName"] + ) + + ark_holdings.index = pd.Series(ark_holdings.index).apply( + lambda x: x.strftime("%Y-%m-%d") + ) + + df = ark_holdings.head(num) + df = df.fillna(0) + dindex = len(df.head(num).index) + formats = {"Close": "{:.2f}", "Total": "{:.2f}"} + for col, f in formats.items(): + df[col] = df[col].map(lambda x: f.format(x)) # pylint: disable=W0640 + + title = f"Stocks: [cathiesark.com] {ticker.upper()} Trades by Ark" + + embeds: list = [] + + i, i2, end = 0, 0, 20 + df_pg = [] + embeds_img = [] + dindex = len(df.index) + while i < dindex: + df_pg = df.iloc[i:end] + df_pg.append(df_pg) + fig = df2img.plot_dataframe( + df_pg, + fig_size=(900, (40 + (40 * 20))), + col_width=[5, 10, 4, 4, 3, 4, 5], + tbl_cells=dict( + height=35, + ), + font=dict( + family="Consolas", + size=20, + ), + template="plotly_dark", + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image(f"dd-arktrades{i}.png", fig) + + uploaded_image = gst_imgur.upload_image(imagefile, title="something") + image_link = uploaded_image.link + embeds_img.append( + f"{image_link}", + ) + embeds.append( + disnake.Embed( + title=title, + colour=cfg.COLOR, + ), + ) + i2 += 1 + i += 20 + end += 20 + os.remove(imagefile) + + # Author/Footer + for i in range(0, i2): + embeds[i].set_author( + name=cfg.AUTHOR_NAME, + url=cfg.AUTHOR_URL, + icon_url=cfg.AUTHOR_ICON_URL, + ) + embeds[i].set_footer( + text=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + i = 0 + for i in range(0, i2): + embeds[i].set_image(url=embeds_img[i]) + + i += 1 + embeds[0].set_footer(text=f"Page 1 of {len(embeds)}") + choices = [ + disnake.SelectOption(label="Home", value="0", emoji="🟢"), + ] + + return { + "view": Menu, + "title": title, + "embed": embeds, + "choices": choices, + "embeds_img": embeds_img, + } diff --git a/bots/stocks/due_diligence/customer.py b/bots/stocks/due_diligence/customer.py new file mode 100644 index 00000000000..a80b3116477 --- /dev/null +++ b/bots/stocks/due_diligence/customer.py @@ -0,0 +1,30 @@ +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from gamestonk_terminal.stocks.due_diligence import csimarket_model + + +def customer_command(ticker: str = ""): + """Displays customers of the company [CSIMarket]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("dd-customer %s", ticker) + + if not ticker: + raise Exception("A ticker is required") + + tickers = csimarket_model.get_customers(ticker) + + if not tickers: + raise Exception("Enter a valid ticker") + + # Debug user output + if cfg.DEBUG: + logger.debug(tickers) + + # Output data + + return { + "title": f"Stocks: [CSIMarket] {ticker} Customers", + "description": tickers, + } diff --git a/bots/stocks/due_diligence/est.py b/bots/stocks/due_diligence/est.py new file mode 100644 index 00000000000..871ab584b08 --- /dev/null +++ b/bots/stocks/due_diligence/est.py @@ -0,0 +1,151 @@ +import os + +import df2img +import disnake + +import bots.config_discordbot as cfg +from bots.config_discordbot import gst_imgur, logger +from bots.helpers import save_image +from bots.menus.menu import Menu +from gamestonk_terminal.stocks.due_diligence import business_insider_model + + +def est_command(ticker: str = ""): + """Displays earning estimates [Business Insider]""" + + # Debug + if cfg.DEBUG: + logger.debug("dd-est %s", ticker) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + ( + df_year_estimates, + df_quarter_earnings, + df_quarter_revenues, + ) = business_insider_model.get_estimates(ticker) + + if ( + df_quarter_revenues.empty + and df_year_estimates.empty + and df_quarter_earnings.empty + ): + raise Exception("Enter a valid ticker") + + # Debug user output + if cfg.DEBUG: + logger.debug(df_year_estimates.to_string()) + logger.debug(df_quarter_earnings.to_string()) + logger.debug(df_quarter_revenues.to_string()) + + dindex = len(df_year_estimates.index) + fig = df2img.plot_dataframe( + df_year_estimates, + fig_size=(1200, (40 + (60 * dindex))), + col_width=[12, 4, 4, 4, 4], + tbl_cells=dict( + height=35, + ), + font=dict( + family="Consolas", + size=20, + ), + template="plotly_dark", + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("estimates.png", fig) + + uploaded_image = gst_imgur.upload_image(imagefile, title="something") + link_estimates = uploaded_image.link + + os.remove(imagefile) + + fig = df2img.plot_dataframe( + df_quarter_earnings, + fig_size=(1200, (40 + (40 * 20))), + col_width=[5, 5, 4, 4, 5, 4], + tbl_cells=dict( + height=35, + ), + font=dict( + family="Consolas", + size=20, + ), + template="plotly_dark", + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("earnings.png", fig) + + uploaded_image = gst_imgur.upload_image(imagefile, title="something") + link_earnings = uploaded_image.link + os.remove(imagefile) + + fig = df2img.plot_dataframe( + df_quarter_revenues, + fig_size=(1200, (40 + (40 * 20))), + col_width=[5, 5, 4, 4, 5, 4], + tbl_cells=dict( + height=35, + ), + font=dict( + family="Consolas", + size=20, + ), + template="plotly_dark", + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("revenues.png", fig) + + uploaded_image = gst_imgur.upload_image(imagefile, title="something") + link_revenues = uploaded_image.link + os.remove(imagefile) + + embeds = [ + disnake.Embed( + title=f"**{ticker.upper()} Year Estimates**", + color=cfg.COLOR, + ), + disnake.Embed( + title=f"**{ticker.upper()} Quarter Earnings**", + colour=cfg.COLOR, + ), + disnake.Embed( + title=f"**{ticker.upper()} Quarter Revenues**", + colour=cfg.COLOR, + ), + ] + embeds[0].set_image(url=link_estimates) + embeds[1].set_image(url=link_earnings) + embeds[2].set_image(url=link_revenues) + titles = [ + f"**{ticker.upper()} Year Estimates**", + f"**{ticker.upper()} Quarter Earnings**", + f"**{ticker.upper()} Quarter Revenues**", + ] + embeds_img = [ + f"{link_estimates}", + f"{link_earnings}", + f"{link_revenues}", + ] + # Output data + choices = [ + disnake.SelectOption( + label=f"{ticker.upper()} Year Estimates", value="0", emoji="🟢" + ), + disnake.SelectOption( + label=f"{ticker.upper()} Quarter Earnings", value="1", emoji="🟢" + ), + disnake.SelectOption( + label=f"{ticker.upper()} Quarter Revenues", value="2", emoji="🟢" + ), + ] + + return { + "view": Menu, + "titles": titles, + "embed": embeds, + "choices": choices, + "embeds_img": embeds_img, + } diff --git a/bots/stocks/due_diligence/pt.py b/bots/stocks/due_diligence/pt.py new file mode 100644 index 00000000000..f6bd1868941 --- /dev/null +++ b/bots/stocks/due_diligence/pt.py @@ -0,0 +1,75 @@ +from datetime import datetime, timedelta + +from matplotlib import pyplot as plt + +import bots.config_discordbot as cfg +import bots.helpers +from bots.config_discordbot import logger +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal.helper_funcs import plot_autoscale +from gamestonk_terminal.stocks.due_diligence import business_insider_model + + +def pt_command(ticker: str = "", raw: bool = False, start=""): + """Displays price targets [Business Insider]""" + + # Debug + if cfg.DEBUG: + logger.debug("dd-pt %s", ticker) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if raw not in [True, False]: + raise Exception("raw argument has to be true or false") + + df_analyst_data = business_insider_model.get_price_target_from_analysts(ticker) + stock = bots.helpers.load(ticker, start) + title = f"Stocks: [Business Insider] Price Targets {ticker}" + if df_analyst_data.empty or stock.empty: + raise Exception("Enter valid ticker") + + # Output Data + + if raw: + df_analyst_data.sort_index(ascending=False) + report = "```" + df_analyst_data.to_string() + "```" + + output = { + "title": title, + "description": report, + } + else: + plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + if start: + df_analyst_data = df_analyst_data[start:] + + plt.plot(stock.index, stock["Adj Close"].values, lw=3) + + plt.plot(df_analyst_data.groupby(by=["Date"]).mean()) + + plt.scatter(df_analyst_data.index, df_analyst_data["Price Target"], c="r", s=40) + + plt.legend(["Closing Price", "Average Price Target", "Price Target"]) + + plt.title(f"{ticker.upper} (Time Series) and Price Target") + plt.xlim(stock.index[0], stock.index[-1]) + plt.xlabel("Time") + plt.ylabel("Share Price") + plt.grid(b=True, which="major", color="#666666", linestyle="-") + plt.savefig("ta_pt.png") + + imagefile = bots.helpers.image_border("ta_pt.png") + + output = { + "title": title, + "imagefile": imagefile, + } + + return output diff --git a/bots/stocks/due_diligence/sec.py b/bots/stocks/due_diligence/sec.py new file mode 100644 index 00000000000..c9958f5d1bb --- /dev/null +++ b/bots/stocks/due_diligence/sec.py @@ -0,0 +1,33 @@ +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from gamestonk_terminal.stocks.due_diligence import marketwatch_model + + +def sec_command(ticker=""): + """Displays sec filings [Market Watch]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("dd-sec %s", ticker) + + if ticker == "": + raise Exception("A ticker is required") + + df_financials = marketwatch_model.get_sec_filings(ticker) + + if df_financials.empty: + raise Exception("Enter a valid ticker") + + # Debug user output + if cfg.DEBUG: + logger.debug(df_financials.to_string()) + + df = df_financials + df.loc[:, "Link"] = "[Link Source](" + df.loc[:, "Link"].astype(str) + df.loc[:, "Link"] = df.loc[:, "Link"] + ")" + + # Output data + return { + "title": "Stocks: [Market Watch] SEC Filings", + "description": df.to_string(), + } diff --git a/bots/stocks/due_diligence/supplier.py b/bots/stocks/due_diligence/supplier.py new file mode 100644 index 00000000000..125d4c8407a --- /dev/null +++ b/bots/stocks/due_diligence/supplier.py @@ -0,0 +1,29 @@ +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from gamestonk_terminal.stocks.due_diligence import csimarket_model + + +def supplier_command(ticker=""): + """Displays suppliers of the company [CSIMarket]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("dd-supplier %s", ticker) + + if not ticker: + raise Exception("A ticker is required") + + tickers = csimarket_model.get_suppliers(ticker) + + if not tickers: + raise Exception("Enter a valid ticker") + + # Debug user output + if cfg.DEBUG: + logger.debug(tickers) + + # Output data + return { + "title": "Stocks: [CSIMarket] Company Suppliers", + "description": tickers, + } diff --git a/discordbot/stocks/government/__init__.py b/bots/stocks/government/__init__.py similarity index 100% rename from discordbot/stocks/government/__init__.py rename to bots/stocks/government/__init__.py diff --git a/bots/stocks/government/contracts.py b/bots/stocks/government/contracts.py new file mode 100644 index 00000000000..2aea0894a9f --- /dev/null +++ b/bots/stocks/government/contracts.py @@ -0,0 +1,52 @@ +import pandas as pd +from matplotlib import pyplot as plt + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import image_border +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal.helper_funcs import plot_autoscale +from gamestonk_terminal.stocks.government import quiverquant_model + + +def contracts_command( + ticker: str = "", past_transaction_days: int = 10, raw: bool = False +): + """Displays contracts associated with tickers [quiverquant.com]""" + # Debug user input + if cfg.DEBUG: + logger.debug("gov-contracts %s %s %s", ticker, past_transaction_days, raw) + + if ticker == "": + raise Exception("A ticker is required") + + # Retrieve Data + df_contracts = quiverquant_model.get_government_trading("contracts", ticker) + + if df_contracts.empty: + raise Exception("No government contracts found") + + # Output Data + df_contracts["Date"] = pd.to_datetime(df_contracts["Date"]).dt.date + + df_contracts = df_contracts[ + df_contracts["Date"].isin(df_contracts["Date"].unique()[:past_transaction_days]) + ] + + df_contracts.drop_duplicates(inplace=True) + + fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + + df_contracts.groupby("Date").sum().div(1000).plot(kind="bar", rot=0, ax=ax) + ax.set_ylabel("Amount ($1k)") + ax.set_title(f"Sum of latest government contracts to {ticker}") + fig.tight_layout() + + plt.savefig("gov_contracts.png") + imagefile = "gov_contracts.png" + + imagefile = image_border(imagefile) + return { + "title": f"Stocks: [quiverquant.com] Contracts by {ticker}", + "imagefile": imagefile, + } diff --git a/bots/stocks/government/gtrades.py b/bots/stocks/government/gtrades.py new file mode 100644 index 00000000000..7d9bea4da11 --- /dev/null +++ b/bots/stocks/government/gtrades.py @@ -0,0 +1,115 @@ +from datetime import datetime, timedelta + +import matplotlib.dates as mdates +import pandas as pd +from matplotlib import pyplot as plt + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import image_border +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal.helper_funcs import plot_autoscale +from gamestonk_terminal.stocks.government import quiverquant_model + + +def gtrades_command( + ticker: str = "", + gov_type="", + past_transactions_months: int = 10, + raw: bool = False, +): + """Displays government trades [quiverquant.com]""" + # Debug user input + if cfg.DEBUG: + logger.debug( + "gov-gtrades %s %s %s %s", + ticker, + gov_type, + past_transactions_months, + raw, + ) + + if ticker == "": + raise Exception("A ticker is required") + + possible_args = ["congress", "senate", "house"] + if gov_type == "": + gov_type = "congress" + elif gov_type not in possible_args: + raise Exception( + "Enter a valid government argument, options are: congress, senate and house" + ) + + # Retrieve Data + df_gov = quiverquant_model.get_government_trading(gov_type, ticker) + + if df_gov.empty: + raise Exception(f"No {gov_type} trading data found") + + # Output Data + df_gov = df_gov.sort_values("TransactionDate", ascending=False) + + start_date = datetime.now() - timedelta(days=past_transactions_months * 30) + + df_gov["TransactionDate"] = pd.to_datetime(df_gov["TransactionDate"]) + + df_gov = df_gov[df_gov["TransactionDate"] > start_date] + + if df_gov.empty: + logger.debug("No recent %s trading data found", gov_type) + raise Exception(f"No recent {gov_type} trading data found") + + df_gov["min"] = df_gov["Range"].apply( + lambda x: x.split("-")[0].strip("$").replace(",", "").strip() + ) + df_gov["max"] = df_gov["Range"].apply( + lambda x: x.split("-")[1].replace(",", "").strip().strip("$") + if "-" in x + else x.strip("$").replace(",", "").split("\n")[0] + ) + + df_gov["lower"] = df_gov[["min", "max", "Transaction"]].apply( + lambda x: int(float(x["min"])) + if x["Transaction"] == "Purchase" + else -int(float(x["max"])), + axis=1, + ) + df_gov["upper"] = df_gov[["min", "max", "Transaction"]].apply( + lambda x: int(float(x["max"])) + if x["Transaction"] == "Purchase" + else -1 * int(float(x["min"])), + axis=1, + ) + + df_gov = df_gov.sort_values("TransactionDate", ascending=True) + + fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + + ax.fill_between( + df_gov["TransactionDate"].unique(), + df_gov.groupby("TransactionDate")["lower"].sum().values / 1000, + df_gov.groupby("TransactionDate")["upper"].sum().values / 1000, + ) + + ax.set_xlim( + [ + df_gov["TransactionDate"].values[0], + df_gov["TransactionDate"].values[-1], + ] + ) + ax.grid() + ax.set_title(f"{gov_type.capitalize()} trading on {ticker}") + ax.set_xlabel("Date") + ax.set_ylabel("Amount ($1k)") + plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%Y/%m/%d")) + plt.gcf().autofmt_xdate() + fig.tight_layout() + + plt.savefig("gov_gtrades.png") + imagefile = "gov_gtrades.png" + + imagefile = image_border(imagefile) + return { + "title": "Stocks: [quiverquant.com] Government Trades", + "imagefile": imagefile, + } diff --git a/bots/stocks/government/histcont.py b/bots/stocks/government/histcont.py new file mode 100644 index 00000000000..b2f73d8eada --- /dev/null +++ b/bots/stocks/government/histcont.py @@ -0,0 +1,60 @@ +import numpy as np +from matplotlib import pyplot as plt + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import image_border +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal.helper_funcs import plot_autoscale +from gamestonk_terminal.stocks.government import quiverquant_model + + +def histcont_command(ticker=""): + """Displays historical quarterly-contracts [quiverquant.com]""" + # Debug user input + if cfg.DEBUG: + logger.debug("gov-histcont %s", ticker) + + if ticker == "": + raise Exception("A ticker is required") + + # Retrieve Data + df_contracts = quiverquant_model.get_government_trading( + "quarter-contracts", ticker=ticker + ) + + if df_contracts.empty: + logger.debug("No quarterly government contracts found") + raise Exception("No quarterly government contracts found") + + # Output Data + amounts = df_contracts.sort_values(by=["Year", "Qtr"])["Amount"].values + + qtr = df_contracts.sort_values(by=["Year", "Qtr"])["Qtr"].values + year = df_contracts.sort_values(by=["Year", "Qtr"])["Year"].values + + quarter_ticks = [ + f"{quarter[0]}" if quarter[1] == 1 else "" for quarter in zip(year, qtr) + ] + + fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + + ax.plot(np.arange(0, len(amounts)), amounts / 1000, "-*", lw=2, ms=15) + + ax.set_xlim([-0.5, len(amounts) - 0.5]) + ax.set_xticks(np.arange(0, len(amounts))) + ax.set_xticklabels(quarter_ticks) + ax.grid() + ax.set_title(f"Historical Quarterly Government Contracts for {ticker.upper()}") + ax.set_xlabel("Date") + ax.set_ylabel("Amount ($1k)") + fig.tight_layout() + + plt.savefig("gov_histcont.png") + imagefile = "gov_histcont.png" + + imagefile = image_border(imagefile) + return { + "title": "Stocks: Historical Quarterly Government Contract ", + "imagefile": imagefile, + } diff --git a/bots/stocks/government/lastcontracts.py b/bots/stocks/government/lastcontracts.py new file mode 100644 index 00000000000..13f31a39611 --- /dev/null +++ b/bots/stocks/government/lastcontracts.py @@ -0,0 +1,79 @@ +import disnake +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.menus.menu import Menu +from gamestonk_terminal.stocks.government import quiverquant_model + + +def lastcontracts_command(past_transactions_days: int = 2, num: int = 20): + """Displays last government contracts [quiverquant.com]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("gov lastcontracts %s %s", past_transactions_days, num) + + df_contracts = quiverquant_model.get_government_trading("contracts") + + if df_contracts.empty: + logger.debug("No government contracts found") + raise Exception("No government contracts found") + + df_contracts.sort_values("Date", ascending=False) + + df_contracts["Date"] = pd.to_datetime(df_contracts["Date"]) + df_contracts["Date"] = df_contracts["Date"].dt.date + + df_contracts.drop_duplicates(inplace=True) + df_contracts = df_contracts[ + df_contracts["Date"].isin( + df_contracts["Date"].unique()[:past_transactions_days] + ) + ] + + df_contracts = df_contracts[["Date", "Ticker", "Amount", "Agency"]][:num] + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + title = "Stocks: [quiverquant.com] Top buy government trading" + initial_str = "Overview" + i = 1 + for col_name in df_contracts["Ticker"].values: + menu = f"\nPage {i}: {col_name}" + initial_str += f"\nPage {i}: {col_name}" + choices.append( + disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), + ) + i += 1 + + embeds = [] + df_contracts = df_contracts.T + reports = [f"{initial_str}"] + embeds.append( + disnake.Embed( + title=title, + description=initial_str, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + for column in df_contracts.columns.values: + description = "```" + df_contracts[column].fillna("").to_string() + "```" + embeds.append( + disnake.Embed(description=description, colour=cfg.COLOR,).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{description}") + + return { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } diff --git a/bots/stocks/government/lasttrades.py b/bots/stocks/government/lasttrades.py new file mode 100644 index 00000000000..21d34fc8b3f --- /dev/null +++ b/bots/stocks/government/lasttrades.py @@ -0,0 +1,181 @@ +import disnake + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.menus.menu import Menu +from gamestonk_terminal.stocks.government import quiverquant_model + + +def lasttrades_command(gov_type="", past_days: int = 5, representative=""): + """Displays trades made by the congress/senate/house [quiverquant.com]""" + + # Debug user input + if cfg.DEBUG: + logger.debug( + "gov-lasttrades %s %s %s", + gov_type, + past_days, + representative, + ) + + possible_args = ["congress", "senate", "house"] + if gov_type == "": + gov_type = "congress" + elif gov_type not in possible_args: + raise Exception( + "Enter a valid government argument, options are: congress, senate and house" + ) + + # Retrieve Data + df_gov = quiverquant_model.get_government_trading(gov_type) + + # Output Data + if df_gov.empty: + raise Exception(f"No {gov_type} trading data found") + df_gov = df_gov.sort_values("TransactionDate", ascending=False) + + df_gov = df_gov[ + df_gov["TransactionDate"].isin(df_gov["TransactionDate"].unique()[:past_days]) + ] + + if gov_type == "congress": + df_gov = df_gov[ + [ + "TransactionDate", + "Ticker", + "Representative", + "Transaction", + "Range", + "House", + "ReportDate", + ] + ].rename( + columns={ + "TransactionDate": "Transaction Date", + "ReportDate": "Report Date", + } + ) + else: + df_gov = df_gov[ + [ + "TransactionDate", + "Ticker", + "Representative", + "Transaction", + "Range", + ] + ].rename(columns={"TransactionDate": "Transaction Date"}) + + if representative: + df_gov_rep = df_gov[ + df_gov["Representative"].str.split().str[0] == representative + ] + + if df_gov_rep.empty: + raise Exception( + f"No representative {representative} found in the past {past_days}" + f" days. The following are available: " + f"{', '.join(df_gov['Representative'].str.split().str[0].unique())}" + ) + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + title = f"Stocks: [quiverquant.com] Trades by {representative}" + initial_str = "Overview" + i = 1 + for col_name in df_gov_rep["Ticker"].values: + menu = f"\nPage {i}: {col_name}" + initial_str += f"\nPage {i}: {col_name}" + if i < 19: + choices.append( + disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), + ) + if i == 20: + choices.append( + disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), + ) + i += 1 + + embeds = [] + df_gov_rep = df_gov_rep.T + embeds.append( + disnake.Embed( + title=title, + description=initial_str, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports = [f"{initial_str}"] + for column in df_gov_rep.columns.values: + description = "```" + df_gov_rep[column].fillna("").to_string() + "```" + embeds.append( + disnake.Embed(description=description, colour=cfg.COLOR,).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{description}") + + output = { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } + + else: + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + title = f"Stocks: [quiverquant.com] Trades for {gov_type.upper()}" + initial_str = "Overview" + i = 1 + for col_name in df_gov["Ticker"].values: + menu = f"\nPage {i}: {col_name}" + initial_str += f"\nPage {i}: {col_name}" + if i < 19: + choices.append( + disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), + ) + if i == 20: + choices.append( + disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), + ) + i += 1 + + embeds = [] + df_gov = df_gov.T + embeds.append( + disnake.Embed( + title=title, + description=initial_str, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports = [f"{initial_str}"] + for column in df_gov.columns.values: + description = "```" + df_gov[column].fillna("").to_string() + "```" + embeds.append( + disnake.Embed(description=description, colour=cfg.COLOR,).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{description}") + + output = { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } + + return output diff --git a/bots/stocks/government/lobbying.py b/bots/stocks/government/lobbying.py new file mode 100644 index 00000000000..c9482688036 --- /dev/null +++ b/bots/stocks/government/lobbying.py @@ -0,0 +1,92 @@ +from typing import Any, Dict +import disnake + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.menus.menu import Menu +from gamestonk_terminal.stocks.government import quiverquant_model + + +def lobbying_command(ticker="", num: int = 10): + """Displays lobbying details [quiverquant.com]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("gov-lobbying %s", ticker) + + if ticker == "": + raise Exception("A ticker is required") + + # Retrieve Data + df_lobbying = quiverquant_model.get_government_trading( + "corporate-lobbying", ticker=ticker + ) + + if df_lobbying.empty: + logger.debug("No corporate lobbying found") + raise Exception("No corporate lobbying found") + + # Output Data + report = "" + title = (f"Stocks: [quiverquant.com] {ticker.upper()} Lobbying Details",) + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + for _, row in ( + df_lobbying.sort_values(by=["Date"], ascending=False).head(num).iterrows() + ): + amount = ( + "$" + str(int(float(row["Amount"]))) if row["Amount"] is not None else "N/A" + ) + report += f"{row['Date']}: {row['Client']} {amount}" + if row["Amount"] is not None: + report += "\t" + row["Specific_Issue"].replace("\n", " ").replace("\r", "") + report += "\n" + + if len(report) <= 4000: + description = f"```{report}```" + embed = disnake.Embed( + title=title, + description=description, + colour=cfg.COLOR, + ) + embed.set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + output: Dict[str, Any] = { + "title": title, + "description": description, + } + else: + i = 0 + str_start = 0 + str_end = 4000 + embeds = [] + reports = [] + while i <= len(report) / 4000: + descript = f"```{report[str_start:str_end]}```" + embeds.append( + disnake.Embed( + title=title, + description=descript, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{descript}") + str_end = str_start + str_start += 4000 + i += 1 + + output = { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } + + return output diff --git a/bots/stocks/government/qtrcontracts.py b/bots/stocks/government/qtrcontracts.py new file mode 100644 index 00000000000..5d03603e83d --- /dev/null +++ b/bots/stocks/government/qtrcontracts.py @@ -0,0 +1,101 @@ +import numpy as np +from matplotlib import pyplot as plt + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import image_border +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal.helper_funcs import plot_autoscale +from gamestonk_terminal.stocks.government import quiverquant_model + + +def qtrcontracts_command(num: int = 20, analysis=""): + """Displays a look at government contracts [quiverquant.com]""" + # Debug user input + if cfg.DEBUG: + logger.debug("gov-qtrcontracts %s %s", num, analysis) + + possible_args = ["total", "upmom", "downmom"] + if analysis == "": + analysis = "total" + elif analysis not in possible_args: + raise Exception( + "Enter a valid analysis argument, options are: total, upmom and downmom" + ) + + # Retrieve Data + df_contracts = quiverquant_model.get_government_trading("quarter-contracts") + + if df_contracts.empty: + raise Exception("No quarterly government contracts found") + + tickers = quiverquant_model.analyze_qtr_contracts(analysis, num) + + # Output Data + if analysis in {"upmom", "downmom"}: + description = tickers.to_string() + fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + max_amount = 0 + quarter_ticks = [] + for symbol in tickers: + amounts = ( + df_contracts[df_contracts["Ticker"] == symbol] + .sort_values(by=["Year", "Qtr"])["Amount"] + .values + ) + + qtr = ( + df_contracts[df_contracts["Ticker"] == symbol] + .sort_values(by=["Year", "Qtr"])["Qtr"] + .values + ) + year = ( + df_contracts[df_contracts["Ticker"] == symbol] + .sort_values(by=["Year", "Qtr"])["Year"] + .values + ) + + ax.plot(np.arange(0, len(amounts)), amounts / 1_000_000, "-*", lw=2, ms=15) + + if len(amounts) > max_amount: + max_amount = len(amounts) + quarter_ticks = [ + f"{quarter[0]} - Q{quarter[1]} " for quarter in zip(year, qtr) + ] + + ax.set_xlim([-0.5, max_amount - 0.5]) + ax.set_xticks(np.arange(0, max_amount)) + ax.set_xticklabels(quarter_ticks) + ax.grid() + ax.legend(tickers) + titles = { + "upmom": "Highest increasing quarterly Government Contracts", + "downmom": "Highest decreasing quarterly Government Contracts", + } + ax.set_title(titles[analysis]) + ax.set_xlabel("Date") + ax.set_ylabel("Amount ($1M)") + fig.tight_layout() + + plt.savefig("gov_qtrcontracts.png") + imagefile = "gov_qtrcontracts.png" + + imagefile = image_border(imagefile) + output = { + "title": "Stocks: [quiverquant.com] Government Contracts", + "imagefile": imagefile, + "description": description, + } + + elif analysis == "total": + + tickers.index = [ind + " " * (7 - len(ind)) for ind in tickers.index] + tickers[:] = [str(round(val[0] / 1e9, 2)) for val in tickers.values] + tickers.columns = ["Amount [M]"] + + output = { + "title": "Stocks: [quiverquant.com] Government Contracts", + "description": tickers.to_string(), + } + + return output diff --git a/bots/stocks/government/topbuys.py b/bots/stocks/government/topbuys.py new file mode 100644 index 00000000000..71e38c55e8b --- /dev/null +++ b/bots/stocks/government/topbuys.py @@ -0,0 +1,111 @@ +from datetime import datetime, timedelta + +import pandas as pd +from matplotlib import pyplot as plt + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import image_border +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal.helper_funcs import plot_autoscale +from gamestonk_terminal.stocks.government import quiverquant_model + + +def topbuys_command( + gov_type="", + past_transactions_months: int = 5, + num: int = 20, + raw: bool = False, +): + """Displays most purchased stocks by the congress/senate/house [quiverquant.com]""" + # Debug user input + if cfg.DEBUG: + logger.debug( + "gov-topbuys %s %s %s %s", + gov_type, + past_transactions_months, + num, + raw, + ) + + possible_args = ["congress", "senate", "house"] + if gov_type == "": + gov_type = "congress" + elif gov_type not in possible_args: + raise Exception( + "Enter a valid government argument, options are: congress, senate and house" + ) + + # Retrieve Data + df_gov = quiverquant_model.get_government_trading(gov_type) + + if df_gov.empty: + logger.debug("No %s trading data found", gov_type) + raise Exception("No trading data found") + + df_gov = df_gov.sort_values("TransactionDate", ascending=False) + start_date = datetime.now() - timedelta(days=past_transactions_months * 30) + + df_gov["TransactionDate"] = pd.to_datetime(df_gov["TransactionDate"]) + + df_gov = df_gov[df_gov["TransactionDate"] > start_date].dropna() + # Catch bug where error shown for purchase of >5,000,000 + df_gov["Range"] = df_gov["Range"].apply( + lambda x: "$5,000,001-$5,000,001" if x == ">$5,000,000" else x + ) + df_gov["min"] = df_gov["Range"].apply( + lambda x: x.split("-")[0].strip("$").replace(",", "").strip() + ) + df_gov["max"] = df_gov["Range"].apply( + lambda x: x.split("-")[1].replace(",", "").strip().strip("$") + if "-" in x + else x.strip("$").replace(",", "") + ) + df_gov["lower"] = df_gov[["min", "max", "Transaction"]].apply( + lambda x: float(x["min"]) + if x["Transaction"] == "Purchase" + else -float(x["max"]), + axis=1, + ) + df_gov["upper"] = df_gov[["min", "max", "Transaction"]].apply( + lambda x: float(x["max"]) + if x["Transaction"] == "Purchase" + else -float(x["min"]), + axis=1, + ) + description = None + + df_gov = df_gov.sort_values("TransactionDate", ascending=True) + if raw: + df = pd.DataFrame( + df_gov.groupby("Ticker")["upper"] + .sum() + .div(1000) + .sort_values(ascending=False) + .head(n=num) + ) + description = "```" + df.to_string() + "```" + + fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + + df_gov.groupby("Ticker")["upper"].sum().div(1000).sort_values(ascending=False).head( + n=num + ).plot(kind="bar", rot=0, ax=ax) + + ax.set_ylabel("Amount [1k $]") + ax.set_title( + f"Top {num} purchased stocks over last {past_transactions_months} " + f"months (upper bound) for {gov_type.upper()}" + ) + plt.gcf().axes[0].yaxis.get_major_formatter().set_scientific(False) + fig.tight_layout() + + plt.savefig("gov_topbuys.png") + imagefile = "gov_topbuys.png" + + imagefile = image_border(imagefile) + return { + "title": f"Stocks: [quiverquant.com] Top purchases for {gov_type.upper()}", + "imagefile": imagefile, + "description": description, + } diff --git a/bots/stocks/government/toplobbying.py b/bots/stocks/government/toplobbying.py new file mode 100644 index 00000000000..6e276a3f4eb --- /dev/null +++ b/bots/stocks/government/toplobbying.py @@ -0,0 +1,43 @@ +import pandas as pd +from matplotlib import pyplot as plt + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import image_border +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal.helper_funcs import plot_autoscale +from gamestonk_terminal.stocks.government import quiverquant_model + + +def toplobbying_command(num: int = 10, raw: bool = False): + """Displays top lobbying firms [quiverquant.com]""" + # Debug user input + if cfg.DEBUG: + logger.debug("gov-toplobbying %s %s", num, raw) + + # Retrieve Data + df_lobbying = quiverquant_model.get_government_trading("corporate-lobbying") + + if df_lobbying.empty: + raise Exception("No corporate lobbying found") + + df_lobbying["Amount"] = df_lobbying.Amount.astype(float).fillna(0) / 100_000 + + lobbying_by_ticker = pd.DataFrame( + df_lobbying.groupby("Ticker")["Amount"].agg("sum") + ).sort_values(by="Amount", ascending=False) + fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + lobbying_by_ticker.head(num).plot(kind="bar", ax=ax) + ax.set_xlabel("Ticker") + ax.set_ylabel("Total Amount ($100k)") + ax.set_title(f"Corporate Lobbying Spent since {df_lobbying['Date'].min()}") + fig.tight_layout() + + plt.savefig("ta_toplobbying.png") + imagefile = "ta_toplobbying.png" + + imagefile = image_border(imagefile) + return { + "title": "Stocks: [quiverquant.com] Top Lobbying Firms", + "imagefile": imagefile, + } diff --git a/bots/stocks/government/topsells.py b/bots/stocks/government/topsells.py new file mode 100644 index 00000000000..3a19c18da01 --- /dev/null +++ b/bots/stocks/government/topsells.py @@ -0,0 +1,122 @@ +from datetime import datetime, timedelta + +import pandas as pd +from matplotlib import pyplot as plt + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import image_border +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal.helper_funcs import plot_autoscale +from gamestonk_terminal.stocks.government import quiverquant_model + + +def topsells_command( + gov_type="", + past_transactions_months: int = 5, + num: int = 10, + raw: bool = False, +): + """Displays most sold stocks by the congress/senate/house [quiverquant.com]""" + # Debug user input + if cfg.DEBUG: + logger.debug( + "gov-topsells %s %s %s %s", + gov_type, + past_transactions_months, + num, + raw, + ) + + possible_args = ["congress", "senate", "house"] + if gov_type == "": + gov_type = "congress" + elif gov_type not in possible_args: + raise Exception( + "Enter a valid government argument, options are: congress, senate and house" + ) + + # Retrieve Data + df_gov = quiverquant_model.get_government_trading(gov_type) + + # Output Data + if df_gov.empty: + raise Exception(f"No {gov_type} trading data found\n") + + df_gov = df_gov.sort_values("TransactionDate", ascending=False) + + start_date = datetime.now() - timedelta(days=past_transactions_months * 30) + + df_gov["TransactionDate"] = pd.to_datetime(df_gov["TransactionDate"]) + + df_gov = df_gov[df_gov["TransactionDate"] > start_date].dropna() + + df_gov["Range"] = df_gov["Range"].apply( + lambda x: "$5,000,001-$5,000,001" if x == ">$5,000,000" else x + ) + + df_gov["min"] = df_gov["Range"].apply( + lambda x: x.split("-")[0] + .strip("$") + .replace(",", "") + .strip() + .replace(">$", "") + .strip() + ) + df_gov["max"] = df_gov["Range"].apply( + lambda x: x.split("-")[1] + .replace(",", "") + .strip() + .strip("$") + .replace(">$", "") + .strip() + if "-" in x + else x.strip("$").replace(",", "").replace(">$", "").strip() + ) + + df_gov["lower"] = df_gov[["min", "max", "Transaction"]].apply( + lambda x: float(x["min"]) + if x["Transaction"] == "Purchase" + else -float(x["max"]), + axis=1, + ) + df_gov["upper"] = df_gov[["min", "max", "Transaction"]].apply( + lambda x: float(x["max"]) + if x["Transaction"] == "Purchase" + else -float(x["min"]), + axis=1, + ) + description = None + df_gov = df_gov.sort_values("TransactionDate", ascending=True) + if raw: + df = pd.DataFrame( + df_gov.groupby("Ticker")["upper"] + .sum() + .div(1000) + .sort_values(ascending=True) + .abs() + .head(n=num) + ) + description = "```" + df.to_string() + "```" + fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + + df_gov.groupby("Ticker")["upper"].sum().div(1000).sort_values().abs().head( + n=num + ).plot(kind="bar", rot=0, ax=ax) + ax.set_ylabel("Amount ($1k)") + ax.set_title( + f"{num} most sold stocks over last {past_transactions_months} months" + f" (upper bound) for {gov_type}" + ) + plt.gcf().axes[0].yaxis.get_major_formatter().set_scientific(False) + fig.tight_layout() + + plt.savefig("gov_topsells.png") + imagefile = "gov_topsells.png" + + imagefile = image_border(imagefile) + return { + "title": f"Stocks: [quiverquant.com] Top sells for {gov_type.upper()}", + "imagefile": imagefile, + "description": description, + } diff --git a/bots/stocks/insider/lins.py b/bots/stocks/insider/lins.py new file mode 100644 index 00000000000..f4433e338b1 --- /dev/null +++ b/bots/stocks/insider/lins.py @@ -0,0 +1,119 @@ +import os + +import df2img +import disnake +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import gst_imgur, logger +from bots.helpers import save_image +from bots.menus.menu import Menu +from gamestonk_terminal.stocks.insider import finviz_model + + +def lins_command(ticker: str = "", num: int = 10): + """Display insider activity for a given stock ticker. [Source: Finviz] + + Parameters + ---------- + ticker : Stock Ticker + num : Number of latest insider activity to display + """ + + # Debug + if cfg.DEBUG: + logger.debug("disc-lins %s", num) + + d_finviz_insider = finviz_model.get_last_insider_activity(ticker) + + df = pd.DataFrame.from_dict(d_finviz_insider) + df.set_index("Date", inplace=True) + + df = df[ + [ + "Relationship", + "Transaction", + "#Shares", + "Cost", + "Value ($)", + "#Shares Total", + "Insider Trading", + "SEC Form 4", + ] + ] + + df = df.head(num) + df = df.replace(to_replace="Option Exercise", value="Opt Ex.", regex=True) + + title = f"Insider Trading for {ticker.upper()}" + + embeds: list = [] + + i, i2, end = 0, 0, 20 + df_pg = [] + embeds_img = [] + dindex = len(df.index) + while i < dindex: + df_pg = df.iloc[i:end] + df_pg.append(df_pg) + fig = df2img.plot_dataframe( + df_pg, + fig_size=(1600, (40 + (40 * 20))), + col_width=[5, 14, 4, 4, 3, 4, 5, 8, 7], + tbl_cells=dict( + height=35, + ), + font=dict( + family="Consolas", + size=20, + ), + template="plotly_dark", + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image(f"disc-insider{i}.png", fig) + + uploaded_image = gst_imgur.upload_image(imagefile, title="something") + image_link = uploaded_image.link + embeds_img.append( + f"{image_link}", + ) + embeds.append( + disnake.Embed( + title=title, + colour=cfg.COLOR, + ), + ) + i2 += 1 + i += 20 + end += 20 + os.remove(imagefile) + + # Author/Footer + for i in range(0, i2): + embeds[i].set_author( + name=cfg.AUTHOR_NAME, + url=cfg.AUTHOR_URL, + icon_url=cfg.AUTHOR_ICON_URL, + ) + embeds[i].set_footer( + text=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + i = 0 + for i in range(0, i2): + embeds[i].set_image(url=embeds_img[i]) + + i += 1 + embeds[0].set_footer(text=f"Page 1 of {len(embeds)}") + choices = [ + disnake.SelectOption(label="Home", value="0", emoji="🟢"), + ] + + return { + "view": Menu, + "title": title, + "embed": embeds, + "choices": choices, + "embeds_img": embeds_img, + } diff --git a/discordbot/stocks/options/__init__.py b/bots/stocks/options/__init__.py similarity index 100% rename from discordbot/stocks/options/__init__.py rename to bots/stocks/options/__init__.py diff --git a/bots/stocks/options/cc_hist.py b/bots/stocks/options/cc_hist.py new file mode 100644 index 00000000000..c902e404bd2 --- /dev/null +++ b/bots/stocks/options/cc_hist.py @@ -0,0 +1,122 @@ +import numpy as np +import plotly.graph_objects as go +import yfinance as yf +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.helpers import image_border + + +def cc_hist_command( + ticker: str = None, expiry: str = "", strike: float = 10, opt_type: str = "" +): + """Plot historical option prices + + Parameters + ---------- + ticker: str + Stock ticker + expiry: str + expiration date + strike: float + Option strike price + put: bool + Calls for call + Puts for put + """ + + # Debug + if cfg.DEBUG: + print(f"opt-hist {ticker} {strike} {opt_type} {expiry}") + + # Check for argument + if ticker is None: + raise Exception("Stock ticker is required") + yf_ticker = yf.Ticker(ticker) + dates = list(yf_ticker.options) + + if not dates: + raise Exception("Stock ticker is invalid") + + options = yf.Ticker(ticker).option_chain(expiry) + + if opt_type == "Calls": + options = options.calls + if opt_type == "Puts": + options = options.puts + + chain_id = options.loc[options.strike == strike, "contractSymbol"].values[0] + df_hist = yf.download(chain_id) + df_hist.index.name = "date" + + plt_title = [ + f"\n{ticker.upper()} {strike} {opt_type} expiring {expiry} Historical", + "Volume", + ] + + fig = make_subplots( + rows=2, + cols=1, + shared_xaxes=True, + vertical_spacing=0.06, + subplot_titles=plt_title, + row_width=[0.2, 0.7], + ) + fig.add_trace( + go.Candlestick( + x=df_hist.index, + open=df_hist.Open, + high=df_hist.High, + low=df_hist.Low, + close=df_hist.Close, + name="OHLC", + ), + row=1, + col=1, + ) + colors = [ + "green" if row.Open < row["Close"] else "red" for _, row in df_hist.iterrows() + ] # pylint: disable=E1120 + fig.add_trace( + go.Bar( + x=df_hist.index, + y=df_hist.Volume, + name="Volume", + marker_color=colors, + ), + row=2, + col=1, + ) + fig.update_layout( + margin=dict(l=0, r=0, t=25, b=5), + template=cfg.PLT_CANDLE_STYLE_TEMPLATE, + showlegend=False, + yaxis_title="Premium", + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + ) + fig.update_xaxes( + rangebreaks=[ + dict(bounds=["sat", "mon"]), + ], + ) + config = dict({"scrollZoom": True}) + rand = np.random.randint(70000) + imagefile = f"opt_hist{rand}.png" + fig.write_image(imagefile) + imagefile = image_border(imagefile) + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + fig.write_html(f"in/cc_hist_{rand}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/cc_hist_{rand}.html)" + + return { + "title": f"{ticker.upper()} {strike} {opt_type} expiring {expiry} Historical", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/options/hist.py b/bots/stocks/options/hist.py new file mode 100644 index 00000000000..26372e2e4de --- /dev/null +++ b/bots/stocks/options/hist.py @@ -0,0 +1,154 @@ +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots import helpers +from gamestonk_terminal.stocks.options import syncretism_model + + +def hist_command( + ticker: str = None, + expiry: str = "", + strike: float = 10, + opt_type: str = "", + greek: str = "", +): + """Plot historical option prices + + Parameters + ---------- + ticker: str + Stock ticker + expiry: str + expiration date + strike: float + Option strike price + put: bool + Calls for call + Puts for put + """ + + # Debug + if cfg.DEBUG: + print(f"opt-hist {ticker} {strike} {opt_type} {expiry} {greek}") + + # Check for argument + if ticker is None: + raise Exception("Stock ticker is required") + + if opt_type == "Puts": + put = bool(True) + if opt_type == "Calls": + put = bool(False) + chain_id = "" + + df_hist = syncretism_model.get_historical_greeks( + ticker, expiry, chain_id, strike, put + ) + title = f"\n{ticker.upper()} {strike} {opt_type} expiring {expiry} Historical {greek.upper()}" + # Output data + fig = make_subplots(shared_xaxes=True, specs=[[{"secondary_y": True}]]) + + fig.add_trace( + go.Scatter( + name=ticker.upper(), + x=df_hist.index, + y=df_hist.price, + line=dict(color="#fdc708", width=2), + opacity=1, + ), + ) + fig.add_trace( + go.Scatter( + name="Premium", + x=df_hist.index, + y=df_hist["premium"], + opacity=1, + yaxis="y2", + ), + ) + if greek: + fig.add_trace( + go.Scatter( + name=f"{greek.upper()}", + x=df_hist.index, + y=df_hist[greek], + opacity=1, + yaxis="y3", + ), + ) + fig.update_layout( + margin=dict(l=10, r=10, t=30, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=title, + title_x=0.03, + yaxis_title="Stock Price ($)", + yaxis=dict( + side="right", + fixedrange=False, + titlefont=dict(color="#fdc708"), + tickfont=dict(color="#fdc708"), + position=0.02, + nticks=20, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + fixedrange=False, + domain=[0.037, 1], + ), + xaxis2=dict( + rangeslider=dict(visible=False), + type="date", + fixedrange=False, + ), + xaxis3=dict( + rangeslider=dict(visible=False), + type="date", + fixedrange=False, + ), + dragmode="pan", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), + yaxis2=dict( + side="left", + fixedrange=False, + anchor="x", + overlaying="y", + titlefont=dict(color="#d81aea"), + tickfont=dict(color="#d81aea"), + nticks=20, + ), + yaxis3=dict( + side="left", + position=0, + fixedrange=False, + overlaying="y", + titlefont=dict(color="#00e6c3"), + tickfont=dict(color="#00e6c3"), + nticks=20, + ), + hovermode="x unified", + ) + config = dict({"scrollZoom": True}) + imagefile = "opt_hist.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/hist_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/hist_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"{ticker.upper()} {strike} {opt_type} expiring {expiry} Historical", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/options/iv.py b/bots/stocks/options/iv.py new file mode 100644 index 00000000000..6a1d225f5f7 --- /dev/null +++ b/bots/stocks/options/iv.py @@ -0,0 +1,50 @@ +import df2img + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.stocks.options import barchart_model + + +def iv_command(ticker: str = None): + """Options IV""" + + # Debug + if cfg.DEBUG: + logger.debug("opt-iv %s", ticker) + + # Check for argument + if ticker is None: + raise Exception("Stock ticker is required") + + df = barchart_model.get_options_info(ticker) + df = df.fillna("") + df = df.set_axis( + [ + " ", + "", + ], + axis="columns", + ) + df.set_index(" ", inplace=True) + fig = df2img.plot_dataframe( + df, + fig_size=(600, 1500), + col_width=[3, 3], + tbl_cells=dict( + align="left", + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("opt-iv.png", fig) + + return { + "title": f"{ticker.upper()} Options: IV", + "imagefile": imagefile, + } diff --git a/bots/stocks/options/oi.py b/bots/stocks/options/oi.py new file mode 100644 index 00000000000..9319d6c4446 --- /dev/null +++ b/bots/stocks/options/oi.py @@ -0,0 +1,137 @@ +import pandas as pd +import plotly.graph_objects as go + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.stocks.options import op_helpers, yfinance_model + + +def oi_command( + ticker: str = None, + expiry: str = "", + min_sp: float = None, + max_sp: float = None, +): + """Options OI""" + + # Debug + if cfg.DEBUG: + logger.debug("opt-oi %s %s %s %s", 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) + calls = options.calls + puts = options.puts + current_price = yfinance_model.get_price(ticker) + + min_strike = 0.75 * current_price + max_strike = 1.95 * current_price + + if len(calls) > 40: + min_strike = 0.75 * current_price + max_strike = 1.25 * current_price + + if min_sp: + min_strike = min_sp + if max_sp: + max_strike = max_sp + + call_oi = calls.set_index("strike")["openInterest"] / 1000 + put_oi = puts.set_index("strike")["openInterest"] / 1000 + + df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True) + df_opt = df_opt.rename( + columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"} + ) + + max_pain = op_helpers.calculate_max_pain(df_opt) + fig = go.Figure() + + dmax = df_opt[["OI_call", "OI_put"]].values.max() + dmin = df_opt[["OI_call", "OI_put"]].values.min() + fig.add_trace( + go.Scatter( + x=df_opt.index, + y=df_opt["OI_call"], + name="Calls", + mode="lines+markers", + line=dict(color="green", width=3), + ) + ) + + fig.add_trace( + go.Scatter( + x=df_opt.index, + y=df_opt["OI_put"], + name="Puts", + mode="lines+markers", + line=dict(color="red", width=3), + ) + ) + fig.add_trace( + go.Scatter( + x=[current_price, current_price], + y=[dmin, dmax], + mode="lines", + line=dict(color="gold", width=2), + name="Current Price", + ) + ) + fig.add_trace( + go.Scatter( + x=[max_pain, max_pain], + y=[dmin, dmax], + mode="lines", + line=dict(color="grey", width=3, dash="dash"), + name=f"Max Pain: {max_pain}", + ) + ) + fig.update_xaxes( + range=[min_strike, max_strike], + constrain="domain", + ) + fig.update_layout( + margin=dict(l=0, r=0, t=60, b=20), + template=cfg.PLT_SCAT_STYLE_TEMPLATE, + title=f"Open Interest for {ticker.upper()} expiring {expiry}", + title_x=0.5, + legend_title="", + xaxis_title="Strike", + yaxis_title="Open Interest (1k)", + xaxis=dict( + rangeslider=dict(visible=False), + ), + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + dragmode="pan", + ) + config = dict({"scrollZoom": True}) + imagefile = "opt_oi.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/oi_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/oi_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Open Interest for {ticker.upper()} expiring {expiry}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/options/opt_chain.py b/bots/stocks/options/opt_chain.py new file mode 100644 index 00000000000..8f08d332561 --- /dev/null +++ b/bots/stocks/options/opt_chain.py @@ -0,0 +1,151 @@ +import os + +import df2img +import disnake +import numpy as np +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import gst_imgur, logger +from bots.helpers import save_image +from bots.menus.menu import Menu +from gamestonk_terminal.stocks.options import yfinance_model + + +def chain_command( + ticker: str = None, + expiry: str = None, + opt_type: str = None, + min_sp: float = None, + max_sp: float = None, +): + """Show calls/puts for given ticker and expiration""" + + # Debug + if cfg.DEBUG: + logger.debug( + "opt-chain %s %s %s %s %s", ticker, expiry, opt_type, min_sp, max_sp + ) + + # Check for argument + if not ticker: + 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, str(expiry)) + calls_df = options.calls.fillna(0) + puts_df = options.puts.fillna(0) + + column_map = {"openInterest": "oi", "volume": "vol", "impliedVolatility": "iv"} + columns = [ + "strike", + "bid", + "ask", + "volume", + "openInterest", + "impliedVolatility", + ] + + if opt_type == "Calls": + df = calls_df[columns].rename(columns=column_map) + if opt_type == "Puts": + df = puts_df[columns].rename(columns=column_map) + + min_strike = np.percentile(df["strike"], 1) + max_strike = np.percentile(df["strike"], 100) + + if min_sp: + min_strike = min_sp + if max_sp: + max_strike = max_sp + if min_sp > max_sp: # type: ignore + min_sp, max_sp = max_strike, min_strike + + df = df[df["strike"] >= min_strike] + df = df[df["strike"] <= max_strike] + + df["iv"] = pd.to_numeric(df["iv"].astype(float)) + + formats = {"iv": "{:.2f}"} + for col, f in formats.items(): + df[col] = df[col].map(lambda x: f.format(x)) # pylint: disable=W0640 + df.set_index("strike", inplace=True) + + title = ( + f"Stocks: {opt_type} Option Chain for {ticker.upper()} on {expiry} [yfinance]" + ) + + embeds: list = [] + # Output + i, i2, end = 0, 0, 20 + df_pg = [] + embeds_img = [] + dindex = len(df.index) + while i < dindex: + df_pg = df.iloc[i:end] + df_pg.append(df_pg) + fig = df2img.plot_dataframe( + df_pg, + fig_size=(1000, (40 + (40 * 20))), + col_width=[3, 3, 3, 3], + tbl_cells=dict( + height=35, + ), + font=dict( + family="Consolas", + size=20, + ), + template="plotly_dark", + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image(f"opt-chain{i}.png", fig) + + uploaded_image = gst_imgur.upload_image(imagefile, title="something") + image_link = uploaded_image.link + embeds_img.append( + f"{image_link}", + ) + embeds.append( + disnake.Embed( + title=title, + colour=cfg.COLOR, + ), + ) + i2 += 1 + i += 20 + end += 20 + os.remove(imagefile) + + # Author/Footer + for i in range(0, i2): + embeds[i].set_author( + name=cfg.AUTHOR_NAME, + url=cfg.AUTHOR_URL, + icon_url=cfg.AUTHOR_ICON_URL, + ) + embeds[i].set_footer( + text=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + i = 0 + for i in range(0, i2): + embeds[i].set_image(url=embeds_img[i]) + + i += 1 + embeds[0].set_footer(text=f"Page 1 of {len(embeds)}") + choices = [ + disnake.SelectOption(label="Home", value="0", emoji="🟢"), + ] + + return { + "view": Menu, + "title": title, + "embed": embeds, + "choices": choices, + "embeds_img": embeds_img, + } diff --git a/bots/stocks/options/overview.py b/bots/stocks/options/overview.py new file mode 100644 index 00000000000..330ae65fb6e --- /dev/null +++ b/bots/stocks/options/overview.py @@ -0,0 +1,380 @@ +import os + +import df2img +import disnake +import numpy as np +import pandas as pd +import plotly.graph_objects as go +import yfinance as yf + +import bots.config_discordbot as cfg +from bots import helpers +from bots.config_discordbot import gst_imgur, logger +from bots.menus.menu import Menu +from gamestonk_terminal.stocks.options import op_helpers, yfinance_model +from gamestonk_terminal.stocks.options.barchart_model import get_options_info + + +# pylint: disable=R0914 +# pylint: disable=R0915 +def overview_command( + ticker: str = None, + expiry: str = None, + min_sp: float = None, + max_sp: float = None, +): + """Options Overview""" + + # Debug + if cfg.DEBUG: + logger.debug("opt-overview %s %s %s %s", ticker, expiry, min_sp, max_sp) + + # Check for argument + if ticker is None: + raise Exception("Stock ticker is required") + + # Get options info/dates, Look for logo_url + df = get_options_info(ticker) # Barchart Options IV Overview + + dates = yfinance_model.option_expirations(ticker) # Expiration dates + tup = f"{ticker.upper()}" + url = yf.Ticker(tup).info["logo_url"] + url += "?raw=true" if url else "" + + if not dates: + raise Exception("Stock ticker is invalid") + + options = yfinance_model.get_option_chain(ticker, str(expiry)) + calls = options.calls.fillna(0) + puts = options.puts.fillna(0) + current_price = yfinance_model.get_price(ticker) + + min_strike2 = np.percentile(calls["strike"], 1) + max_strike2 = np.percentile(calls["strike"], 100) + min_strike = 0.75 * current_price + max_strike = 1.95 * current_price + + if len(calls) > 40: + min_strike = 0.75 * current_price + max_strike = 1.25 * current_price + + if min_sp: + min_strike = min_sp + min_strike2 = min_sp + if max_sp: + max_strike = max_sp + max_strike2 = max_sp + if min_sp > max_sp: # type: ignore + min_sp, max_sp = max_strike2, min_strike2 + + call_oi = calls.set_index("strike")["openInterest"] / 1000 + put_oi = puts.set_index("strike")["openInterest"] / 1000 + + df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True) + df_opt = df_opt.rename( + columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"} + ) + + max_pain = op_helpers.calculate_max_pain(df_opt) + fig = go.Figure() + + dmax = df_opt[["OI_call", "OI_put"]].values.max() + dmin = df_opt[["OI_call", "OI_put"]].values.min() + fig.add_trace( + go.Scatter( + x=df_opt.index, + y=df_opt["OI_call"], + name="Calls", + mode="lines+markers", + line=dict(color="green", width=3), + ) + ) + + fig.add_trace( + go.Scatter( + x=df_opt.index, + y=df_opt["OI_put"], + name="Puts", + mode="lines+markers", + line=dict(color="red", width=3), + ) + ) + fig.add_trace( + go.Scatter( + x=[current_price, current_price], + y=[dmin, dmax], + mode="lines", + line=dict(color="gold", width=2), + name="Current Price", + ) + ) + fig.add_trace( + go.Scatter( + x=[max_pain, max_pain], + y=[dmin, dmax], + mode="lines", + line=dict(color="grey", width=3, dash="dash"), + name=f"Max Pain: {max_pain}", + ) + ) + fig.update_xaxes( + range=[min_strike, max_strike], + constrain="domain", + ) + fig.update_layout( + margin=dict(l=0, r=0, t=60, b=20), + template=cfg.PLT_SCAT_STYLE_TEMPLATE, + title=f"Open Interest for {ticker.upper()} expiring {expiry}", + title_x=0.5, + legend_title="", + xaxis_title="Strike", + yaxis_title="Open Interest (1k)", + xaxis=dict( + rangeslider=dict(visible=False), + ), + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + dragmode="pan", + ) + config = dict({"scrollZoom": True}) + imagefile = "opt_oi.png" + + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/oi_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/oi_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + uploaded_image_oi = gst_imgur.upload_image(imagefile, title="something") + image_link_oi = uploaded_image_oi.link + os.remove(imagefile) + + column_map = {"openInterest": "oi", "volume": "vol", "impliedVolatility": "iv"} + columns = [ + "strike", + "bid", + "ask", + "volume", + "openInterest", + "impliedVolatility", + ] + + calls_df = calls[columns].rename(columns=column_map) + puts_df = puts[columns].rename(columns=column_map) + + calls_df = calls_df[calls_df["strike"] >= min_strike2] + calls_df = calls_df[calls_df["strike"] <= max_strike2] + puts_df = puts_df[puts_df["strike"] >= min_strike2] + puts_df = puts_df[puts_df["strike"] <= max_strike2] + + calls_df["iv"] = pd.to_numeric(calls_df["iv"].astype(float)) + puts_df["iv"] = pd.to_numeric(puts_df["iv"].astype(float)) + + formats = {"iv": "{:.2f}"} + for col, f in formats.items(): + calls_df[col] = calls_df[col].map( + lambda x: f.format(x) # pylint: disable=W0640 + ) + puts_df[col] = puts_df[col].map(lambda x: f.format(x)) # pylint: disable=W0640 + + calls_df.set_index("strike", inplace=True) + puts_df.set_index("strike", inplace=True) + + if "-" in df.iloc[0, 1]: + iv = f"```diff\n- {df.iloc[0, 1]}\n```" + else: + iv = f"```yaml\n {df.iloc[0, 1]}\n```" + + pfix, sfix = f"{ticker.upper()} ", f" expiring {expiry}" + if expiry == dates[0]: + pfix = f"{ticker.upper()} Weekly " + sfix = "" + + titles = [f"{ticker.upper()} Overview", f"{pfix}Open Interest{sfix}"] + embeds = [ + disnake.Embed( + title=f"{ticker.upper()} Overview", + color=cfg.COLOR, + ), + disnake.Embed( + title=f"{pfix}Open Interest{sfix}", + description=plt_link, + colour=cfg.COLOR, + ), + ] + choices = [ + disnake.SelectOption(label=f"{ticker.upper()} Overview", value="0", emoji="🟢"), + disnake.SelectOption(label=f"{pfix}Open Interest{sfix}", value="1", emoji="🟢"), + ] + embeds_img = [] + + i, i2, end = 0, 0, 20 + df_calls = [] + dindex = len(calls_df.index) + while i <= dindex: + df_calls = calls_df.iloc[i:end] + df_calls.append(df_calls) + figc = df2img.plot_dataframe( + df_calls, + fig_size=(1000, (40 + (40 * 20))), + col_width=[3, 3, 3, 3], + tbl_cells=cfg.PLT_TBL_CELLS, + font=cfg.PLT_TBL_FONT, + template=cfg.PLT_TBL_STYLE_TEMPLATE, + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = helpers.save_image("opt-calls.png", figc) + uploaded_image = gst_imgur.upload_image(imagefile, title="something") + image_link = uploaded_image.link + embeds_img.append( + f"{image_link}", + ) + titles.append(f"{pfix}Calls{sfix}") + embeds.append( + disnake.Embed( + title=f"{pfix}Calls{sfix}", + colour=cfg.COLOR, + ), + ) + i2 += 1 + i += 20 + end += 20 + os.remove(imagefile) + + # Add Calls page field + i, page, puts_page = 2, 0, 3 + i3 = i2 + 2 + choices.append( + disnake.SelectOption(label="Calls Page 1", value="2", emoji="🟢"), + ) + for i in range(2, i3): + page += 1 + puts_page += 1 + + embeds[i].add_field(name=f"Calls Page {page}", value="_ _", inline=True) + + # Puts Pages + i, end = 0, 20 + df_puts = [] + + dindex = len(puts_df.index) + while i <= dindex: + df_puts = puts_df.iloc[i:end] + df_puts.append(df_puts) + figp = df2img.plot_dataframe( + df_puts, + fig_size=(1000, (40 + (40 * 20))), + col_width=[3, 3, 3, 3], + tbl_cells=cfg.PLT_TBL_CELLS, + font=cfg.PLT_TBL_FONT, + template=cfg.PLT_TBL_STYLE_TEMPLATE, + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = helpers.save_image("opt-puts.png", figp) + uploaded_image = gst_imgur.upload_image(imagefile, title="something") + image_link = uploaded_image.link + embeds_img.append( + f"{image_link}", + ) + titles.append(f"{pfix}Puts{sfix}") + embeds.append( + disnake.Embed( + title=f"{pfix}Puts{sfix}", + colour=cfg.COLOR, + ), + ) + i2 += 1 + i += 20 + end += 20 + os.remove(imagefile) + + # Add Puts page field + i, page = 0, 0 + puts_page -= 1 + i2 += 2 + choices.append( + disnake.SelectOption(label="Puts Page 1", value=f"{puts_page}", emoji="🟢"), + ) + for i in range(puts_page, i2): + page += 1 + embeds[i].add_field(name=f"Puts Page {page}", value="_ _", inline=True) + + # Author/Footer + for i in range(0, i2): + embeds[i].set_author( + name=cfg.AUTHOR_NAME, + url=cfg.AUTHOR_URL, + icon_url=cfg.AUTHOR_ICON_URL, + ) + embeds[i].set_footer( + text=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + + # Set images to Pages + i = 0 + img_i = 0 + embeds[1].set_image(url=image_link_oi) + for i in range(2, i2): + embeds[i].set_image(url=embeds_img[img_i]) + img_i += 1 + i += 1 + + if url: + embeds[0].set_thumbnail(url=f"{url}") + else: + embeds[0].set_thumbnail(url=cfg.AUTHOR_ICON_URL) + + # Overview Section + embeds[0].add_field(name=f"{df.iloc[0, 0]}", value=iv, inline=False) + + embeds[0].add_field( + name=f"•{df.iloc[1, 0]}", value=f"```css\n{df.iloc[1, 1]}\n```", inline=True + ) + for N in range(2, 6): + embeds[0].add_field( + name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}", + value=f"```css\n{df.iloc[N, 1]}\n```", + inline=True, + ) + + embeds[0].add_field(name="_ _", value="_ _", inline=False) + for N in range(6, 8): + embeds[0].add_field( + name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}", + value=f"```css\n{df.iloc[N, 1]}\n```", + inline=True, + ) + + embeds[0].add_field(name="_ _", value="_ _", inline=False) + for N in range(8, 10): + embeds[0].add_field( + name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}", + value=f"```css\n{df.iloc[N, 1]}\n```", + inline=True, + ) + + embeds[0].add_field(name="_ _", value="_ _", inline=False) + for N in range(10, 12): + embeds[0].add_field( + name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}", + value=f"```css\n{df.iloc[N, 1]}\n```", + inline=True, + ) + + embeds[0].set_footer(text=f"Page 1 of {len(embeds)}") + os.remove(imagefile) + + return { + "view": Menu, + "titles": titles, + "embed": embeds, + "choices": choices, + "embeds_img": embeds_img, + } diff --git a/bots/stocks/options/unu.py b/bots/stocks/options/unu.py new file mode 100644 index 00000000000..ff447fc1414 --- /dev/null +++ b/bots/stocks/options/unu.py @@ -0,0 +1,96 @@ +import df2img +import numpy as np +import pandas as pd +import requests + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import save_image +from gamestonk_terminal.helper_funcs import get_user_agent + + +def unu_command(num: int = None): + """Unusual Options""" + + # Debug + if cfg.DEBUG: + logger.debug("opt-unu %s", 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: + logger.debug("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) + df.set_index("Ticker", inplace=True) + dindex = len(df.index) + fig = df2img.plot_dataframe( + df, + fig_size=(800, (40 + (40 * dindex))), + col_width=[3, 3, 3, 3, 3, 3, 3], + tbl_cells=dict( + align="left", + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("opt-unu.png", fig) + + return { + "title": "Unusual Options", + "imagefile": imagefile, + } diff --git a/bots/stocks/options/vol.py b/bots/stocks/options/vol.py new file mode 100644 index 00000000000..5e59d20cbf2 --- /dev/null +++ b/bots/stocks/options/vol.py @@ -0,0 +1,119 @@ +import pandas as pd +import plotly.graph_objects as go + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.stocks.options import yfinance_model + + +def vol_command( + ticker: str = None, + expiry: str = "", + min_sp: float = None, + max_sp: float = None, +): + """Options VOL""" + + # Debug + if cfg.DEBUG: + logger.debug("opt-vol %s %s %s %s", 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.25 * 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 + + df_opt = pd.merge(put_v, call_v, left_index=True, right_index=True) + dmax = df_opt.values.max() + + fig = go.Figure() + fig.add_trace( + go.Scatter( + x=call_v.index, + y=call_v.values, + name="Calls", + mode="lines+markers", + line=dict(color="green", width=3), + ) + ) + fig.add_trace( + go.Scatter( + x=put_v.index, + y=put_v.values, + name="Puts", + mode="lines+markers", + line=dict(color="red", width=3), + ) + ) + fig.add_trace( + go.Scatter( + x=[current_price, current_price], + y=[0, dmax], + mode="lines", + line=dict(color="gold", width=2), + name="Current Price", + ) + ) + fig.update_xaxes( + range=[min_strike, max_strike], + constrain="domain", + ) + fig.update_layout( + margin=dict(l=0, r=0, t=60, b=20), + template=cfg.PLT_SCAT_STYLE_TEMPLATE, + title=f"Volume for {ticker.upper()} expiring {expiry}", + title_x=0.5, + legend_title="", + xaxis_title="Strike", + yaxis_title="Volume (1k)", + xaxis=dict( + rangeslider=dict(visible=False), + ), + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + dragmode="pan", + ) + config = dict({"scrollZoom": True}) + imagefile = "opt_vol.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/vol_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/vol_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Volume for {ticker.upper()} expiring {expiry}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/options/vsurf.py b/bots/stocks/options/vsurf.py new file mode 100644 index 00000000000..950984d451b --- /dev/null +++ b/bots/stocks/options/vsurf.py @@ -0,0 +1,124 @@ +import numpy as np +import plotly.graph_objects as go +from scipy.spatial import Delaunay + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.stocks.options import yfinance_model + + +def vsurf_command( + ticker: str = "", + z: str = "IV", +): + """Display vol surface + + Parameters + ---------- + ticker: Stock Ticker + z : The variable for the Z axis + """ + + # Debug + if cfg.DEBUG: + logger.debug("opt-oi %s %s", ticker, z) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + data = yfinance_model.get_iv_surface(ticker) + if data.empty: + raise Exception(f"No options data found for {ticker}.\n") + + Y = data.dte + X = data.strike + if z == "IV": + Z = data.impliedVolatility + label = "Volatility" + elif z == "OI": + Z = data.openInterest + label = "Open Interest" + elif z == "LP": + Z = data.lastPrice + label = "Last Price" + + points3D = np.vstack((X, Y, Z)).T + points2D = points3D[:, :2] + tri = Delaunay(points2D) + I, J, K = tri.simplices.T + + lighting_effects = dict( + ambient=0.5, diffuse=0.5, roughness=0.5, specular=0.4, fresnel=0.4 + ) + fig = go.Figure( + data=[ + go.Mesh3d( + z=Z, + x=X, + y=Y, + i=I, + j=J, + k=K, + intensity=Z, + colorscale=cfg.PLT_3DMESH_COLORSCALE, + hovertemplate="DTE: %{y}
Strike: %{x}
" + + z + + ": %{z}", + showscale=False, + flatshading=True, + lighting=lighting_effects, + ) + ] + ) + fig.update_layout( + scene=dict( + xaxis=dict( + title="Strike", + tickfont=dict(size=11), + titlefont=dict(size=12), + ), + yaxis=dict( + title="DTE", + ), + zaxis=dict( + title=z, + ), + ), + ) + fig.update_layout( + margin=dict(l=0, r=0, t=40, b=20), + template=cfg.PLT_3DMESH_STYLE_TEMPLATE, + title=f"{label} Surface for {ticker.upper()}", + title_x=0.5, + hoverlabel=cfg.PLT_3DMESH_HOVERLABEL, + scene_camera=dict( + up=dict(x=0, y=0, z=2), + center=dict(x=0, y=0, z=-0.3), + eye=dict(x=1.25, y=1.25, z=0.69), + ), + scene=cfg.PLT_3DMESH_SCENE, + ) + config = dict({"scrollZoom": True}) + imagefile = "opt-vsurf.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/vsurf_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/vsurf_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"{label} Surface for {ticker.upper()}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/quote.py b/bots/stocks/quote.py new file mode 100644 index 00000000000..ff17138ef54 --- /dev/null +++ b/bots/stocks/quote.py @@ -0,0 +1,40 @@ +import df2img + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import quote, save_image + + +def quote_command(ticker: str = None): + """Ticker Quote""" + + # Debug + if cfg.DEBUG: + logger.debug("quote %s", ticker) + + # Check for argument + if ticker is None: + raise Exception("Stock ticker is required") + + df = quote(ticker) + fig = df2img.plot_dataframe( + df, + fig_size=(600, 1500), + col_width=[2, 3], + tbl_cells=dict( + align="left", + height=35, + ), + template="plotly_dark", + font=dict( + family="Consolas", + size=20, + ), + paper_bgcolor="rgba(0, 0, 0, 0)", + ) + imagefile = save_image("quote.png", fig) + + return { + "title": f"{ticker.upper()} Quote", + "imagefile": imagefile, + } diff --git a/discordbot/stocks/screener/__init__.py b/bots/stocks/screener/__init__.py similarity index 100% rename from discordbot/stocks/screener/__init__.py rename to bots/stocks/screener/__init__.py diff --git a/bots/stocks/screener/financial.py b/bots/stocks/screener/financial.py new file mode 100644 index 00000000000..d079bc9bf2a --- /dev/null +++ b/bots/stocks/screener/financial.py @@ -0,0 +1,125 @@ +import difflib + +import disnake +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.menus.menu import Menu +from bots.stocks.screener import screener_options as so +from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data + + +def financial_command(preset="template", sort="", limit: int = 5, ascend: bool = False): + """Displays returned results from preset by financial metrics [Finviz]""" + + # Check for argument + if preset == "template" or preset not in so.all_presets: + raise Exception("Invalid preset selected!") + + # Debug + if cfg.DEBUG: + logger.debug("scr-financial %s %s %s %s", preset, sort, limit, ascend) + + # Check for argument + if limit < 0: + raise Exception("Number has to be above 0") + + # Output Data + df_screen = get_screener_data( + preset, + "financial", + limit, + ascend, + ) + + description = "" + title = "Stocks: [Finviz] Financial Screener" + if isinstance(df_screen, pd.DataFrame): + if df_screen.empty: + raise Exception("No data found.") + + df_screen = df_screen.dropna(axis="columns", how="all") + + if sort: + if " ".join(sort) in so.d_cols_to_sort["financial"]: + df_screen = df_screen.sort_values( + by=[" ".join(sort)], + ascending=ascend, + na_position="last", + ) + else: + similar_cmd = difflib.get_close_matches( + " ".join(sort), + so.d_cols_to_sort["financial"], + n=1, + cutoff=0.7, + ) + if similar_cmd: + description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n" + df_screen = df_screen.sort_values( + by=[similar_cmd[0]], + ascending=ascend, + na_position="last", + ) + else: + raise ValueError( + f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['financial'])}" + ) + + df_screen = df_screen.fillna("") + future_column_name = df_screen["Ticker"] + df_screen = df_screen.head(n=limit).transpose() + df_screen.columns = future_column_name + df_screen.drop("Ticker") + + embeds = [] + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + initial_str = description + "Overview" + i = 1 + for column in df_screen.columns.values: + menu = f"\nPage {i}: {column}" + initial_str += f"\nPage {i}: {column}" + if i < 19: + choices.append( + disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), + ) + if i == 20: + choices.append( + disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), + ) + i += 1 + reports = [f"{initial_str}"] + embeds.append( + disnake.Embed( + title=title, + description=initial_str, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + for column in df_screen.columns.values: + description = f"```{df_screen[column].fillna('')}```" + embeds.append( + disnake.Embed( + title=title, + description=description, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{description}") + + return { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } diff --git a/bots/stocks/screener/historical.py b/bots/stocks/screener/historical.py new file mode 100644 index 00000000000..483d2e29bee --- /dev/null +++ b/bots/stocks/screener/historical.py @@ -0,0 +1,137 @@ +import configparser +import random +from datetime import datetime, timedelta + +import yfinance as yf +from finvizfinance.screener import ticker +from matplotlib import pyplot as plt +from pandas.plotting import register_matplotlib_converters + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import image_border +from bots.stocks.screener import screener_options as so +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal.helper_funcs import plot_autoscale +from gamestonk_terminal.stocks.screener import finviz_model + +# pylint:disable=no-member + + +def historical_command(signal: str = "", start=""): + """Displays historical price comparison between similar companies [Yahoo Finance]""" + + # Debug user input + if cfg.DEBUG: + logger.debug("scr-historical %s %s", signal, start) + + # Check for argument + if signal not in so.d_signals_desc: + raise Exception("Invalid preset selected!") + + register_matplotlib_converters() + + screen = ticker.Ticker() + + if signal in finviz_model.d_signals: + screen.set_filter(signal=finviz_model.d_signals[signal]) + + else: + preset_filter = configparser.RawConfigParser() + preset_filter.optionxform = str # type: ignore + preset_filter.read(so.presets_path + signal + ".ini") + + d_general = preset_filter["General"] + d_filters = { + **preset_filter["Descriptive"], + **preset_filter["Fundamental"], + **preset_filter["Technical"], + } + + d_filters = {k: v for k, v in d_filters.items() if v} + + if d_general["Signal"]: + screen.set_filter(filters_dict=d_filters, signal=d_general["Signal"]) + else: + screen.set_filter(filters_dict=d_filters) + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + # Output Data + l_min = [] + l_leg = [] + l_stocks = screen.ScreenerView(verbose=0) + + if len(l_stocks) > 10: + description = ( + "\nThe limit of stocks to compare with are 10. Hence, 10 random similar stocks will be displayed." + "\nThe selected list will be: " + ) + random.shuffle(l_stocks) + l_stocks = sorted(l_stocks[:10]) + description = description + (", ".join(l_stocks)) + logger.debug(description) + + plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) + + while l_stocks: + l_parsed_stocks = [] + for symbol in l_stocks: + try: + df_similar_stock = yf.download( + symbol, + start=datetime.strftime(start, "%Y-%m-%d"), + progress=False, + threads=False, + ) + if not df_similar_stock.empty: + plt.plot( + df_similar_stock.index, + df_similar_stock["Adj Close"].values, + ) + l_min.append(df_similar_stock.index[0]) + l_leg.append(symbol) + + l_parsed_stocks.append(symbol) + except Exception as e: + error = ( + f"{e}\nDisregard previous error, which is due to API Rate limits from Yahoo Finance. " + f"Because we like '{symbol}', and we won't leave without getting data from it." + ) + + return { + "title": "ERROR Stocks: [Yahoo Finance] Historical Screener", + "description": error, + } + + for parsed_stock in l_parsed_stocks: + l_stocks.remove(parsed_stock) + + if signal: + plt.title( + f"Screener Historical Price using {finviz_model.d_signals[signal]} signal" + ) + else: + plt.title(f"Screener Historical Price using {signal} preset") + + plt.xlabel("Time") + plt.ylabel("Share Price ($)") + plt.legend(l_leg) + plt.grid(b=True, which="major", color="#666666", linestyle="-") + plt.minorticks_on() + plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + # ensures that the historical data starts from same datapoint + plt.xlim([max(l_min), df_similar_stock.index[-1]]) + + imagefile = "scr_historical.png" + plt.savefig(imagefile) + imagefile = image_border(imagefile) + + return { + "title": "Stocks: [Yahoo Finance] Historical Screener", + "description": description, + "imagefile": imagefile, + } diff --git a/bots/stocks/screener/overview.py b/bots/stocks/screener/overview.py new file mode 100644 index 00000000000..3e92b3d2c5c --- /dev/null +++ b/bots/stocks/screener/overview.py @@ -0,0 +1,128 @@ +import difflib + +import disnake +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.menus.menu import Menu +from bots.stocks.screener import screener_options as so +from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data + + +def overview_command( + preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False +): + """Displays stocks with overview data such as Sector and Industry [Finviz]""" + + # Check for argument + if preset == "template" or preset not in so.all_presets: + raise Exception("Invalid preset selected!") + + # Debug + if cfg.DEBUG: + logger.debug("scr-overview %s %s %s %s", preset, sort, limit, ascend) + + # Check for argument + if limit < 0: + raise Exception("Number has to be above 0") + + # Output Data + df_screen = get_screener_data( + preset, + "overview", + limit, + ascend, + ) + + description = "" + title = "Stocks: [Finviz] Overview Screener" + if isinstance(df_screen, pd.DataFrame): + if df_screen.empty: + raise Exception("No data found.") + + df_screen = df_screen.dropna(axis="columns", how="all") + + if sort: + if " ".join(sort) in so.d_cols_to_sort["overview"]: + df_screen = df_screen.sort_values( + by=[" ".join(sort)], + ascending=ascend, + na_position="last", + ) + else: + similar_cmd = difflib.get_close_matches( + " ".join(sort), + so.d_cols_to_sort["overview"], + n=1, + cutoff=0.7, + ) + if similar_cmd: + description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n" + df_screen = df_screen.sort_values( + by=[similar_cmd[0]], + ascending=ascend, + na_position="last", + ) + else: + raise ValueError( + f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['overview'])}" + ) + + df_screen = df_screen.fillna("") + future_column_name = df_screen["Ticker"] + df_screen = df_screen.head(n=limit).transpose() + df_screen.columns = future_column_name + df_screen.drop("Ticker") + + embeds = [] + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + initial_str = description + "Overview" + i = 1 + for column in df_screen.columns.values: + menu = f"\nPage {i}: {column}" + initial_str += f"\nPage {i}: {column}" + if i < 19: + choices.append( + disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), + ) + if i == 20: + choices.append( + disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), + ) + i += 1 + + reports = [f"{initial_str}"] + embeds.append( + disnake.Embed( + title=title, + description=initial_str, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + for column in df_screen.columns.values: + description = f"```{df_screen[column].fillna('')}```" + embeds.append( + disnake.Embed( + title=title, + description=description, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{description}") + + return { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } diff --git a/bots/stocks/screener/ownership.py b/bots/stocks/screener/ownership.py new file mode 100644 index 00000000000..d20df410a3f --- /dev/null +++ b/bots/stocks/screener/ownership.py @@ -0,0 +1,127 @@ +import difflib + +import disnake +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.menus.menu import Menu +from bots.stocks.screener import screener_options as so +from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data + + +def ownership_command( + preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False +): + """Displays stocks based on own share float and ownership data [Finviz]""" + + # Check for argument + if preset == "template" or preset not in so.all_presets: + raise Exception("Invalid preset selected!") + + # Debug + if cfg.DEBUG: + logger.debug("scr-ownership %s %s %s %s", preset, sort, limit, ascend) + + # Check for argument + if limit < 0: + raise Exception("Number has to be above 0") + + # Output Data + df_screen = get_screener_data( + preset, + "ownership", + limit, + ascend, + ) + + description = "" + title = "Stocks: [Finviz] Ownership Screener" + if isinstance(df_screen, pd.DataFrame): + if df_screen.empty: + raise Exception("No data found.") + + df_screen = df_screen.dropna(axis="columns", how="all") + + if sort: + if " ".join(sort) in so.d_cols_to_sort["ownership"]: + df_screen = df_screen.sort_values( + by=[" ".join(sort)], + ascending=ascend, + na_position="last", + ) + else: + similar_cmd = difflib.get_close_matches( + " ".join(sort), + so.d_cols_to_sort["ownership"], + n=1, + cutoff=0.7, + ) + if similar_cmd: + description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n" + df_screen = df_screen.sort_values( + by=[similar_cmd[0]], + ascending=ascend, + na_position="last", + ) + else: + raise ValueError( + f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['ownership'])}" + ) + + df_screen = df_screen.fillna("") + future_column_name = df_screen["Ticker"] + df_screen = df_screen.head(n=limit).transpose() + df_screen.columns = future_column_name + df_screen.drop("Ticker") + + embeds = [] + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + initial_str = description + "Overview" + i = 1 + for column in df_screen.columns.values: + menu = f"\nPage {i}: {column}" + initial_str += f"\nPage {i}: {column}" + if i < 19: + choices.append( + disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), + ) + if i == 20: + choices.append( + disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), + ) + i += 1 + reports = [f"{initial_str}"] + embeds.append( + disnake.Embed( + title=title, + description=initial_str, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + for column in df_screen.columns.values: + description = f"```{df_screen[column].fillna('')}```" + embeds.append( + disnake.Embed( + title=title, + description=description, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{description}") + + return { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } diff --git a/bots/stocks/screener/performance.py b/bots/stocks/screener/performance.py new file mode 100644 index 00000000000..8252b798a63 --- /dev/null +++ b/bots/stocks/screener/performance.py @@ -0,0 +1,148 @@ +import difflib + +import disnake +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.menus.menu import Menu +from bots.stocks.screener import screener_options as so +from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data + + +def performance_command( + preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False +): + """Displays stocks and sort by performance categories [Finviz]""" + + # Check for argument + if preset == "template" or preset not in so.all_presets: + raise Exception("Invalid preset selected!") + + # Debug + if cfg.DEBUG: + logger.debug("scr-performance %s %s %s %s", preset, sort, limit, ascend) + + # Check for argument + if limit < 0: + raise Exception("Number has to be above 0") + + # Output Data + df_screen = get_screener_data( + preset, + "performance", + limit, + ascend, + ) + + d_cols_to_sort = { + "performance": [ + "Ticker", + "Perf Week", + "Perf Month", + "Perf Quart", + "Perf Half", + "Perf Year", + "Perf YTD", + "Volatility W", + "Volatility M", + "Recom", + "Avg Volume", + "Rel Volume", + "Price", + "Change", + "Volume", + ], + } + + description = "" + title = "Stocks: [Finviz] Performance Screener" + if isinstance(df_screen, pd.DataFrame): + if df_screen.empty: + raise Exception("No data found.") + + df_screen = df_screen.dropna(axis="columns", how="all") + + if sort: + if " ".join(sort) in d_cols_to_sort["performance"]: + df_screen = df_screen.sort_values( + by=[" ".join(sort)], + ascending=ascend, + na_position="last", + ) + else: + similar_cmd = difflib.get_close_matches( + " ".join(sort), + d_cols_to_sort["performance"], + n=1, + cutoff=0.7, + ) + if similar_cmd: + description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n" + df_screen = df_screen.sort_values( + by=[similar_cmd[0]], + ascending=ascend, + na_position="last", + ) + else: + raise ValueError( + "Wrong sort column provided! Provide one of these:" + f"{', '.join(d_cols_to_sort['performance'])}" + ) + + df_screen = df_screen.fillna("") + future_column_name = df_screen["Ticker"] + df_screen = df_screen.head(n=limit).transpose() + df_screen.columns = future_column_name + df_screen.drop("Ticker") + + embeds = [] + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + initial_str = description + "Overview" + i = 1 + for column in df_screen.columns.values: + menu = f"\nPage {i}: {column}" + initial_str += f"\nPage {i}: {column}" + if i < 19: + choices.append( + disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), + ) + if i == 20: + choices.append( + disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), + ) + i += 1 + reports = [f"{initial_str}"] + embeds.append( + disnake.Embed( + title=title, + description=initial_str, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + for column in df_screen.columns.values: + description = f"```{df_screen[column].fillna('')}```" + embeds.append( + disnake.Embed( + title=title, + description=description, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{description}") + + return { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } diff --git a/bots/stocks/screener/presets_custom.py b/bots/stocks/screener/presets_custom.py new file mode 100644 index 00000000000..6dd16960f44 --- /dev/null +++ b/bots/stocks/screener/presets_custom.py @@ -0,0 +1,29 @@ +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.stocks.screener import screener_options as so + + +def presets_custom_command(): + """Displays every custom preset""" + + # Debug + if cfg.DEBUG: + logger.debug("scr-presets") + + description = "" + for preset in so.presets: + with open( + so.presets_path + preset + ".ini", + encoding="utf8", + ) as f: + preset_line = "" + for line in f: + if line.strip() == "[General]": + break + preset_line += line.strip() + description += f"**{preset}:** *{preset_line.split('Description: ')[1].replace('#', '')}*\n" + + return { + "title": "Stocks: Screener Custom Presets", + "description": description, + } diff --git a/bots/stocks/screener/presets_default.py b/bots/stocks/screener/presets_default.py new file mode 100644 index 00000000000..193138453b3 --- /dev/null +++ b/bots/stocks/screener/presets_default.py @@ -0,0 +1,20 @@ +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.stocks.screener import screener_options as so + + +def presets_default_command(): + """Displays default presets""" + + # Debug + if cfg.DEBUG: + logger.debug("scr-presets") + + description = "" + for signame, sigdesc in so.d_signals_desc.items(): + description += f"**{signame}:** *{sigdesc}*\n" + + return { + "title": "Stocks: Screener Default Presets", + "description": description, + } diff --git a/discordbot/stocks/screener/screener_options.py b/bots/stocks/screener/screener_options.py similarity index 99% rename from discordbot/stocks/screener/screener_options.py rename to bots/stocks/screener/screener_options.py index 450eba73834..77eca2cb11a 100644 --- a/discordbot/stocks/screener/screener_options.py +++ b/bots/stocks/screener/screener_options.py @@ -1,6 +1,6 @@ import os -import discordbot.config_discordbot as cfg +import bots.config_discordbot as cfg presets_path = os.path.join( cfg.GST_PATH, "gamestonk_terminal", "stocks", "screener", "presets", "" diff --git a/bots/stocks/screener/technical.py b/bots/stocks/screener/technical.py new file mode 100644 index 00000000000..2eff5b247d8 --- /dev/null +++ b/bots/stocks/screener/technical.py @@ -0,0 +1,127 @@ +import difflib + +import disnake +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.menus.menu import Menu +from bots.stocks.screener import screener_options as so +from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data + + +def technical_command( + preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False +): + """Displays stocks according to chosen preset, sorting by technical factors [Finviz]""" + + # Check for argument + if preset == "template" or preset not in so.all_presets: + raise Exception("Invalid preset selected!") + + # Debug + if cfg.DEBUG: + logger.debug("scr-technical %s %s %s %s", preset, sort, limit, ascend) + + # Check for argument + if limit < 0: + raise Exception("Number has to be above 0") + + # Output Data + df_screen = get_screener_data( + preset, + "technical", + limit, + ascend, + ) + + description = "" + title = "Stocks: [Finviz] Technical Screener" + if isinstance(df_screen, pd.DataFrame): + if df_screen.empty: + raise Exception("No data found.") + + df_screen = df_screen.dropna(axis="columns", how="all") + + if sort: + if " ".join(sort) in so.d_cols_to_sort["technical"]: + df_screen = df_screen.sort_values( + by=[" ".join(sort)], + ascending=ascend, + na_position="last", + ) + else: + similar_cmd = difflib.get_close_matches( + " ".join(sort), + so.d_cols_to_sort["technical"], + n=1, + cutoff=0.7, + ) + if similar_cmd: + description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n" + df_screen = df_screen.sort_values( + by=[similar_cmd[0]], + ascending=ascend, + na_position="last", + ) + else: + raise ValueError( + f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['technical'])}" + ) + + df_screen = df_screen.fillna("") + future_column_name = df_screen["Ticker"] + df_screen = df_screen.head(n=limit).transpose() + df_screen.columns = future_column_name + df_screen.drop("Ticker") + + embeds = [] + initial_str = description + "Overview" + i = 1 + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + for column in df_screen.columns.values: + menu = f"\nPage {i}: {column}" + initial_str += f"\nPage {i}: {column}" + if i < 19: + choices.append( + disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), + ) + if i == 20: + choices.append( + disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), + ) + i += 1 + reports = [f"{initial_str}"] + embeds.append( + disnake.Embed( + title=title, + description=initial_str, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + for column in df_screen.columns.values: + description = f"```{df_screen[column].fillna('')}```" + embeds.append( + disnake.Embed( + title=title, + description=description, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{description}") + + return { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } diff --git a/bots/stocks/screener/valuation.py b/bots/stocks/screener/valuation.py new file mode 100644 index 00000000000..5e28d20c212 --- /dev/null +++ b/bots/stocks/screener/valuation.py @@ -0,0 +1,127 @@ +import difflib + +import disnake +import pandas as pd + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.menus.menu import Menu +from bots.stocks.screener import screener_options as so +from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data + + +def valuation_command( + preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False +): + """Displays results from chosen preset focusing on valuation metrics [Finviz]""" + + # Check for argument + if preset == "template" or preset not in so.all_presets: + raise Exception("Invalid preset selected!") + + # Debug + if cfg.DEBUG: + logger.debug("scr-valuation %s %s %s %s", preset, sort, limit, ascend) + + # Check for argument + if limit < 0: + raise Exception("Number has to be above 0") + + # Output Data + df_screen = get_screener_data( + preset, + "valuation", + limit, + ascend, + ) + + description = "" + title = "Stocks: [Finviz] Valuation Screener" + if isinstance(df_screen, pd.DataFrame): + if df_screen.empty: + raise Exception("No data found.") + + df_screen = df_screen.dropna(axis="columns", how="all") + + if sort: + if " ".join(sort) in so.d_cols_to_sort["valuation"]: + df_screen = df_screen.sort_values( + by=[" ".join(sort)], + ascending=ascend, + na_position="last", + ) + else: + similar_cmd = difflib.get_close_matches( + " ".join(sort), + so.d_cols_to_sort["valuation"], + n=1, + cutoff=0.7, + ) + if similar_cmd: + description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n" + df_screen = df_screen.sort_values( + by=[similar_cmd[0]], + ascending=ascend, + na_position="last", + ) + else: + raise ValueError( + f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['valuation'])}" + ) + + df_screen = df_screen.fillna("") + future_column_name = df_screen["Ticker"] + df_screen = df_screen.head(n=limit).transpose() + df_screen.columns = future_column_name + df_screen.drop("Ticker") + + embeds = [] + choices = [ + disnake.SelectOption(label="Overview", value="0", emoji="🟢"), + ] + initial_str = description + "Overview" + i = 1 + for column in df_screen.columns.values: + menu = f"\nPage {i}: {column}" + initial_str += f"\nPage {i}: {column}" + if i < 19: + choices.append( + disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), + ) + if i == 20: + choices.append( + disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), + ) + i += 1 + reports = [f"{initial_str}"] + embeds.append( + disnake.Embed( + title=title, + description=initial_str, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + for column in df_screen.columns.values: + description = f"```{df_screen[column].fillna('')}```" + embeds.append( + disnake.Embed( + title=title, + description=description, + colour=cfg.COLOR, + ).set_author( + name=cfg.AUTHOR_NAME, + icon_url=cfg.AUTHOR_ICON_URL, + ) + ) + reports.append(f"{description}") + + return { + "view": Menu, + "title": title, + "description": reports, + "embed": embeds, + "choices": choices, + } diff --git a/discordbot/stocks/technical_analysis/__init__.py b/bots/stocks/technical_analysis/__init__.py similarity index 100% rename from discordbot/stocks/technical_analysis/__init__.py rename to bots/stocks/technical_analysis/__init__.py diff --git a/bots/stocks/technical_analysis/ad.py b/bots/stocks/technical_analysis/ad.py new file mode 100644 index 00000000000..57012fccd45 --- /dev/null +++ b/bots/stocks/technical_analysis/ad.py @@ -0,0 +1,150 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import volume_model + + +def ad_command(ticker="", is_open="False", start="", end=""): + """Displays chart with accumulation/distribution line [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug("ta-ad %s %s %s %s", ticker, is_open, start, end) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + if is_open in ["false", "False", "FALSE", ""]: + is_open = False + + if is_open in ["true", "True", "TRUE"]: + is_open = True + + if is_open not in [True, False]: + raise Exception("is_open argument has to be true or false") + + ticker = ticker.upper() + df_stock = helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + + divisor = 1_000_000 + df_vol = df_stock["Volume"].dropna() + df_vol = df_vol.values / divisor + df_ta = volume_model.ad(df_stock, is_open) + df_cal = df_ta.values + df_cal = df_cal / divisor + + fig = make_subplots( + rows=3, + cols=1, + shared_xaxes=True, + vertical_spacing=0.06, + row_width=[0.2, 0.2, 0.2], + ) + fig.add_trace( + go.Scatter( + name=ticker, + x=df_stock.index, + y=df_stock["Adj Close"].values, + line=dict(color="#fdc708", width=2), + opacity=1, + ), + row=1, + col=1, + ) + colors = [ + "green" if row.Open < row["Adj Close"] else "red" + for _, row in df_stock.iterrows() + ] + fig.add_trace( + go.Bar( + x=df_stock.index, + y=df_vol, + name="Volume [M]", + marker_color=colors, + ), + row=2, + col=1, + ) + fig.add_trace( + go.Scatter( + name="A/D [M]", + x=df_ta.index, + y=df_ta.iloc[:, 0].values, + line=dict(width=2), + opacity=1, + ), + row=3, + col=1, + ) + fig.add_hline( + y=0, + fillcolor="grey", + opacity=1, + layer="below", + line_width=3, + line=dict(color="grey", dash="dash"), + row=3, + col=1, + ) + fig.update_layout( + margin=dict(l=10, r=0, t=40, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} AD", + title_x=0.4, + yaxis_title="Stock Price ($)", + yaxis2_title="Volume [M]", + yaxis3_title="A/D [M]", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_ad.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/ad_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/ad_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Accumulation/Distribution Line {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/adosc.py b/bots/stocks/technical_analysis/adosc.py new file mode 100644 index 00000000000..e206751351d --- /dev/null +++ b/bots/stocks/technical_analysis/adosc.py @@ -0,0 +1,145 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import volume_model + + +def adosc_command(ticker="", is_open="False", fast="3", slow="10", start="", end=""): + """Displays chart with chaikin oscillator [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + # pylint: disable=logging-too-many-args + logger.debug( + "ta-adosc %s %s %s %s %s", + ticker, + is_open, + fast, + slow, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + if not fast.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + fast = float(fast) + if not slow.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + slow = float(slow) + + ticker = ticker.upper() + df_stock = helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + + # Output Data + divisor = 1_000_000 + df_vol = df_stock["Volume"].dropna() + df_vol = df_vol.values / divisor + df_ta = volume_model.adosc(df_stock, is_open, fast, slow) + df_cal = df_ta.values + df_cal = df_cal / divisor + + fig = make_subplots( + rows=3, + cols=1, + shared_xaxes=True, + vertical_spacing=0.06, + row_width=[0.2, 0.2, 0.2], + ) + fig.add_trace( + go.Scatter( + name=ticker, + x=df_stock.index, + y=df_stock["Adj Close"].values, + line=dict(color="#fdc708", width=2), + opacity=1, + ), + row=1, + col=1, + ) + colors = [ + "green" if row.Open < row["Adj Close"] else "red" + for _, row in df_stock.iterrows() + ] + fig.add_trace( + go.Bar( + x=df_stock.index, + y=df_vol, + name="Volume", + marker_color=colors, + ), + row=2, + col=1, + ) + fig.add_trace( + go.Scatter( + name="AD Osc [M]", + x=df_ta.index, + y=df_ta.iloc[:, 0].values, + line=dict(width=2), + opacity=1, + ), + row=3, + col=1, + ) + fig.update_layout( + margin=dict(l=10, r=0, t=30, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} AD Oscillator", + title_x=0.3, + yaxis_title="Stock Price ($)", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_adosc.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/adosc_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/adosc_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Accumulation/Distribution Oscillator {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/adx.py b/bots/stocks/technical_analysis/adx.py new file mode 100644 index 00000000000..3585fea17d8 --- /dev/null +++ b/bots/stocks/technical_analysis/adx.py @@ -0,0 +1,176 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import trend_indicators_model + + +def adx_command(ticker="", length="14", scalar="100", drift="1", start="", end=""): + """Displays chart with average directional movement index [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + # pylint: disable=logging-too-many-args + logger.debug( + "ta-adx %s %s %s %s %s", + ticker, + length, + scalar, + drift, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + if not length.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + length = float(length) + if not scalar.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + scalar = float(scalar) + if not drift.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + drift = float(drift) + + ticker = ticker.upper() + df_stock = helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + + df_ta = trend_indicators_model.adx( + df_stock["High"], + df_stock["Low"], + df_stock["Adj Close"], + length, + scalar, + drift, + ) + + # Output Data + leg_adx = df_ta.columns[0].replace("_", " ") + neg_di = df_ta.columns[1].replace("_", " ") + pos_di = df_ta.columns[2].replace("_", " ") + + fig = make_subplots( + rows=2, + cols=1, + shared_xaxes=True, + vertical_spacing=0.07, + row_width=[0.5, 0.5], + ) + fig.add_trace( + go.Scatter( + name=ticker, + x=df_stock.index, + y=df_stock["Adj Close"].values, + line=dict(color="#fdc708", width=2), + opacity=1, + ), + row=1, + col=1, + ) + fig.add_trace( + go.Scatter( + mode="lines", + name=f"ADX ({leg_adx})", + x=df_ta.index, + y=df_ta.iloc[:, 0].values, + opacity=1, + line=dict(width=2), + ), + row=2, + col=1, + ) + fig.add_trace( + go.Scatter( + mode="lines", + name=f"+DI ({pos_di})", + x=df_ta.index, + y=df_ta.iloc[:, 1].values, + opacity=1, + line=dict(width=1), + ), + row=2, + col=1, + ) + fig.add_trace( + go.Scatter( + mode="lines", + name=f"- DI ({neg_di})", + x=df_ta.index, + y=df_ta.iloc[:, 2].values, + opacity=1, + line=dict(width=1), + ), + row=2, + col=1, + ) + fig.add_hline( + y=25, + fillcolor="grey", + opacity=1, + layer="below", + line_width=3, + line=dict(color="grey", dash="dash"), + row=2, + col=1, + ) + fig.update_layout( + margin=dict(l=0, r=20, t=30, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"Average Directional Movement Index (ADX) on {ticker}", + title_x=0.3, + yaxis_title="Stock Price ($)", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_adx.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/adx_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/adx_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Average-Directional-Movement-Index {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/aroon.py b/bots/stocks/technical_analysis/aroon.py new file mode 100644 index 00000000000..b7a8a559f92 --- /dev/null +++ b/bots/stocks/technical_analysis/aroon.py @@ -0,0 +1,160 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import trend_indicators_model + + +def aroon_command(ticker="", length="25", scalar="100", start="", end=""): + """Displays chart with aroon indicator [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug( + "ta-aroon %s %s %s %s %s", + ticker, + length, + scalar, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + if not length.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + length = int(length) + if not scalar.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + scalar = float(scalar) + + ticker = ticker.upper() + df_stock = helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + + df_ta = trend_indicators_model.aroon( + df_stock["High"], df_stock["Low"], length, scalar + ) + + # Output Data + aadown = df_ta.columns[0].replace("_", " ") + aaup = df_ta.columns[1].replace("_", " ") + aaosc = df_ta.columns[2].replace("_", " ") + + fig = make_subplots( + rows=3, + cols=1, + shared_xaxes=True, + vertical_spacing=0.07, + row_width=[0.2, 0.2, 0.2], + ) + fig.add_trace( + go.Scatter( + name=ticker, + x=df_stock.index, + y=df_stock["Adj Close"].values, + line=dict(color="#fdc708", width=2), + opacity=1, + showlegend=False, + ), + row=1, + col=1, + ) + fig.add_trace( + go.Scatter( + name=f"Aroon DOWN ({aadown})", + x=df_ta.index, + y=df_ta.iloc[:, 0].values, + opacity=1, + ), + row=2, + col=1, + ) + fig.add_trace( + go.Scatter( + name=f"Aroon UP ({aaup})", + x=df_ta.index, + y=df_ta.iloc[:, 1].values, + opacity=1, + ), + row=2, + col=1, + ) + fig.add_trace( + go.Scatter( + name=f"Aroon OSC ({aaosc})", + x=df_ta.index, + y=df_ta.iloc[:, 2].values, + opacity=1, + ), + row=3, + col=1, + ) + fig.add_hline( + y=50, + fillcolor="grey", + opacity=1, + layer="below", + line_width=3, + line=dict(color="grey", dash="dash"), + row=2, + col=1, + ) + fig.update_layout( + margin=dict(l=0, r=20, t=30, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"Aroon on {ticker}", + title_x=0.5, + yaxis_title="Stock Price ($)", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_aroon.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/aroon_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/aroon_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Aroon-Indicator {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/bbands.py b/bots/stocks/technical_analysis/bbands.py new file mode 100644 index 00000000000..77eee908bf5 --- /dev/null +++ b/bots/stocks/technical_analysis/bbands.py @@ -0,0 +1,169 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import volatility_model + + +def bbands_command(ticker="", length="5", n_std="2", mamode="sma", start="", end=""): + """Displays chart with bollinger bands [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + # pylint: disable=logging-too-many-args + logger.debug( + "ta-bbands %s %s %s %s %s %s", + ticker, + length, + n_std, + mamode, + start, + end, + ) + + # Check for argument + possible_ma = [ + "dema", + "ema", + "fwma", + "hma", + "linreg", + "midpoint", + "pwma", + "rma", + "sinwma", + "sma", + "swma", + "t3", + "tema", + "trima", + "vidya", + "wma", + "zlma", + ] + + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + if not length.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + length = float(length) + if not n_std.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + n_std = float(n_std) + + if mamode not in possible_ma: + raise Exception("Invalid ma entered") + + ticker = ticker.upper() + df_stock = helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + + df_ta = volatility_model.bbands(df_stock["Adj Close"], length, n_std, mamode) + + # Output Data + bbu = df_ta.columns[2].replace("_", " ") + bbm = df_ta.columns[1].replace("_", " ") + bbl = df_ta.columns[0].replace("_", " ") + + fig = go.Figure() + fig.add_trace( + go.Scatter( + name=f"{bbu}", + x=df_ta.index, + y=df_ta.iloc[:, 2].values, + opacity=1, + mode="lines", + line_color="indigo", + ), + ) + fig.add_trace( + go.Scatter( + name=f"{bbl}", + x=df_ta.index, + y=df_ta.iloc[:, 0].values, + opacity=1, + mode="lines", + line_color="indigo", + fill="tonexty", + fillcolor="rgba(74, 0, 128, 0.2)", + ), + ) + fig.add_trace( + go.Scatter( + name=f"{bbm}", + x=df_ta.index, + y=df_ta.iloc[:, 1].values, + opacity=1, + line=dict( + width=1.5, + dash="dash", + ), + ), + ) + fig.add_trace( + go.Scatter( + name=f"{ticker}", + x=df_stock.index, + y=df_stock["Adj Close"], + line=dict(color="#fdc708", width=2), + opacity=1, + ), + ) + fig.update_layout( + margin=dict(l=0, r=0, t=50, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} Bollinger Bands ({mamode.upper()})", + title_x=0.5, + yaxis_title="Stock Price ($)", + xaxis_title="Time", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_bbands.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/bbands_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/bbands_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Bollinger-Bands {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/cci.py b/bots/stocks/technical_analysis/cci.py new file mode 100644 index 00000000000..7bb8126b735 --- /dev/null +++ b/bots/stocks/technical_analysis/cci.py @@ -0,0 +1,170 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import momentum_model + + +def cci_command(ticker="", length="14", scalar="0.015", start="", end=""): + """Displays chart with commodity channel index [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug( + "ta-cci %s %s %s %s %s", + ticker, + length, + scalar, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + # pylint + try: + length = int(length) + except ValueError as e: + raise Exception("Length has to be an integer") from e + try: + scalar = float(scalar) + except ValueError as e: + raise Exception("Scalar has to be an integer") from e + + ticker = ticker.upper() + df_stock = helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + df_ta = momentum_model.cci( + df_stock["High"], df_stock["Low"], df_stock["Adj Close"], length, scalar + ) + + dmin = df_ta.values.min() + dmax = df_ta.values.max() + # Output Data + + fig = make_subplots( + rows=2, + cols=1, + shared_xaxes=True, + vertical_spacing=0.07, + row_width=[0.5, 0.6], + ) + fig.add_trace( + go.Scatter( + name=ticker, + x=df_stock.index, + y=df_stock["Adj Close"].values, + line=dict(color="#fdc708", width=2), + opacity=1, + showlegend=False, + ), + row=1, + col=1, + ) + fig.add_trace( + go.Scatter( + showlegend=False, + x=df_ta.index, + y=df_ta.iloc[:, 0].values, + opacity=1, + ), + row=2, + col=1, + ) + fig.add_hrect( + y0=100, + y1=dmax, + fillcolor="red", + opacity=0.2, + layer="below", + line_width=0, + row=2, + col=1, + ) + fig.add_hrect( + y0=-100, + y1=dmin, + fillcolor="green", + opacity=0.2, + layer="below", + line_width=0, + row=2, + col=1, + ) + fig.add_hline( + y=-100, + fillcolor="green", + opacity=1, + layer="below", + line_width=3, + line=dict(color="green", dash="dash"), + row=2, + col=1, + ) + fig.add_hline( + y=100, + fillcolor="red", + opacity=1, + layer="below", + line_width=3, + line=dict(color="red", dash="dash"), + row=2, + col=1, + ) + fig.update_layout( + margin=dict(l=0, r=20, t=30, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} CCI", + title_x=0.5, + yaxis_title="Stock Price ($)", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_cci.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/cci_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/cci_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Commodity-Channel-Index {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/cg.py b/bots/stocks/technical_analysis/cg.py new file mode 100644 index 00000000000..77866d3333b --- /dev/null +++ b/bots/stocks/technical_analysis/cg.py @@ -0,0 +1,86 @@ +from datetime import datetime, timedelta + +import numpy as np +from matplotlib import pyplot as plt + +import bots.config_discordbot as cfg +import bots.helpers +from bots.config_discordbot import logger +from bots.helpers import image_border +from gamestonk_terminal.common.technical_analysis import momentum_model +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal.helper_funcs import plot_autoscale + + +def cg_command(ticker="", length="14", start="", end=""): + """Displays chart with centre of gravity [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug( + "ta-cg %s %s %s %s", + ticker, + length, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + if not length.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + length = float(length) + + ticker = ticker.upper() + df_stock = bots.helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + df_close = df_stock["Adj Close"] + df_close.columns = ["values"] + df_ta = momentum_model.cg(df_close, length) + + # Output Data + fig, axes = plt.subplots(2, 1, figsize=plot_autoscale(), dpi=PLOT_DPI) + ax = axes[0] + ax.set_title(f"{ticker} Centre of Gravity") + ax.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=1) + ax.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax.set_ylabel("Share Price ($)") + ax.grid(b=True, which="major", color="#666666", linestyle="-") + + ax2 = axes[1] + ax2.plot(df_ta.index, df_ta.values, "b", lw=2, label="CG") + # shift cg 1 bar forward for signal + signal = df_ta.values + signal = np.roll(signal, 1) + ax2.plot(df_ta.index, signal, "g", lw=1, label="Signal") + ax2.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax2.grid(b=True, which="major", color="#666666", linestyle="-") + + plt.gcf().autofmt_xdate() + fig.tight_layout(pad=1) + plt.legend() + imagefile = "ta_cg.png" + plt.savefig(imagefile) + + imagefile = image_border(imagefile) + + return { + "title": f"Stocks: Center-of-Gravity {ticker}", + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/donchian.py b/bots/stocks/technical_analysis/donchian.py new file mode 100644 index 00000000000..6100c28f2d4 --- /dev/null +++ b/bots/stocks/technical_analysis/donchian.py @@ -0,0 +1,96 @@ +from datetime import datetime, timedelta + +from matplotlib import pyplot as plt + +import bots.config_discordbot as cfg +import bots.helpers +from bots.config_discordbot import logger +from bots.helpers import image_border +from gamestonk_terminal.common.technical_analysis import volatility_model +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal.helper_funcs import plot_autoscale + + +def donchian_command( + ticker="", upper_length="25", lower_length="100", start="", end="" +): + """Displays chart with donchian channel [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug( + "ta donchian %s %s %s %s %s", + ticker, + upper_length, + lower_length, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + if not upper_length.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + upper_length = float(upper_length) + if not lower_length.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + lower_length = float(lower_length) + + ticker = ticker.upper() + df_stock = bots.helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + + df_ta = volatility_model.donchian( + df_stock["High"], df_stock["Low"], upper_length, lower_length + ) + + # Output Data + fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + ax.plot(df_stock.index, df_stock["Adj Close"].values, color="k", lw=3) + ax.plot(df_ta.index, df_ta.iloc[:, 0].values, "b", lw=1.5, label="upper") + ax.plot(df_ta.index, df_ta.iloc[:, 1].values, "b", lw=1.5, ls="--") + ax.plot(df_ta.index, df_ta.iloc[:, 2].values, "b", lw=1.5, label="lower") + ax.set_title(f"{ticker} donchian") + ax.set_xlim(df_stock.index[0], df_stock.index[-1]) + ax.set_xlabel("Time") + ax.set_ylabel("Price ($)") + + ax.legend([ticker, df_ta.columns[0], df_ta.columns[1], df_ta.columns[2]]) + ax.fill_between( + df_ta.index, + df_ta.iloc[:, 0].values, + df_ta.iloc[:, 2].values, + alpha=0.1, + color="b", + ) + ax.grid(b=True, which="major", color="#666666", linestyle="-") + + plt.gcf().autofmt_xdate() + fig.tight_layout(pad=1) + + plt.legend() + imagefile = "ta_donchian.png" + plt.savefig(imagefile) + + imagefile = image_border(imagefile) + + return { + "title": f"Stocks: Donchian-Channels {ticker}", + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/ema.py b/bots/stocks/technical_analysis/ema.py new file mode 100644 index 00000000000..73e8f43d125 --- /dev/null +++ b/bots/stocks/technical_analysis/ema.py @@ -0,0 +1,139 @@ +from datetime import datetime, timedelta + +import pandas as pd +import plotly.graph_objects as go + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import overlap_model + + +def ema_command(ticker="", window="", offset="", start="", end=""): + """Displays chart with exponential moving average [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug( + "ta-ema %s %s %s %s %s", + ticker, + window, + offset, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + l_legend = [ticker] + + if window == "": + window = [20, 50] + else: + window_temp = list() + for wind in window.split(","): + try: + window_temp.append(float(wind)) + except Exception as e: + raise Exception("Window needs to be a float") from e + window = window_temp + + if offset == "": + offset = 0 + + ticker = ticker.upper() + stock = helpers.load(ticker, start) + if stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + price_df = pd.DataFrame( + stock["Adj Close"].values, columns=["Price"], index=stock.index + ) + + i = 1 + for win in window: + ema_data = overlap_model.ema( + values=stock["Adj Close"], length=win, offset=offset + ) + price_df = price_df.join(ema_data) + l_legend.append(f"EMA {win}") + i += 1 + + # Output Data + start = start.strftime("%Y-%m-%d") + end = end.strftime("%Y-%m-%d") + price_df = price_df.loc[(price_df.index >= start) & (price_df.index < end)] + + fig = go.Figure() + fig.add_trace( + go.Scatter( + name=f"{ticker}", + x=price_df.index, + y=price_df["Price"], + line=dict(color="#fdc708", width=2), + opacity=1, + ), + ) + for i in range(1, price_df.shape[1]): + trace_name = price_df.columns[i].replace("_", " ") + fig.add_trace( + go.Scatter( + name=trace_name, + x=price_df.index, + y=price_df.iloc[:, i], + opacity=1, + ), + ) + fig.update_layout( + margin=dict(l=0, r=0, t=50, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} EMA", + title_x=0.5, + yaxis_title="Stock Price ($)", + xaxis_title="Time", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_ema.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/ema_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/ema_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Exponential-Moving-Average {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/fib.py b/bots/stocks/technical_analysis/fib.py new file mode 100644 index 00000000000..3efd705cf05 --- /dev/null +++ b/bots/stocks/technical_analysis/fib.py @@ -0,0 +1,93 @@ +from datetime import datetime, timedelta + +from matplotlib import pyplot as plt + +import bots.config_discordbot as cfg +import bots.helpers +from bots.config_discordbot import logger +from bots.helpers import image_border +from gamestonk_terminal import config_plot as cfp +from gamestonk_terminal.common.technical_analysis import custom_indicators_model +from gamestonk_terminal.helper_funcs import plot_autoscale + + +def fib_command(ticker="", start="", end=""): + """Displays chart with fibonacci retracement [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug( + "ta-fib %s %s %s", + ticker, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + f_start = None + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + f_start = start + + if end == "": + end = datetime.now() + f_end = None + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + f_end = None + + ticker = ticker.upper() + df_stock = bots.helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + + start = start.strftime("%Y-%m-%d") + end = end.strftime("%Y-%m-%d") + ( + df_fib, + min_date, + max_date, + min_pr, + max_pr, + ) = custom_indicators_model.calculate_fib_levels(df_stock, 120, f_start, f_end) + + levels = df_fib.Price + fig, ax = plt.subplots(figsize=(plot_autoscale()), dpi=cfp.PLOT_DPI) + + ax.plot(df_stock["Adj Close"], "b") + ax.plot([min_date, max_date], [min_pr, max_pr], c="k") + + for i in levels: + ax.axhline(y=i, c="g", alpha=0.5) + + for i in range(5): + ax.fill_between(df_stock.index, levels[i], levels[i + 1], alpha=0.6) + + ax.set_ylabel("Price") + ax.set_title(f"Fibonacci Support for {ticker.upper()}") + ax.set_xlim(df_stock.index[0], df_stock.index[-1]) + + ax1 = ax.twinx() + ax1.set_ylim(ax.get_ylim()) + ax1.set_yticks(levels) + ax1.set_yticklabels([0, 0.235, 0.382, 0.5, 0.618, 1]) + + plt.gcf().autofmt_xdate() + fig.tight_layout(pad=1) + imagefile = "ta_fib.png" + plt.savefig(imagefile) + + imagefile = image_border(imagefile) + + return { + "title": f"Stocks: Fibonacci-Retracement-Levels {ticker}", + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/fisher.py b/bots/stocks/technical_analysis/fisher.py new file mode 100644 index 00000000000..0378b217543 --- /dev/null +++ b/bots/stocks/technical_analysis/fisher.py @@ -0,0 +1,167 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import momentum_model + + +def fisher_command(ticker="", length="14", start="", end=""): + """Displays chart with fisher transformation [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug("ta-fisher %s %s %s %s", ticker, length, start, end) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + if not length.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + length = int(length) + + ticker = ticker.upper() + df_stock = helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + + df_ta = momentum_model.fisher(df_stock["High"], df_stock["Low"], length) + dmin = df_ta.values.min() + dmax = df_ta.values.max() + # Output Data + + fig = make_subplots( + rows=2, + cols=1, + shared_xaxes=True, + vertical_spacing=0.07, + row_width=[0.5, 0.6], + ) + fig.add_trace( + go.Scatter( + name=ticker, + x=df_stock.index, + y=df_stock["Adj Close"].values, + line=dict(color="#fdc708", width=2), + opacity=1, + showlegend=False, + ), + row=1, + col=1, + ) + fig.add_trace( + go.Scatter( + name="Fisher", + x=df_ta.index, + y=df_ta.iloc[:, 0].values, + opacity=1, + ), + row=2, + col=1, + ) + fig.add_trace( + go.Scatter( + name="Signal", + x=df_ta.index, + y=df_ta.iloc[:, 1].values, + opacity=1, + ), + row=2, + col=1, + ) + fig.add_hrect( + y0=2, + y1=dmax, + fillcolor="red", + opacity=0.2, + layer="below", + line_width=0, + row=2, + col=1, + ) + fig.add_hrect( + y0=-2, + y1=dmin, + fillcolor="green", + opacity=0.2, + layer="below", + line_width=0, + row=2, + col=1, + ) + fig.add_hline( + y=-2, + fillcolor="green", + opacity=1, + layer="below", + line_width=3, + line=dict(color="green", dash="dash"), + row=2, + col=1, + ) + fig.add_hline( + y=2, + fillcolor="red", + opacity=1, + layer="below", + line_width=3, + line=dict(color="red", dash="dash"), + row=2, + col=1, + ) + fig.update_layout( + margin=dict(l=0, r=20, t=30, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} Fisher Transform", + title_x=0.5, + yaxis_title="Stock Price ($)", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_fisher.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/fisher_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/fisher_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Fisher-Transform {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/hma.py b/bots/stocks/technical_analysis/hma.py new file mode 100644 index 00000000000..a7d95e82e65 --- /dev/null +++ b/bots/stocks/technical_analysis/hma.py @@ -0,0 +1,134 @@ +from datetime import datetime, timedelta + +import pandas as pd +import plotly.graph_objects as go + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import overlap_model + + +def hma_command(ticker="", window="", offset="", start="", end=""): + """Displays chart with hull moving average [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug( + "ta-hma %s %s %s %s %s", + ticker, + window, + offset, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + l_legend = [ticker] + + if window == "": + window = [20, 50] + else: + window_temp = list() + for wind in window.split(","): + try: + window_temp.append(float(wind)) + except Exception as e: + raise Exception("Window needs to be a float") from e + window = window_temp + + ticker = ticker.upper() + stock = helpers.load(ticker, start) + if stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + price_df = pd.DataFrame( + stock["Adj Close"].values, columns=["Price"], index=stock.index + ) + i = 1 + for win in window: + hma_data = overlap_model.hma( + values=stock["Adj Close"], length=win, offset=offset + ) + price_df = price_df.join(hma_data) + l_legend.append(f"HMA {win}") + i += 1 + + # Output Data + start = start.strftime("%Y-%m-%d") + end = end.strftime("%Y-%m-%d") + price_df = price_df.loc[(price_df.index >= start) & (price_df.index < end)] + + fig = go.Figure() + fig.add_trace( + go.Scatter( + name=f"{ticker}", + x=price_df.index, + y=price_df["Price"], + line=dict(color="#fdc708", width=2), + opacity=1, + ), + ) + for i in range(1, price_df.shape[1]): + trace_name = price_df.columns[i].replace("_", " ") + fig.add_trace( + go.Scatter( + name=trace_name, + x=price_df.index, + y=price_df.iloc[:, i], + opacity=1, + ), + ) + fig.update_layout( + margin=dict(l=0, r=0, t=50, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} HMA", + title_x=0.5, + yaxis_title="Stock Price ($)", + xaxis_title="Time", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_hma.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/hma_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/hma_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Hull-Moving-Average {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/kc.py b/bots/stocks/technical_analysis/kc.py new file mode 100644 index 00000000000..8bce979b139 --- /dev/null +++ b/bots/stocks/technical_analysis/kc.py @@ -0,0 +1,163 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import volatility_model + + +def kc_command( + ticker="", length="20", scalar="2", mamode="sma", offset="0", start="", end="" +): + """Displays chart with keltner channel [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + # pylint: disable=logging-too-many-args + logger.debug( + "ta-kc %s %s %s %s %s %s %s", + ticker, + length, + scalar, + mamode, + offset, + start, + end, + ) + + # Check for argument + possible_ma = ["sma", "ema", "wma", "hma", "zlma"] + + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + if not length.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + length = int(length) + if not scalar.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + scalar = float(scalar) + if not offset.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + offset = float(offset) + + if mamode not in possible_ma: + raise Exception("Invalid ma entered") + + ticker = ticker.upper() + df_stock = helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + + df_ta = volatility_model.kc( + df_stock["High"], + df_stock["Low"], + df_stock["Adj Close"], + length, + scalar, + mamode, + offset, + ) + + # Output Data + fig = go.Figure() + fig.add_trace( + go.Scatter( + name="upper", + x=df_ta.index, + y=df_ta.iloc[:, 2].values, + opacity=1, + mode="lines", + line_color="indigo", + showlegend=False, + ), + ) + fig.add_trace( + go.Scatter( + name="lower", + x=df_ta.index, + y=df_ta.iloc[:, 0].values, + opacity=1, + mode="lines", + line_color="indigo", + fill="tonexty", + fillcolor="rgba(74, 0, 128, 0.2)", + showlegend=False, + ), + ) + fig.add_trace( + go.Scatter( + name="mid", + x=df_ta.index, + y=df_ta.iloc[:, 1].values, + opacity=1, + line=dict( + width=1.5, + dash="dash", + ), + ), + ) + fig.add_trace( + go.Scatter( + name=f"{ticker}", + x=df_stock.index, + y=df_stock["Adj Close"], + line=dict(color="#fdc708", width=2), + opacity=1, + ), + ) + fig.update_layout( + margin=dict(l=0, r=0, t=50, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} Keltner Channels ({mamode.upper()})", + title_x=0.5, + yaxis_title="Stock Price ($)", + xaxis_title="Time", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_kc.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/kc_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/kc_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Keltner-Channel {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/macd.py b/bots/stocks/technical_analysis/macd.py new file mode 100644 index 00000000000..2268fe28e09 --- /dev/null +++ b/bots/stocks/technical_analysis/macd.py @@ -0,0 +1,152 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import momentum_model + + +def macd_command(ticker="", fast="12", slow="26", signal="9", start="", end=""): + """Displays chart with moving average convergence/divergence [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + # pylint: disable=logging-too-many-args + logger.debug( + "ta-macd %s %s %s %s %s %s", + ticker, + fast, + slow, + signal, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + if not fast.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + fast = float(fast) + if not slow.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + slow = float(slow) + if not signal.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + signal = float(signal) + + ticker = ticker.upper() + df_stock = helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + + df_ta = momentum_model.macd(df_stock["Adj Close"], fast, slow, signal) + trace_name = df_ta.columns[0].replace("_", " ") + + fig = make_subplots( + rows=2, + cols=1, + shared_xaxes=True, + vertical_spacing=0.07, + row_width=[0.5, 0.6], + ) + fig.add_trace( + go.Scatter( + name=ticker, + x=df_stock.index, + y=df_stock["Adj Close"].values, + line=dict(color="#fdc708", width=2), + opacity=1, + showlegend=False, + ), + row=1, + col=1, + ) + fig.add_trace( + go.Bar( + name="MACD Histogram", + x=df_ta.index, + y=df_ta.iloc[:, 1].values, + opacity=1, + ), + row=2, + col=1, + ) + fig.add_trace( + go.Scatter( + mode="lines", + name="MACD Line", + x=df_ta.index, + y=df_ta.iloc[:, 0].values, + opacity=1, + ), + row=2, + col=1, + ) + fig.add_trace( + go.Scatter( + mode="lines", + name="Signal Line", + x=df_ta.index, + y=df_ta.iloc[:, 2].values, + opacity=1, + ), + row=2, + col=1, + ) + fig.update_layout( + margin=dict(l=0, r=20, t=30, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} {trace_name}", + title_x=0.3, + yaxis_title="Stock Price ($)", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_macd.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/macd_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/macd_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Moving-Average-Convergence-Divergence {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/obv.py b/bots/stocks/technical_analysis/obv.py new file mode 100644 index 00000000000..e441dd06ca0 --- /dev/null +++ b/bots/stocks/technical_analysis/obv.py @@ -0,0 +1,136 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import volume_model + + +def obv_command(ticker="", start="", end=""): + """Displays chart with on balance volume [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug( + "ta-obv %s %s %s", + ticker, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + ticker = ticker.upper() + df_stock = helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + + # Output Data + divisor = 1_000_000 + df_vol = df_stock["Volume"].dropna() + df_vol = df_vol.values / divisor + df_ta = volume_model.obv(df_stock) + df_cal = df_ta.values + df_cal = df_cal / divisor + + fig = make_subplots( + rows=3, + cols=1, + shared_xaxes=True, + vertical_spacing=0.06, + row_width=[0.2, 0.2, 0.2], + ) + fig.add_trace( + go.Scatter( + name=ticker, + x=df_stock.index, + y=df_stock["Adj Close"].values, + line=dict(color="#fdc708", width=2), + opacity=1, + ), + row=1, + col=1, + ) + colors = [ + "green" if row.Open < row["Adj Close"] else "red" + for _, row in df_stock.iterrows() + ] + fig.add_trace( + go.Bar( + x=df_stock.index, + y=df_vol, + name="Volume [M]", + marker_color=colors, + ), + row=2, + col=1, + ) + fig.add_trace( + go.Scatter( + name="OBC [M]", + x=df_ta.index, + y=df_ta.iloc[:, 0].values, + line=dict(width=2), + opacity=1, + ), + row=3, + col=1, + ) + fig.update_layout( + margin=dict(l=10, r=0, t=40, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} OBV", + title_x=0.4, + yaxis_title="Stock Price ($)", + yaxis2_title="Volume [M]", + yaxis3_title="OBV [M]", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_obv.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/obv_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/obv_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: On-Balance-Volume {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/recom.py b/bots/stocks/technical_analysis/recom.py new file mode 100644 index 00000000000..bb4747b7038 --- /dev/null +++ b/bots/stocks/technical_analysis/recom.py @@ -0,0 +1,26 @@ +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from gamestonk_terminal.stocks.technical_analysis import tradingview_model + + +def recom_command(ticker=""): + """Displays text of a given stocks recommendation based on ta [Tradingview API]""" + + # Debug + if cfg.DEBUG: + logger.debug("ta-recom %s", ticker) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + recom = tradingview_model.get_tradingview_recommendation(ticker, "america", "", "") + + recom = recom[["BUY", "NEUTRAL", "SELL", "RECOMMENDATION"]] + + report = "```" + recom.to_string() + "```" + + return { + "title": "Stocks: [Tradingview API] Recommendation based on TA", + "description": report, + } diff --git a/bots/stocks/technical_analysis/rsi.py b/bots/stocks/technical_analysis/rsi.py new file mode 100644 index 00000000000..26bfc09fbe9 --- /dev/null +++ b/bots/stocks/technical_analysis/rsi.py @@ -0,0 +1,171 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import momentum_model + + +def rsi_command(ticker="", length="14", scalar="100", drift="1", start="", end=""): + """Displays chart with relative strength index [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + # pylint: disable=logging-too-many-args + logger.debug( + "ta-rsi %s %s %s %s %s %s", + ticker, + length, + scalar, + drift, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + if not length.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + length = float(length) + if not scalar.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + scalar = float(scalar) + if not drift.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + drift = float(drift) + + ticker = ticker.upper() + df_stock = helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + df_ta = momentum_model.rsi(df_stock["Adj Close"], length, scalar, drift) + + # Output Data + fig = make_subplots( + rows=2, + cols=1, + shared_xaxes=True, + vertical_spacing=0.07, + row_width=[0.5, 0.6], + ) + fig.add_trace( + go.Scatter( + name=ticker, + x=df_stock.index, + y=df_stock["Adj Close"].values, + line=dict(color="#fdc708", width=2), + opacity=1, + showlegend=False, + ), + row=1, + col=1, + ) + for i in range(0, df_ta.shape[1]): + trace_name = df_ta.columns[i].replace("_", " ") + fig.add_trace( + go.Scatter( + name=trace_name, + x=df_ta.index, + y=df_ta.iloc[:, i], + opacity=1, + ), + row=2, + col=1, + ) + fig.add_hrect( + y0=70, + y1=100, + fillcolor="red", + opacity=0.2, + layer="below", + line_width=0, + row=2, + col=1, + ) + fig.add_hrect( + y0=0, + y1=30, + fillcolor="green", + opacity=0.2, + layer="below", + line_width=0, + row=2, + col=1, + ) + fig.add_hline( + y=70, + fillcolor="green", + opacity=1, + layer="below", + line_width=3, + line=dict(color="red", dash="dash"), + row=2, + col=1, + ) + fig.add_hline( + y=30, + fillcolor="green", + opacity=1, + layer="below", + line_width=3, + line=dict(color="green", dash="dash"), + row=2, + col=1, + ) + fig.update_layout( + margin=dict(l=0, r=20, t=30, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} {trace_name}", + title_x=0.5, + yaxis_title="Stock Price ($)", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_rsi.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/rsi_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/rsi_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Relative-Strength-Index {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/sma.py b/bots/stocks/technical_analysis/sma.py new file mode 100644 index 00000000000..b459f7d5136 --- /dev/null +++ b/bots/stocks/technical_analysis/sma.py @@ -0,0 +1,135 @@ +from datetime import datetime, timedelta + +import pandas as pd +import plotly.graph_objects as go + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import overlap_model + + +def sma_command(ticker="", window="", offset="", start="", end=""): + """Displays chart with simple moving average [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug( + "ta-sma %s %s %s %s %s", + ticker, + window, + offset, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + l_legend = [ticker] + + if window == "": + window = [20, 50] + else: + window_temp = list() + for wind in window.split(","): + try: + window_temp.append(float(wind)) + except Exception as e: + raise Exception("Window needs to be a float") from e + window = window_temp + + ticker = ticker.upper() + stock = helpers.load(ticker, start) + if stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + price_df = pd.DataFrame( + stock["Adj Close"].values, columns=["Price"], index=stock.index + ) + i = 1 + for win in window: + sma_data = overlap_model.sma( + values=stock["Adj Close"], length=win, offset=offset + ) + price_df = price_df.join(sma_data) + l_legend.append(f"SMA {win}") + i += 1 + + # Output Data + start = start.strftime("%Y-%m-%d") + end = end.strftime("%Y-%m-%d") + price_df = price_df.loc[(price_df.index >= start) & (price_df.index < end)] + + fig = go.Figure() + fig.add_trace( + go.Scatter( + name=f"{ticker}", + x=price_df.index, + y=price_df["Price"], + line=dict(color="#fdc708", width=2), + opacity=1, + ), + ) + for i in range(1, price_df.shape[1]): + trace_name = price_df.columns[i].replace("_", " ") + fig.add_trace( + go.Scatter( + name=trace_name, + x=price_df.index, + y=price_df.iloc[:, i], + opacity=1, + ), + ) + fig.update_layout( + margin=dict(l=0, r=0, t=50, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} SMA", + title_x=0.5, + yaxis_title="Stock Price ($)", + xaxis_title="Time", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_sma.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/sma_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/sma_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Simple-Moving-Average {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/stoch.py b/bots/stocks/technical_analysis/stoch.py new file mode 100644 index 00000000000..bafa48e4aac --- /dev/null +++ b/bots/stocks/technical_analysis/stoch.py @@ -0,0 +1,188 @@ +from datetime import datetime, timedelta + +import plotly.graph_objects as go +from plotly.subplots import make_subplots + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import momentum_model + + +def stoch_command(ticker="", fast_k="14", slow_d="3", slow_k="3", start="", end=""): + """Displays chart with stochastic relative strength average [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug( + "ta-stoch %s %s %s %s %s %s", + ticker, + fast_k, + slow_k, + slow_d, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + if not fast_k.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + fast_k = int(fast_k) + if not slow_k.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + slow_k = int(slow_k) + if not slow_d.lstrip("-").isnumeric(): + raise Exception("Number has to be an integer") + slow_d = int(slow_d) + + ticker = ticker.upper() + df_stock = helpers.load(ticker, start) + if df_stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] + + df_ta = momentum_model.stoch( + df_stock["High"], + df_stock["Low"], + df_stock["Adj Close"], + fast_k, + slow_d, + slow_k, + ) + + fig = make_subplots( + rows=2, + cols=1, + shared_xaxes=True, + vertical_spacing=0.07, + row_width=[0.5, 0.7], + ) + fig.add_trace( + go.Scatter( + x=df_stock.index, + y=df_stock["Adj Close"].values, + line=dict(color="#fdc708", width=2), + opacity=1, + showlegend=False, + ), + row=1, + col=1, + ) + K = df_ta.columns[0].replace("_", " ") + D = df_ta.columns[1].replace("_", " ") + fig.add_trace( + go.Scatter( + name=f"%K {K}", + x=df_stock.index, + y=df_ta.iloc[:, 0].values, + line=dict(width=1.8), + opacity=1, + ), + row=2, + col=1, + ) + fig.add_trace( + go.Scatter( + name=f"%D {D}", + x=df_stock.index, + y=df_ta.iloc[:, 1].values, + line=dict(width=1.8, dash="dash"), + opacity=1, + ), + row=2, + col=1, + ) + fig.add_hrect( + y0=80, + y1=100, + fillcolor="red", + opacity=0.2, + layer="below", + line_width=0, + row=2, + col=1, + ) + fig.add_hrect( + y0=0, + y1=20, + fillcolor="green", + opacity=0.2, + layer="below", + line_width=0, + row=2, + col=1, + ) + fig.add_hline( + y=80, + fillcolor="green", + opacity=1, + layer="below", + line_width=3, + line=dict(color="red", dash="dash"), + row=2, + col=1, + ) + fig.add_hline( + y=20, + fillcolor="green", + opacity=1, + layer="below", + line_width=3, + line=dict(color="green", dash="dash"), + row=2, + col=1, + ) + fig.update_layout( + margin=dict(l=0, r=0, t=40, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"Stochastic Relative Strength Index (STOCH RSI) on {ticker}", + title_x=0.5, + yaxis_title="Stock Price ($)", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_stoch.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/stoch_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/stoch_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Stochastic-Relative-Strength-Index {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/summary.py b/bots/stocks/technical_analysis/summary.py new file mode 100644 index 00000000000..f0fb77fea01 --- /dev/null +++ b/bots/stocks/technical_analysis/summary.py @@ -0,0 +1,24 @@ +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from gamestonk_terminal.stocks.technical_analysis import finbrain_model + + +def summary_command(ticker=""): + """Displays text of a given stocks ta summary [FinBrain API]""" + + # Debug + if cfg.DEBUG: + logger.debug("ta-summary %s", ticker) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + report = finbrain_model.get_technical_summary_report(ticker) + if report: + report = "```" + report.replace(". ", ".\n") + "```" + + return { + "title": "Stocks: [FinBrain API] Summary", + "description": report, + } diff --git a/bots/stocks/technical_analysis/view.py b/bots/stocks/technical_analysis/view.py new file mode 100644 index 00000000000..dbda69e9268 --- /dev/null +++ b/bots/stocks/technical_analysis/view.py @@ -0,0 +1,33 @@ +import io + +from PIL import Image +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots.helpers import image_border +from gamestonk_terminal.stocks.technical_analysis import finviz_model + + +def view_command(ticker=""): + """Displays image from Finviz [Finviz]""" + + # Debug + if cfg.DEBUG: + logger.debug("ta-view %s", ticker) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + image_data = finviz_model.get_finviz_image(ticker) + dataBytesIO = io.BytesIO(image_data) + im = Image.open(dataBytesIO) + im = im.resize((800, 340), Image.ANTIALIAS) + imagefile = "ta_view.png" + + im.save(imagefile, "PNG", quality=100) + imagefile = image_border(imagefile) + + return { + "title": f"Stocks: [Finviz] Trendlines & Data {ticker}", + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/wma.py b/bots/stocks/technical_analysis/wma.py new file mode 100644 index 00000000000..e5c3ac7ab38 --- /dev/null +++ b/bots/stocks/technical_analysis/wma.py @@ -0,0 +1,138 @@ +from datetime import datetime, timedelta + +import pandas as pd +import plotly.graph_objects as go + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import overlap_model + + +def wma_command(ticker="", window="", offset="", start="", end=""): + """Displays chart with weighted moving average [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug( + "ta-wma %s %s %s %s %s", + ticker, + window, + offset, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + l_legend = [ticker] + + if window == "": + window = [20, 50] + else: + window_temp = list() + for wind in window.split(","): + try: + window_temp.append(float(wind)) + except Exception as e: + raise Exception("Window needs to be a float") from e + window = window_temp + + if offset == "": + offset = 0 + + ticker = ticker.upper() + stock = helpers.load(ticker, start) + if stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + price_df = pd.DataFrame( + stock["Adj Close"].values, columns=["Price"], index=stock.index + ) + i = 1 + for win in window: + wma_data = overlap_model.wma( + values=stock["Adj Close"], length=win, offset=offset + ) + price_df = price_df.join(wma_data) + l_legend.append(f"WMA {win}") + i += 1 + + # Output Data + start = start.strftime("%Y-%m-%d") + end = end.strftime("%Y-%m-%d") + price_df = price_df.loc[(price_df.index >= start) & (price_df.index < end)] + + fig = go.Figure() + fig.add_trace( + go.Scatter( + name=f"{ticker}", + x=price_df.index, + y=price_df["Price"], + line=dict(color="#fdc708", width=2), + opacity=1, + ), + ) + for i in range(1, price_df.shape[1]): + trace_name = price_df.columns[i].replace("_", " ") + fig.add_trace( + go.Scatter( + name=trace_name, + x=price_df.index, + y=price_df.iloc[:, i], + opacity=1, + ), + ) + fig.update_layout( + margin=dict(l=0, r=0, t=50, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} WMA", + title_x=0.5, + yaxis_title="Stock Price ($)", + xaxis_title="Time", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_wma.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/wma_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/wma_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Weighted-Moving-Average {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/bots/stocks/technical_analysis/zlma.py b/bots/stocks/technical_analysis/zlma.py new file mode 100644 index 00000000000..98e6bd14279 --- /dev/null +++ b/bots/stocks/technical_analysis/zlma.py @@ -0,0 +1,138 @@ +from datetime import datetime, timedelta + +import pandas as pd +import plotly.graph_objects as go + +import bots.config_discordbot as cfg +from bots.config_discordbot import logger +from bots import helpers +from gamestonk_terminal.common.technical_analysis import overlap_model + + +def zlma_command(ticker="", window="", offset="", start="", end=""): + """Displays chart with zero lag moving average [Yahoo Finance]""" + + # Debug + if cfg.DEBUG: + logger.debug( + "ta-zlma %s %s %s %s %s", + ticker, + window, + offset, + start, + end, + ) + + # Check for argument + if ticker == "": + raise Exception("Stock ticker is required") + + if start == "": + start = datetime.now() - timedelta(days=365) + else: + start = datetime.strptime(start, cfg.DATE_FORMAT) + + if end == "": + end = datetime.now() + else: + end = datetime.strptime(end, cfg.DATE_FORMAT) + + l_legend = [ticker] + + if window == "": + window = [20] + else: + window_temp = list() + for wind in window.split(","): + try: + window_temp.append(float(wind)) + except Exception as e: + raise Exception("Window needs to be a float") from e + window = window_temp + + if offset == "": + offset = 0 + + ticker = ticker.upper() + stock = helpers.load(ticker, start) + if stock.empty: + raise Exception("Stock ticker is invalid") + + # Retrieve Data + price_df = pd.DataFrame( + stock["Adj Close"].values, columns=["Price"], index=stock.index + ) + i = 1 + for win in window: + zlma_data = overlap_model.zlma( + values=stock["Adj Close"], length=win, offset=offset + ) + price_df = price_df.join(zlma_data) + l_legend.append(f"ZLMA {win}") + i += 1 + + # Output Data + start = start.strftime("%Y-%m-%d") + end = end.strftime("%Y-%m-%d") + price_df = price_df.loc[(price_df.index >= start) & (price_df.index < end)] + + fig = go.Figure() + fig.add_trace( + go.Scatter( + name=f"{ticker}", + x=price_df.index, + y=price_df["Price"], + line=dict(color="#fdc708", width=2), + opacity=1, + ), + ) + for i in range(1, price_df.shape[1]): + trace_name = price_df.columns[i].replace("_", " ") + fig.add_trace( + go.Scatter( + name=trace_name, + x=price_df.index, + y=price_df.iloc[:, i], + opacity=1, + ), + ) + fig.update_layout( + margin=dict(l=0, r=0, t=50, b=20), + template=cfg.PLT_TA_STYLE_TEMPLATE, + colorway=cfg.PLT_TA_COLORWAY, + title=f"{ticker} ZLMA", + title_x=0.5, + yaxis_title="Stock Price ($)", + xaxis_title="Time", + yaxis=dict( + fixedrange=False, + ), + xaxis=dict( + rangeslider=dict(visible=False), + type="date", + ), + dragmode="pan", + legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), + ) + config = dict({"scrollZoom": True}) + imagefile = "ta_zlma.png" + + # Check if interactive settings are enabled + plt_link = "" + if cfg.INTERACTIVE: + html_ran = helpers.uuid_get() + fig.write_html(f"in/zlma_{html_ran}.html", config=config) + plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/zlma_{html_ran}.html)" + + fig.update_layout( + width=800, + height=500, + ) + + imagefile = helpers.image_border(imagefile, fig=fig) + + return { + "title": f"Stocks: Zero-Lag-Moving-Average {ticker}", + "description": plt_link, + "imagefile": imagefile, + } diff --git a/discordbot/cmds/dps_cmds.py b/discordbot/cmds/dps_cmds.py deleted file mode 100644 index c527b0e0efb..00000000000 --- a/discordbot/cmds/dps_cmds.py +++ /dev/null @@ -1,206 +0,0 @@ -from __future__ import annotations - -import disnake -import pandas as pd -from disnake.ext import commands - -from discordbot.config_discordbot import logger -from discordbot.stocks.dark_pool_shorts.dpotc import dpotc_command -from discordbot.stocks.dark_pool_shorts.ftd import ftd_command -from discordbot.stocks.dark_pool_shorts.hsi import hsi_command -from discordbot.stocks.dark_pool_shorts.pos import pos_command -from discordbot.stocks.dark_pool_shorts.psi import psi_command -from discordbot.stocks.dark_pool_shorts.shorted import shorted_command -from discordbot.stocks.dark_pool_shorts.sidtc import sidtc_command -from discordbot.stocks.dark_pool_shorts.spos import spos_command - -pos_choices = [ - "Short Vol (1M)", - "Short Vol %", - "Net Short Vol (1M)", - "Net Short Vol ($100M)", - "DP Position (1M)", - "DP Position ($1B)", -] - - -def default_completion(inter: disnake.AppCmdInter) -> list[str]: - return ["Start Typing", "for a", "stock ticker"] - - -def ticker_autocomp(inter: disnake.AppCmdInter, ticker: str): - if not ticker: - return default_completion(inter) - print(f"ticker_autocomp [ticker]: {ticker}") - tlow = ticker.lower() - col_list = ["Name"] - df = pd.read_csv("files/tickers.csv", usecols=col_list) - df = df["Name"] - return [ticker for ticker in df if ticker.lower().startswith(tlow)][:24] - - -class DarkPoolShortsCommands(commands.Cog): - """Dark Pool Shorts menu""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - self.bot.help_command.cog = self - - def cog_unload(self): - self.bot.help_command = None - - @commands.slash_command(name="dps-shorted") - async def shorted(self, ctx: disnake.AppCmdInter, num: int = 10): - """Show most shorted stocks [Yahoo Finance] - - Parameters - ----------- - num: Number of the most shorted stocks to retrieve - """ - await ctx.response.defer() - logger.info("dps-shorted") - await shorted_command(ctx, num) - - @commands.slash_command(name="dps-hsi") - async def hsi(self, ctx: disnake.AppCmdInter, num: int = 10): - """Show top high short interest stocks of over 20% ratio [shortinterest.com] - - Parameters - ----------- - num: Number of top stocks to print - """ - await ctx.response.defer() - logger.info("dps-hsi") - await hsi_command(ctx, num) - - @commands.slash_command(name="dps-pos") - async def pos( - self, - ctx: disnake.AppCmdInter, - sort: str = commands.Param(choices=pos_choices), - num: int = 10, - ): - """Dark pool short position [Stockgrid] - - Parameters - ----------- - sort: Field for which to sort. - num: Number of top tickers to show - """ - await ctx.response.defer() - logger.info("dps-pos") - - if str(sort) == "Short Vol (1M)": - sort = "sv" - if str(sort) == "Short Vol %": - sort = "sv_pct" - if str(sort) == "Net Short Vol (1M)": - sort = "nsv" - if str(sort) == "Net Short Vol ($100M)": - sort = "nsv_dollar" - if str(sort) == "DP Position (1M)": - sort = "dpp" - if str(sort) == "DP Position ($1B)": - sort = "dpp_dollar" - - await pos_command(ctx, sort, num) - - @commands.slash_command(name="dps-sidtc") - async def sidtc( - self, - ctx: disnake.AppCmdInter, - sort: str = commands.Param( - choices=["Float Short %", "Days to Cover", "Short Interest"] - ), - num: int = 10, - ): - """Short interest and days to cover [Stockgrid] - - Parameters - ----------- - sort: Field for which to sort. Possible are: `float`, `dtc`, `si`. - num: Number of top tickers to show - """ - await ctx.response.defer() - logger.info("dps-sidtc") - - if str(sort) == "Float Short %": - sort = "float" - if str(sort) == "Days to Cover": - sort = "dtc" - if str(sort) == "Short Interest": - sort = "si" - - await sidtc_command(ctx, sort, num) - - @commands.slash_command(name="dps-ftd") - async def ftd( - self, - ctx: disnake.AppCmdInter, - ticker: str = commands.Param(autocomplete=ticker_autocomp), - start="", - end="", - ): - """Fails-to-deliver data [SEC] - - Parameters - ---------- - ticker: Stock Ticker - start: YYYY-MM-DD format - end: YYYY-MM-DD format - """ - await ctx.response.defer() - logger.info("dps-ftd") - await ftd_command(ctx, ticker, start, end) - - @commands.slash_command(name="dps-dpotc") - async def dpotc( - self, - ctx: disnake.AppCmdInter, - ticker: str = commands.Param(autocomplete=ticker_autocomp), - ): - """Dark pools (ATS) vs OTC data [FINRA] - - Parameters - ---------- - ticker: Stock Ticker - """ - await ctx.response.defer() - logger.info("dps-dpotc") - await dpotc_command(ctx, ticker) - - @commands.slash_command(name="dps-spos") - async def spos( - self, - ctx: disnake.AppCmdInter, - ticker: str = commands.Param(autocomplete=ticker_autocomp), - ): - """Net short vs position [Stockgrid] - - Parameters - ---------- - ticker: Stock Ticker - """ - await ctx.response.defer() - logger.info("dps-spos") - await spos_command(ctx, ticker) - - @commands.slash_command(name="dps-psi") - async def psi( - self, - ctx: disnake.AppCmdInter, - ticker: str = commands.Param(autocomplete=ticker_autocomp), - ): - """Price vs short interest volume [Stockgrid] - - Parameters - ---------- - ticker: Stock Ticker - """ - await ctx.response.defer() - logger.info("dps-psi") - await psi_command(ctx, ticker) - - -def setup(bot: commands.Bot): - bot.add_cog(DarkPoolShortsCommands(bot)) diff --git a/discordbot/cmds/economy_cmds.py b/discordbot/cmds/economy_cmds.py deleted file mode 100644 index d44cd8a4c54..00000000000 --- a/discordbot/cmds/economy_cmds.py +++ /dev/null @@ -1,195 +0,0 @@ -from __future__ import annotations - -import disnake -from disnake.ext import commands - -from discordbot.config_discordbot import logger -from discordbot.economy.currencies import currencies_command -from discordbot.economy.energy import energy_command -from discordbot.economy.feargreed import feargreed_command -from discordbot.economy.futures import futures_command -from discordbot.economy.glbonds import glbonds_command -from discordbot.economy.grains import grains_command -from discordbot.economy.indices import indices_command -from discordbot.economy.meats import meats_command -from discordbot.economy.metals import metals_command -from discordbot.economy.overview import overview_command -from discordbot.economy.performance import performance_command -from discordbot.economy.softs import softs_command -from discordbot.economy.usbonds import usbonds_command -from discordbot.economy.valuation import valuation_command - -group = [ - "basic_materials", - "capitalization", - "communication_services", - "consumer_cyclical", - "consumer_defensive", - "country", - "energy", - "financial", - "healthcare", - "industry", - "industrials", - "real_estate", - "sector", - "technology", - "utilities", -] -fgind = [ - "Junk Bond Demand", - "Market Volatility", - "Put and Call Options", - "Market Momentum", - "Stock Price Strength", - "Stock Price Breadth", - "Safe Heaven Demand", -] - - -class EconomyCommands(commands.Cog): - """Economy Commands menu""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - - @commands.slash_command(name="econ-feargreed") - async def feargreed( - self, ctx: disnake.AppCmdInter, indicator: str = commands.Param(choices=fgind) - ): - """CNN Fear and Greed Index [CNN] - - Parameters - ---------- - indicator: Select an Indicator - """ - await ctx.response.defer() - logger.info("econ-feargreed") - if indicator == "Junk Bond Demand": - indicator = "jbd" - if indicator == "Market Volatility": - indicator = "mv" - if indicator == "Put and Call Options": - indicator = "pco" - if indicator == "Market Momentum": - indicator = "mm" - if indicator == "Stock Price Strength": - indicator = "sps" - if indicator == "Stock Price Breadth": - indicator = "spb" - if indicator == "Safe Heaven Demand": - indicator = "shd" - await feargreed_command(ctx, indicator) - - @commands.slash_command(name="econ-overview") - async def overview(self, ctx: disnake.AppCmdInter): - """Market data overview [Wall St. Journal]""" - await ctx.response.defer() - logger.info("econ-overview") - await overview_command(ctx) - - @commands.slash_command(name="econ-indices") - async def indices(self, ctx: disnake.AppCmdInter): - """US indices overview [Wall St. Journal]""" - await ctx.response.defer() - logger.info("econ-indices") - await indices_command(ctx) - - @commands.slash_command(name="econ-futures") - async def futures(self, ctx: disnake.AppCmdInter): - """Futures and commodities overview [Wall St. Journal]""" - await ctx.response.defer() - logger.info("econ-futures") - await futures_command(ctx) - - @commands.slash_command(name="econ-usbonds") - async def usbonds(self, ctx: disnake.AppCmdInter): - """US bonds overview [Wall St. Journal]""" - await ctx.response.defer() - logger.info("econ-usbonds") - await usbonds_command(ctx) - - @commands.slash_command(name="econ-glbonds") - async def glbonds(self, ctx: disnake.AppCmdInter): - """Global bonds overview [Wall St. Journal]""" - await ctx.response.defer() - logger.info("econ-glbonds") - await glbonds_command(ctx) - - @commands.slash_command(name="econ-energy") - async def energy(self, ctx: disnake.AppCmdInter): - """Displays energy futures data [Finviz]""" - await ctx.response.defer() - logger.info("econ-energy") - await energy_command(ctx) - - @commands.slash_command(name="econ-metals") - async def metals(self, ctx: disnake.AppCmdInter): - """Displays metals futures data [Finviz]""" - await ctx.response.defer() - logger.info("econ-metals") - await metals_command(ctx) - - @commands.slash_command(name="econ-meats") - async def meats(self, ctx: disnake.AppCmdInter): - """Displays meats futures data [Finviz]""" - await ctx.response.defer() - logger.info("econ-meats") - await meats_command(ctx) - - @commands.slash_command(name="econ-grains") - async def grains(self, ctx: disnake.AppCmdInter): - """Displays grains futures data [Finviz]""" - await ctx.response.defer() - logger.info("econ-grains") - await grains_command(ctx) - - @commands.slash_command(name="econ-softs") - async def softs(self, ctx: disnake.AppCmdInter): - """Displays softs futures data [Finviz]""" - await ctx.response.defer() - logger.info("econ-softs") - await softs_command(ctx) - - @commands.slash_command(name="econ-currencies") - async def currencies(self, ctx: disnake.AppCmdInter): - """Currencies overview [Wall St. Journal]""" - await ctx.response.defer() - logger.info("econ-currencies") - await currencies_command(ctx) - - @commands.slash_command(name="econ-valuation") - async def valuation( - self, - ctx: disnake.AppCmdInter, - economy_group: str = commands.Param(choices=group), - ): - """Valuation of sectors, industry, country [Finviz] - - Parameters - ----------- - economy_group: Economy Group - """ - await ctx.response.defer() - logger.info("econ-valuation") - await valuation_command(ctx, economy_group) - - @commands.slash_command(name="econ-performance") - async def performance( - self, - ctx: disnake.AppCmdInter, - economy_group: str = commands.Param(choices=group), - ): - """Performance of sectors, industry, country [Finviz] - - Parameters - ----------- - economy_group: Economy Group - """ - await ctx.response.defer() - logger.info("econ-performance") - await performance_command(ctx, economy_group) - - -def setup(bot: commands.Bot): - bot.add_cog(EconomyCommands(bot)) diff --git a/discordbot/economy/currencies.py b/discordbot/economy/currencies.py deleted file mode 100644 index 6a177f73dcb..00000000000 --- a/discordbot/economy/currencies.py +++ /dev/null @@ -1,99 +0,0 @@ -import os - -import df2img -import disnake -import pandas as pd -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.economy import wsj_model - - -async def currencies_command(ctx): - """Currencies overview [Wall St. Journal]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-currencies") - - # Retrieve data - df = wsj_model.global_currencies() - df = pd.DataFrame.from_dict(df) - - # Check for argument - if df.empty: - raise Exception("No available data found") - - df["Last"] = pd.to_numeric(df["Last"].astype(float)) - df["Chng"] = pd.to_numeric(df["Chng"].astype(float)) - df["%Chng"] = pd.to_numeric(df["%Chng"].astype(float)) - - formats = {"Last": "{:.2f}", "Chng": "{:.2f}", "%Chng": "{:.2f}%"} - for col, value in formats.items(): - df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 - - df = df.fillna("") - df.set_index(" ", inplace=True) - - # Debug user output - if cfg.DEBUG: - logger.debug(df.to_string()) - - df = df[ - [ - "Last", - "Chng", - "%Chng", - ] - ] - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(800, (40 + (40 * dindex))), - col_width=[8, 3, 3], - tbl_cells=dict( - align="left", - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - - imagefile = "econ-currencies.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - title = "Economy: [WSJ] Currencies" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Economy: [WSJ] Currencies", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/economy/energy.py b/discordbot/economy/energy.py deleted file mode 100644 index 09c3aeacf03..00000000000 --- a/discordbot/economy/energy.py +++ /dev/null @@ -1,98 +0,0 @@ -import os - -import df2img -import disnake -import pandas as pd -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.economy import finviz_model - - -async def energy_command(ctx): - """Displays energy futures data [Finviz]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-energy") - - # Retrieve data - d_futures = finviz_model.get_futures() - df = pd.DataFrame(d_futures["Energy"]) - - # Check for argument - if df.empty: - raise Exception("No available data found") - - formats = {"last": "${:.2f}", "prevClose": "${:.2f}"} - for col, value in formats.items(): - df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 - - df = df.sort_values(by="ticker", ascending=False) - - # Debug user output - if cfg.DEBUG: - logger.debug(df.to_string()) - - df = df.fillna("") - df.set_index("label", inplace=True) - - df = df[ - [ - "prevClose", - "last", - "change", - ] - ] - df.index.names = [""] - df = df.rename( - columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"} - ) - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(800, (40 + (40 * dindex))), - col_width=[8, 3, 2], - tbl_cells=dict( - align="left", - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "econ-energy.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - image = disnake.File(imagefile) - title = "Economy: [Finviz] Energy Futures" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Economy: [Finviz] Energy Futures", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/economy/feargreed.py b/discordbot/economy/feargreed.py deleted file mode 100644 index 2cec7090f94..00000000000 --- a/discordbot/economy/feargreed.py +++ /dev/null @@ -1,88 +0,0 @@ -import datetime -import os - -import disnake -import matplotlib.pyplot as plt - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from gamestonk_terminal.economy import cnn_model, cnn_view - - -async def feargreed_command(ctx, indicator=""): - """CNN Fear and Greed Index [CNN]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-futures") - - # Check for argument - possible_indicators = ("", "jbd", "mv", "pco", "mm", "sps", "spb", "shd") - - if indicator not in possible_indicators: - raise Exception( - f"Select a valid indicator from {', '.join(possible_indicators)}" # nosec - ) - - # Retrieve data - fig = plt.figure(figsize=[1, 1], dpi=10) - - report, _ = cnn_model.get_feargreed_report(indicator, fig) - cnn_view.fear_and_greed_index(indicator=indicator, export="png") - plt.close("all") - - # Output data - now = datetime.datetime.now() - image_path = os.path.join( - cfg.GST_PATH, - "exports", - "economy", - f"feargreed_{now.strftime('%Y%m%d_%H%M%S')}.png", - ) - - i = 0 - while not os.path.exists(image_path) and i < 10: - now -= datetime.timedelta(seconds=1) - image_path = os.path.join( - cfg.GST_PATH, - "exports", - "economy", - f"feargreed_{now.strftime('%Y%m%d_%H%M%S')}.png", - ) - i += 1 - - embed = disnake.Embed( - title="Economy: [CNN] Fear Geed Index", - description=report, - colour=cfg.COLOR, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - if os.path.exists(image_path): - uploaded_image = gst_imgur.upload_image( - image_path, title="FearGreed Charts" - ) - embed.set_image(url=uploaded_image.link) - - else: - logger.error("Error when uploading the the image to Imgur.") - - await ctx.send(embed=embed) - - except Exception as e: - logger.error("ERROR Economy: [CNN] Feargreed. %s", e) - embed = disnake.Embed( - title="ERROR Economy: [CNN] Feargreed", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/economy/futures.py b/discordbot/economy/futures.py deleted file mode 100644 index 4539ef0936a..00000000000 --- a/discordbot/economy/futures.py +++ /dev/null @@ -1,95 +0,0 @@ -import os - -import df2img -import disnake -import pandas as pd -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.economy import wsj_model - - -async def futures_command(ctx): - """Futures and commodities overview [Wall St. Journal]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-futures") - - # Retrieve data - df = wsj_model.top_commodities() - - # Check for argument - if df.empty: - raise Exception("No available data found") - - df["Price"] = pd.to_numeric(df["Price"].astype(float)) - df["Chg"] = pd.to_numeric(df["Chg"].astype(float)) - df["%Chg"] = pd.to_numeric(df["%Chg"].astype(float)) - - # Debug user output - if cfg.DEBUG: - logger.debug(df.to_string()) - - formats = {"Price": "${:.2f}", "Chg": "${:.2f}", "%Chg": "{:.2f}%"} - for col, value in formats.items(): - df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 - - df = df.fillna("") - df.set_index(" ", inplace=True) - - df = df[ - [ - "Price", - "Chg", - "%Chg", - ] - ] - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(800, (40 + (40 * dindex))), - col_width=[8, 3, 3], - tbl_cells=dict( - align="left", - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "econ-futures.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - image = disnake.File(imagefile) - title = "Economy: [WSJ] Futures/Commodities" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Economy: [WSJ] Futures/Commodities", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/economy/glbonds.py b/discordbot/economy/glbonds.py deleted file mode 100644 index c6bf2ba36b0..00000000000 --- a/discordbot/economy/glbonds.py +++ /dev/null @@ -1,102 +0,0 @@ -import os - -import df2img -import disnake -import pandas as pd -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.economy import wsj_model - - -async def glbonds_command(ctx): - """Global bonds overview [Wall St. Journal]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-glbonds") - - # Retrieve data - df = wsj_model.global_bonds() - - # Check for argument - if df.empty: - raise Exception("No available data found") - - df["Rate (%)"] = pd.to_numeric(df["Rate (%)"].astype(float)) - df["Yld (%)"] = pd.to_numeric(df["Yld (%)"].astype(float)) - df["Yld Chg (%)"] = pd.to_numeric(df["Yld Chg (%)"].astype(float)) - - formats = { - "Rate (%)": "{:.2f}%", - "Yld (%)": "{:.2f}%", - "Yld Chg (%)": "{:.2f}%", - } - for col, value in formats.items(): - df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 - - # Debug user output - if cfg.DEBUG: - logger.debug(df.to_string()) - - df = df.fillna("") - df = df.replace( - ("Government Bond", "10 Year"), ("Gov .Bond", "10yr"), regex=True - ) - df.set_index(" ", inplace=True) - df = df.set_axis( - [ - "Rate", - "Yld", - "Yld Chg", - ], - axis="columns", - ) - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(800, (40 + (40 * dindex))), - col_width=[8, 3, 3], - tbl_cells=dict( - align="left", - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "econ-glbonds.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - image = disnake.File(imagefile) - title = "Economy: [WSJ] Global Bonds" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Economy: [WSJ] Global Bonds", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/economy/grains.py b/discordbot/economy/grains.py deleted file mode 100644 index 1702bb2b1c6..00000000000 --- a/discordbot/economy/grains.py +++ /dev/null @@ -1,97 +0,0 @@ -import os - -import df2img -import disnake -import pandas as pd -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.economy import finviz_model - - -async def grains_command(ctx): - """Displays grains futures data [Finviz]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-grains") - - # Retrieve data - d_futures = finviz_model.get_futures() - df = pd.DataFrame(d_futures["Grains"]) - - # Check for argument - if df.empty: - raise Exception("No available data found") - - formats = {"last": "${:.2f}", "prevClose": "${:.2f}"} - for col, value in formats.items(): - df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 - - df = df.fillna("") - df = df.set_index("label") - df = df.sort_values(by="ticker", ascending=False) - - # Debug user output - if cfg.DEBUG: - logger.debug(df) - - df = df[ - [ - "prevClose", - "last", - "change", - ] - ] - df.index.names = [""] - df = df.rename( - columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"} - ) - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(800, (40 + (40 * dindex))), - col_width=[6, 3, 3], - tbl_cells=dict( - align="left", - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "econ-grains.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - image = disnake.File(imagefile) - title = "Economy: [Finviz] Grains Futures" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Economy: [Finviz] Grains Futures", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/economy/indices.py b/discordbot/economy/indices.py deleted file mode 100644 index 8f3dbe54a12..00000000000 --- a/discordbot/economy/indices.py +++ /dev/null @@ -1,96 +0,0 @@ -import os - -import df2img -import disnake -import pandas as pd -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.economy import wsj_model - - -async def indices_command(ctx): - """US indices overview [Wall St. Journal]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-indices") - - # Retrieve data - df = wsj_model.us_indices() - - # Check for argument - if df.empty: - raise Exception("No available data found") - - df["Price"] = pd.to_numeric(df["Price"].astype(float)) - df["Chg"] = pd.to_numeric(df["Chg"].astype(float)) - df["%Chg"] = pd.to_numeric(df["%Chg"].astype(float)) - - formats = {"Price": "${:.2f}", "Chg": "${:.2f}", "%Chg": "{:.2f}%"} - for col, value in formats.items(): - df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 - - # Debug user output - if cfg.DEBUG: - logger.debug(df) - - df = df[ - [ - "Price", - "Chg", - "%Chg", - ] - ] - - df = df.fillna("") - df.set_index(" ", inplace=True) - - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(800, (40 + (40 * dindex))), - col_width=[8, 3, 3], - tbl_cells=dict( - align=["left", "center"], - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "econ-indices.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - image = disnake.File(imagefile) - title = "Economy: [WSJ] US Indices" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Economy: [WSJ] US Indices", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/economy/meats.py b/discordbot/economy/meats.py deleted file mode 100644 index 171d059171d..00000000000 --- a/discordbot/economy/meats.py +++ /dev/null @@ -1,97 +0,0 @@ -import os - -import df2img -import disnake -import pandas as pd -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.economy import finviz_model - - -async def meats_command(ctx): - """Displays meats futures data [Finviz]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-meats") - - # Retrieve data - d_futures = finviz_model.get_futures() - df = pd.DataFrame(d_futures["Meats"]) - - # Check for argument - if df.empty: - raise Exception("No available data found") - - df = df.fillna("") - df = df.set_index("label") - df = df.sort_values(by="ticker", ascending=False) - - formats = {"last": "${:.2f}", "prevClose": "${:.2f}"} - for col, value in formats.items(): - df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 - - # Debug user output - if cfg.DEBUG: - logger.debug(df) - - df = df[ - [ - "prevClose", - "last", - "change", - ] - ] - df.index.names = [""] - df = df.rename( - columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"} - ) - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(800, (40 + (40 * dindex))), - col_width=[6, 3, 3], - tbl_cells=dict( - align="left", - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "econ-meats.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - image = disnake.File(imagefile) - title = "Economy: [Finviz] Meats Futures" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Economy: [Finviz] Meats Futures", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/economy/metals.py b/discordbot/economy/metals.py deleted file mode 100644 index b11ad4abc51..00000000000 --- a/discordbot/economy/metals.py +++ /dev/null @@ -1,102 +0,0 @@ -import os - -import df2img -import disnake -import pandas as pd -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.economy import finviz_model - - -async def metals_command(ctx): - """Displays metals futures data [Finviz]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-metals") - - # Retrieve data - d_futures = finviz_model.get_futures() - df = pd.DataFrame(d_futures["Metals"]) - - # Check for argument - if df.empty: - raise Exception("No available data found") - - formats = {"last": "${:.2f}", "prevClose": "${:.2f}"} - for col, value in formats.items(): - df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 - - # Debug user output - if cfg.DEBUG: - logger.debug(df) - - df = df.sort_values(by="ticker", ascending=False) - df = df.fillna("") - df.set_index("label", inplace=True) - - df = df[ - [ - "prevClose", - "last", - "change", - ] - ] - - df.index.names = [""] - df = df.rename( - columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"} - ) - - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(800, (40 + (40 * dindex))), - col_width=[6, 3, 3], - tbl_cells=dict( - align="left", - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "econ-metals.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - title = "Economy: [Finviz] Metals Futures" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Economy: [Finviz] Metals Futures", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/economy/overview.py b/discordbot/economy/overview.py deleted file mode 100644 index 3fbeba9d780..00000000000 --- a/discordbot/economy/overview.py +++ /dev/null @@ -1,95 +0,0 @@ -import os - -import df2img -import disnake -import pandas as pd -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.economy import wsj_model - - -async def overview_command(ctx): - """Market data overview [Wall St. Journal]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-overview") - - # Retrieve data - df = wsj_model.market_overview() - - # Check for argument - if df.empty: - raise Exception("No available data found") - - df["Price"] = pd.to_numeric(df["Price"].astype(float)) - df["Chg"] = pd.to_numeric(df["Chg"].astype(float)) - df["%Chg"] = pd.to_numeric(df["%Chg"].astype(float)) - - formats = {"Price": "${:.2f}", "Chg": "${:.2f}", "%Chg": "{:.2f}%"} - for col, v in formats.items(): - df[col] = df[col].map(lambda x: v.format(x)) # pylint: disable=W0640 - - df = df[ - [ - "Price", - "Chg", - "%Chg", - ] - ] - - df = df.fillna("") - df.set_index(" ", inplace=True) - - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(800, (40 + (40 * dindex))), - col_width=[8, 3, 3], - tbl_cells=dict( - align="left", - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "econ-indices.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - title = "Economy: [WSJ] Market Overview" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Economy: [WSJ] Market Overview", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/economy/performance.py b/discordbot/economy/performance.py deleted file mode 100644 index 4c7a15bb374..00000000000 --- a/discordbot/economy/performance.py +++ /dev/null @@ -1,151 +0,0 @@ -import os - -import df2img -import disnake -import numpy as np -import pandas as pd -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.economy import finviz_model - - -async def performance_command(ctx, economy_group="sector"): - """Performance of sectors, industry, country [Finviz]""" - - d_economy_group = { - "sector": "Sector", - "industry": "Industry", - "basic_materials": "Industry (Basic Materials)", - "communication_services": "Industry (Communication Services)", - "consumer_cyclical": "Industry (Consumer Cyclical)", - "consumer_defensive": "Industry (Consumer Defensive)", - "energy": "Industry (Energy)", - "financial": "Industry (Financial)", - "healthcare": "Industry (Healthcare)", - "industrials": "Industry (Industrials)", - "real_estate": "Industry (Real Estate)", - "technology": "Industry (Technology)", - "utilities": "Industry (Utilities)", - "country": "Country (U.S. listed stocks only)", - "capitalization": "Capitalization", - } - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-performance %s", economy_group) - - # Select default group - if not economy_group: - if cfg.DEBUG: - logger.debug("Use default economy_group: 'sector'") - economy_group = "sector" - - # Check for argument - possible_groups = list(d_economy_group.keys()) - - if economy_group not in possible_groups: - possible_group_list = ", ".join(possible_groups) - raise Exception(f"Select a valid group from {possible_group_list}") # nosec - - group = d_economy_group[economy_group] - - # Retrieve data - df_group = finviz_model.get_valuation_performance_data(group, "performance") - - # Check for argument - if df_group.empty: - raise Exception("No available data found") - - # Output data - df = pd.DataFrame(df_group) - df = df.replace(np.nan, 0) - - df["Volume"] = df["Volume"] / 1_000_000 - df["Avg Volume"] = df["Avg Volume"] / 1_000_000 - - formats = { - "Perf Month": "{:.2f}", - "Perf Quart": "{:.2f}", - "Perf Half": "{:.2f}", - "Perf Year": "{:.2f}", - "Perf YTD": "{:.2f}", - "Avg Volume": "{:.0f}M", - "Change": "{:.2f}", - "Volume": "{:.0f}M", - } - for col, value in formats.items(): - df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 - - df = df.set_axis( - [ - "Name", - "Week", - "Month", - "3Month", - "6Month", - "1Year", - "YTD", - "Recom", - "Avg Vol.", - "RelVolume", - "Change", - "Volume", - ], - axis="columns", - ) - - df = df.fillna("") - df.set_index("Name", inplace=True) - - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(1500, (40 + (50 * dindex))), - col_width=[10, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3.5], - tbl_cells=dict( - align=["left", "center"], - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "econ-performance.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - title = f"Economy: [Finviz] Performance {group}" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Economy: [Finviz] Performance", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/economy/softs.py b/discordbot/economy/softs.py deleted file mode 100644 index 491396131b5..00000000000 --- a/discordbot/economy/softs.py +++ /dev/null @@ -1,102 +0,0 @@ -import os - -import df2img -import disnake -import pandas as pd -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.economy import finviz_model - - -async def softs_command(ctx): - """Displays softs futures data [Finviz]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-softs") - - # Retrieve data - d_futures = finviz_model.get_futures() - df = pd.DataFrame(d_futures["Softs"]) - - # Check for argument - if df.empty: - raise Exception("No available data found") - - formats = {"last": "${:.2f}", "prevClose": "${:.2f}"} - for col, value in formats.items(): - df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 - - # Debug user output - if cfg.DEBUG: - logger.debug(df) - - df = df.sort_values(by="ticker", ascending=False) - df = df.fillna("") - df.set_index("label", inplace=True) - - df = df[ - [ - "prevClose", - "last", - "change", - ] - ] - - df.index.names = [""] - df = df.rename( - columns={"prevClose": "PrevClose", "last": "Last", "change": "Change"} - ) - - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(800, (40 + (40 * dindex))), - col_width=[6, 3, 3], - tbl_cells=dict( - align="left", - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "econ-softs.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - title = "Economy: [Finviz] Softs Futures" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Economy: [Finviz] Softs Futures", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/economy/usbonds.py b/discordbot/economy/usbonds.py deleted file mode 100644 index c4652530390..00000000000 --- a/discordbot/economy/usbonds.py +++ /dev/null @@ -1,100 +0,0 @@ -import os - -import df2img -import disnake -import pandas as pd -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.economy import wsj_model - - -async def usbonds_command(ctx): - """US bonds overview [Wall St. Journal]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-usbonds") - - # Retrieve data - df = wsj_model.us_bonds() - - # Check for argument - if df.empty: - raise Exception("No available data found") - - df["Rate (%)"] = pd.to_numeric(df["Rate (%)"].astype(float)) - df["Yld (%)"] = pd.to_numeric(df["Yld (%)"].astype(float)) - df["Yld Chg (%)"] = pd.to_numeric(df["Yld Chg (%)"].astype(float)) - - formats = { - "Rate (%)": "{:.2f}%", - "Yld (%)": "{:.2f}%", - "Yld Chg (%)": "{:.2f}%", - } - for col, value in formats.items(): - df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 - - df = df.fillna("") - df.set_index(" ", inplace=True) - - df = df.set_axis( - [ - "Rate", - "Yld", - "Yld Chg", - ], - axis="columns", - ) - - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(800, (40 + (40 * dindex))), - col_width=[8, 3, 3], - tbl_cells=dict( - align="left", - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "econ-usbonds.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - title = "Economy: [WSJ] US Bonds" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Economy: [WSJ] US Bonds", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/economy/valuation.py b/discordbot/economy/valuation.py deleted file mode 100644 index 6b69b8cc660..00000000000 --- a/discordbot/economy/valuation.py +++ /dev/null @@ -1,158 +0,0 @@ -import os - -import df2img -import disnake -import numpy as np -import pandas as pd -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.economy import finviz_model - - -async def valuation_command(ctx, economy_group="sector"): - """Valuation of sectors, industry, country [Finviz]""" - - d_economy_group = { - "sector": "Sector", - "industry": "Industry", - "basic_materials": "Industry (Basic Materials)", - "communication_services": "Industry (Communication Services)", - "consumer_cyclical": "Industry (Consumer Cyclical)", - "consumer_defensive": "Industry (Consumer Defensive)", - "energy": "Industry (Energy)", - "financial": "Industry (Financial)", - "healthcare": "Industry (Healthcare)", - "industrials": "Industry (Industrials)", - "real_estate": "Industry (Real Estate)", - "technology": "Industry (Technology)", - "utilities": "Industry (Utilities)", - "country": "Country (U.S. listed stocks only)", - "capitalization": "Capitalization", - } - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("econ-valuation %s", economy_group) - - # Select default group - if economy_group == "": - if cfg.DEBUG: - logger.debug("Use default economy_group: 'sector'") - economy_group = "sector" - - # Check for argument - possible_groups = list(d_economy_group.keys()) - - if economy_group not in possible_groups: - raise Exception( - f"Select a valid group from {', '.join(possible_groups)}" # nosec - ) - - group = d_economy_group[economy_group] - - # Retrieve data - df_group = finviz_model.get_valuation_performance_data(group, "valuation") - - # Check for argument - if df_group.empty: - raise Exception("No available data found") - - # Output data - df = pd.DataFrame(df_group) - df = df.replace(np.nan, 0) - - df = df.set_axis( - [ - "Name", - "MarketCap", - "P/E", - "FwdP/E", - "PEG", - "P/S", - "P/B", - "P/C", - "P/FCF", - "EPSpast5Y", - "EPSnext5Y", - "Salespast5Y", - "Change", - "Volume", - ], - axis="columns", - ) - - df["P/E"] = pd.to_numeric(df["P/E"].astype(float)) - df["FwdP/E"] = pd.to_numeric(df["FwdP/E"].astype(float)) - df["EPSpast5Y"] = pd.to_numeric(df["EPSpast5Y"].astype(float)) - df["EPSnext5Y"] = pd.to_numeric(df["EPSnext5Y"].astype(float)) - df["Salespast5Y"] = pd.to_numeric(df["Salespast5Y"].astype(float)) - df["Volume"] = pd.to_numeric(df["Volume"].astype(float)) - df["Volume"] = df["Volume"] / 1_000_000 - - formats = { - "P/E": "{:.2f}", - "FwdP/E": "{:.2f}", - "EPSpast5Y": "{:.2f}", - "EPSnext5Y": "{:.2f}", - "Salespast5Y": "{:.2f}", - "Change": "{:.2f}", - "Volume": "{:.0f}M", - } - for col, value in formats.items(): - df[col] = df[col].map(lambda x: value.format(x)) # pylint: disable=W0640 - - df = df.fillna("") - df.set_index("Name", inplace=True) - - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(1600, (40 + (50 * dindex))), - col_width=[12, 5, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 4, 4], - tbl_cells=dict( - align=["left", "center"], - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "econ-valuation.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - title = f"Economy: [Finviz] Valuation {group}" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Economy: [Finviz] Valuation", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/helpers.py b/discordbot/helpers.py deleted file mode 100644 index 1c5f892cea9..00000000000 --- a/discordbot/helpers.py +++ /dev/null @@ -1,19 +0,0 @@ -from PIL import Image -import yfinance as yf - - -def load(ticker, start_date): - df_stock_candidate = yf.download(ticker, start=start_date, progress=False) - df_stock_candidate.index.name = "date" - return df_stock_candidate - - -def autocrop_image(image, border=0): - bbox = image.getbbox() - image = image.crop(bbox) - (width, height) = image.size - width += border * 2 - height += border * 2 - cropped_image = Image.new("RGBA", (width, height), (0, 0, 0, 0)) - cropped_image.paste(image, (border, border)) - return cropped_image diff --git a/discordbot/reaction_helper.py b/discordbot/reaction_helper.py deleted file mode 100644 index 6c5a4e9ad56..00000000000 --- a/discordbot/reaction_helper.py +++ /dev/null @@ -1,127 +0,0 @@ -import asyncio -import discord - -from gamestonk_terminal.config_terminal import TRADIER_TOKEN -from gamestonk_terminal.stocks.options import tradier_model, yfinance_model - -import discordbot.config_discordbot as cfg -from discordbot.run_discordbot import gst_bot, logger - - -async def expiry_dates_reaction(ctx, ticker, expiry, func_cmd, call_arg: tuple = None): - if TRADIER_TOKEN == "REPLACE_ME": # nosec - 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, delete_after=10.0) - 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, delete_after=10.0) - 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, delete_after=15.0) - - 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]: - logger.info("Reaction selected: %d", 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) - if cfg.DEBUG: - 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, delete_after=10.0) diff --git a/discordbot/stocks/candle.py b/discordbot/stocks/candle.py deleted file mode 100644 index c8027115840..00000000000 --- a/discordbot/stocks/candle.py +++ /dev/null @@ -1,240 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -import yfinance as yf -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import overlap_model - - -async def candle_command( - ctx, - ticker: str = "", - interval: int = 15, - past_days: int = 1, - start="", - end="", -): - """Shows candle plot of loaded ticker. [Source: Yahoo Finance, IEX Cloud or Alpha Vantage] - - Parameters - ---------- - ticker: str - Ticker name - interval: int - chart mins or daily - past_days: int - Display the past * days. Default: 1(Not for Daily) - start: str - start date format YYYY-MM-DD - end: str - end date format YYYY-MM-DD - """ - try: - - logger.info("candle %s %s %s %s", ticker, interval, start, end) - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - futures = "=F" - if interval == 1440: - df_stock_candidate = yf.download( - ticker, - start=start, - end=end, - progress=False, - ) - - # Check that loading a stock was not successful - if df_stock_candidate.empty: - raise Exception(f"No data found for {ticker.upper()}") - - df_stock_candidate.index.name = "date" - else: - s_int = str(interval) + "m" - d_granularity = { - "1m": past_days, - "5m": past_days, - "15m": past_days, - "30m": past_days, - "60m": past_days, - } - s_start_dt = datetime.utcnow() - timedelta(days=d_granularity[s_int]) - s_date_start = s_start_dt.strftime("%Y-%m-%d") - - df_stock_candidate = yf.download( - ticker, - start=s_date_start - if s_start_dt > start - else start.strftime("%Y-%m-%d"), - progress=False, - interval=s_int, - prepost=True, - ) - - # Check that loading a stock was not successful - if df_stock_candidate.empty: - raise Exception(f"No data found for {ticker.upper()}.") - - df_stock_candidate.index = df_stock_candidate.index.tz_localize(None) - df_stock_candidate.index.name = "date" - df_stock = df_stock_candidate - price_df = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - - df_vwap = overlap_model.vwap(price_df, 0) - - plt_title = [f"{ticker.upper()} Intraday {interval}min", "Volume"] - title = f"Intraday {interval}min Chart for {ticker.upper()}" - if interval == 1440: - plt_title = [f"{ticker.upper()} Daily", "Volume"] - title = f"Daily Chart for {ticker.upper()}" - - fig = make_subplots( - rows=2, - cols=1, - shared_xaxes=True, - vertical_spacing=0.06, - subplot_titles=plt_title, - row_width=[0.2, 0.7], - ) - fig.add_trace( - go.Candlestick( - x=df_stock.index, - open=df_stock.Open, - high=df_stock.High, - low=df_stock.Low, - close=df_stock.Close, - name="OHLC", - showlegend=False, - ), - row=1, - col=1, - ) - colors = [ - "green" if row.Open < row["Adj Close"] else "red" - for _, row in df_stock.iterrows() - ] - fig.add_trace( - go.Bar( - x=df_stock.index, - y=df_stock.Volume, - name="Volume", - marker_color=colors, - showlegend=False, - ), - row=2, - col=1, - ) - fig.update_layout( - margin=dict(l=0, r=0, t=25, b=20), - template=cfg.PLT_CANDLE_STYLE_TEMPLATE, - yaxis_title="Stock Price ($)", - yaxis=dict( - fixedrange=False, - showspikes=True, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - showspikes=True, - ), - legend=dict( - orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 - ), - dragmode="pan", - hovermode="x unified", - ) - if interval != 1440: - fig.add_trace( - go.Scatter( - name="VWAP", - x=df_stock.index, - y=df_vwap["VWAP_D"], - opacity=1, - line=dict(color="#d81aea", width=2), - showlegend=True, - ), - ) - if futures in ticker.upper(): - fig.update_xaxes( - rangebreaks=[ - dict(bounds=["fri", "sun"]), - dict(bounds=[17, 17.5], pattern="hour"), - ], - ) - else: - fig.update_xaxes( - rangebreaks=[ - dict(bounds=["sat", "mon"]), - dict(bounds=[19, 9.5], pattern="hour"), - ], - ) - config = dict({"scrollZoom": True}) - rand = random.randint(69, 69420) - imagefile = f"candle{rand}.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - fig.write_html(f"in/candle_{rand}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/candle_{rand}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Intraday", - colour=cfg.COLOR, - description=e, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/dark_pool_shorts/dpotc.py b/discordbot/stocks/dark_pool_shorts/dpotc.py deleted file mode 100644 index 87b4f35a144..00000000000 --- a/discordbot/stocks/dark_pool_shorts/dpotc.py +++ /dev/null @@ -1,165 +0,0 @@ -import os - -import disnake -import matplotlib.dates as mdates -import yfinance as yf -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.dark_pool_shorts import finra_model - - -async def dpotc_command(ctx, ticker: str = ""): - """Dark pools (ATS) vs OTC data [FINRA]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.dps.dpotc %s", ticker) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - ticker = ticker.upper() - - stock = yf.download(ticker, progress=False) - if stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve data - ats, otc = finra_model.getTickerFINRAdata(ticker) - - # Debug user output - if cfg.DEBUG: - logger.debug(ats.to_string()) - logger.debug(otc.to_string()) - - # Output data - title = f"Stocks: [FINRA] Dark Pools (ATS) vs OTC {ticker}" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - plt.style.use("seaborn") - plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - if ats.empty and otc.empty: - raise Exception("Stock ticker is invalid") - _, _ = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - - plt.subplot(3, 1, (1, 2)) - if not ats.empty and not otc.empty: - plt.bar( - ats.index, - (ats["totalWeeklyShareQuantity"] + otc["totalWeeklyShareQuantity"]) - / 1_000_000, - color="tab:orange", - ) - plt.bar( - otc.index, - otc["totalWeeklyShareQuantity"] / 1_000_000, - color="tab:blue", - ) - plt.legend(["ATS", "OTC"]) - - elif not ats.empty: - plt.bar( - ats.index, - ats["totalWeeklyShareQuantity"] / 1_000_000, - color="tab:orange", - ) - plt.legend(["ATS"]) - - elif not otc.empty: - plt.bar( - otc.index, - otc["totalWeeklyShareQuantity"] / 1_000_000, - color="tab:blue", - ) - plt.legend(["OTC"]) - - plt.ylabel("Total Weekly Shares [Million]") - plt.grid(b=True, which="major", color="#666666", linestyle="-", alpha=0.2) - plt.title(f"Dark Pools (ATS) vs OTC (Non-ATS) Data for {ticker}") - - plt.subplot(313) - if not ats.empty: - plt.plot( - ats.index, - ats["totalWeeklyShareQuantity"] / ats["totalWeeklyTradeCount"], - color="tab:orange", - ) - plt.legend(["ATS"]) - - if not otc.empty: - plt.plot( - otc.index, - otc["totalWeeklyShareQuantity"] / otc["totalWeeklyTradeCount"], - color="tab:blue", - ) - plt.legend(["ATS", "OTC"]) - - else: - plt.plot( - otc.index, - otc["totalWeeklyShareQuantity"] / otc["totalWeeklyTradeCount"], - color="tab:blue", - ) - plt.legend(["OTC"]) - - plt.ylabel("Shares per Trade") - plt.grid(b=True, which="major", color="#666666", linestyle="-", alpha=0.2) - plt.gcf().autofmt_xdate() - plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%m/%d")) - plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=4)) - plt.xlabel("Weeks") - file_name = ticker + "_dpotc.png" - plt.savefig(file_name) - imagefile = file_name - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - plt.close("all") - uploaded_image = gst_imgur.upload_image(file_name, title="something") - image_link = uploaded_image.link - embed.set_image(url=image_link) - os.remove(file_name) - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title=f"ERROR Stocks: [FINRA] Dark Pools (ATS) vs OTC {ticker}", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/dark_pool_shorts/ftd.py b/discordbot/stocks/dark_pool_shorts/ftd.py deleted file mode 100644 index a10e9eecf73..00000000000 --- a/discordbot/stocks/dark_pool_shorts/ftd.py +++ /dev/null @@ -1,170 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.stocks.dark_pool_shorts import sec_model - - -async def ftd_command(ctx, ticker: str = "", start="", end=""): - """Fails-to-deliver data [SEC]""" - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.dps.ftd %s %s %s", ticker, start, end) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - ticker = ticker.upper() - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - # Retrieve data - ftds_data = sec_model.get_fails_to_deliver(ticker, start, end, 0) - - # Debug user output - if cfg.DEBUG: - logger.debug(ftds_data.to_string()) - - stock = discordbot.helpers.load(ticker, start) - stock_ftd = stock[stock.index > start] - stock_ftd = stock_ftd[stock_ftd.index < end] - - # Output data - fig = make_subplots(shared_xaxes=True, specs=[[{"secondary_y": True}]]) - - fig.add_trace( - go.Scatter( - name=ticker, - x=stock_ftd.index, - y=stock_ftd["Adj Close"], - line=dict(color="#fdc708", width=2), - opacity=1, - showlegend=False, - ), - secondary_y=False, - ) - fig.add_trace( - go.Bar( - name="FTDs", - x=ftds_data["SETTLEMENT DATE"], - y=ftds_data["QUANTITY (FAILS)"] / 1000, - opacity=1, - ), - secondary_y=True, - ) - # Set y-axes titles - fig.update_yaxes(title_text="Shares [K]", secondary_y=True) - fig.update_layout( - margin=dict(l=0, r=20, t=30, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker}", - title_x=0.5, - yaxis_title="Stock Price ($)", - yaxis=dict( - side="right", - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - fixedrange=False, - ), - xaxis2=dict( - rangeslider=dict(visible=False), - type="date", - fixedrange=False, - ), - dragmode="pan", - legend=dict( - orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 - ), - yaxis2=dict( - side="left", - position=0.15, - fixedrange=False, - ), - hovermode="x unified", - ) - config = dict({"scrollZoom": True}) - imagefile = "dps_ftd.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/ftds_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/ftds_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = "Stocks: [SEC] Failure-to-deliver " + ticker - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [SEC] Failure-to-deliver", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/dark_pool_shorts/hsi.py b/discordbot/stocks/dark_pool_shorts/hsi.py deleted file mode 100644 index 3a137198748..00000000000 --- a/discordbot/stocks/dark_pool_shorts/hsi.py +++ /dev/null @@ -1,82 +0,0 @@ -import disnake -from menus.menu import Menu - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from gamestonk_terminal.stocks.dark_pool_shorts import shortinterest_model - - -async def hsi_command(ctx, num: int = 10): - """Show top high short interest stocks of over 20% ratio [shortinterest.com]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.dps.hsi %s", num) - - # Check for argument - if num < 0: - raise Exception("Number has to be above 0") - - # Retrieve data - df = shortinterest_model.get_high_short_interest() - df = df.iloc[1:].head(n=num) - - # Debug user output - if cfg.DEBUG: - logger.debug(df.to_string()) - - # Output data - future_column_name = df["Ticker"] - df = df.transpose() - df.columns = future_column_name - df.drop("Ticker") - columns = [] - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - initial_str = "Overview" - i = 1 - for col_name in df.columns.values: - menu = f"\nPage {i}: {col_name}" - initial_str += f"\nPage {i}: {col_name}" - choices.append( - disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), - ) - i += 1 - columns.append( - disnake.Embed( - title="Stocks: [highshortinterest.com] Top High Short Interest", - description=initial_str, - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - for column in df.columns.values: - columns.append( - disnake.Embed( - title="Stocks: [highshortinterest.com] Top High Short Interest", - description="```" + df[column].fillna("").to_string() + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [highshortinterest.com] Top High Short Interest", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/dark_pool_shorts/pos.py b/discordbot/stocks/dark_pool_shorts/pos.py deleted file mode 100644 index 2cb7fbbde7d..00000000000 --- a/discordbot/stocks/dark_pool_shorts/pos.py +++ /dev/null @@ -1,113 +0,0 @@ -import disnake -from menus.menu import Menu - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from gamestonk_terminal.stocks.dark_pool_shorts import stockgrid_model - - -async def pos_command(ctx, sort="dpp_dollar", num: int = 10): - """Dark pool short position [Stockgrid]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.dps.pos %s %s", sort, num) - - # Check for argument - possible_sorts = ("sv", "sv_pct", "nsv", "nsv_dollar", "dpp", "dpp_dollar") - - if sort not in possible_sorts: - raise Exception(f"The possible sorts are: {', '.join(possible_sorts)}") - - if num < 0: - raise Exception("Number has to be above 0") - - # Retrieve data - df = stockgrid_model.get_dark_pool_short_positions(sort, False) - df = df.iloc[:num] - - # Debug user output - if cfg.DEBUG: - logger.debug(df.to_string()) - - # Output data - dp_date = df["Date"].values[0] - df = df.drop(columns=["Date"]) - df["Net Short Volume $"] = df["Net Short Volume $"] / 100_000_000 - df["Short Volume"] = df["Short Volume"] / 1_000_000 - df["Net Short Volume"] = df["Net Short Volume"] / 1_000_000 - df["Short Volume %"] = df["Short Volume %"] * 100 - df["Dark Pools Position $"] = df["Dark Pools Position $"] / (1_000_000_000) - df["Dark Pools Position"] = df["Dark Pools Position"] / 1_000_000 - df.columns = [ - "Ticker", - "Short Vol. (1M)", - "Short Vol. %", - "Net Short Vol. (1M)", - "Net Short Vol. ($100M)", - "DP Position (1M)", - "DP Position ($1B)", - ] - future_column_name = df["Ticker"] - df = df.transpose() - df.columns = future_column_name - df.drop("Ticker") - columns = [] - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - initial_str = "Overview" - i = 1 - for col_name in df.columns.values: - menu = f"\nPage {i}: {col_name}" - initial_str += f"\nPage {i}: {col_name}" - if i < 19: - choices.append( - disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), - ) - if i == 20: - choices.append( - disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), - ) - i += 1 - columns.append( - disnake.Embed( - title="Stocks: [Stockgrid] Dark Pool Short Position", - description=initial_str, - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - for column in df.columns.values: - columns.append( - disnake.Embed( - title="High Short Interest", - description="```The following data corresponds to the date: " - + dp_date - + "\n\n" - + df[column].fillna("").to_string() - + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [Stockgrid] Dark Pool Short Position", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/dark_pool_shorts/psi.py b/discordbot/stocks/dark_pool_shorts/psi.py deleted file mode 100644 index 5d5e7ac0e04..00000000000 --- a/discordbot/stocks/dark_pool_shorts/psi.py +++ /dev/null @@ -1,148 +0,0 @@ -import os -from datetime import timedelta - -import disnake -import yfinance as yf -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.dark_pool_shorts import stockgrid_model - - -async def psi_command(ctx, ticker: str = ""): - """Price vs short interest volume [Stockgrid]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.dps.psi %s", ticker) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - ticker = ticker.upper() - - stock = yf.download(ticker, progress=False) - if stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve data - df, prices = stockgrid_model.get_short_interest_volume(ticker) - - # Debug user output - if cfg.DEBUG: - logger.debug(df.to_string()) - - # Output data - title = f"Stocks: [Stockgrid] Price vs Short Interest Volume {ticker}" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - plt.style.use("seaborn") - _, axes = plt.subplots( - 2, - 1, - figsize=plot_autoscale(), - dpi=PLOT_DPI, - gridspec_kw={"height_ratios": [2, 1]}, - ) - - axes[0].bar( - df["date"], - df["total_volume"] / 1_000_000, - width=timedelta(days=1), - color="b", - alpha=0.4, - label="Total Volume", - ) - axes[0].bar( - df["date"], - df["short_volume"] / 1_000_000, - width=timedelta(days=1), - color="r", - alpha=0.4, - label="Short Volume", - ) - - axes[0].set_ylabel("Volume (1M)") - ax2 = axes[0].twinx() - ax2.plot( - df["date"].values, - prices[len(prices) - len(df) :], # noqa: E203 - c="k", - label="Price", - ) - ax2.set_ylabel("Price ($)") - - lines, labels = axes[0].get_legend_handles_labels() - lines2, labels2 = ax2.get_legend_handles_labels() - ax2.legend(lines + lines2, labels + labels2, loc="upper left") - - axes[0].grid() - axes[0].ticklabel_format(style="plain", axis="y") - plt.title(f"Price vs Short Volume Interest for {ticker}") - plt.gcf().autofmt_xdate() - - axes[1].plot( - df["date"].values, - 100 * df["short_volume%"], - c="green", - label="Short Vol. %", - ) - - axes[1].set_ylabel("Short Vol. %") - - axes[1].grid(axis="y") - lines, labels = axes[1].get_legend_handles_labels() - axes[1].legend(lines, labels, loc="upper left") - axes[1].set_ylim([0, 100]) - file_name = ticker + "_psi.png" - plt.savefig(file_name) - imagefile = file_name - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - plt.close("all") - uploaded_image = gst_imgur.upload_image(file_name, title="something") - image_link = uploaded_image.link - embed.set_image(url=image_link) - os.remove(file_name) - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title=f"ERROR Stocks: [Stockgrid] Price vs Short Interest Volume {ticker}", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/dark_pool_shorts/shorted.py b/discordbot/stocks/dark_pool_shorts/shorted.py deleted file mode 100644 index 2bf75fc6deb..00000000000 --- a/discordbot/stocks/dark_pool_shorts/shorted.py +++ /dev/null @@ -1,88 +0,0 @@ -import disnake -from menus.menu import Menu - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from gamestonk_terminal.stocks.dark_pool_shorts import yahoofinance_model - - -async def shorted_command(ctx, num: int = 10): - """Show most shorted stocks [Yahoo Finance]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.dps.shorted %s", num) - - # Check for argument - if num < 0: - raise Exception("Number has to be above 0") - - # Retrieve data - df = yahoofinance_model.get_most_shorted().head(num) - - # Debug user output - if cfg.DEBUG: - logger.debug(df.to_string()) - - # Output data - df.dropna(how="all", axis=1, inplace=True) - df = df.replace(float("NaN"), "") - future_column_name = df["Symbol"] - df = df.transpose() - df.columns = future_column_name - df.drop("Symbol") - columns = [] - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - initial_str = "Overview" - i = 1 - for col_name in df.columns.values: - menu = f"\nPage {i}: {col_name}" - initial_str += f"\nPage {i}: {col_name}" - if i < 19: - choices.append( - disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), - ) - if i == 20: - choices.append( - disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), - ) - i += 1 - - columns.append( - disnake.Embed( - title="Stocks: [Yahoo Finance] Most Shorted", - description=initial_str, - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - for column in df.columns.values: - columns.append( - disnake.Embed( - description="```" + df[column].fillna("").to_string() + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [Yahoo Finance] Most Shorted", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/dark_pool_shorts/sidtc.py b/discordbot/stocks/dark_pool_shorts/sidtc.py deleted file mode 100644 index 9654f7d7b10..00000000000 --- a/discordbot/stocks/dark_pool_shorts/sidtc.py +++ /dev/null @@ -1,104 +0,0 @@ -import disnake -from menus.menu import Menu - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from gamestonk_terminal.stocks.dark_pool_shorts import stockgrid_model - - -async def sidtc_command(ctx, sort="float", num: int = 10): - """Short interest and days to cover [Stockgrid]""" - - try: - # Debug - if cfg.DEBUG: - logger.debug("!stocks.dps.sidtc %s %s", sort, num) - - # Check for argument - possible_sorts = ("float", "dtc", "si") - - if sort not in possible_sorts: - raise Exception(f"The possible sorts are: {', '.join(possible_sorts)}") - - if num < 0: - raise Exception("Number has to be above 0") - - # Retrieve data - df = stockgrid_model.get_short_interest_days_to_cover(sort) - df = df.iloc[:num] - - # Debug user output - if cfg.DEBUG: - logger.debug(df.to_string()) - - # Output data - dp_date = df["Date"].values[0] - df = df.drop(columns=["Date"]) - df["Short Interest"] = df["Short Interest"] / 1_000_000 - df.head() - df.columns = [ - "Ticker", - "Float Short %", - "Days to Cover", - "Short Interest (1M)", - ] - future_column_name = df["Ticker"] - df = df.transpose() - df.columns = future_column_name - df.drop("Ticker") - columns = [] - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - initial_str = "Overview" - i = 1 - for col_name in df.columns.values: - menu = f"\nPage {i}: {col_name}" - initial_str += f"\nPage {i}: {col_name}" - if i < 19: - choices.append( - disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), - ) - if i == 20: - choices.append( - disnake.SelectOption(label="Max Reached", value=f"{i}", emoji="🟢"), - ) - i += 1 - columns.append( - disnake.Embed( - title="Dark Pool Shorts", description=initial_str, colour=cfg.COLOR - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - for column in df.columns.values: - columns.append( - disnake.Embed( - title="Stocks: [Stockgrid] Short Interest and Days to Cover", - description="```The following data corresponds to the date: " - + dp_date - + "\n\n" - + df[column].fillna("").to_string() - + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [Stockgrid] Short Interest and Days to Cover", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/dark_pool_shorts/spos.py b/discordbot/stocks/dark_pool_shorts/spos.py deleted file mode 100644 index d7716878f02..00000000000 --- a/discordbot/stocks/dark_pool_shorts/spos.py +++ /dev/null @@ -1,119 +0,0 @@ -import os - -import disnake -import yfinance as yf -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.dark_pool_shorts import stockgrid_model - - -async def spos_command(ctx, ticker: str = ""): - """Net short vs position [Stockgrid]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.dps.spos %s", ticker) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - ticker = ticker.upper() - - stock = yf.download(ticker, progress=False) - if stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve data - df = stockgrid_model.get_net_short_position(ticker) - - # Debug user output - if cfg.DEBUG: - logger.debug(df.to_string()) - - # Output data - title = f"Stocks: [Stockgrid] Net Short vs Position {ticker}" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - plt.style.use("seaborn") - fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - - ax = fig.add_subplot(111) - ax.bar( - df["dates"], - df["dollar_net_volume"] / 1_000, - color="r", - alpha=0.4, - label="Net Short Vol. (1k $)", - ) - ax.set_ylabel("Net Short Vol. (1k $)") - - ax2 = ax.twinx() - ax2.plot( - df["dates"].values, - df["dollar_dp_position"], - c="tab:blue", - label="Position (1M $)", - ) - ax2.set_ylabel("Position (1M $)") - - lines, labels = ax.get_legend_handles_labels() - lines2, labels2 = ax2.get_legend_handles_labels() - ax2.legend(lines + lines2, labels + labels2, loc="upper left") - - ax.grid() - plt.title(f"Net Short Vol. vs Position for {ticker}") - plt.gcf().autofmt_xdate() - file_name = ticker + "_spos.png" - plt.savefig(file_name) - imagefile = file_name - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - plt.close("all") - uploaded_image = gst_imgur.upload_image(file_name, title="something") - image_link = uploaded_image.link - embed.set_image(url=image_link) - os.remove(file_name) - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title=f"ERROR Stocks: [Stockgrid] Net Short vs Position {ticker}", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/disc/ford.py b/discordbot/stocks/disc/ford.py deleted file mode 100644 index a764fd08065..00000000000 --- a/discordbot/stocks/disc/ford.py +++ /dev/null @@ -1,92 +0,0 @@ -import os - -import df2img -import disnake -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.stocks.discovery import fidelity_model - - -async def ford_command(ctx): - """Display Orders by Fidelity Customers. [Source: Fidelity] - - Parameters - ---------- - num: Number of stocks to display - """ - - try: - # Debug - if cfg.DEBUG: - logger.debug("!ford") - order_header, df_orders = fidelity_model.get_orders() - - df_orders = df_orders.head(n=30).iloc[:, :-1] - df_orders = df_orders.applymap(str) - - font_color = ( - ["white"] * 2 - + [ - [ - "#ad0000" if boolv else "#0d5700" - for boolv in df_orders["Price Change"].str.contains("-") - ] - ] - + [["white"] * 3] - ) - - df_orders.set_index("Symbol", inplace=True) - df_orders = df_orders.apply(lambda x: x.str.slice(0, 20)) - - dindex = len(df_orders.index) - fig = df2img.plot_dataframe( - df_orders, - fig_size=(1500, (40 + (40 * dindex))), - col_width=[1, 3, 2.2, 3, 2, 2], - tbl_cells=dict( - align=["left", "center", "left", "left", "center"], - font=dict(color=font_color), - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "disc-ford.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - title = "Fidelity Customer Orders" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Fidelity Customer Orders", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/due_diligence/analyst.py b/discordbot/stocks/due_diligence/analyst.py deleted file mode 100644 index 27131410617..00000000000 --- a/discordbot/stocks/due_diligence/analyst.py +++ /dev/null @@ -1,87 +0,0 @@ -import os - -import df2img -import disnake -import numpy as np -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.stocks.due_diligence import finviz_model - - -async def analyst_command(ctx, ticker=""): - """Displays analyst recommendations [Finviz]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug("!stocks.dd.analyst %s", ticker) - - # Check for argument - if not ticker: - raise Exception("Stock ticker is required") - - df = finviz_model.get_analyst_data(ticker) - df = df.replace(np.nan, 0) - df.index.names = ["Date"] - df = df.rename( - columns={ - "category": "Category", - "analyst": "Analyst", - "rating": "Rating", - "target": "Target", - "target_from": "Target From", - "target_to": "Target To", - } - ) - - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(1500, (40 + (40 * dindex))), - col_width=[5, 5, 8, 14, 5, 5, 5], - tbl_cells=dict( - height=35, - ), - font=dict( - family="Consolas", - size=20, - ), - template="plotly_dark", - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "dd-analyst.png" - - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - title = f"Stocks: [Finviz] Analyst Recommendations {ticker.upper()}" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [Finviz] Analyst Recommendations", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/due_diligence/arktrades.py b/discordbot/stocks/due_diligence/arktrades.py deleted file mode 100644 index b4de85c7ec7..00000000000 --- a/discordbot/stocks/due_diligence/arktrades.py +++ /dev/null @@ -1,129 +0,0 @@ -import os - -import df2img -import disnake -import pandas as pd -from menus.menu import Menu -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.stocks.due_diligence import ark_model - - -async def arktrades_command(ctx, ticker: str = "", num: int = 10): - """Displays trades made by ark [cathiesark.com]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.dd.arktrades %s", ticker) - - if ticker: - ark_holdings = ark_model.get_ark_trades_by_ticker(ticker) - ark_holdings = ark_holdings.drop( - columns=["ticker", "everything.profile.companyName"] - ) - - if ark_holdings.empty: - raise Exception( - "Issue getting data from cathiesark.com. Likely no trades found.\n" - ) - - ark_holdings["Total"] = ark_holdings["Total"] / 1_000_000 - ark_holdings.rename(columns={"direction": "B/S", "weight": "F %"}, inplace=True) - - ark_holdings.index = pd.Series(ark_holdings.index).apply( - lambda x: x.strftime("%Y-%m-%d") - ) - - df = ark_holdings.head(num) - dindex = len(df.head(num).index) - formats = {"Close": "{:.2f}", "Total": "{:.2f}"} - for col, f in formats.items(): - df[col] = df[col].map(lambda x: f.format(x)) # pylint: disable=W0640 - - title = f"Stocks: [cathiesark.com] {ticker.upper()} Trades by Ark" - - embeds: list = [] - - i, i2, end = 0, 0, 20 - df_pg = [] - embeds_img = [] - dindex = len(df.index) - while i < dindex: - df_pg = df.iloc[i:end] - df_pg.append(df_pg) - fig = df2img.plot_dataframe( - df_pg, - fig_size=(900, (40 + (40 * 20))), - col_width=[5, 10, 4, 4, 3, 4, 5], - tbl_cells=dict( - height=35, - ), - font=dict( - family="Consolas", - size=20, - ), - template="plotly_dark", - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = f"disc-insider{i}.png" - - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - uploaded_image = gst_imgur.upload_image(imagefile, title="something") - image_link = uploaded_image.link - embeds_img.append( - f"{image_link}", - ) - embeds.append( - disnake.Embed( - title=title, - colour=cfg.COLOR, - ), - ) - i2 += 1 - i += 20 - end += 20 - os.remove(imagefile) - - # Author/Footer - for i in range(0, i2): - embeds[i].set_author( - name=cfg.AUTHOR_NAME, - url=cfg.AUTHOR_URL, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embeds[i].set_footer( - text=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - i = 0 - for i in range(0, i2): - embeds[i].set_image(url=embeds_img[i]) - - i += 1 - embeds[0].set_footer(text=f"Page 1 of {len(embeds)}") - options = [ - disnake.SelectOption(label="Home", value="0", emoji="🟢"), - ] - - await ctx.send(embed=embeds[0], view=Menu(embeds, options)) - - except Exception as e: - embed = disnake.Embed( - title=f"ERROR Stocks: [cathiesark.com] {ticker} Trades by Ark", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/due_diligence/customer.py b/discordbot/stocks/due_diligence/customer.py deleted file mode 100644 index 237f5e63c42..00000000000 --- a/discordbot/stocks/due_diligence/customer.py +++ /dev/null @@ -1,52 +0,0 @@ -import disnake - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from gamestonk_terminal.stocks.due_diligence import csimarket_model - - -async def customer_command(ctx, ticker: str = ""): - """Displays customers of the company [CSIMarket]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.dd.customer %s", ticker) - - if not ticker: - raise Exception("A ticker is required") - - tickers = csimarket_model.get_customers(ticker) - - if not tickers: - raise Exception("Enter a valid ticker") - - # Debug user output - if cfg.DEBUG: - logger.debug(tickers) - - # Output data - embed = disnake.Embed( - title=f"Stocks: [CSIMarket] {ticker} Customers", - description=f"{tickers}", - 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 = disnake.Embed( - title="ERROR Stocks: [CSIMarket] Company Customers", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/due_diligence/est.py b/discordbot/stocks/due_diligence/est.py deleted file mode 100644 index 5026ed2b208..00000000000 --- a/discordbot/stocks/due_diligence/est.py +++ /dev/null @@ -1,162 +0,0 @@ -import os - -import df2img -import disnake -from menus.menu import Menu -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.stocks.due_diligence import business_insider_model - - -async def est_command(ctx, ticker: str = ""): - """Displays earning estimates [Business Insider]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug("!stocks.dd.est %s", ticker) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - ( - df_year_estimates, - df_quarter_earnings, - df_quarter_revenues, - ) = business_insider_model.get_estimates(ticker) - - if ( - df_quarter_revenues.empty - and df_year_estimates.empty - and df_quarter_earnings.empty - ): - raise Exception("Enter a valid ticker") - - # Debug user output - if cfg.DEBUG: - logger.debug(df_year_estimates.to_string()) - logger.debug(df_quarter_earnings.to_string()) - logger.debug(df_quarter_revenues.to_string()) - - dindex = len(df_year_estimates.index) - fig = df2img.plot_dataframe( - df_year_estimates, - fig_size=(1200, (40 + (60 * dindex))), - col_width=[12, 4, 4, 4, 4], - tbl_cells=dict( - height=35, - ), - font=dict( - family="Consolas", - size=20, - ), - template="plotly_dark", - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "estimates.png" - - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - uploaded_image = gst_imgur.upload_image(imagefile, title="something") - link_estimates = uploaded_image.link - - os.remove(imagefile) - - fig = df2img.plot_dataframe( - df_quarter_earnings, - fig_size=(1200, (40 + (40 * 20))), - col_width=[5, 5, 4, 4, 5, 4], - tbl_cells=dict( - height=35, - ), - font=dict( - family="Consolas", - size=20, - ), - template="plotly_dark", - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "earnings.png" - - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - uploaded_image = gst_imgur.upload_image(imagefile, title="something") - link_earnings = uploaded_image.link - os.remove(imagefile) - - fig = df2img.plot_dataframe( - df_quarter_revenues, - fig_size=(1200, (40 + (40 * 20))), - col_width=[5, 5, 4, 4, 5, 4], - tbl_cells=dict( - height=35, - ), - font=dict( - family="Consolas", - size=20, - ), - template="plotly_dark", - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "revenues.png" - - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - uploaded_image = gst_imgur.upload_image(imagefile, title="something") - link_revenues = uploaded_image.link - os.remove(imagefile) - - embeds = [ - disnake.Embed( - title=f"**{ticker.upper()} Year Estimates**", - color=cfg.COLOR, - ), - disnake.Embed( - title=f"**{ticker.upper()} Quarter Earnings**", - colour=cfg.COLOR, - ), - disnake.Embed( - title=f"**{ticker.upper()} Quarter Revenues**", - colour=cfg.COLOR, - ), - ] - embeds[0].set_image(url=link_estimates) - embeds[1].set_image(url=link_earnings) - embeds[2].set_image(url=link_revenues) - # Output data - choices = [ - disnake.SelectOption( - label=f"{ticker.upper()} Year Estimates", value="0", emoji="🟢" - ), - disnake.SelectOption( - label=f"{ticker.upper()} Quarter Earnings", value="1", emoji="🟢" - ), - disnake.SelectOption( - label=f"{ticker.upper()} Quarter Revenues", value="2", emoji="🟢" - ), - ] - - await ctx.send(embed=embeds[0], view=Menu(embeds, choices)) - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [Business Insider] Earning Estimates", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/due_diligence/pt.py b/discordbot/stocks/due_diligence/pt.py deleted file mode 100644 index d28c2ded9ef..00000000000 --- a/discordbot/stocks/due_diligence/pt.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -from datetime import datetime, timedelta - -import disnake -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import gst_imgur, logger -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.due_diligence import business_insider_model - - -async def pt_command(ctx, ticker: str = "", raw: bool = False, start=""): - """Displays price targets [Business Insider]""" - - # Debug - if cfg.DEBUG: - logger.debug("!stocks.dd.pt %s", ticker) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if raw not in [True, False]: - raise Exception("raw argument has to be true or false") - - df_analyst_data = business_insider_model.get_price_target_from_analysts(ticker) - stock = discordbot.helpers.load(ticker, start) - print(df_analyst_data) - if df_analyst_data.empty or stock.empty: - raise Exception("Enter valid ticker") - - # Output Data - - if raw: - df_analyst_data.sort_index(ascending=False) - report = "```" + df_analyst_data.to_string() + "```" - embed = disnake.Embed( - title="Stocks: [Business Insider] Price Targets", - description=report, - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ctx.send(embed=embed) - else: - plt.style.use("seaborn") - plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - if start: - df_analyst_data = df_analyst_data[start:] - - plt.plot(stock.index, stock["Adj Close"].values, lw=3) - - plt.plot(df_analyst_data.groupby(by=["Date"]).mean()) - - plt.scatter(df_analyst_data.index, df_analyst_data["Price Target"], c="r", s=40) - - plt.legend(["Closing Price", "Average Price Target", "Price Target"]) - - plt.title(f"{ticker} (Time Series) and Price Target") - plt.xlim(stock.index[0], stock.index[-1]) - plt.xlabel("Time") - plt.ylabel("Share Price") - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.savefig("ta_pt.png") - imagefile = "ta_pt.png" - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = discordbot.helpers.autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image("ta_pt.png", title="something") - image_link = uploaded_image.link - if cfg.DEBUG: - logger.debug("Image URL: %s", image_link) - title = "Stocks: [Business Insider] Price Targets " + ticker - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embed.set_image(url=image_link) - os.remove("ta_pt.png") - - await ctx.send(embed=embed) diff --git a/discordbot/stocks/due_diligence/sec.py b/discordbot/stocks/due_diligence/sec.py deleted file mode 100644 index 5027546413f..00000000000 --- a/discordbot/stocks/due_diligence/sec.py +++ /dev/null @@ -1,55 +0,0 @@ -import disnake - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from gamestonk_terminal.stocks.due_diligence import marketwatch_model - - -async def sec_command(ctx, ticker=""): - """Displays sec filings [Market Watch]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.dd.sec %s", ticker) - - if ticker == "": - raise Exception("A ticker is required") - - df_financials = marketwatch_model.get_sec_filings(ticker) - - if df_financials.empty: - raise Exception("Enter a valid ticker") - - # Debug user output - if cfg.DEBUG: - logger.debug(df_financials.to_string()) - - df = df_financials - df.loc[:, "Link"] = "[Link Source](" + df.loc[:, "Link"].astype(str) - df.loc[:, "Link"] = df.loc[:, "Link"] + ")" - # Output data - embed = disnake.Embed( - title="Stocks: [Market Watch] SEC Filings", - description=df.to_string(), - 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 = disnake.Embed( - title="ERROR Stocks: [Market Watch] SEC Filings", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/due_diligence/supplier.py b/discordbot/stocks/due_diligence/supplier.py deleted file mode 100644 index 4d0f21295aa..00000000000 --- a/discordbot/stocks/due_diligence/supplier.py +++ /dev/null @@ -1,52 +0,0 @@ -import disnake - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from gamestonk_terminal.stocks.due_diligence import csimarket_model - - -async def supplier_command(ctx, ticker=""): - """Displays suppliers of the company [CSIMarket]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.dd.supplier %s", ticker) - - if not ticker: - raise Exception("A ticker is required") - - tickers = csimarket_model.get_suppliers(ticker) - - if not tickers: - raise Exception("Enter a valid ticker") - - # Debug user output - if cfg.DEBUG: - logger.debug(tickers) - - # Output data - embed = disnake.Embed( - title="Stocks: [CSIMarket] Company Suppliers", - description=tickers, - 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 = disnake.Embed( - title="ERROR Stocks: [CSIMarket] Company Suppliers", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/government/contracts.py b/discordbot/stocks/government/contracts.py deleted file mode 100644 index 23f5400f430..00000000000 --- a/discordbot/stocks/government/contracts.py +++ /dev/null @@ -1,109 +0,0 @@ -import os - -import disnake -import pandas as pd -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.government import quiverquant_model - - -async def contracts_command( - ctx, ticker: str = "", past_transaction_days: int = 10, raw: bool = False -): - """Displays contracts associated with tickers [quiverquant.com]""" - try: - # Debug user input - if cfg.DEBUG: - logger.debug( - "!stocks.gov.contracts %s %s %s", ticker, past_transaction_days, raw - ) - - if ticker == "": - raise Exception("A ticker is required") - - # Retrieve Data - df_contracts = quiverquant_model.get_government_trading("contracts", ticker) - - if df_contracts.empty: - raise Exception("No government contracts found") - - # Output Data - df_contracts["Date"] = pd.to_datetime(df_contracts["Date"]).dt.date - - df_contracts = df_contracts[ - df_contracts["Date"].isin( - df_contracts["Date"].unique()[:past_transaction_days] - ) - ] - - df_contracts.drop_duplicates(inplace=True) - plt.style.use("seaborn") - - fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - - df_contracts.groupby("Date").sum().div(1000).plot(kind="bar", rot=0, ax=ax) - ax.set_ylabel("Amount ($1k)") - ax.set_title(f"Sum of latest government contracts to {ticker}") - fig.tight_layout() - - plt.savefig("gov_contracts.png") - imagefile = "gov_contracts.png" - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image("gov_contracts.png", title="something") - image_link = uploaded_image.link - if cfg.DEBUG: - logger.debug("Image URL: %s", image_link) - title = f"Stocks: [quiverquant.com] Contracts by {ticker}" - if raw: - description = df_contracts.to_string() - embed = disnake.Embed( - title=title, description=description, colour=cfg.COLOR - ) - else: - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embed.set_image(url=image_link) - os.remove("gov_contracts.png") - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title=f"ERROR Stocks: [quiverquant.com] Contracts by {ticker}", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/government/gtrades.py b/discordbot/stocks/government/gtrades.py deleted file mode 100644 index ebff7d95fd1..00000000000 --- a/discordbot/stocks/government/gtrades.py +++ /dev/null @@ -1,168 +0,0 @@ -import os -from datetime import datetime, timedelta - -import disnake -import matplotlib.dates as mdates -import pandas as pd -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.government import quiverquant_model - - -async def gtrades_command( - ctx, - ticker: str = "", - gov_type="", - past_transactions_months: int = 10, - raw: bool = False, -): - """Displays government trades [quiverquant.com]""" - try: - # Debug user input - if cfg.DEBUG: - logger.debug( - "!stocks.gov.gtrades %s %s %s %s", - ticker, - gov_type, - past_transactions_months, - raw, - ) - - if ticker == "": - raise Exception("A ticker is required") - - possible_args = ["congress", "senate", "house"] - if gov_type == "": - gov_type = "congress" - elif gov_type not in possible_args: - raise Exception( - "Enter a valid government argument, options are: congress, senate and house" - ) - - # Retrieve Data - df_gov = quiverquant_model.get_government_trading(gov_type, ticker) - - if df_gov.empty: - raise Exception(f"No {gov_type} trading data found") - - # Output Data - df_gov = df_gov.sort_values("TransactionDate", ascending=False) - - start_date = datetime.now() - timedelta(days=past_transactions_months * 30) - - df_gov["TransactionDate"] = pd.to_datetime(df_gov["TransactionDate"]) - - df_gov = df_gov[df_gov["TransactionDate"] > start_date] - - if df_gov.empty: - logger.debug("No recent %s trading data found", gov_type) - return - - df_gov["min"] = df_gov["Range"].apply( - lambda x: x.split("-")[0].strip("$").replace(",", "").strip() - ) - df_gov["max"] = df_gov["Range"].apply( - lambda x: x.split("-")[1].replace(",", "").strip().strip("$") - if "-" in x - else x.strip("$").replace(",", "").split("\n")[0] - ) - - df_gov["lower"] = df_gov[["min", "max", "Transaction"]].apply( - lambda x: int(float(x["min"])) - if x["Transaction"] == "Purchase" - else -int(float(x["max"])), - axis=1, - ) - df_gov["upper"] = df_gov[["min", "max", "Transaction"]].apply( - lambda x: int(float(x["max"])) - if x["Transaction"] == "Purchase" - else -1 * int(float(x["min"])), - axis=1, - ) - - df_gov = df_gov.sort_values("TransactionDate", ascending=True) - plt.style.use("seaborn") - - fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - - ax.fill_between( - df_gov["TransactionDate"].unique(), - df_gov.groupby("TransactionDate")["lower"].sum().values / 1000, - df_gov.groupby("TransactionDate")["upper"].sum().values / 1000, - ) - - ax.set_xlim( - [ - df_gov["TransactionDate"].values[0], - df_gov["TransactionDate"].values[-1], - ] - ) - ax.grid() - ax.set_title(f"{gov_type.capitalize()} trading on {ticker}") - ax.set_xlabel("Date") - ax.set_ylabel("Amount ($1k)") - plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%Y/%m/%d")) - plt.gcf().autofmt_xdate() - fig.tight_layout() - - plt.savefig("gov_gtrades.png") - imagefile = "gov_gtrades.png" - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image("gov_gtrades.png", title="something") - image_link = uploaded_image.link - if cfg.DEBUG: - logger.debug("Image URL: %s", image_link) - title = "Stocks: [quiverquant.com] Government Trades" - if raw: - description = df_gov.to_string() - embed = disnake.Embed( - title=title, description=description, colour=cfg.COLOR - ) - else: - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embed.set_image(url=image_link) - os.remove("gov_gtrades.png") - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [quiverquant.com] Government Trades", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/government/histcont.py b/discordbot/stocks/government/histcont.py deleted file mode 100644 index 28926a06f3f..00000000000 --- a/discordbot/stocks/government/histcont.py +++ /dev/null @@ -1,107 +0,0 @@ -import os - -import disnake -import numpy as np -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.government import quiverquant_model - - -async def histcont_command(ctx, ticker=""): - """Displays historical quarterly-contracts [quiverquant.com]""" - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.gov.histcont %s", ticker) - - if ticker == "": - raise Exception("A ticker is required") - - # Retrieve Data - df_contracts = quiverquant_model.get_government_trading( - "quarter-contracts", ticker=ticker - ) - - if df_contracts.empty: - logger.debug("No quarterly government contracts found") - return - - # Output Data - amounts = df_contracts.sort_values(by=["Year", "Qtr"])["Amount"].values - - qtr = df_contracts.sort_values(by=["Year", "Qtr"])["Qtr"].values - year = df_contracts.sort_values(by=["Year", "Qtr"])["Year"].values - - quarter_ticks = [ - f"{quarter[0]}" if quarter[1] == 1 else "" for quarter in zip(year, qtr) - ] - plt.style.use("seaborn") - - fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - - ax.plot(np.arange(0, len(amounts)), amounts / 1000, "-*", lw=2, ms=15) - - ax.set_xlim([-0.5, len(amounts) - 0.5]) - ax.set_xticks(np.arange(0, len(amounts))) - ax.set_xticklabels(quarter_ticks) - ax.grid() - ax.set_title(f"Historical Quarterly Government Contracts for {ticker.upper()}") - ax.set_xlabel("Date") - ax.set_ylabel("Amount ($1k)") - fig.tight_layout() - - plt.savefig("gov_histcont.png") - imagefile = "gov_histcont.png" - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image("gov_histcont.png", title="something") - image_link = uploaded_image.link - if cfg.DEBUG: - logger.debug("Image URL: %s", image_link) - title = "Stocks: Historical Quarterly Government Contract " + ticker - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embed.set_image(url=image_link) - os.remove("gov_histcont.png") - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Historical Quarterly Government Contract", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/government/lastcontracts.py b/discordbot/stocks/government/lastcontracts.py deleted file mode 100644 index a19f11fb728..00000000000 --- a/discordbot/stocks/government/lastcontracts.py +++ /dev/null @@ -1,87 +0,0 @@ -import disnake -import pandas as pd -from menus.menu import Menu - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from gamestonk_terminal.stocks.government import quiverquant_model - - -async def lastcontracts_command(ctx, past_transactions_days: int = 2, num: int = 20): - """Displays last government contracts [quiverquant.com]""" - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.gov.lastcontracts %s %s", past_transactions_days, num) - - df_contracts = quiverquant_model.get_government_trading("contracts") - - if df_contracts.empty: - logger.debug("No government contracts found") - return - - df_contracts.sort_values("Date", ascending=False) - - df_contracts["Date"] = pd.to_datetime(df_contracts["Date"]) - df_contracts["Date"] = df_contracts["Date"].dt.date - - df_contracts.drop_duplicates(inplace=True) - df_contracts = df_contracts[ - df_contracts["Date"].isin( - df_contracts["Date"].unique()[:past_transactions_days] - ) - ] - - df_contracts = df_contracts[["Date", "Ticker", "Amount", "Agency"]][:num] - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - initial_str = "Overview" - i = 1 - for col_name in df_contracts["Ticker"].values: - menu = f"\nPage {i}: {col_name}" - initial_str += f"\nPage {i}: {col_name}" - choices.append( - disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), - ) - i += 1 - - columns = [] - df_contracts = df_contracts.T - columns.append( - disnake.Embed( - title="Stocks: [quiverquant.com] Top buy government trading", - description=initial_str, - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - for column in df_contracts.columns.values: - columns.append( - disnake.Embed( - description="```" - + df_contracts[column].fillna("").to_string() - + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [quiverquant.com] Top buy government trading", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/government/lasttrades.py b/discordbot/stocks/government/lasttrades.py deleted file mode 100644 index 2efbe722355..00000000000 --- a/discordbot/stocks/government/lasttrades.py +++ /dev/null @@ -1,189 +0,0 @@ -import disnake -from menus.menu import Menu - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from gamestonk_terminal.stocks.government import quiverquant_model - - -async def lasttrades_command(ctx, gov_type="", past_days: int = 5, representative=""): - """Displays trades made by the congress/senate/house [quiverquant.com]""" - try: - # Debug user input - if cfg.DEBUG: - logger.debug( - "!stocks.gov.lasttrades %s %s %s", - gov_type, - past_days, - representative, - ) - - possible_args = ["congress", "senate", "house"] - if gov_type == "": - gov_type = "congress" - elif gov_type not in possible_args: - raise Exception( - "Enter a valid government argument, options are: congress, senate and house" - ) - - # Retrieve Data - df_gov = quiverquant_model.get_government_trading(gov_type) - - # Output Data - if df_gov.empty: - raise Exception(f"No {gov_type} trading data found") - df_gov = df_gov.sort_values("TransactionDate", ascending=False) - - df_gov = df_gov[ - df_gov["TransactionDate"].isin( - df_gov["TransactionDate"].unique()[:past_days] - ) - ] - - if gov_type == "congress": - df_gov = df_gov[ - [ - "TransactionDate", - "Ticker", - "Representative", - "Transaction", - "Range", - "House", - "ReportDate", - ] - ].rename( - columns={ - "TransactionDate": "Transaction Date", - "ReportDate": "Report Date", - } - ) - else: - df_gov = df_gov[ - [ - "TransactionDate", - "Ticker", - "Representative", - "Transaction", - "Range", - ] - ].rename(columns={"TransactionDate": "Transaction Date"}) - - if representative: - df_gov_rep = df_gov[ - df_gov["Representative"].str.split().str[0] == representative - ] - - if df_gov_rep.empty: - raise Exception( - f"No representative {representative} found in the past {past_days}" - f" days. The following are available: " - f"{', '.join(df_gov['Representative'].str.split().str[0].unique())}" - ) - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - initial_str = "Overview" - i = 1 - for col_name in df_gov_rep["Ticker"].values: - menu = f"\nPage {i}: {col_name}" - initial_str += f"\nPage {i}: {col_name}" - if i < 19: - choices.append( - disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), - ) - if i == 20: - choices.append( - disnake.SelectOption( - label="Max Reached", value=f"{i}", emoji="🟢" - ), - ) - i += 1 - - columns = [] - df_gov_rep = df_gov_rep.T - columns.append( - disnake.Embed( - title=f"Stocks: [quiverquant.com] Trades by {representative}", - description=initial_str, - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - for column in df_gov_rep.columns.values: - columns.append( - disnake.Embed( - description="```" - + df_gov_rep[column].fillna("").to_string() - + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - else: - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - initial_str = "Overview" - i = 1 - for col_name in df_gov["Ticker"].values: - menu = f"\nPage {i}: {col_name}" - initial_str += f"\nPage {i}: {col_name}" - if i < 19: - choices.append( - disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), - ) - if i == 20: - choices.append( - disnake.SelectOption( - label="Max Reached", value=f"{i}", emoji="🟢" - ), - ) - i += 1 - - columns = [] - df_gov = df_gov.T - columns.append( - disnake.Embed( - title=f"Stocks: [quiverquant.com] Trades for {gov_type.upper()}", - description=initial_str, - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - for column in df_gov.columns.values: - columns.append( - disnake.Embed( - description="```" - + df_gov[column].fillna("").to_string() - + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - except Exception as e: - embed = disnake.Embed( - title=f"ERROR Stocks: [quiverquant.com] Trades by {gov_type.upper()}", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/government/lobbying.py b/discordbot/stocks/government/lobbying.py deleted file mode 100644 index 746b4c038f5..00000000000 --- a/discordbot/stocks/government/lobbying.py +++ /dev/null @@ -1,93 +0,0 @@ -import disnake -from menus.menu import Menu - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from gamestonk_terminal.stocks.government import quiverquant_model - - -async def lobbying_command(ctx, ticker="", num: int = 10): - """Displays lobbying details [quiverquant.com]""" - - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.gov.lobbying %s", ticker) - - if ticker == "": - raise Exception("A ticker is required") - - # Retrieve Data - df_lobbying = quiverquant_model.get_government_trading( - "corporate-lobbying", ticker=ticker - ) - - if df_lobbying.empty: - logger.debug("No corporate lobbying found") - return - - # Output Data - report = "" - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - for _, row in ( - df_lobbying.sort_values(by=["Date"], ascending=False).head(num).iterrows() - ): - amount = ( - "$" + str(int(float(row["Amount"]))) - if row["Amount"] is not None - else "N/A" - ) - report += f"{row['Date']}: {row['Client']} {amount}" - if row["Amount"] is not None: - report += "\t" + row["Specific_Issue"].replace("\n", " ").replace( - "\r", "" - ) - report += "\n" - - if len(report) <= 4000: - embed = disnake.Embed( - title=f"Stocks: [quiverquant.com] {ticker.upper()} Lobbying Details", - description="```" + report + "```", - colour=cfg.COLOR, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - await ctx.send(embed=embed) - else: - i = 0 - str_start = 0 - str_end = 4000 - columns = [] - while i <= len(report) / 4000: - columns.append( - disnake.Embed( - title=f"Stocks: [quiverquant.com] {ticker.upper()} Lobbying Details", - description="```" + report[str_start:str_end] + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - str_end = str_start - str_start += 4000 - i += 1 - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - except Exception as e: - embed = disnake.Embed( - title=f"ERROR Stocks: [quiverquant.com] {ticker.upper()} Lobbying Details", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/government/qtrcontracts.py b/discordbot/stocks/government/qtrcontracts.py deleted file mode 100644 index 29cd986e189..00000000000 --- a/discordbot/stocks/government/qtrcontracts.py +++ /dev/null @@ -1,158 +0,0 @@ -import os - -import disnake -import numpy as np -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.government import quiverquant_model - - -async def qtrcontracts_command(ctx, num: int = 20, analysis=""): - """Displays a look at government contracts [quiverquant.com]""" - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.gov.qtrcontracts %s %s", num, analysis) - - possible_args = ["total", "upmom", "downmom"] - if analysis == "": - analysis = "total" - elif analysis not in possible_args: - raise Exception( - "Enter a valid analysis argument, options are: total, upmom and downmom" - ) - - # Retrieve Data - df_contracts = quiverquant_model.get_government_trading("quarter-contracts") - - if df_contracts.empty: - raise Exception("No quarterly government contracts found") - - tickers = quiverquant_model.analyze_qtr_contracts(analysis, num) - plt.style.use("seaborn") - - # Output Data - if analysis in {"upmom", "downmom"}: - description = tickers.to_string() - fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - max_amount = 0 - quarter_ticks = [] - for symbol in tickers: - amounts = ( - df_contracts[df_contracts["Ticker"] == symbol] - .sort_values(by=["Year", "Qtr"])["Amount"] - .values - ) - - qtr = ( - df_contracts[df_contracts["Ticker"] == symbol] - .sort_values(by=["Year", "Qtr"])["Qtr"] - .values - ) - year = ( - df_contracts[df_contracts["Ticker"] == symbol] - .sort_values(by=["Year", "Qtr"])["Year"] - .values - ) - - ax.plot( - np.arange(0, len(amounts)), amounts / 1_000_000, "-*", lw=2, ms=15 - ) - - if len(amounts) > max_amount: - max_amount = len(amounts) - quarter_ticks = [ - f"{quarter[0]} - Q{quarter[1]} " for quarter in zip(year, qtr) - ] - - ax.set_xlim([-0.5, max_amount - 0.5]) - ax.set_xticks(np.arange(0, max_amount)) - ax.set_xticklabels(quarter_ticks) - ax.grid() - ax.legend(tickers) - titles = { - "upmom": "Highest increasing quarterly Government Contracts", - "downmom": "Highest decreasing quarterly Government Contracts", - } - ax.set_title(titles[analysis]) - ax.set_xlabel("Date") - ax.set_ylabel("Amount ($1M)") - fig.tight_layout() - - plt.savefig("gov_qtrcontracts.png") - imagefile = "gov_qtrcontracts.png" - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image( - "gov_qtrcontracts.png", title="something" - ) - image_link = uploaded_image.link - if cfg.DEBUG: - logger.debug("Image URL: %s", image_link) - title = "Stocks: [quiverquant.com] Government contracts" - embed = disnake.Embed( - title=title, description=description, colour=cfg.COLOR - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embed.set_image(url=image_link) - os.remove("gov_qtrcontracts.png") - - await ctx.send(embed=embed) - - elif analysis == "total": - - tickers.index = [ind + " " * (7 - len(ind)) for ind in tickers.index] - tickers[:] = [str(round(val[0] / 1e9, 2)) for val in tickers.values] - tickers.columns = ["Amount [M]"] - - embed = disnake.Embed( - title="Stocks: [quiverquant.com] Government contracts", - description=tickers.to_string(), - 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 = disnake.Embed( - title="ERROR Stocks: [quiverquant.com] Government contracts", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/government/topbuys.py b/discordbot/stocks/government/topbuys.py deleted file mode 100644 index 7dced8952fc..00000000000 --- a/discordbot/stocks/government/topbuys.py +++ /dev/null @@ -1,161 +0,0 @@ -import os -from datetime import datetime, timedelta - -import disnake -import pandas as pd -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.government import quiverquant_model - - -async def topbuys_command( - ctx, - gov_type="", - past_transactions_months: int = 5, - num: int = 20, - raw: bool = False, -): - """Displays most purchased stocks by the congress/senate/house [quiverquant.com]""" - try: - # Debug user input - if cfg.DEBUG: - logger.debug( - "!stocks.gov.topbuys %s %s %s %s", - gov_type, - past_transactions_months, - num, - raw, - ) - - possible_args = ["congress", "senate", "house"] - if gov_type == "": - gov_type = "congress" - elif gov_type not in possible_args: - raise Exception( - "Enter a valid government argument, options are: congress, senate and house" - ) - - # Retrieve Data - df_gov = quiverquant_model.get_government_trading(gov_type) - - if df_gov.empty: - logger.debug("No %s trading data found", gov_type) - return - - df_gov = df_gov.sort_values("TransactionDate", ascending=False) - start_date = datetime.now() - timedelta(days=past_transactions_months * 30) - - df_gov["TransactionDate"] = pd.to_datetime(df_gov["TransactionDate"]) - - df_gov = df_gov[df_gov["TransactionDate"] > start_date].dropna() - # Catch bug where error shown for purchase of >5,000,000 - df_gov["Range"] = df_gov["Range"].apply( - lambda x: "$5,000,001-$5,000,001" if x == ">$5,000,000" else x - ) - df_gov["min"] = df_gov["Range"].apply( - lambda x: x.split("-")[0].strip("$").replace(",", "").strip() - ) - df_gov["max"] = df_gov["Range"].apply( - lambda x: x.split("-")[1].replace(",", "").strip().strip("$") - if "-" in x - else x.strip("$").replace(",", "") - ) - df_gov["lower"] = df_gov[["min", "max", "Transaction"]].apply( - lambda x: float(x["min"]) - if x["Transaction"] == "Purchase" - else -float(x["max"]), - axis=1, - ) - df_gov["upper"] = df_gov[["min", "max", "Transaction"]].apply( - lambda x: float(x["max"]) - if x["Transaction"] == "Purchase" - else -float(x["min"]), - axis=1, - ) - - df_gov = df_gov.sort_values("TransactionDate", ascending=True) - if raw: - df = pd.DataFrame( - df_gov.groupby("Ticker")["upper"] - .sum() - .div(1000) - .sort_values(ascending=False) - .head(n=num) - ) - description = "```" + df.to_string() + "```" - - plt.style.use("seaborn") - fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - - df_gov.groupby("Ticker")["upper"].sum().div(1000).sort_values( - ascending=False - ).head(n=num).plot(kind="bar", rot=0, ax=ax) - - ax.set_ylabel("Amount [1k $]") - ax.set_title( - f"Top {num} purchased stocks over last {past_transactions_months} " - f"months (upper bound) for {gov_type.upper()}" - ) - plt.gcf().axes[0].yaxis.get_major_formatter().set_scientific(False) - fig.tight_layout() - - plt.savefig("gov_topbuys.png") - imagefile = "gov_topbuys.png" - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image("gov_topbuys.png", title="something") - image_link = uploaded_image.link - if cfg.DEBUG: - logger.debug("Image URL: %s", image_link) - title = f"Stocks: [quiverquant.com] Top purchases for {gov_type.upper()}" - if raw: - embed = disnake.Embed( - title=title, description=description, colour=cfg.COLOR - ) - else: - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embed.set_image(url=image_link) - os.remove("gov_topbuys.png") - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title=f"ERROR Stocks: [quiverquant.com] Top purchases for {gov_type.upper()}", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/government/toplobbying.py b/discordbot/stocks/government/toplobbying.py deleted file mode 100644 index ecaef16ade8..00000000000 --- a/discordbot/stocks/government/toplobbying.py +++ /dev/null @@ -1,96 +0,0 @@ -import os - -import disnake -import pandas as pd -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.government import quiverquant_model - - -async def toplobbying_command(ctx, num: int = 10, raw: bool = False): - """Displays top lobbying firms [quiverquant.com]""" - try: - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.gov.toplobbying %s %s", num, raw) - - # Retrieve Data - df_lobbying = quiverquant_model.get_government_trading("corporate-lobbying") - - if df_lobbying.empty: - raise Exception("No corporate lobbying found") - - df_lobbying["Amount"] = df_lobbying.Amount.astype(float).fillna(0) / 100_000 - - lobbying_by_ticker = pd.DataFrame( - df_lobbying.groupby("Ticker")["Amount"].agg("sum") - ).sort_values(by="Amount", ascending=False) - plt.style.use("seaborn") - fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - lobbying_by_ticker.head(num).plot(kind="bar", ax=ax) - ax.set_xlabel("Ticker") - ax.set_ylabel("Total Amount ($100k)") - ax.set_title(f"Corporate Lobbying Spent since {df_lobbying['Date'].min()}") - fig.tight_layout() - - plt.savefig("ta_toplobbying.png") - imagefile = "ta_toplobbying.png" - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image("ta_toplobbying.png", title="something") - image_link = uploaded_image.link - if cfg.DEBUG: - logger.debug("Image URL: %s", image_link) - title = "Stocks: [quiverquant.com] Top Lobbying Firms" - if raw: - description = lobbying_by_ticker.head(num).to_string() - embed = disnake.Embed( - title=title, description=description, colour=cfg.COLOR - ) - else: - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embed.set_image(url=image_link) - os.remove("ta_toplobbying.png") - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [quiverquant.com] Top Lobbying Firms", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/government/topsells.py b/discordbot/stocks/government/topsells.py deleted file mode 100644 index 1f97408eb1a..00000000000 --- a/discordbot/stocks/government/topsells.py +++ /dev/null @@ -1,173 +0,0 @@ -import os -from datetime import datetime, timedelta - -import disnake -import pandas as pd -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.government import quiverquant_model - - -async def topsells_command( - ctx, - gov_type="", - past_transactions_months: int = 5, - num: int = 10, - raw: bool = False, -): - """Displays most sold stocks by the congress/senate/house [quiverquant.com]""" - try: - # Debug user input - if cfg.DEBUG: - logger.debug( - "!stocks.gov.topsells %s %s %s %s", - gov_type, - past_transactions_months, - num, - raw, - ) - - possible_args = ["congress", "senate", "house"] - if gov_type == "": - gov_type = "congress" - elif gov_type not in possible_args: - raise Exception( - "Enter a valid government argument, options are: congress, senate and house" - ) - - # Retrieve Data - df_gov = quiverquant_model.get_government_trading(gov_type) - - # Output Data - if df_gov.empty: - raise Exception(f"No {gov_type} trading data found\n") - - df_gov = df_gov.sort_values("TransactionDate", ascending=False) - - start_date = datetime.now() - timedelta(days=past_transactions_months * 30) - - df_gov["TransactionDate"] = pd.to_datetime(df_gov["TransactionDate"]) - - df_gov = df_gov[df_gov["TransactionDate"] > start_date].dropna() - - df_gov["Range"] = df_gov["Range"].apply( - lambda x: "$5,000,001-$5,000,001" if x == ">$5,000,000" else x - ) - - df_gov["min"] = df_gov["Range"].apply( - lambda x: x.split("-")[0] - .strip("$") - .replace(",", "") - .strip() - .replace(">$", "") - .strip() - ) - df_gov["max"] = df_gov["Range"].apply( - lambda x: x.split("-")[1] - .replace(",", "") - .strip() - .strip("$") - .replace(">$", "") - .strip() - if "-" in x - else x.strip("$").replace(",", "").replace(">$", "").strip() - ) - - df_gov["lower"] = df_gov[["min", "max", "Transaction"]].apply( - lambda x: float(x["min"]) - if x["Transaction"] == "Purchase" - else -float(x["max"]), - axis=1, - ) - df_gov["upper"] = df_gov[["min", "max", "Transaction"]].apply( - lambda x: float(x["max"]) - if x["Transaction"] == "Purchase" - else -float(x["min"]), - axis=1, - ) - - df_gov = df_gov.sort_values("TransactionDate", ascending=True) - if raw: - df = pd.DataFrame( - df_gov.groupby("Ticker")["upper"] - .sum() - .div(1000) - .sort_values(ascending=True) - .abs() - .head(n=num) - ) - description = "```" + df.to_string() + "```" - plt.style.use("seaborn") - fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - - df_gov.groupby("Ticker")["upper"].sum().div(1000).sort_values().abs().head( - n=num - ).plot(kind="bar", rot=0, ax=ax) - ax.set_ylabel("Amount ($1k)") - ax.set_title( - f"{num} most sold stocks over last {past_transactions_months} months" - f" (upper bound) for {gov_type}" - ) - plt.gcf().axes[0].yaxis.get_major_formatter().set_scientific(False) - fig.tight_layout() - - plt.savefig("gov_topsells.png") - imagefile = "gov_topsells.png" - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image("gov_topsells.png", title="something") - image_link = uploaded_image.link - if cfg.DEBUG: - logger.debug("Image URL: %s", image_link) - title = f"Stocks: [quiverquant.com] Top sells for {gov_type.upper()}" - if raw: - embed = disnake.Embed( - title=title, description=description, colour=cfg.COLOR - ) - else: - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embed.set_image(url=image_link) - os.remove("gov_topsells.png") - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title=f"ERROR Stocks: [quiverquant.com] Top sells for {gov_type.upper()}", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/insider/lins.py b/discordbot/stocks/insider/lins.py deleted file mode 100644 index 3385df3ca3e..00000000000 --- a/discordbot/stocks/insider/lins.py +++ /dev/null @@ -1,131 +0,0 @@ -import os - -import df2img -import disnake -import pandas as pd -from menus.menu import Menu -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.stocks.insider import finviz_model - - -async def lins_command(ctx, ticker: str = "", num: int = 10): - """Display insider activity for a given stock ticker. [Source: Finviz] - - Parameters - ---------- - ticker : Stock Ticker - num : Number of latest insider activity to display - """ - try: - # Debug - if cfg.DEBUG: - logger.debug("disc-lins %s", num) - - d_finviz_insider = finviz_model.get_last_insider_activity(ticker) - - df = pd.DataFrame.from_dict(d_finviz_insider) - df.set_index("Date", inplace=True) - - df = df[ - [ - "Relationship", - "Transaction", - "#Shares", - "Cost", - "Value ($)", - "#Shares Total", - "Insider Trading", - "SEC Form 4", - ] - ] - - df = df.head(num) - df = df.replace(to_replace="Option Exercise", value="Opt Ex.", regex=True) - - title = f"Insider Trading for {ticker.upper()}" - - embeds: list = [] - - i, i2, end = 0, 0, 20 - df_pg = [] - embeds_img = [] - dindex = len(df.index) - while i < dindex: - df_pg = df.iloc[i:end] - df_pg.append(df_pg) - fig = df2img.plot_dataframe( - df_pg, - fig_size=(1600, (40 + (40 * 20))), - col_width=[5, 14, 4, 4, 3, 4, 5, 8, 7], - tbl_cells=dict( - height=35, - ), - font=dict( - family="Consolas", - size=20, - ), - template="plotly_dark", - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = f"disc-insider{i}.png" - - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - uploaded_image = gst_imgur.upload_image(imagefile, title="something") - image_link = uploaded_image.link - embeds_img.append( - f"{image_link}", - ) - embeds.append( - disnake.Embed( - title=title, - colour=cfg.COLOR, - ), - ) - i2 += 1 - i += 20 - end += 20 - os.remove(imagefile) - - # Author/Footer - for i in range(0, i2): - embeds[i].set_author( - name=cfg.AUTHOR_NAME, - url=cfg.AUTHOR_URL, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embeds[i].set_footer( - text=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - i = 0 - for i in range(0, i2): - embeds[i].set_image(url=embeds_img[i]) - - i += 1 - embeds[0].set_footer(text=f"Page 1 of {len(embeds)}") - options = [ - disnake.SelectOption(label="Home", value="0", emoji="🟢"), - ] - - await ctx.send(embed=embeds[0], view=Menu(embeds, options)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Insider Activity [Source: Finviz]", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/options/cc_hist.py b/discordbot/stocks/options/cc_hist.py deleted file mode 100644 index 1b501bd113c..00000000000 --- a/discordbot/stocks/options/cc_hist.py +++ /dev/null @@ -1,174 +0,0 @@ -import os -import random -import time - -import disnake -import plotly.graph_objects as go -import yfinance as yf -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -from discordbot.helpers import autocrop_image - -startTime = time.time() - - -async def cc_hist_command( - ctx, ticker: str = None, expiry: str = "", strike: float = 10, opt_type: str = "" -): - """Plot historical option prices - - Parameters - ---------- - ticker: str - Stock ticker - expiry: str - expiration date - strike: float - Option strike price - put: bool - Calls for call - Puts for put - """ - try: - - # Debug - if cfg.DEBUG: - print(f"opt-hist {ticker} {strike} {opt_type} {expiry}") - - # Check for argument - if ticker is None: - raise Exception("Stock ticker is required") - yf_ticker = yf.Ticker(ticker) - dates = list(yf_ticker.options) - - if not dates: - raise Exception("Stock ticker is invalid") - - options = yf.Ticker(ticker).option_chain(expiry) - - if opt_type == "Calls": - options = options.calls - if opt_type == "Puts": - options = options.puts - - chain_id = options.loc[options.strike == strike, "contractSymbol"].values[0] - df_hist = yf.download(chain_id) - df_hist.index.name = "date" - - plt_title = [ - f"\n{ticker.upper()} {strike} {opt_type} expiring {expiry} Historical", - "Volume", - ] - title = f"\n{ticker.upper()} {strike} {opt_type} expiring {expiry} Historical" - - fig = make_subplots( - rows=2, - cols=1, - shared_xaxes=True, - vertical_spacing=0.06, - subplot_titles=plt_title, - row_width=[0.2, 0.7], - ) - fig.add_trace( - go.Candlestick( - x=df_hist.index, - open=df_hist.Open, - high=df_hist.High, - low=df_hist.Low, - close=df_hist.Close, - name="OHLC", - ), - row=1, - col=1, - ) - colors = [ - "green" if row.Open < row["Close"] else "red" - for _, row in df_hist.iterrows() # pylint: disable=E1120 - ] - fig.add_trace( - go.Bar( - x=df_hist.index, - y=df_hist.Volume, - name="Volume", - marker_color=colors, - ), - row=2, - col=1, - ) - fig.update_layout( - margin=dict(l=0, r=0, t=25, b=5), - template=cfg.PLT_CANDLE_STYLE_TEMPLATE, - showlegend=False, - yaxis_title="Premium", - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - ) - fig.update_xaxes( - rangebreaks=[ - dict(bounds=["sat", "mon"]), - ], - ) - config = dict({"scrollZoom": True}) - rand = random.randint(69, 69420) - imagefile = f"opt_hist{rand}.png" - fig.write_image(imagefile) - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - fig.write_html(f"in/cc_hist_{rand}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/cc_hist_{rand}.html)" - - img = Image.open(imagefile) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - if cfg.DEBUG: - print(f"Image: {imagefile}") - title = f"{ticker.upper()} {strike} {opt_type} expiring {expiry} Historical" - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.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, delete_after=30.0) - - -executionTime = time.time() - startTime -print(f"> Extension {__name__} is ready: time in seconds: {str(executionTime)}\n") diff --git a/discordbot/stocks/options/hist.py b/discordbot/stocks/options/hist.py deleted file mode 100644 index 80019884111..00000000000 --- a/discordbot/stocks/options/hist.py +++ /dev/null @@ -1,208 +0,0 @@ -import os -import random -import time - -import disnake -import plotly.graph_objects as go -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -from discordbot.helpers import autocrop_image -from gamestonk_terminal.stocks.options import syncretism_model - -startTime = time.time() - - -async def hist_command( - ctx, - ticker: str = None, - expiry: str = "", - strike: float = 10, - opt_type: str = "", - greek: str = "", -): - """Plot historical option prices - - Parameters - ---------- - ticker: str - Stock ticker - expiry: str - expiration date - strike: float - Option strike price - put: bool - Calls for call - Puts for put - """ - try: - - # Debug - if cfg.DEBUG: - print(f"opt-hist {ticker} {strike} {opt_type} {expiry} {greek}") - - # Check for argument - if ticker is None: - raise Exception("Stock ticker is required") - - if opt_type == "Puts": - put = bool(True) - if opt_type == "Calls": - put = bool(False) - chain_id = "" - - df_hist = syncretism_model.get_historical_greeks( - ticker, expiry, chain_id, strike, put - ) - title = f"\n{ticker.upper()} {strike} {opt_type} expiring {expiry} Historical {greek.upper()}" - # Output data - fig = make_subplots(shared_xaxes=True, specs=[[{"secondary_y": True}]]) - - fig.add_trace( - go.Scatter( - name=ticker.upper(), - x=df_hist.index, - y=df_hist.price, - line=dict(color="#fdc708", width=2), - opacity=1, - ), - ) - fig.add_trace( - go.Scatter( - name="Premium", - x=df_hist.index, - y=df_hist["premium"], - opacity=1, - yaxis="y2", - ), - ) - if greek: - fig.add_trace( - go.Scatter( - name=f"{greek.upper()}", - x=df_hist.index, - y=df_hist[greek], - opacity=1, - yaxis="y3", - ), - ) - fig.update_layout( - margin=dict(l=10, r=10, t=30, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=title, - title_x=0.03, - yaxis_title="Stock Price ($)", - yaxis=dict( - side="right", - fixedrange=False, - titlefont=dict(color="#fdc708"), - tickfont=dict(color="#fdc708"), - position=0.02, - nticks=20, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - fixedrange=False, - domain=[0.037, 1], - ), - xaxis2=dict( - rangeslider=dict(visible=False), - type="date", - fixedrange=False, - ), - xaxis3=dict( - rangeslider=dict(visible=False), - type="date", - fixedrange=False, - ), - dragmode="pan", - legend=dict( - orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 - ), - yaxis2=dict( - side="left", - fixedrange=False, - anchor="x", - overlaying="y", - titlefont=dict(color="#d81aea"), - tickfont=dict(color="#d81aea"), - nticks=20, - ), - yaxis3=dict( - side="left", - position=0, - fixedrange=False, - overlaying="y", - titlefont=dict(color="#00e6c3"), - tickfont=dict(color="#00e6c3"), - nticks=20, - ), - hovermode="x unified", - ) - config = dict({"scrollZoom": True}) - rand = random.randint(69, 69420) - imagefile = f"opt_hist{rand}.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - fig.write_html(f"in/hist_{rand}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/hist_{rand}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - if cfg.DEBUG: - print(f"Image: {imagefile}") - title = f"{ticker.upper()} {strike} {opt_type} expiring {expiry} Historical" - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.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, delete_after=30.0) - - -executionTime = time.time() - startTime -print(f"> Extension {__name__} is ready: time in seconds: {str(executionTime)}\n") diff --git a/discordbot/stocks/options/iv.py b/discordbot/stocks/options/iv.py deleted file mode 100644 index 16486bc5a6b..00000000000 --- a/discordbot/stocks/options/iv.py +++ /dev/null @@ -1,74 +0,0 @@ -import df2img -import disnake -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.stocks.options import barchart_model - - -async def iv_command(ctx, ticker: str = None): - """Options IV""" - - try: - # Debug - if cfg.DEBUG: - logger.debug("!stocks.opt.iv %s", ticker) - - # Check for argument - if ticker is None: - raise Exception("Stock ticker is required") - - df = barchart_model.get_options_info(ticker) - df = df.fillna("") - df = df.set_axis( - [ - " ", - "", - ], - axis="columns", - ) - df.set_index(" ", inplace=True) - fig = df2img.plot_dataframe( - df, - fig_size=(600, 1500), - col_width=[3, 3], - tbl_cells=dict( - align="left", - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "opt-iv.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - image = disnake.File(imagefile) - title = f"{ticker.upper()} Options: IV" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.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, delete_after=30.0) diff --git a/discordbot/stocks/options/oi.py b/discordbot/stocks/options/oi.py deleted file mode 100644 index 33518a7de43..00000000000 --- a/discordbot/stocks/options/oi.py +++ /dev/null @@ -1,188 +0,0 @@ -import os -import random -import time - -import disnake -import pandas as pd -import plotly.graph_objects as go -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.helpers import autocrop_image -from gamestonk_terminal.stocks.options import op_helpers, yfinance_model - -startTime = time.time() - - -async def oi_command( - ctx, - ticker: str = None, - expiry: str = "", - min_sp: float = None, - max_sp: float = None, -): - """Options OI""" - - try: - - # Debug - print(f"!stocks.opt.oi {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) - calls = options.calls - puts = options.puts - current_price = yfinance_model.get_price(ticker) - - min_strike = 0.75 * current_price - max_strike = 1.95 * current_price - - if len(calls) > 40: - min_strike = 0.75 * current_price - max_strike = 1.25 * current_price - - if min_sp: - min_strike = min_sp - if max_sp: - max_strike = max_sp - - call_oi = calls.set_index("strike")["openInterest"] / 1000 - put_oi = puts.set_index("strike")["openInterest"] / 1000 - - df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True) - df_opt = df_opt.rename( - columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"} - ) - - max_pain = op_helpers.calculate_max_pain(df_opt) - fig = go.Figure() - - dmax = df_opt[["OI_call", "OI_put"]].values.max() - dmin = df_opt[["OI_call", "OI_put"]].values.min() - fig.add_trace( - go.Scatter( - x=df_opt.index, - y=df_opt["OI_call"], - name="Calls", - mode="lines+markers", - line=dict(color="green", width=3), - ) - ) - - fig.add_trace( - go.Scatter( - x=df_opt.index, - y=df_opt["OI_put"], - name="Puts", - mode="lines+markers", - line=dict(color="red", width=3), - ) - ) - fig.add_trace( - go.Scatter( - x=[current_price, current_price], - y=[dmin, dmax], - mode="lines", - line=dict(color="gold", width=2), - name="Current Price", - ) - ) - fig.add_trace( - go.Scatter( - x=[max_pain, max_pain], - y=[dmin, dmax], - mode="lines", - line=dict(color="grey", width=3, dash="dash"), - name=f"Max Pain: {max_pain}", - ) - ) - fig.update_xaxes( - range=[min_strike, max_strike], - constrain="domain", - ) - fig.update_layout( - margin=dict(l=0, r=0, t=60, b=20), - template=cfg.PLT_SCAT_STYLE_TEMPLATE, - title=f"Open Interest for {ticker.upper()} expiring {expiry}", - title_x=0.5, - legend_title="", - xaxis_title="Strike", - yaxis_title="Open Interest (1k)", - xaxis=dict( - rangeslider=dict(visible=False), - ), - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - dragmode="pan", - ) - config = dict({"scrollZoom": True}) - imagefile = "opt_oi.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/cci_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/cci_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - if cfg.DEBUG: - print(f"Image URL: {imagefile}") - title = f"Open Interest for {ticker.upper()} expiring {expiry}" - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Options: Open Interest", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) - - -executionTime = time.time() - startTime -print(f"> Extension {__name__} is ready: time in seconds: {str(executionTime)}\n") diff --git a/discordbot/stocks/options/opt_chain.py b/discordbot/stocks/options/opt_chain.py deleted file mode 100644 index fc798568d1c..00000000000 --- a/discordbot/stocks/options/opt_chain.py +++ /dev/null @@ -1,162 +0,0 @@ -import os - -import df2img -import disnake -import numpy as np -import pandas as pd -from menus.menu import Menu -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.stocks.options import yfinance_model - - -async def chain_command( - ctx, - ticker: str = None, - expiry: str = None, - opt_type: str = None, - min_sp: float = None, - max_sp: float = None, -): - """Show calls/puts for given ticker and expiration""" - try: - # Debug - if cfg.DEBUG: - logger.debug( - "opt-chain %s %s %s %s %s", ticker, expiry, opt_type, min_sp, max_sp - ) - - # Check for argument - if not ticker: - 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, str(expiry)) - calls_df = options.calls - puts_df = options.puts - - column_map = {"openInterest": "oi", "volume": "vol", "impliedVolatility": "iv"} - columns = [ - "strike", - "bid", - "ask", - "volume", - "openInterest", - "impliedVolatility", - ] - - if opt_type == "Calls": - df = calls_df[columns].rename(columns=column_map) - if opt_type == "Puts": - df = puts_df[columns].rename(columns=column_map) - - min_strike = np.percentile(df["strike"], 1) - max_strike = np.percentile(df["strike"], 100) - - if min_sp: - min_strike = min_sp - if max_sp: - max_strike = max_sp - if min_sp > max_sp: # type: ignore - min_sp, max_sp = max_strike, min_strike - - df = df[df["strike"] >= min_strike] - df = df[df["strike"] <= max_strike] - - df["iv"] = pd.to_numeric(df["iv"].astype(float)) - - formats = {"iv": "{:.2f}"} - for col, f in formats.items(): - df[col] = df[col].map(lambda x: f.format(x)) # pylint: disable=W0640 - df.set_index("strike", inplace=True) - - title = f"Stocks: {opt_type} Option Chain for {ticker.upper()} on {expiry} [yfinance]" - - embeds: list = [] - # Weekly Calls Pages - i, i2, end = 0, 0, 20 - df_pg = [] - embeds_img = [] - dindex = len(df.index) - while i < dindex: - df_pg = df.iloc[i:end] - df_pg.append(df_pg) - figp = df2img.plot_dataframe( - df_pg, - fig_size=(1000, (40 + (40 * 20))), - col_width=[3, 3, 3, 3], - tbl_cells=dict( - height=35, - ), - font=dict( - family="Consolas", - size=20, - ), - template="plotly_dark", - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = f"opt-chain{i}.png" - - df2img.save_dataframe(fig=figp, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image(imagefile, title="something") - image_link = uploaded_image.link - embeds_img.append( - f"{image_link}", - ) - embeds.append( - disnake.Embed( - title=title, - colour=cfg.COLOR, - ), - ) - i2 += 1 - i += 20 - end += 20 - os.remove(imagefile) - - # Author/Footer - for i in range(0, i2): - embeds[i].set_author( - name=cfg.AUTHOR_NAME, - url=cfg.AUTHOR_URL, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embeds[i].set_footer( - text=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - i = 0 - for i in range(0, i2): - embeds[i].set_image(url=embeds_img[i]) - - i += 1 - embeds[0].set_footer(text=f"Page 1 of {len(embeds)}") - options = [ - disnake.SelectOption(label="Home", value="0", emoji="🟢"), - ] - - await ctx.send(embed=embeds[0], view=Menu(embeds, options)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stock-Options: Expirations", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/options/overview.py b/discordbot/stocks/options/overview.py deleted file mode 100644 index 49ff719afdc..00000000000 --- a/discordbot/stocks/options/overview.py +++ /dev/null @@ -1,424 +0,0 @@ -import os -import random -import time - -import df2img -import disnake -import numpy as np -import pandas as pd -import plotly.graph_objects as go -import yfinance as yf -from menus.menu import Menu -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur -from discordbot.helpers import autocrop_image -from gamestonk_terminal.stocks.options import op_helpers, yfinance_model -from gamestonk_terminal.stocks.options.barchart_model import get_options_info - -startTime = time.time() - - -# pylint: disable=R0914 -# pylint: disable=R0915 -async def overview_command( - ctx, - ticker: str = None, - expiry: str = None, - min_sp: float = None, - max_sp: float = None, -): - """Options Overview""" - - try: - - # Debug - startTime2 = time.time() - if cfg.DEBUG: - print(f"!stocks.opt.iv {ticker} {expiry} {min_sp} {max_sp}") - - # Check for argument - if ticker is None: - raise Exception("Stock ticker is required") - - # Get options info/dates, Look for logo_url - df = get_options_info(ticker) # Barchart Options IV Overview - - dates = yfinance_model.option_expirations(ticker) # Expiration dates - tup = f"{ticker.upper()}" - url = yf.Ticker(tup).info["logo_url"] - url += "?raw=true" if url else "" - - if not dates: - raise Exception("Stock ticker is invalid") - - options = yfinance_model.get_option_chain(ticker, str(expiry)) - calls = options.calls - puts = options.puts - current_price = yfinance_model.get_price(ticker) - - min_strike2 = np.percentile(calls["strike"], 1) - max_strike2 = np.percentile(calls["strike"], 100) - min_strike = 0.75 * current_price - max_strike = 1.95 * current_price - - if len(calls) > 40: - min_strike = 0.75 * current_price - max_strike = 1.25 * current_price - - if min_sp: - min_strike = min_sp - min_strike2 = min_sp - if max_sp: - max_strike = max_sp - max_strike2 = max_sp - if min_sp > max_sp: # type: ignore - min_sp, max_sp = max_strike2, min_strike2 - - call_oi = calls.set_index("strike")["openInterest"] / 1000 - put_oi = puts.set_index("strike")["openInterest"] / 1000 - - df_opt = pd.merge(call_oi, put_oi, left_index=True, right_index=True) - df_opt = df_opt.rename( - columns={"openInterest_x": "OI_call", "openInterest_y": "OI_put"} - ) - - max_pain = op_helpers.calculate_max_pain(df_opt) - fig = go.Figure() - - dmax = df_opt[["OI_call", "OI_put"]].values.max() - dmin = df_opt[["OI_call", "OI_put"]].values.min() - fig.add_trace( - go.Scatter( - x=df_opt.index, - y=df_opt["OI_call"], - name="Calls", - mode="lines+markers", - line=dict(color="green", width=3), - ) - ) - - fig.add_trace( - go.Scatter( - x=df_opt.index, - y=df_opt["OI_put"], - name="Puts", - mode="lines+markers", - line=dict(color="red", width=3), - ) - ) - fig.add_trace( - go.Scatter( - x=[current_price, current_price], - y=[dmin, dmax], - mode="lines", - line=dict(color="gold", width=2), - name="Current Price", - ) - ) - fig.add_trace( - go.Scatter( - x=[max_pain, max_pain], - y=[dmin, dmax], - mode="lines", - line=dict(color="grey", width=3, dash="dash"), - name=f"Max Pain: {max_pain}", - ) - ) - fig.update_xaxes( - range=[min_strike, max_strike], - constrain="domain", - ) - fig.update_layout( - margin=dict(l=0, r=0, t=60, b=20), - template=cfg.PLT_SCAT_STYLE_TEMPLATE, - title=f"Open Interest for {ticker.upper()} expiring {expiry}", - title_x=0.5, - legend_title="", - xaxis_title="Strike", - yaxis_title="Open Interest (1k)", - xaxis=dict( - rangeslider=dict(visible=False), - ), - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - dragmode="pan", - ) - config = dict({"scrollZoom": True}) - imagefile = "opt_oi.png" - fig.write_image(imagefile) - - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/oi_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/oi_{html_ran}.html)" - - img = Image.open(imagefile) - im_bg = Image.open(cfg.IMG_BG) - - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image_oi = gst_imgur.upload_image(imagefile, title="something") - image_link_oi = uploaded_image_oi.link - - column_map = {"openInterest": "oi", "volume": "vol", "impliedVolatility": "iv"} - columns = [ - "strike", - "bid", - "ask", - "volume", - "openInterest", - "impliedVolatility", - ] - - calls_df = calls[columns].rename(columns=column_map) - puts_df = puts[columns].rename(columns=column_map) - - calls_df = calls_df[calls_df["strike"] >= min_strike2] - calls_df = calls_df[calls_df["strike"] <= max_strike2] - puts_df = puts_df[puts_df["strike"] >= min_strike2] - puts_df = puts_df[puts_df["strike"] <= max_strike2] - - calls_df["iv"] = pd.to_numeric(calls_df["iv"].astype(float)) - puts_df["iv"] = pd.to_numeric(puts_df["iv"].astype(float)) - - formats = {"iv": "{:.2f}"} - for col, f in formats.items(): - calls_df[col] = calls_df[col].map( - lambda x: f.format(x) # pylint: disable=W0640 - ) - puts_df[col] = puts_df[col].map( - lambda x: f.format(x) # pylint: disable=W0640 - ) - - calls_df.set_index("strike", inplace=True) - puts_df.set_index("strike", inplace=True) - - if "-" in df.iloc[0, 1]: - iv = f"```diff\n- {df.iloc[0, 1]}\n```" - else: - iv = f"```yaml\n {df.iloc[0, 1]}\n```" - - pfix, sfix = f"{ticker.upper()} ", f" expiring {expiry}" - if expiry == dates[0]: - pfix = f"{ticker.upper()} Weekly " - sfix = "" - - embeds = [ - disnake.Embed( - title=f"{ticker.upper()} Overview", - color=cfg.COLOR, - ), - disnake.Embed( - title=f"{pfix}Open Interest{sfix}", - description=plt_link, - colour=cfg.COLOR, - ), - ] - choices = [ - disnake.SelectOption( - label=f"{ticker.upper()} Overview", value="0", emoji="🟢" - ), - disnake.SelectOption( - label=f"{pfix}Open Interest{sfix}", value="1", emoji="🟢" - ), - ] - embeds_img = [] - - i, i2, end = 0, 0, 20 - df_calls = [] - dindex = len(calls_df.index) - while i <= dindex: - df_calls = calls_df.iloc[i:end] - df_calls.append(df_calls) - figp = df2img.plot_dataframe( - df_calls, - fig_size=(1000, (40 + (40 * 20))), - col_width=[3, 3, 3, 3], - tbl_cells=cfg.PLT_TBL_CELLS, - font=cfg.PLT_TBL_FONT, - template=cfg.PLT_TBL_STYLE_TEMPLATE, - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = f"opt-calls{i}.png" - - df2img.save_dataframe(fig=figp, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - uploaded_image = gst_imgur.upload_image(imagefile, title="something") - image_link = uploaded_image.link - embeds_img.append( - f"{image_link}", - ) - embeds.append( - disnake.Embed( - title=f"{pfix}Calls{sfix}", - colour=cfg.COLOR, - ), - ) - i2 += 1 - i += 20 - end += 20 - os.remove(imagefile) - - # Add Calls page field - i, page, puts_page = 2, 0, 3 - i3 = i2 + 2 - choices.append( - disnake.SelectOption(label="Calls Page 1", value="2", emoji="🟢"), - ) - for i in range(2, i3): - page += 1 - puts_page += 1 - - embeds[i].add_field(name=f"Calls Page {page}", value="_ _", inline=True) - - # Puts Pages - i, end = 0, 20 - df_puts = [] - - dindex = len(puts_df.index) - while i <= dindex: - df_puts = puts_df.iloc[i:end] - df_puts.append(df_puts) - figp = df2img.plot_dataframe( - df_puts, - fig_size=(1000, (40 + (40 * 20))), - col_width=[3, 3, 3, 3], - tbl_cells=cfg.PLT_TBL_CELLS, - font=cfg.PLT_TBL_FONT, - template=cfg.PLT_TBL_STYLE_TEMPLATE, - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = f"opt-puts{i}.png" - - df2img.save_dataframe(fig=figp, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - uploaded_image = gst_imgur.upload_image(imagefile, title="something") - image_link = uploaded_image.link - embeds_img.append( - f"{image_link}", - ) - embeds.append( - disnake.Embed( - title=f"{pfix}Puts{sfix}", - colour=cfg.COLOR, - ), - ) - i2 += 1 - i += 20 - end += 20 - os.remove(imagefile) - - # Add Puts page field - i, page = 0, 0 - puts_page -= 1 - i2 += 2 - choices.append( - disnake.SelectOption(label="Puts Page 1", value=f"{puts_page}", emoji="🟢"), - ) - for i in range(puts_page, i2): - page += 1 - embeds[i].add_field(name=f"Puts Page {page}", value="_ _", inline=True) - - # Author/Footer - for i in range(0, i2): - embeds[i].set_author( - name=cfg.AUTHOR_NAME, - url=cfg.AUTHOR_URL, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embeds[i].set_footer( - text=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - # Set images to Pages - i = 0 - img_i = 0 - embeds[1].set_image(url=image_link_oi) - for i in range(2, i2): - embeds[i].set_image(url=embeds_img[img_i]) - img_i += 1 - i += 1 - - if url: - embeds[0].set_thumbnail(url=f"{url}") - else: - embeds[0].set_thumbnail(url=cfg.AUTHOR_ICON_URL) - - # Overview Section - embeds[0].add_field(name=f"{df.iloc[0, 0]}", value=iv, inline=False) - - embeds[0].add_field( - name=f"•{df.iloc[1, 0]}", value=f"```css\n{df.iloc[1, 1]}\n```", inline=True - ) - for N in range(2, 6): - embeds[0].add_field( - name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}", - value=f"```css\n{df.iloc[N, 1]}\n```", - inline=True, - ) - - embeds[0].add_field(name="_ _", value="_ _", inline=False) - for N in range(6, 8): - embeds[0].add_field( - name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}", - value=f"```css\n{df.iloc[N, 1]}\n```", - inline=True, - ) - - embeds[0].add_field(name="_ _", value="_ _", inline=False) - for N in range(8, 10): - embeds[0].add_field( - name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}", - value=f"```css\n{df.iloc[N, 1]}\n```", - inline=True, - ) - - embeds[0].add_field(name="_ _", value="_ _", inline=False) - for N in range(10, 12): - embeds[0].add_field( - name=f"_ _ _ _ _ _ _ _ _ _ •{df.iloc[N, 0]}", - value=f"```css\n{df.iloc[N, 1]}\n```", - inline=True, - ) - - embeds[0].set_footer(text=f"Page 1 of {len(embeds)}") - executionTime2 = time.time() - startTime2 - print(f"\n> {__name__} is finished: time in seconds: {executionTime2}\n") - await ctx.send(embed=embeds[0], view=Menu(embeds, choices)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Options: Overview", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) - - -executionTime = time.time() - startTime -print(f"> Extension {__name__} is ready: time in seconds: {str(executionTime)}\n") diff --git a/discordbot/stocks/options/unu.py b/discordbot/stocks/options/unu.py deleted file mode 100644 index 08fc2b10579..00000000000 --- a/discordbot/stocks/options/unu.py +++ /dev/null @@ -1,120 +0,0 @@ -import df2img -import disnake -import numpy as np -import pandas as pd -import requests -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.helper_funcs import get_user_agent - - -async def unu_command(ctx, num: int = None): - """Unusual Options""" - try: - - # Debug - if cfg.DEBUG: - logger.debug("!stocks.opt.unu %s", 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: - logger.debug("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) - df.set_index("Ticker", inplace=True) - dindex = len(df.index) - fig = df2img.plot_dataframe( - df, - fig_size=(800, (40 + (40 * dindex))), - col_width=[3, 3, 3, 3, 3, 3, 3], - tbl_cells=dict( - align="left", - height=35, - ), - template="plotly_dark", - font=dict( - family="Consolas", - size=20, - ), - paper_bgcolor="rgba(0, 0, 0, 0)", - ) - imagefile = "opt-unu.png" - df2img.save_dataframe(fig=fig, filename=imagefile) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - image = disnake.File(imagefile) - title = "Unusual Options" - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.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, delete_after=30.0) diff --git a/discordbot/stocks/options/vol.py b/discordbot/stocks/options/vol.py deleted file mode 100644 index 9e0dbbb61b8..00000000000 --- a/discordbot/stocks/options/vol.py +++ /dev/null @@ -1,167 +0,0 @@ -import os -import random -import time - -import disnake as discord -import pandas as pd -import plotly.graph_objects as go -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.helpers import autocrop_image -from gamestonk_terminal.stocks.options import yfinance_model - -startTime = time.time() - - -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"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.25 * 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 - - df_opt = pd.merge(put_v, call_v, left_index=True, right_index=True) - dmax = df_opt.values.max() - - fig = go.Figure() - fig.add_trace( - go.Scatter( - x=call_v.index, - y=call_v.values, - name="Calls", - mode="lines+markers", - line=dict(color="green", width=3), - ) - ) - fig.add_trace( - go.Scatter( - x=put_v.index, - y=put_v.values, - name="Puts", - mode="lines+markers", - line=dict(color="red", width=3), - ) - ) - fig.add_trace( - go.Scatter( - x=[current_price, current_price], - y=[0, dmax], - mode="lines", - line=dict(color="gold", width=2), - name="Current Price", - ) - ) - fig.update_xaxes( - range=[min_strike, max_strike], - constrain="domain", - ) - fig.update_layout( - margin=dict(l=0, r=0, t=60, b=20), - template=cfg.PLT_SCAT_STYLE_TEMPLATE, - title=f"Volume for {ticker.upper()} expiring {expiry}", - title_x=0.5, - legend_title="", - xaxis_title="Strike", - yaxis_title="Volume (1k)", - xaxis=dict( - rangeslider=dict(visible=False), - ), - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - dragmode="pan", - ) - config = dict({"scrollZoom": True}) - imagefile = "opt_vol.png" - fig.write_image(imagefile) - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/vol_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/vol_{html_ran}.html)" - - img = Image.open(imagefile) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = discord.File(imagefile) - - if cfg.DEBUG: - print(f"Image: {imagefile}") - title = f"Volume for {ticker.upper()} expiring {expiry}" - embed = discord.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - 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, delete_after=30.0) - - -executionTime = time.time() - startTime -print(f"> Extension {__name__} is ready: time in seconds: {str(executionTime)}\n") diff --git a/discordbot/stocks/options/vsurf.py b/discordbot/stocks/options/vsurf.py deleted file mode 100644 index 6e6094a6fb9..00000000000 --- a/discordbot/stocks/options/vsurf.py +++ /dev/null @@ -1,184 +0,0 @@ -import os -import random -import time - -import disnake -import numpy as np -import plotly.graph_objects as go -from PIL import Image -from scipy.spatial import Delaunay - -import discordbot.config_discordbot as cfg -from discordbot.helpers import autocrop_image -from gamestonk_terminal.stocks.options import yfinance_model - -startTime = time.time() - - -async def vsurf_command( - ctx, - ticker: str = "", - z: str = "IV", -): - """Display vol surface - - Parameters - ---------- - ticker: Stock Ticker - z : The variable for the Z axis - """ - try: - - # Debug - print(f"!stocks.opt.oi {ticker} {z}") - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - data = yfinance_model.get_iv_surface(ticker) - if data.empty: - raise Exception(f"No options data found for {ticker}.\n") - - Y = data.dte - X = data.strike - if z == "IV": - Z = data.impliedVolatility - label = "Volatility" - elif z == "OI": - Z = data.openInterest - label = "Open Interest" - elif z == "LP": - Z = data.lastPrice - label = "Last Price" - - points3D = np.vstack((X, Y, Z)).T - points2D = points3D[:, :2] - tri = Delaunay(points2D) - I, J, K = tri.simplices.T - - lighting_effects = dict( - ambient=0.5, diffuse=0.5, roughness=0.5, specular=0.4, fresnel=0.4 - ) - fig = go.Figure( - data=[ - go.Mesh3d( - z=Z, - x=X, - y=Y, - i=I, - j=J, - k=K, - intensity=Z, - colorscale=cfg.PLT_3DMESH_COLORSCALE, - hovertemplate="DTE: %{y}
Strike: %{x}
" - + z - + ": %{z}", - showscale=False, - flatshading=True, - lighting=lighting_effects, - ) - ] - ) - fig.update_layout( - scene=dict( - xaxis=dict( - title="Strike", - tickfont=dict(size=11), - titlefont=dict(size=12), - ), - yaxis=dict( - title="DTE", - ), - zaxis=dict( - title=z, - ), - ), - ) - fig.update_layout( - margin=dict(l=0, r=0, t=40, b=20), - width=1320, - height=740, - template=cfg.PLT_3DMESH_STYLE_TEMPLATE, - title=f"{label} Surface for {ticker.upper()}", - title_x=0.5, - hoverlabel=cfg.PLT_3DMESH_HOVERLABEL, - scene_camera=dict( - up=dict(x=0, y=0, z=2), - center=dict(x=0, y=0, z=-0.3), - eye=dict(x=1.25, y=1.25, z=0.69), - ), - scene=cfg.PLT_3DMESH_SCENE, - ) - config = dict({"scrollZoom": True}) - imagefile = "opt-vsurf.png" - fig.write_image(imagefile) - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/vsurf_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/vsurf_{html_ran}.html)" - - img = Image.open(imagefile) - im_bg = Image.open(cfg.IMG_BG) - h = img.height - w = img.width - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - title = f"{label} Surface for {ticker.upper()}" - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except IndexError: - embed = disnake.Embed( - title="ERROR Options: Volatility Surface", - colour=cfg.COLOR, - description=f"Inconistent data for {ticker.upper()} {z}", - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Options: Volatility Surface", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) - - -executionTime = time.time() - startTime -print(f"> Extension {__name__} is ready: time in seconds: {str(executionTime)}\n") diff --git a/discordbot/stocks/screener/financial.py b/discordbot/stocks/screener/financial.py deleted file mode 100644 index dffbb74ad1c..00000000000 --- a/discordbot/stocks/screener/financial.py +++ /dev/null @@ -1,136 +0,0 @@ -import difflib - -import disnake -import pandas as pd -from menus.menu import Menu - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.stocks.screener import screener_options as so -from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data - - -async def financial_command( - ctx, preset="template", sort="", limit: int = 5, ascend: bool = False -): - """Displays returned results from preset by financial metrics [Finviz]""" - try: - # Check for argument - if preset == "template" or preset not in so.all_presets: - raise Exception("Invalid preset selected!") - - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.scr.financial %s %s %s %s", preset, sort, limit, ascend - ) - - # Check for argument - if limit < 0: - raise Exception("Number has to be above 0") - - # Output Data - df_screen = get_screener_data( - preset, - "financial", - limit, - ascend, - ) - - description = "" - if isinstance(df_screen, pd.DataFrame): - if df_screen.empty: - return [] - - df_screen = df_screen.dropna(axis="columns", how="all") - - if sort: - if " ".join(sort) in so.d_cols_to_sort["financial"]: - df_screen = df_screen.sort_values( - by=[" ".join(sort)], - ascending=ascend, - na_position="last", - ) - else: - similar_cmd = difflib.get_close_matches( - " ".join(sort), - so.d_cols_to_sort["financial"], - n=1, - cutoff=0.7, - ) - if similar_cmd: - description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n" - df_screen = df_screen.sort_values( - by=[similar_cmd[0]], - ascending=ascend, - na_position="last", - ) - else: - raise ValueError( - f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['financial'])}" - ) - - df_screen = df_screen.fillna("") - future_column_name = df_screen["Ticker"] - df_screen = df_screen.head(n=limit).transpose() - df_screen.columns = future_column_name - df_screen.drop("Ticker") - - columns = [] - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - initial_str = description + "Overview" - i = 1 - for column in df_screen.columns.values: - menu = f"\nPage {i}: {column}" - initial_str += f"\nPage {i}: {column}" - if i < 19: - choices.append( - disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), - ) - if i == 20: - choices.append( - disnake.SelectOption( - label="Max Reached", value=f"{i}", emoji="🟢" - ), - ) - i += 1 - columns.append( - disnake.Embed( - title="Stocks: [Finviz] Financial Screener", - description=initial_str, - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - for column in df_screen.columns.values: - columns.append( - disnake.Embed( - title="Stocks: [Finviz] Financial Screener", - description="```" - + df_screen[column].fillna("").to_string() - + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [Finviz] Financial Screener", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/screener/historical.py b/discordbot/stocks/screener/historical.py deleted file mode 100644 index ec2c07afd81..00000000000 --- a/discordbot/stocks/screener/historical.py +++ /dev/null @@ -1,188 +0,0 @@ -import configparser -import os -import random -from datetime import datetime, timedelta - -import disnake -import yfinance as yf -from finvizfinance.screener import ticker -from matplotlib import pyplot as plt -from pandas.plotting import register_matplotlib_converters -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from discordbot.stocks.screener import screener_options as so -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.screener import finviz_model - -# pylint:disable=no-member - - -async def historical_command(ctx, signal: str = "", start=""): - """Displays historical price comparison between similar companies [Yahoo Finance]""" - try: - - # Debug user input - if cfg.DEBUG: - logger.debug("!stocks.scr.historical %s %s", signal, start) - - # Check for argument - if signal not in so.d_signals_desc: - raise Exception("Invalid preset selected!") - - register_matplotlib_converters() - - screen = ticker.Ticker() - - if signal in finviz_model.d_signals: - screen.set_filter(signal=finviz_model.d_signals[signal]) - - else: - preset_filter = configparser.RawConfigParser() - preset_filter.optionxform = str # type: ignore - preset_filter.read(so.presets_path + signal + ".ini") - - d_general = preset_filter["General"] - d_filters = { - **preset_filter["Descriptive"], - **preset_filter["Fundamental"], - **preset_filter["Technical"], - } - - d_filters = {k: v for k, v in d_filters.items() if v} - - if d_general["Signal"]: - screen.set_filter(filters_dict=d_filters, signal=d_general["Signal"]) - else: - screen.set_filter(filters_dict=d_filters) - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - # Output Data - l_min = [] - l_leg = [] - l_stocks = screen.ScreenerView(verbose=0) - - if len(l_stocks) > 10: - description = ( - "\nThe limit of stocks to compare with are 10. Hence, 10 random similar stocks will be displayed." - "\nThe selected list will be: " - ) - random.shuffle(l_stocks) - l_stocks = sorted(l_stocks[:10]) - description = description + (", ".join(l_stocks)) - logger.debug(description) - - plt.style.use("seaborn") - plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) - - while l_stocks: - l_parsed_stocks = [] - for symbol in l_stocks: - try: - df_similar_stock = yf.download( - symbol, - start=datetime.strftime(start, "%Y-%m-%d"), - progress=False, - threads=False, - ) - if not df_similar_stock.empty: - plt.plot( - df_similar_stock.index, - df_similar_stock["Adj Close"].values, - ) - l_min.append(df_similar_stock.index[0]) - l_leg.append(symbol) - - l_parsed_stocks.append(symbol) - except Exception as e: - error = ( - f"{e}\nDisregard previous error, which is due to API Rate limits from Yahoo Finance. " - f"Because we like '{symbol}', and we won't leave without getting data from it." - ) - embed = disnake.Embed( - title="ERROR Stocks: [Yahoo Finance] Historical Screener", - colour=cfg.COLOR, - description=error, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) - - for parsed_stock in l_parsed_stocks: - l_stocks.remove(parsed_stock) - - if signal: - plt.title( - f"Screener Historical Price using {finviz_model.d_signals[signal]} signal" - ) - else: - plt.title(f"Screener Historical Price using {signal} preset") - - plt.xlabel("Time") - plt.ylabel("Share Price ($)") - plt.legend(l_leg) - plt.grid(b=True, which="major", color="#666666", linestyle="-") - plt.minorticks_on() - plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) - # ensures that the historical data starts from same datapoint - plt.xlim([max(l_min), df_similar_stock.index[-1]]) - - plt.savefig("scr_historical.png") - imagefile = "scr_historical.png" - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image("scr_historical.png", title="something") - image_link = uploaded_image.link - if cfg.DEBUG: - logger.debug("Image URL: %s", image_link) - title = "Stocks: [Yahoo Finance] Historical Screener" - embed = disnake.Embed(title=title, description=description, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embed.set_image(url=image_link) - os.remove("scr_historical.png") - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [Yahoo Finance] Historical Screener", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/screener/overview.py b/discordbot/stocks/screener/overview.py deleted file mode 100644 index 9e7b6e3ffad..00000000000 --- a/discordbot/stocks/screener/overview.py +++ /dev/null @@ -1,137 +0,0 @@ -import difflib - -import disnake -import pandas as pd -from menus.menu import Menu - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.stocks.screener import screener_options as so -from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data - - -async def overview_command( - ctx, preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False -): - """Displays stocks with overview data such as Sector and Industry [Finviz]""" - try: - # Check for argument - if preset == "template" or preset not in so.all_presets: - raise Exception("Invalid preset selected!") - - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.scr.overview %s %s %s %s", preset, sort, limit, ascend - ) - - # Check for argument - if limit < 0: - raise Exception("Number has to be above 0") - - # Output Data - df_screen = get_screener_data( - preset, - "overview", - limit, - ascend, - ) - - description = "" - - if isinstance(df_screen, pd.DataFrame): - if df_screen.empty: - return [] - - df_screen = df_screen.dropna(axis="columns", how="all") - - if sort: - if " ".join(sort) in so.d_cols_to_sort["overview"]: - df_screen = df_screen.sort_values( - by=[" ".join(sort)], - ascending=ascend, - na_position="last", - ) - else: - similar_cmd = difflib.get_close_matches( - " ".join(sort), - so.d_cols_to_sort["overview"], - n=1, - cutoff=0.7, - ) - if similar_cmd: - description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n" - df_screen = df_screen.sort_values( - by=[similar_cmd[0]], - ascending=ascend, - na_position="last", - ) - else: - raise ValueError( - f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['overview'])}" - ) - - df_screen = df_screen.fillna("") - future_column_name = df_screen["Ticker"] - df_screen = df_screen.head(n=limit).transpose() - df_screen.columns = future_column_name - df_screen.drop("Ticker") - - columns = [] - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - initial_str = description + "Overview" - i = 1 - for column in df_screen.columns.values: - menu = f"\nPage {i}: {column}" - initial_str += f"\nPage {i}: {column}" - if i < 19: - choices.append( - disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), - ) - if i == 20: - choices.append( - disnake.SelectOption( - label="Max Reached", value=f"{i}", emoji="🟢" - ), - ) - i += 1 - columns.append( - disnake.Embed( - title="Stocks: [Finviz] Overview Screener", - description=initial_str, - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - for column in df_screen.columns.values: - columns.append( - disnake.Embed( - title="Stocks: [Finviz] Overview Screener", - description="```" - + df_screen[column].fillna("").to_string() - + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [Finviz] Overview Screener", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/screener/ownership.py b/discordbot/stocks/screener/ownership.py deleted file mode 100644 index 57fa72be51b..00000000000 --- a/discordbot/stocks/screener/ownership.py +++ /dev/null @@ -1,137 +0,0 @@ -import difflib - -import disnake -import pandas as pd -from menus.menu import Menu - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.stocks.screener import screener_options as so -from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data - - -async def ownership_command( - ctx, preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False -): - """Displays stocks based on own share float and ownership data [Finviz]""" - try: - # Check for argument - if preset == "template" or preset not in so.all_presets: - raise Exception("Invalid preset selected!") - - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.scr.ownership %s %s %s %s", preset, sort, limit, ascend - ) - - # Check for argument - if limit < 0: - raise Exception("Number has to be above 0") - - # Output Data - df_screen = get_screener_data( - preset, - "ownership", - limit, - ascend, - ) - - description = "" - - if isinstance(df_screen, pd.DataFrame): - if df_screen.empty: - return [] - - df_screen = df_screen.dropna(axis="columns", how="all") - - if sort: - if " ".join(sort) in so.d_cols_to_sort["ownership"]: - df_screen = df_screen.sort_values( - by=[" ".join(sort)], - ascending=ascend, - na_position="last", - ) - else: - similar_cmd = difflib.get_close_matches( - " ".join(sort), - so.d_cols_to_sort["ownership"], - n=1, - cutoff=0.7, - ) - if similar_cmd: - description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n" - df_screen = df_screen.sort_values( - by=[similar_cmd[0]], - ascending=ascend, - na_position="last", - ) - else: - raise ValueError( - f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['ownership'])}" - ) - - df_screen = df_screen.fillna("") - future_column_name = df_screen["Ticker"] - df_screen = df_screen.head(n=limit).transpose() - df_screen.columns = future_column_name - df_screen.drop("Ticker") - - columns = [] - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - initial_str = description + "Overview" - i = 1 - for column in df_screen.columns.values: - menu = f"\nPage {i}: {column}" - initial_str += f"\nPage {i}: {column}" - if i < 19: - choices.append( - disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), - ) - if i == 20: - choices.append( - disnake.SelectOption( - label="Max Reached", value=f"{i}", emoji="🟢" - ), - ) - i += 1 - columns.append( - disnake.Embed( - title="Stocks: [Finviz] Ownership Screener", - description=initial_str, - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - for column in df_screen.columns.values: - columns.append( - disnake.Embed( - title="Stocks: [Finviz] Ownership Screener", - description="```" - + df_screen[column].fillna("").to_string() - + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [Finviz] Ownership Screener", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/screener/performance.py b/discordbot/stocks/screener/performance.py deleted file mode 100644 index 4001e1bd35a..00000000000 --- a/discordbot/stocks/screener/performance.py +++ /dev/null @@ -1,158 +0,0 @@ -import difflib - -import disnake -import pandas as pd -from menus.menu import Menu - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.stocks.screener import screener_options as so -from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data - - -async def performance_command( - ctx, preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False -): - """Displays stocks and sort by performance categories [Finviz]""" - try: - # Check for argument - if preset == "template" or preset not in so.all_presets: - raise Exception("Invalid preset selected!") - - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.scr.performance %s %s %s %s", preset, sort, limit, ascend - ) - - # Check for argument - if limit < 0: - raise Exception("Number has to be above 0") - - # Output Data - df_screen = get_screener_data( - preset, - "performance", - limit, - ascend, - ) - - d_cols_to_sort = { - "performance": [ - "Ticker", - "Perf Week", - "Perf Month", - "Perf Quart", - "Perf Half", - "Perf Year", - "Perf YTD", - "Volatility W", - "Volatility M", - "Recom", - "Avg Volume", - "Rel Volume", - "Price", - "Change", - "Volume", - ], - } - - description = "" - - if isinstance(df_screen, pd.DataFrame): - if df_screen.empty: - return [] - - df_screen = df_screen.dropna(axis="columns", how="all") - - if sort: - if " ".join(sort) in d_cols_to_sort["performance"]: - df_screen = df_screen.sort_values( - by=[" ".join(sort)], - ascending=ascend, - na_position="last", - ) - else: - similar_cmd = difflib.get_close_matches( - " ".join(sort), - d_cols_to_sort["performance"], - n=1, - cutoff=0.7, - ) - if similar_cmd: - description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n" - df_screen = df_screen.sort_values( - by=[similar_cmd[0]], - ascending=ascend, - na_position="last", - ) - else: - raise ValueError( - "Wrong sort column provided! Provide one of these:" - f"{', '.join(d_cols_to_sort['performance'])}" - ) - - df_screen = df_screen.fillna("") - future_column_name = df_screen["Ticker"] - df_screen = df_screen.head(n=limit).transpose() - df_screen.columns = future_column_name - df_screen.drop("Ticker") - - columns = [] - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - initial_str = description + "Overview" - i = 1 - for column in df_screen.columns.values: - menu = f"\nPage {i}: {column}" - initial_str += f"\nPage {i}: {column}" - if i < 19: - choices.append( - disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), - ) - if i == 20: - choices.append( - disnake.SelectOption( - label="Max Reached", value=f"{i}", emoji="🟢" - ), - ) - i += 1 - columns.append( - disnake.Embed( - title="Stocks: [Finviz] Performance Screener", - description=initial_str, - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - for column in df_screen.columns.values: - columns.append( - disnake.Embed( - title="Stocks: [Finviz] Performance Screener", - description="```" - + df_screen[column].fillna("").to_string() - + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [Finviz] Performance Screener", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/screener/presets_custom.py b/discordbot/stocks/screener/presets_custom.py deleted file mode 100644 index f1379e459a6..00000000000 --- a/discordbot/stocks/screener/presets_custom.py +++ /dev/null @@ -1,52 +0,0 @@ -import disnake - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.stocks.screener import screener_options as so - - -async def presets_custom_command(ctx): - """Displays every custom preset""" - try: - - # Debug - if cfg.DEBUG: - logger.debug("!stocks.scr.presets") - - description = "" - for preset in so.presets: - with open( - so.presets_path + preset + ".ini", - encoding="utf8", - ) as f: - preset_line = "" - for line in f: - if line.strip() == "[General]": - break - preset_line += line.strip() - description += f"**{preset}:** *{preset_line.split('Description: ')[1].replace('#', '')}*\n" - - embed = disnake.Embed( - title="Stocks: Screener Custom Presets", - description=description, - 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 = disnake.Embed( - title="ERROR Stocks: Screener Presets", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/screener/presets_default.py b/discordbot/stocks/screener/presets_default.py deleted file mode 100644 index 664e61d37dd..00000000000 --- a/discordbot/stocks/screener/presets_default.py +++ /dev/null @@ -1,42 +0,0 @@ -import disnake - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.stocks.screener import screener_options as so - - -async def presets_default_command(ctx): - """Displays default presets""" - try: - - # Debug - if cfg.DEBUG: - logger.debug("!stocks.scr.presets") - - description = "" - for signame, sigdesc in so.d_signals_desc.items(): - description += f"**{signame}:** *{sigdesc}*\n" - embed = disnake.Embed( - title="Stocks: Screener Default Presets", - description=description, - 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 = disnake.Embed( - title="ERROR Stocks: Screener Presets", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/screener/technical.py b/discordbot/stocks/screener/technical.py deleted file mode 100644 index dfe6c194e01..00000000000 --- a/discordbot/stocks/screener/technical.py +++ /dev/null @@ -1,137 +0,0 @@ -import difflib - -import disnake -import pandas as pd -from menus.menu import Menu - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.stocks.screener import screener_options as so -from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data - - -async def technical_command( - ctx, preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False -): - """Displays stocks according to chosen preset, sorting by technical factors [Finviz]""" - try: - # Check for argument - if preset == "template" or preset not in so.all_presets: - raise Exception("Invalid preset selected!") - - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.scr.technical %s %s %s %s", preset, sort, limit, ascend - ) - - # Check for argument - if limit < 0: - raise Exception("Number has to be above 0") - - # Output Data - df_screen = get_screener_data( - preset, - "technical", - limit, - ascend, - ) - - description = "" - - if isinstance(df_screen, pd.DataFrame): - if df_screen.empty: - return [] - - df_screen = df_screen.dropna(axis="columns", how="all") - - if sort: - if " ".join(sort) in so.d_cols_to_sort["technical"]: - df_screen = df_screen.sort_values( - by=[" ".join(sort)], - ascending=ascend, - na_position="last", - ) - else: - similar_cmd = difflib.get_close_matches( - " ".join(sort), - so.d_cols_to_sort["technical"], - n=1, - cutoff=0.7, - ) - if similar_cmd: - description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n" - df_screen = df_screen.sort_values( - by=[similar_cmd[0]], - ascending=ascend, - na_position="last", - ) - else: - raise ValueError( - f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['technical'])}" - ) - - df_screen = df_screen.fillna("") - future_column_name = df_screen["Ticker"] - df_screen = df_screen.head(n=limit).transpose() - df_screen.columns = future_column_name - df_screen.drop("Ticker") - - columns = [] - initial_str = description + "Overview" - i = 1 - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - for column in df_screen.columns.values: - menu = f"\nPage {i}: {column}" - initial_str += f"\nPage {i}: {column}" - if i < 19: - choices.append( - disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), - ) - if i == 20: - choices.append( - disnake.SelectOption( - label="Max Reached", value=f"{i}", emoji="🟢" - ), - ) - i += 1 - columns.append( - disnake.Embed( - title="Stocks: [Finviz] Technical Screener", - description=initial_str, - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - for column in df_screen.columns.values: - columns.append( - disnake.Embed( - title="Stocks: [Finviz] Technical Screener", - description="```" - + df_screen[column].fillna("").to_string() - + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [Finviz] Technical Screener", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/screener/valuation.py b/discordbot/stocks/screener/valuation.py deleted file mode 100644 index cc73138d806..00000000000 --- a/discordbot/stocks/screener/valuation.py +++ /dev/null @@ -1,137 +0,0 @@ -import difflib - -import disnake -import pandas as pd -from menus.menu import Menu - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from discordbot.stocks.screener import screener_options as so -from gamestonk_terminal.stocks.screener.finviz_model import get_screener_data - - -async def valuation_command( - ctx, preset: str = "template", sort: str = "", limit: int = 5, ascend: bool = False -): - """Displays results from chosen preset focusing on valuation metrics [Finviz]""" - try: - # Check for argument - if preset == "template" or preset not in so.all_presets: - raise Exception("Invalid preset selected!") - - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.scr.valuation %s %s %s %s", preset, sort, limit, ascend - ) - - # Check for argument - if limit < 0: - raise Exception("Number has to be above 0") - - # Output Data - df_screen = get_screener_data( - preset, - "valuation", - limit, - ascend, - ) - - description = "" - - if isinstance(df_screen, pd.DataFrame): - if df_screen.empty: - return [] - - df_screen = df_screen.dropna(axis="columns", how="all") - - if sort: - if " ".join(sort) in so.d_cols_to_sort["valuation"]: - df_screen = df_screen.sort_values( - by=[" ".join(sort)], - ascending=ascend, - na_position="last", - ) - else: - similar_cmd = difflib.get_close_matches( - " ".join(sort), - so.d_cols_to_sort["valuation"], - n=1, - cutoff=0.7, - ) - if similar_cmd: - description = f"Replacing '{' '.join(sort)}' by '{similar_cmd[0]}' so table can be sorted.\n\n" - df_screen = df_screen.sort_values( - by=[similar_cmd[0]], - ascending=ascend, - na_position="last", - ) - else: - raise ValueError( - f"Wrong sort column provided! Select from: {', '.join(so.d_cols_to_sort['valuation'])}" - ) - - df_screen = df_screen.fillna("") - future_column_name = df_screen["Ticker"] - df_screen = df_screen.head(n=limit).transpose() - df_screen.columns = future_column_name - df_screen.drop("Ticker") - - columns = [] - choices = [ - disnake.SelectOption(label="Overview", value="0", emoji="🟢"), - ] - initial_str = description + "Overview" - i = 1 - for column in df_screen.columns.values: - menu = f"\nPage {i}: {column}" - initial_str += f"\nPage {i}: {column}" - if i < 19: - choices.append( - disnake.SelectOption(label=menu, value=f"{i}", emoji="🟢"), - ) - if i == 20: - choices.append( - disnake.SelectOption( - label="Max Reached", value=f"{i}", emoji="🟢" - ), - ) - i += 1 - columns.append( - disnake.Embed( - title="Stocks: [Finviz] Valuation Screener", - description=initial_str, - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - for column in df_screen.columns.values: - columns.append( - disnake.Embed( - title="Stocks: [Finviz] Valuation Screener", - description="```" - + df_screen[column].fillna("").to_string() - + "```", - colour=cfg.COLOR, - ).set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - ) - - await ctx.send(embed=columns[0], view=Menu(columns, choices)) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [Finviz] Valuation Screener", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/ad.py b/discordbot/stocks/technical_analysis/ad.py deleted file mode 100644 index 3d72dd95453..00000000000 --- a/discordbot/stocks/technical_analysis/ad.py +++ /dev/null @@ -1,200 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import volume_model - - -async def ad_command(ctx, ticker="", is_open="False", start="", end=""): - """Displays chart with accumulation/distribution line [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug("!stocks.ta.ad %s %s %s %s", ticker, is_open, start, end) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - if is_open in ["false", "False", "FALSE", ""]: - is_open = False - - if is_open in ["true", "True", "TRUE"]: - is_open = True - - if is_open not in [True, False]: - raise Exception("is_open argument has to be true or false") - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - - divisor = 1_000_000 - df_vol = df_stock["Volume"].dropna() - df_vol = df_vol.values / divisor - df_ta = volume_model.ad(df_stock, is_open) - df_cal = df_ta.values - df_cal = df_cal / divisor - - fig = make_subplots( - rows=3, - cols=1, - shared_xaxes=True, - vertical_spacing=0.06, - row_width=[0.2, 0.2, 0.2], - ) - fig.add_trace( - go.Scatter( - name=ticker, - x=df_stock.index, - y=df_stock["Adj Close"].values, - line=dict(color="#fdc708", width=2), - opacity=1, - ), - row=1, - col=1, - ) - colors = [ - "green" if row.Open < row["Adj Close"] else "red" - for _, row in df_stock.iterrows() - ] - fig.add_trace( - go.Bar( - x=df_stock.index, - y=df_vol, - name="Volume [M]", - marker_color=colors, - ), - row=2, - col=1, - ) - fig.add_trace( - go.Scatter( - name="A/D [M]", - x=df_ta.index, - y=df_ta.iloc[:, 0].values, - line=dict(width=2), - opacity=1, - ), - row=3, - col=1, - ) - fig.add_hline( - y=0, - fillcolor="grey", - opacity=1, - layer="below", - line_width=3, - line=dict(color="grey", dash="dash"), - row=3, - col=1, - ) - fig.update_layout( - margin=dict(l=10, r=0, t=40, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} AD", - title_x=0.4, - yaxis_title="Stock Price ($)", - yaxis2_title="Volume [M]", - yaxis3_title="A/D [M]", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict( - orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 - ), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_ad.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/ad_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/ad_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = f"Stocks: Accumulation/Distribution Line {ticker}" - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Accumulation/Distribution Line", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/adosc.py b/discordbot/stocks/technical_analysis/adosc.py deleted file mode 100644 index bf577d79ee0..00000000000 --- a/discordbot/stocks/technical_analysis/adosc.py +++ /dev/null @@ -1,197 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import volume_model - - -async def adosc_command( - ctx, ticker="", is_open="False", fast="3", slow="10", start="", end="" -): - """Displays chart with chaikin oscillator [Yahoo Finance]""" - - try: - # Debug - if cfg.DEBUG: - # pylint: disable=logging-too-many-args - logger.debug( - "!stocks.ta.adosc %s %s %s %s %s", - ticker, - is_open, - fast, - slow, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - if not fast.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - fast = float(fast) - if not slow.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - slow = float(slow) - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - - # Output Data - divisor = 1_000_000 - df_vol = df_stock["Volume"].dropna() - df_vol = df_vol.values / divisor - df_ta = volume_model.adosc(df_stock, is_open, fast, slow) - df_cal = df_ta.values - df_cal = df_cal / divisor - - fig = make_subplots( - rows=3, - cols=1, - shared_xaxes=True, - vertical_spacing=0.06, - row_width=[0.2, 0.2, 0.2], - ) - fig.add_trace( - go.Scatter( - name=ticker, - x=df_stock.index, - y=df_stock["Adj Close"].values, - line=dict(color="#fdc708", width=2), - opacity=1, - ), - row=1, - col=1, - ) - colors = [ - "green" if row.Open < row["Adj Close"] else "red" - for _, row in df_stock.iterrows() - ] - fig.add_trace( - go.Bar( - x=df_stock.index, - y=df_vol, - name="Volume", - marker_color=colors, - ), - row=2, - col=1, - ) - fig.add_trace( - go.Scatter( - name="AD Osc [M]", - x=df_ta.index, - y=df_ta.iloc[:, 0].values, - line=dict(width=2), - opacity=1, - ), - row=3, - col=1, - ) - fig.update_layout( - margin=dict(l=10, r=0, t=30, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} AD Oscillator", - title_x=0.3, - yaxis_title="Stock Price ($)", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict( - orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 - ), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_adosc.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/adosc_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/adosc_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = "Stocks: Accumulation/Distribution Oscillator " + ticker - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Accumulation/Distribution Oscillator", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/adx.py b/discordbot/stocks/technical_analysis/adx.py deleted file mode 100644 index 4c383376289..00000000000 --- a/discordbot/stocks/technical_analysis/adx.py +++ /dev/null @@ -1,228 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import trend_indicators_model - - -async def adx_command( - ctx, ticker="", length="14", scalar="100", drift="1", start="", end="" -): - """Displays chart with average directional movement index [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - # pylint: disable=logging-too-many-args - logger.debug( - "!stocks.ta.adx %s %s %s %s %s", - ticker, - length, - scalar, - drift, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - if not length.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - length = float(length) - if not scalar.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - scalar = float(scalar) - if not drift.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - drift = float(drift) - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - - df_ta = trend_indicators_model.adx( - df_stock["High"], - df_stock["Low"], - df_stock["Adj Close"], - length, - scalar, - drift, - ) - - # Output Data - leg_adx = df_ta.columns[0].replace("_", " ") - neg_di = df_ta.columns[1].replace("_", " ") - pos_di = df_ta.columns[2].replace("_", " ") - - fig = make_subplots( - rows=2, - cols=1, - shared_xaxes=True, - vertical_spacing=0.07, - row_width=[0.5, 0.5], - ) - fig.add_trace( - go.Scatter( - name=ticker, - x=df_stock.index, - y=df_stock["Adj Close"].values, - line=dict(color="#fdc708", width=2), - opacity=1, - ), - row=1, - col=1, - ) - fig.add_trace( - go.Scatter( - mode="lines", - name=f"ADX ({leg_adx})", - x=df_ta.index, - y=df_ta.iloc[:, 0].values, - opacity=1, - line=dict(width=2), - ), - row=2, - col=1, - ) - fig.add_trace( - go.Scatter( - mode="lines", - name=f"+DI ({pos_di})", - x=df_ta.index, - y=df_ta.iloc[:, 1].values, - opacity=1, - line=dict(width=1), - ), - row=2, - col=1, - ) - fig.add_trace( - go.Scatter( - mode="lines", - name=f"- DI ({neg_di})", - x=df_ta.index, - y=df_ta.iloc[:, 2].values, - opacity=1, - line=dict(width=1), - ), - row=2, - col=1, - ) - fig.add_hline( - y=25, - fillcolor="grey", - opacity=1, - layer="below", - line_width=3, - line=dict(color="grey", dash="dash"), - row=2, - col=1, - ) - fig.update_layout( - margin=dict(l=0, r=20, t=30, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"Average Directional Movement Index (ADX) on {ticker}", - title_x=0.3, - yaxis_title="Stock Price ($)", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict( - orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 - ), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_adx.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/adx_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/adx_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = f"Stocks: Average-Directional-Movement-Index {ticker}" - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Average-Directional-Movement-Index", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/aroon.py b/discordbot/stocks/technical_analysis/aroon.py deleted file mode 100644 index 8fab7071e71..00000000000 --- a/discordbot/stocks/technical_analysis/aroon.py +++ /dev/null @@ -1,209 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import trend_indicators_model - - -async def aroon_command(ctx, ticker="", length="25", scalar="100", start="", end=""): - """Displays chart with aroon indicator [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.ta.aroon %s %s %s %s %s", - ticker, - length, - scalar, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - if not length.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - length = int(length) - if not scalar.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - scalar = float(scalar) - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - - df_ta = trend_indicators_model.aroon( - df_stock["High"], df_stock["Low"], length, scalar - ) - - # Output Data - aadown = df_ta.columns[0].replace("_", " ") - aaup = df_ta.columns[1].replace("_", " ") - aaosc = df_ta.columns[2].replace("_", " ") - - fig = make_subplots( - rows=3, - cols=1, - shared_xaxes=True, - vertical_spacing=0.07, - row_width=[0.2, 0.2, 0.2], - ) - fig.add_trace( - go.Scatter( - name=ticker, - x=df_stock.index, - y=df_stock["Adj Close"].values, - line=dict(color="#fdc708", width=2), - opacity=1, - showlegend=False, - ), - row=1, - col=1, - ) - fig.add_trace( - go.Scatter( - name=f"Aroon DOWN ({aadown})", - x=df_ta.index, - y=df_ta.iloc[:, 0].values, - opacity=1, - ), - row=2, - col=1, - ) - fig.add_trace( - go.Scatter( - name=f"Aroon UP ({aaup})", - x=df_ta.index, - y=df_ta.iloc[:, 1].values, - opacity=1, - ), - row=2, - col=1, - ) - fig.add_trace( - go.Scatter( - name=f"Aroon OSC ({aaosc})", - x=df_ta.index, - y=df_ta.iloc[:, 2].values, - opacity=1, - ), - row=3, - col=1, - ) - fig.add_hline( - y=50, - fillcolor="grey", - opacity=1, - layer="below", - line_width=3, - line=dict(color="grey", dash="dash"), - row=2, - col=1, - ) - fig.update_layout( - margin=dict(l=0, r=20, t=30, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"Aroon on {ticker}", - title_x=0.5, - yaxis_title="Stock Price ($)", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_aroon.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/aroon_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/aroon_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = f"Stocks: Aroon-Indicator {ticker}" - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Aroon-Indicator", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/bbands.py b/discordbot/stocks/technical_analysis/bbands.py deleted file mode 100644 index 5ae5a44b4d6..00000000000 --- a/discordbot/stocks/technical_analysis/bbands.py +++ /dev/null @@ -1,219 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -from PIL import Image - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import volatility_model - - -async def bbands_command( - ctx, ticker="", length="5", n_std="2", mamode="sma", start="", end="" -): - """Displays chart with bollinger bands [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - # pylint: disable=logging-too-many-args - logger.debug( - "!stocks.ta.bbands %s %s %s %s %s %s", - ticker, - length, - n_std, - mamode, - start, - end, - ) - - # Check for argument - possible_ma = [ - "dema", - "ema", - "fwma", - "hma", - "linreg", - "midpoint", - "pwma", - "rma", - "sinwma", - "sma", - "swma", - "t3", - "tema", - "trima", - "vidya", - "wma", - "zlma", - ] - - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - if not length.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - length = float(length) - if not n_std.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - n_std = float(n_std) - - if mamode not in possible_ma: - raise Exception("Invalid ma entered") - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - - df_ta = volatility_model.bbands(df_stock["Adj Close"], length, n_std, mamode) - - # Output Data - bbu = df_ta.columns[2].replace("_", " ") - bbm = df_ta.columns[1].replace("_", " ") - bbl = df_ta.columns[0].replace("_", " ") - - fig = go.Figure() - fig.add_trace( - go.Scatter( - name=f"{bbu}", - x=df_ta.index, - y=df_ta.iloc[:, 2].values, - opacity=1, - mode="lines", - line_color="indigo", - ), - ) - fig.add_trace( - go.Scatter( - name=f"{bbl}", - x=df_ta.index, - y=df_ta.iloc[:, 0].values, - opacity=1, - mode="lines", - line_color="indigo", - fill="tonexty", - fillcolor="rgba(74, 0, 128, 0.2)", - ), - ) - fig.add_trace( - go.Scatter( - name=f"{bbm}", - x=df_ta.index, - y=df_ta.iloc[:, 1].values, - opacity=1, - line=dict( - width=1.5, - dash="dash", - ), - ), - ) - fig.add_trace( - go.Scatter( - name=f"{ticker}", - x=df_stock.index, - y=df_stock["Adj Close"], - line=dict(color="#fdc708", width=2), - opacity=1, - ), - ) - fig.update_layout( - margin=dict(l=0, r=0, t=50, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} Bollinger Bands ({mamode.upper()})", - title_x=0.5, - yaxis_title="Stock Price ($)", - xaxis_title="Time", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_bbands.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/bbands_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/bbands_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = f"Stocks: Bollinger-Bands {ticker}" - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Bollinger-Bands", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/cci.py b/discordbot/stocks/technical_analysis/cci.py deleted file mode 100644 index 79a4eba3924..00000000000 --- a/discordbot/stocks/technical_analysis/cci.py +++ /dev/null @@ -1,219 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import momentum_model - - -async def cci_command(ctx, ticker="", length="14", scalar="0.015", start="", end=""): - """Displays chart with commodity channel index [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.ta.cci %s %s %s %s %s", - ticker, - length, - scalar, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - # pylint - try: - length = int(length) - except ValueError as e: - raise Exception("Length has to be an integer") from e - try: - scalar = float(scalar) - except ValueError as e: - raise Exception("Scalar has to be an integer") from e - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - df_ta = momentum_model.cci( - df_stock["High"], df_stock["Low"], df_stock["Adj Close"], length, scalar - ) - - dmin = df_ta.values.min() - dmax = df_ta.values.max() - # Output Data - - fig = make_subplots( - rows=2, - cols=1, - shared_xaxes=True, - vertical_spacing=0.07, - row_width=[0.5, 0.6], - ) - fig.add_trace( - go.Scatter( - name=ticker, - x=df_stock.index, - y=df_stock["Adj Close"].values, - line=dict(color="#fdc708", width=2), - opacity=1, - showlegend=False, - ), - row=1, - col=1, - ) - fig.add_trace( - go.Scatter( - showlegend=False, - x=df_ta.index, - y=df_ta.iloc[:, 0].values, - opacity=1, - ), - row=2, - col=1, - ) - fig.add_hrect( - y0=100, - y1=dmax, - fillcolor="red", - opacity=0.2, - layer="below", - line_width=0, - row=2, - col=1, - ) - fig.add_hrect( - y0=-100, - y1=dmin, - fillcolor="green", - opacity=0.2, - layer="below", - line_width=0, - row=2, - col=1, - ) - fig.add_hline( - y=-100, - fillcolor="green", - opacity=1, - layer="below", - line_width=3, - line=dict(color="green", dash="dash"), - row=2, - col=1, - ) - fig.add_hline( - y=100, - fillcolor="red", - opacity=1, - layer="below", - line_width=3, - line=dict(color="red", dash="dash"), - row=2, - col=1, - ) - fig.update_layout( - margin=dict(l=0, r=20, t=30, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} CCI", - title_x=0.5, - yaxis_title="Stock Price ($)", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_cci.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/cci_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/cci_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = "Stocks: Commodity-Channel-Index " + ticker - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Commodity-Channel-Index", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/cg.py b/discordbot/stocks/technical_analysis/cg.py deleted file mode 100644 index 4ac892252d5..00000000000 --- a/discordbot/stocks/technical_analysis/cg.py +++ /dev/null @@ -1,132 +0,0 @@ -import os -from datetime import datetime, timedelta - -import disnake -import numpy as np -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import gst_imgur, logger -from gamestonk_terminal.common.technical_analysis import momentum_model -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale - - -async def cg_command(ctx, ticker="", length="14", start="", end=""): - """Displays chart with centre of gravity [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.ta.cg %s %s %s %s", - ticker, - length, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - if not length.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - length = float(length) - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - df_close = df_stock["Adj Close"] - df_close.columns = ["values"] - df_ta = momentum_model.cg(df_close, length) - - # Output Data - plt.style.use("seaborn") - fig, axes = plt.subplots(2, 1, figsize=plot_autoscale(), dpi=PLOT_DPI) - ax = axes[0] - ax.set_title(f"{ticker} Centre of Gravity") - ax.plot(df_stock.index, df_stock["Adj Close"].values, "k", lw=1) - ax.set_xlim(df_stock.index[0], df_stock.index[-1]) - ax.set_ylabel("Share Price ($)") - ax.grid(b=True, which="major", color="#666666", linestyle="-") - - ax2 = axes[1] - ax2.plot(df_ta.index, df_ta.values, "b", lw=2, label="CG") - # shift cg 1 bar forward for signal - signal = df_ta.values - signal = np.roll(signal, 1) - ax2.plot(df_ta.index, signal, "g", lw=1, label="Signal") - ax2.set_xlim(df_stock.index[0], df_stock.index[-1]) - ax2.grid(b=True, which="major", color="#666666", linestyle="-") - - plt.gcf().autofmt_xdate() - fig.tight_layout(pad=1) - plt.legend() - - plt.savefig("ta_cg.png") - imagefile = "ta_cg.png" - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = discordbot.helpers.autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image("ta_cg.png", title="something") - image_link = uploaded_image.link - if cfg.DEBUG: - logger.debug("Image URL: %s", image_link) - title = "Stocks: Center-of-Gravity " + ticker - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embed.set_image(url=image_link) - os.remove("ta_cg.png") - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Center-of-Gravity", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/donchian.py b/discordbot/stocks/technical_analysis/donchian.py deleted file mode 100644 index eea1b124035..00000000000 --- a/discordbot/stocks/technical_analysis/donchian.py +++ /dev/null @@ -1,142 +0,0 @@ -import os -from datetime import datetime, timedelta - -import disnake -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import gst_imgur, logger -from gamestonk_terminal.common.technical_analysis import volatility_model -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale - - -async def donchian_command( - ctx, ticker="", upper_length="25", lower_length="100", start="", end="" -): - """Displays chart with donchian channel [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.ta.donchian %s %s %s %s %s", - ticker, - upper_length, - lower_length, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - if not upper_length.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - upper_length = float(upper_length) - if not lower_length.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - lower_length = float(lower_length) - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - - df_ta = volatility_model.donchian( - df_stock["High"], df_stock["Low"], upper_length, lower_length - ) - - # Output Data - plt.style.use("seaborn") - fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - ax.plot(df_stock.index, df_stock["Adj Close"].values, color="k", lw=3) - ax.plot(df_ta.index, df_ta.iloc[:, 0].values, "b", lw=1.5, label="upper") - ax.plot(df_ta.index, df_ta.iloc[:, 1].values, "b", lw=1.5, ls="--") - ax.plot(df_ta.index, df_ta.iloc[:, 2].values, "b", lw=1.5, label="lower") - ax.set_title(f"{ticker} donchian") - ax.set_xlim(df_stock.index[0], df_stock.index[-1]) - ax.set_xlabel("Time") - ax.set_ylabel("Price ($)") - - ax.legend([ticker, df_ta.columns[0], df_ta.columns[1], df_ta.columns[2]]) - ax.fill_between( - df_ta.index, - df_ta.iloc[:, 0].values, - df_ta.iloc[:, 2].values, - alpha=0.1, - color="b", - ) - ax.grid(b=True, which="major", color="#666666", linestyle="-") - - plt.gcf().autofmt_xdate() - fig.tight_layout(pad=1) - - plt.legend() - - plt.savefig("ta_donchian.png") - imagefile = "ta_donchian.png" - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = discordbot.helpers.autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image("ta_donchian.png", title="something") - image_link = uploaded_image.link - if cfg.DEBUG: - logger.debug("Image URL: %s", image_link) - title = "Stocks: Donchian-Channels " + ticker - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embed.set_image(url=image_link) - os.remove("ta_donchian.png") - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Donchian-Channels", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/ema.py b/discordbot/stocks/technical_analysis/ema.py deleted file mode 100644 index b2a2aeffcf3..00000000000 --- a/discordbot/stocks/technical_analysis/ema.py +++ /dev/null @@ -1,186 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import pandas as pd -import plotly.graph_objects as go -from PIL import Image - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import overlap_model - - -async def ema_command(ctx, ticker="", window="", offset="", start="", end=""): - """Displays chart with exponential moving average [Yahoo Finance]""" - - try: - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.ta.ema %s %s %s %s %s", - ticker, - window, - offset, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - l_legend = [ticker] - - if window == "": - window = [20, 50] - else: - window_temp = list() - for wind in window.split(","): - try: - window_temp.append(float(wind)) - except Exception as e: - raise Exception("Window needs to be a float") from e - window = window_temp - - if offset == "": - offset = 0 - - ticker = ticker.upper() - stock = discordbot.helpers.load(ticker, start) - if stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - price_df = pd.DataFrame( - stock["Adj Close"].values, columns=["Price"], index=stock.index - ) - - i = 1 - for win in window: - ema_data = overlap_model.ema( - values=stock["Adj Close"], length=win, offset=offset - ) - price_df = price_df.join(ema_data) - l_legend.append(f"EMA {win}") - i += 1 - - # Output Data - start = start.strftime("%Y-%m-%d") - end = end.strftime("%Y-%m-%d") - price_df = price_df.loc[(price_df.index >= start) & (price_df.index < end)] - - fig = go.Figure() - fig.add_trace( - go.Scatter( - name=f"{ticker}", - x=price_df.index, - y=price_df["Price"], - line=dict(color="#fdc708", width=2), - opacity=1, - ), - ) - for i in range(1, price_df.shape[1]): - trace_name = price_df.columns[i].replace("_", " ") - fig.add_trace( - go.Scatter( - name=trace_name, - x=price_df.index, - y=price_df.iloc[:, i], - opacity=1, - ), - ) - fig.update_layout( - margin=dict(l=0, r=0, t=50, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} EMA", - title_x=0.5, - yaxis_title="Stock Price ($)", - xaxis_title="Time", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_ema.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/ema_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/ema_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = "Stocks: Exponential-Moving-Average " + ticker - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Exponential-Moving-Average", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/fib.py b/discordbot/stocks/technical_analysis/fib.py deleted file mode 100644 index 25f88cb8e1d..00000000000 --- a/discordbot/stocks/technical_analysis/fib.py +++ /dev/null @@ -1,140 +0,0 @@ -import os -from datetime import datetime, timedelta - -import disnake -from matplotlib import pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import gst_imgur, logger -from gamestonk_terminal import config_plot as cfp -from gamestonk_terminal.common.technical_analysis import custom_indicators_model -from gamestonk_terminal.helper_funcs import plot_autoscale - - -async def fib_command(ctx, ticker="", start="", end=""): - """Displays chart with fibonacci retracement [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.ta.fib %s %s %s", - ticker, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - f_start = None - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - f_start = start - - if end == "": - end = datetime.now() - f_end = None - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - f_end = None - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - - start = start.strftime("%Y-%m-%d") - end = end.strftime("%Y-%m-%d") - ( - df_fib, - min_date, - max_date, - min_pr, - max_pr, - ) = custom_indicators_model.calculate_fib_levels(df_stock, 120, f_start, f_end) - - levels = df_fib.Price - plt.style.use("seaborn") - fig, ax = plt.subplots(figsize=(plot_autoscale()), dpi=cfp.PLOT_DPI) - - ax.plot(df_stock["Adj Close"], "b") - ax.plot([min_date, max_date], [min_pr, max_pr], c="k") - - for i in levels: - ax.axhline(y=i, c="g", alpha=0.5) - - for i in range(5): - ax.fill_between(df_stock.index, levels[i], levels[i + 1], alpha=0.6) - - ax.set_ylabel("Price") - ax.set_title(f"Fibonacci Support for {ticker.upper()}") - ax.set_xlim(df_stock.index[0], df_stock.index[-1]) - - ax1 = ax.twinx() - ax1.set_ylim(ax.get_ylim()) - ax1.set_yticks(levels) - ax1.set_yticklabels([0, 0.235, 0.382, 0.5, 0.618, 1]) - - plt.gcf().autofmt_xdate() - fig.tight_layout(pad=1) - - plt.savefig("ta_fib.png") - imagefile = "ta_fib.png" - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = discordbot.helpers.autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image("ta_fib.png", title="something") - image_link = uploaded_image.link - if cfg.DEBUG: - logger.debug("Image URL: %s", image_link) - title = "Stocks: Fibonacci-Retracement-Levels " + ticker - str_df_fib = "```" + df_fib.to_string(index=False) + "```" - embed = disnake.Embed(title=title, colour=cfg.COLOR, description=str_df_fib) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embed.set_image(url=image_link) - os.remove("ta_fib.png") - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Fibonacci-Retracement-Levels", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/fisher.py b/discordbot/stocks/technical_analysis/fisher.py deleted file mode 100644 index 9abf514bd84..00000000000 --- a/discordbot/stocks/technical_analysis/fisher.py +++ /dev/null @@ -1,215 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import momentum_model - - -async def fisher_command(ctx, ticker="", length="14", start="", end=""): - """Displays chart with fisher transformation [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug("!stocks.ta.fisher %s %s %s %s", ticker, length, start, end) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - if not length.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - length = int(length) - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - - df_ta = momentum_model.fisher(df_stock["High"], df_stock["Low"], length) - dmin = df_ta.values.min() - dmax = df_ta.values.max() - # Output Data - - fig = make_subplots( - rows=2, - cols=1, - shared_xaxes=True, - vertical_spacing=0.07, - row_width=[0.5, 0.6], - ) - fig.add_trace( - go.Scatter( - name=ticker, - x=df_stock.index, - y=df_stock["Adj Close"].values, - line=dict(color="#fdc708", width=2), - opacity=1, - showlegend=False, - ), - row=1, - col=1, - ) - fig.add_trace( - go.Scatter( - name="Fisher", - x=df_ta.index, - y=df_ta.iloc[:, 0].values, - opacity=1, - ), - row=2, - col=1, - ) - fig.add_trace( - go.Scatter( - name="Signal", - x=df_ta.index, - y=df_ta.iloc[:, 1].values, - opacity=1, - ), - row=2, - col=1, - ) - fig.add_hrect( - y0=2, - y1=dmax, - fillcolor="red", - opacity=0.2, - layer="below", - line_width=0, - row=2, - col=1, - ) - fig.add_hrect( - y0=-2, - y1=dmin, - fillcolor="green", - opacity=0.2, - layer="below", - line_width=0, - row=2, - col=1, - ) - fig.add_hline( - y=-2, - fillcolor="green", - opacity=1, - layer="below", - line_width=3, - line=dict(color="green", dash="dash"), - row=2, - col=1, - ) - fig.add_hline( - y=2, - fillcolor="red", - opacity=1, - layer="below", - line_width=3, - line=dict(color="red", dash="dash"), - row=2, - col=1, - ) - fig.update_layout( - margin=dict(l=0, r=20, t=30, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} Fisher Transform", - title_x=0.5, - yaxis_title="Stock Price ($)", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_fisher.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/fisher_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/fisher_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = "Stocks: Fisher-Transform " + ticker - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Fisher-Transform", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/hma.py b/discordbot/stocks/technical_analysis/hma.py deleted file mode 100644 index 4d5f57f4549..00000000000 --- a/discordbot/stocks/technical_analysis/hma.py +++ /dev/null @@ -1,182 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import pandas as pd -import plotly.graph_objects as go -from PIL import Image - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import overlap_model - - -async def hma_command(ctx, ticker="", window="", offset="", start="", end=""): - """Displays chart with hull moving average [Yahoo Finance]""" - - try: - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.ta.hma %s %s %s %s %s", - ticker, - window, - offset, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - l_legend = [ticker] - - if window == "": - window = [20, 50] - else: - window_temp = list() - for wind in window.split(","): - try: - window_temp.append(float(wind)) - except Exception as e: - raise Exception("Window needs to be a float") from e - window = window_temp - - ticker = ticker.upper() - stock = discordbot.helpers.load(ticker, start) - if stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - price_df = pd.DataFrame( - stock["Adj Close"].values, columns=["Price"], index=stock.index - ) - i = 1 - for win in window: - hma_data = overlap_model.hma( - values=stock["Adj Close"], length=win, offset=offset - ) - price_df = price_df.join(hma_data) - l_legend.append(f"HMA {win}") - i += 1 - - # Output Data - start = start.strftime("%Y-%m-%d") - end = end.strftime("%Y-%m-%d") - price_df = price_df.loc[(price_df.index >= start) & (price_df.index < end)] - - fig = go.Figure() - fig.add_trace( - go.Scatter( - name=f"{ticker}", - x=price_df.index, - y=price_df["Price"], - line=dict(color="#fdc708", width=2), - opacity=1, - ), - ) - for i in range(1, price_df.shape[1]): - trace_name = price_df.columns[i].replace("_", " ") - fig.add_trace( - go.Scatter( - name=trace_name, - x=price_df.index, - y=price_df.iloc[:, i], - opacity=1, - ), - ) - fig.update_layout( - margin=dict(l=0, r=0, t=50, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} HMA", - title_x=0.5, - yaxis_title="Stock Price ($)", - xaxis_title="Time", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_hma.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/hma_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/hma_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = "Stocks: Hull-Moving-Average " + ticker - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Hull-Moving-Average", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/kc.py b/discordbot/stocks/technical_analysis/kc.py deleted file mode 100644 index 379dbf96865..00000000000 --- a/discordbot/stocks/technical_analysis/kc.py +++ /dev/null @@ -1,211 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -from PIL import Image - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import volatility_model - - -async def kc_command( - ctx, ticker="", length="20", scalar="2", mamode="sma", offset="0", start="", end="" -): - """Displays chart with keltner channel [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - # pylint: disable=logging-too-many-args - logger.debug( - "!stocks.ta.kc %s %s %s %s %s %s %s", - ticker, - length, - scalar, - mamode, - offset, - start, - end, - ) - - # Check for argument - possible_ma = ["sma", "ema", "wma", "hma", "zlma"] - - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - if not length.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - length = int(length) - if not scalar.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - scalar = float(scalar) - if not offset.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - offset = float(offset) - - if mamode not in possible_ma: - raise Exception("Invalid ma entered") - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - - df_ta = volatility_model.kc( - df_stock["High"], - df_stock["Low"], - df_stock["Adj Close"], - length, - scalar, - mamode, - offset, - ) - - # Output Data - fig = go.Figure() - fig.add_trace( - go.Scatter( - name="upper", - x=df_ta.index, - y=df_ta.iloc[:, 2].values, - opacity=1, - mode="lines", - line_color="indigo", - showlegend=False, - ), - ) - fig.add_trace( - go.Scatter( - name="lower", - x=df_ta.index, - y=df_ta.iloc[:, 0].values, - opacity=1, - mode="lines", - line_color="indigo", - fill="tonexty", - fillcolor="rgba(74, 0, 128, 0.2)", - showlegend=False, - ), - ) - fig.add_trace( - go.Scatter( - name="mid", - x=df_ta.index, - y=df_ta.iloc[:, 1].values, - opacity=1, - line=dict( - width=1.5, - dash="dash", - ), - ), - ) - fig.add_trace( - go.Scatter( - name=f"{ticker}", - x=df_stock.index, - y=df_stock["Adj Close"], - line=dict(color="#fdc708", width=2), - opacity=1, - ), - ) - fig.update_layout( - margin=dict(l=0, r=0, t=50, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} Keltner Channels ({mamode.upper()})", - title_x=0.5, - yaxis_title="Stock Price ($)", - xaxis_title="Time", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_kc.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/kc_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/kc_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = "Stocks: Keltner-Channel " + ticker - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Keltner-Channel", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/macd.py b/discordbot/stocks/technical_analysis/macd.py deleted file mode 100644 index 0a9142e3d7f..00000000000 --- a/discordbot/stocks/technical_analysis/macd.py +++ /dev/null @@ -1,204 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import momentum_model - - -async def macd_command( - ctx, ticker="", fast="12", slow="26", signal="9", start="", end="" -): - """Displays chart with moving average convergence/divergence [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - # pylint: disable=logging-too-many-args - logger.debug( - "!stocks.ta.macd %s %s %s %s %s %s", - ticker, - fast, - slow, - signal, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - if not fast.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - fast = float(fast) - if not slow.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - slow = float(slow) - if not signal.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - signal = float(signal) - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - - df_ta = momentum_model.macd(df_stock["Adj Close"], fast, slow, signal) - trace_name = df_ta.columns[0].replace("_", " ") - - fig = make_subplots( - rows=2, - cols=1, - shared_xaxes=True, - vertical_spacing=0.07, - row_width=[0.5, 0.6], - ) - fig.add_trace( - go.Scatter( - name=ticker, - x=df_stock.index, - y=df_stock["Adj Close"].values, - line=dict(color="#fdc708", width=2), - opacity=1, - showlegend=False, - ), - row=1, - col=1, - ) - fig.add_trace( - go.Bar( - name="MACD Histogram", - x=df_ta.index, - y=df_ta.iloc[:, 1].values, - opacity=1, - ), - row=2, - col=1, - ) - fig.add_trace( - go.Scatter( - mode="lines", - name="MACD Line", - x=df_ta.index, - y=df_ta.iloc[:, 0].values, - opacity=1, - ), - row=2, - col=1, - ) - fig.add_trace( - go.Scatter( - mode="lines", - name="Signal Line", - x=df_ta.index, - y=df_ta.iloc[:, 2].values, - opacity=1, - ), - row=2, - col=1, - ) - fig.update_layout( - margin=dict(l=0, r=20, t=30, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} {trace_name}", - title_x=0.3, - yaxis_title="Stock Price ($)", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict( - orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 - ), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_macd.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/macd_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/macd_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = "Stocks: Moving-Average-Convergence-Divergence " + ticker - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Moving-Average-Convergence-Divergence", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/obv.py b/discordbot/stocks/technical_analysis/obv.py deleted file mode 100644 index bbcb2c934e9..00000000000 --- a/discordbot/stocks/technical_analysis/obv.py +++ /dev/null @@ -1,187 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import volume_model - - -async def obv_command(ctx, ticker="", start="", end=""): - """Displays chart with on balance volume [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.ta.obv %s %s %s", - ticker, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - - # Output Data - divisor = 1_000_000 - df_vol = df_stock["Volume"].dropna() - df_vol = df_vol.values / divisor - df_ta = volume_model.obv(df_stock) - df_cal = df_ta.values - df_cal = df_cal / divisor - - fig = make_subplots( - rows=3, - cols=1, - shared_xaxes=True, - vertical_spacing=0.06, - row_width=[0.2, 0.2, 0.2], - ) - fig.add_trace( - go.Scatter( - name=ticker, - x=df_stock.index, - y=df_stock["Adj Close"].values, - line=dict(color="#fdc708", width=2), - opacity=1, - ), - row=1, - col=1, - ) - colors = [ - "green" if row.Open < row["Adj Close"] else "red" - for _, row in df_stock.iterrows() - ] - fig.add_trace( - go.Bar( - x=df_stock.index, - y=df_vol, - name="Volume [M]", - marker_color=colors, - ), - row=2, - col=1, - ) - fig.add_trace( - go.Scatter( - name="OBC [M]", - x=df_ta.index, - y=df_ta.iloc[:, 0].values, - line=dict(width=2), - opacity=1, - ), - row=3, - col=1, - ) - fig.update_layout( - margin=dict(l=10, r=0, t=40, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} OBV", - title_x=0.4, - yaxis_title="Stock Price ($)", - yaxis2_title="Volume [M]", - yaxis3_title="OBV [M]", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict( - orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 - ), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_obv.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/obv_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/obv_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = f"Stocks: On-Balance-Volume {ticker}" - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: On-Balance-Volume", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/recom.py b/discordbot/stocks/technical_analysis/recom.py deleted file mode 100644 index e30e4b29194..00000000000 --- a/discordbot/stocks/technical_analysis/recom.py +++ /dev/null @@ -1,51 +0,0 @@ -import disnake - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from gamestonk_terminal.stocks.technical_analysis import tradingview_model - - -async def recom_command(ctx, ticker=""): - """Displays text of a given stocks recommendation based on ta [Tradingview API]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug("!stocks.ta.recom %s", ticker) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - recom = tradingview_model.get_tradingview_recommendation( - ticker, "america", "", "" - ) - - recom = recom[["BUY", "NEUTRAL", "SELL", "RECOMMENDATION"]] - - report = "```" + recom.to_string() + "```" - embed = disnake.Embed( - title="Stocks: [Tradingview API] Recommendation based on TA", - 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 = disnake.Embed( - title="ERROR Stocks: [Tradingview API] Recommendation based on TA", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/rsi.py b/discordbot/stocks/technical_analysis/rsi.py deleted file mode 100644 index 52ba693fc31..00000000000 --- a/discordbot/stocks/technical_analysis/rsi.py +++ /dev/null @@ -1,221 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import momentum_model - - -async def rsi_command( - ctx, ticker="", length="14", scalar="100", drift="1", start="", end="" -): - """Displays chart with relative strength index [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - # pylint: disable=logging-too-many-args - logger.debug( - "!stocks.ta.rsi %s %s %s %s %s %s", - ticker, - length, - scalar, - drift, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - if not length.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - length = float(length) - if not scalar.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - scalar = float(scalar) - if not drift.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - drift = float(drift) - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - df_ta = momentum_model.rsi(df_stock["Adj Close"], length, scalar, drift) - - # Output Data - fig = make_subplots( - rows=2, - cols=1, - shared_xaxes=True, - vertical_spacing=0.07, - row_width=[0.5, 0.6], - ) - fig.add_trace( - go.Scatter( - name=ticker, - x=df_stock.index, - y=df_stock["Adj Close"].values, - line=dict(color="#fdc708", width=2), - opacity=1, - showlegend=False, - ), - row=1, - col=1, - ) - for i in range(0, df_ta.shape[1]): - trace_name = df_ta.columns[i].replace("_", " ") - fig.add_trace( - go.Scatter( - name=trace_name, - x=df_ta.index, - y=df_ta.iloc[:, i], - opacity=1, - ), - row=2, - col=1, - ) - fig.add_hrect( - y0=70, - y1=100, - fillcolor="red", - opacity=0.2, - layer="below", - line_width=0, - row=2, - col=1, - ) - fig.add_hrect( - y0=0, - y1=30, - fillcolor="green", - opacity=0.2, - layer="below", - line_width=0, - row=2, - col=1, - ) - fig.add_hline( - y=70, - fillcolor="green", - opacity=1, - layer="below", - line_width=3, - line=dict(color="red", dash="dash"), - row=2, - col=1, - ) - fig.add_hline( - y=30, - fillcolor="green", - opacity=1, - layer="below", - line_width=3, - line=dict(color="green", dash="dash"), - row=2, - col=1, - ) - fig.update_layout( - margin=dict(l=0, r=20, t=30, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} {trace_name}", - title_x=0.5, - yaxis_title="Stock Price ($)", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_rsi.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/rsi_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/rsi_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = f"Stocks: Relative-Strength-Index {ticker}" - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Relative-Strength-Index", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/sma.py b/discordbot/stocks/technical_analysis/sma.py deleted file mode 100644 index b431ab55a86..00000000000 --- a/discordbot/stocks/technical_analysis/sma.py +++ /dev/null @@ -1,182 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import pandas as pd -import plotly.graph_objects as go -from PIL import Image - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import overlap_model - - -async def sma_command(ctx, ticker="", window="", offset="", start="", end=""): - """Displays chart with simple moving average [Yahoo Finance]""" - - try: - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.ta.sma %s %s %s %s %s", - ticker, - window, - offset, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - l_legend = [ticker] - - if window == "": - window = [20, 50] - else: - window_temp = list() - for wind in window.split(","): - try: - window_temp.append(float(wind)) - except Exception as e: - raise Exception("Window needs to be a float") from e - window = window_temp - - ticker = ticker.upper() - stock = discordbot.helpers.load(ticker, start) - if stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - price_df = pd.DataFrame( - stock["Adj Close"].values, columns=["Price"], index=stock.index - ) - i = 1 - for win in window: - sma_data = overlap_model.sma( - values=stock["Adj Close"], length=win, offset=offset - ) - price_df = price_df.join(sma_data) - l_legend.append(f"SMA {win}") - i += 1 - - # Output Data - start = start.strftime("%Y-%m-%d") - end = end.strftime("%Y-%m-%d") - price_df = price_df.loc[(price_df.index >= start) & (price_df.index < end)] - - fig = go.Figure() - fig.add_trace( - go.Scatter( - name=f"{ticker}", - x=price_df.index, - y=price_df["Price"], - line=dict(color="#fdc708", width=2), - opacity=1, - ), - ) - for i in range(1, price_df.shape[1]): - trace_name = price_df.columns[i].replace("_", " ") - fig.add_trace( - go.Scatter( - name=trace_name, - x=price_df.index, - y=price_df.iloc[:, i], - opacity=1, - ), - ) - fig.update_layout( - margin=dict(l=0, r=0, t=50, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} SMA", - title_x=0.5, - yaxis_title="Stock Price ($)", - xaxis_title="Time", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_sma.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/sma_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/sma_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - embed = disnake.Embed( - title=f"Stocks: Simple-Moving-Average {ticker}", - description=plt_link, - colour=cfg.COLOR, - ) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Simple-Moving-Average", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/stoch.py b/discordbot/stocks/technical_analysis/stoch.py deleted file mode 100644 index b712deef8b8..00000000000 --- a/discordbot/stocks/technical_analysis/stoch.py +++ /dev/null @@ -1,238 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import plotly.graph_objects as go -from PIL import Image -from plotly.subplots import make_subplots - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import momentum_model - - -async def stoch_command( - ctx, ticker="", fast_k="14", slow_d="3", slow_k="3", start="", end="" -): - """Displays chart with stochastic relative strength average [Yahoo Finance]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.ta.stoch %s %s %s %s %s %s", - ticker, - fast_k, - slow_k, - slow_d, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - if not fast_k.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - fast_k = int(fast_k) - if not slow_k.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - slow_k = int(slow_k) - if not slow_d.lstrip("-").isnumeric(): - raise Exception("Number has to be an integer") - slow_d = int(slow_d) - - ticker = ticker.upper() - df_stock = discordbot.helpers.load(ticker, start) - if df_stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - df_stock = df_stock.loc[(df_stock.index >= start) & (df_stock.index < end)] - - df_ta = momentum_model.stoch( - df_stock["High"], - df_stock["Low"], - df_stock["Adj Close"], - fast_k, - slow_d, - slow_k, - ) - - fig = make_subplots( - rows=2, - cols=1, - shared_xaxes=True, - vertical_spacing=0.07, - row_width=[0.5, 0.7], - ) - fig.add_trace( - go.Scatter( - x=df_stock.index, - y=df_stock["Adj Close"].values, - line=dict(color="#fdc708", width=2), - opacity=1, - showlegend=False, - ), - row=1, - col=1, - ) - K = df_ta.columns[0].replace("_", " ") - D = df_ta.columns[1].replace("_", " ") - fig.add_trace( - go.Scatter( - name=f"%K {K}", - x=df_stock.index, - y=df_ta.iloc[:, 0].values, - line=dict(width=1.8), - opacity=1, - ), - row=2, - col=1, - ) - fig.add_trace( - go.Scatter( - name=f"%D {D}", - x=df_stock.index, - y=df_ta.iloc[:, 1].values, - line=dict(width=1.8, dash="dash"), - opacity=1, - ), - row=2, - col=1, - ) - fig.add_hrect( - y0=80, - y1=100, - fillcolor="red", - opacity=0.2, - layer="below", - line_width=0, - row=2, - col=1, - ) - fig.add_hrect( - y0=0, - y1=20, - fillcolor="green", - opacity=0.2, - layer="below", - line_width=0, - row=2, - col=1, - ) - fig.add_hline( - y=80, - fillcolor="green", - opacity=1, - layer="below", - line_width=3, - line=dict(color="red", dash="dash"), - row=2, - col=1, - ) - fig.add_hline( - y=20, - fillcolor="green", - opacity=1, - layer="below", - line_width=3, - line=dict(color="green", dash="dash"), - row=2, - col=1, - ) - fig.update_layout( - margin=dict(l=0, r=0, t=40, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"Stochastic Relative Strength Index (STOCH RSI) on {ticker}", - title_x=0.5, - yaxis_title="Stock Price ($)", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_stoch.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/stoch_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/stoch_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - embed = disnake.Embed( - title=f"Stocks: Stochastic-Relative-Strength-Index {ticker}", - description=plt_link, - colour=cfg.COLOR, - ) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Stochastic-Relative-Strength-Index", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/summary.py b/discordbot/stocks/technical_analysis/summary.py deleted file mode 100644 index 8641a31929d..00000000000 --- a/discordbot/stocks/technical_analysis/summary.py +++ /dev/null @@ -1,47 +0,0 @@ -import disnake - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import logger -from gamestonk_terminal.stocks.technical_analysis import finbrain_model - - -async def summary_command(ctx, ticker=""): - """Displays text of a given stocks ta summary [FinBrain API]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug("!stocks.ta.summary %s", ticker) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - report = finbrain_model.get_technical_summary_report(ticker) - if report: - report = "```" + report.replace(". ", ".\n") + "```" - embed = disnake.Embed( - title="Stocks: [FinBrain API] Summary", - 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 = disnake.Embed( - title="ERROR Stocks: [FinBrain API] Summary", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/view.py b/discordbot/stocks/technical_analysis/view.py deleted file mode 100644 index 10ed036d3c1..00000000000 --- a/discordbot/stocks/technical_analysis/view.py +++ /dev/null @@ -1,87 +0,0 @@ -import io -import os - -import disnake -import matplotlib.pyplot as plt -from PIL import Image - -import discordbot.config_discordbot as cfg -from discordbot.config_discordbot import gst_imgur, logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.config_plot import PLOT_DPI -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal.stocks.technical_analysis import finviz_model - - -async def view_command(ctx, ticker=""): - """Displays image from Finviz [Finviz]""" - - try: - - # Debug - if cfg.DEBUG: - logger.debug("!stocks.ta.view %s", ticker) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - image_data = finviz_model.get_finviz_image(ticker) - dataBytesIO = io.BytesIO(image_data) - im = Image.open(dataBytesIO) - plt.style.use("seaborn") - - fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) - ax.set_axis_off() - fig.subplots_adjust(left=0, right=1, top=1, bottom=0) - - plt.imshow(im) - - plt.savefig("ta_view.png") - imagefile = "ta_view.png" - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - uploaded_image = gst_imgur.upload_image("ta_view.png", title="something") - image_link = uploaded_image.link - if cfg.DEBUG: - logger.debug("Image URL: %s", image_link) - title = "Stocks: [Finviz] Trendlines & Data " + ticker - embed = disnake.Embed(title=title, colour=cfg.COLOR) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - embed.set_image(url=image_link) - os.remove("ta_view.png") - - await ctx.send(embed=embed) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: [Finviz] Trendlines & Data", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/wma.py b/discordbot/stocks/technical_analysis/wma.py deleted file mode 100644 index 35262621be4..00000000000 --- a/discordbot/stocks/technical_analysis/wma.py +++ /dev/null @@ -1,185 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import pandas as pd -import plotly.graph_objects as go -from PIL import Image - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import overlap_model - - -async def wma_command(ctx, ticker="", window="", offset="", start="", end=""): - """Displays chart with weighted moving average [Yahoo Finance]""" - - try: - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.ta.wma %s %s %s %s %s", - ticker, - window, - offset, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - l_legend = [ticker] - - if window == "": - window = [20, 50] - else: - window_temp = list() - for wind in window.split(","): - try: - window_temp.append(float(wind)) - except Exception as e: - raise Exception("Window needs to be a float") from e - window = window_temp - - if offset == "": - offset = 0 - - ticker = ticker.upper() - stock = discordbot.helpers.load(ticker, start) - if stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - price_df = pd.DataFrame( - stock["Adj Close"].values, columns=["Price"], index=stock.index - ) - i = 1 - for win in window: - wma_data = overlap_model.wma( - values=stock["Adj Close"], length=win, offset=offset - ) - price_df = price_df.join(wma_data) - l_legend.append(f"WMA {win}") - i += 1 - - # Output Data - start = start.strftime("%Y-%m-%d") - end = end.strftime("%Y-%m-%d") - price_df = price_df.loc[(price_df.index >= start) & (price_df.index < end)] - - fig = go.Figure() - fig.add_trace( - go.Scatter( - name=f"{ticker}", - x=price_df.index, - y=price_df["Price"], - line=dict(color="#fdc708", width=2), - opacity=1, - ), - ) - for i in range(1, price_df.shape[1]): - trace_name = price_df.columns[i].replace("_", " ") - fig.add_trace( - go.Scatter( - name=trace_name, - x=price_df.index, - y=price_df.iloc[:, i], - opacity=1, - ), - ) - fig.update_layout( - margin=dict(l=0, r=0, t=50, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} WMA", - title_x=0.5, - yaxis_title="Stock Price ($)", - xaxis_title="Time", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_wma.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/wma_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/wma_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - print(img.size) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - if cfg.DEBUG: - logger.debug("Image: %s", imagefile) - title = "Stocks: Weighted-Moving-Average " + ticker - embed = disnake.Embed(title=title, description=plt_link, colour=cfg.COLOR) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Weighted-Moving-Average", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/stocks/technical_analysis/zlma.py b/discordbot/stocks/technical_analysis/zlma.py deleted file mode 100644 index 5feb684da1c..00000000000 --- a/discordbot/stocks/technical_analysis/zlma.py +++ /dev/null @@ -1,185 +0,0 @@ -import os -import random -from datetime import datetime, timedelta - -import disnake -import pandas as pd -import plotly.graph_objects as go -from PIL import Image - -import discordbot.config_discordbot as cfg -import discordbot.helpers -from discordbot.config_discordbot import logger -from discordbot.helpers import autocrop_image -from gamestonk_terminal.common.technical_analysis import overlap_model - - -async def zlma_command(ctx, ticker="", window="", offset="", start="", end=""): - """Displays chart with zero lag moving average [Yahoo Finance]""" - - try: - # Debug - if cfg.DEBUG: - logger.debug( - "!stocks.ta.zlma %s %s %s %s %s", - ticker, - window, - offset, - start, - end, - ) - - # Check for argument - if ticker == "": - raise Exception("Stock ticker is required") - - if start == "": - start = datetime.now() - timedelta(days=365) - else: - start = datetime.strptime(start, cfg.DATE_FORMAT) - - if end == "": - end = datetime.now() - else: - end = datetime.strptime(end, cfg.DATE_FORMAT) - - l_legend = [ticker] - - if window == "": - window = [20] - else: - window_temp = list() - for wind in window.split(","): - try: - window_temp.append(float(wind)) - except Exception as e: - raise Exception("Window needs to be a float") from e - window = window_temp - - if offset == "": - offset = 0 - - ticker = ticker.upper() - stock = discordbot.helpers.load(ticker, start) - if stock.empty: - raise Exception("Stock ticker is invalid") - - # Retrieve Data - price_df = pd.DataFrame( - stock["Adj Close"].values, columns=["Price"], index=stock.index - ) - i = 1 - for win in window: - zlma_data = overlap_model.zlma( - values=stock["Adj Close"], length=win, offset=offset - ) - price_df = price_df.join(zlma_data) - l_legend.append(f"ZLMA {win}") - i += 1 - - # Output Data - start = start.strftime("%Y-%m-%d") - end = end.strftime("%Y-%m-%d") - price_df = price_df.loc[(price_df.index >= start) & (price_df.index < end)] - - fig = go.Figure() - fig.add_trace( - go.Scatter( - name=f"{ticker}", - x=price_df.index, - y=price_df["Price"], - line=dict(color="#fdc708", width=2), - opacity=1, - ), - ) - for i in range(1, price_df.shape[1]): - trace_name = price_df.columns[i].replace("_", " ") - fig.add_trace( - go.Scatter( - name=trace_name, - x=price_df.index, - y=price_df.iloc[:, i], - opacity=1, - ), - ) - fig.update_layout( - margin=dict(l=0, r=0, t=50, b=20), - template=cfg.PLT_TA_STYLE_TEMPLATE, - colorway=cfg.PLT_TA_COLORWAY, - title=f"{ticker} ZLMA", - title_x=0.5, - yaxis_title="Stock Price ($)", - xaxis_title="Time", - yaxis=dict( - fixedrange=False, - ), - xaxis=dict( - rangeslider=dict(visible=False), - type="date", - ), - dragmode="pan", - legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01), - ) - config = dict({"scrollZoom": True}) - imagefile = "ta_zlma.png" - - # Check if interactive settings are enabled - plt_link = "" - if cfg.INTERACTIVE: - html_ran = random.randint(69, 69420) - fig.write_html(f"in/zlma_{html_ran}.html", config=config) - plt_link = f"[Interactive]({cfg.INTERACTIVE_URL}/zlma_{html_ran}.html)" - - fig.update_layout( - width=800, - height=500, - ) - fig.write_image(imagefile) - - img = Image.open(imagefile) - im_bg = Image.open(cfg.IMG_BG) - h = img.height + 240 - w = img.width + 520 - - # Paste fig onto background img and autocrop background - img = img.resize((w, h), Image.ANTIALIAS) - x1 = int(0.5 * im_bg.size[0]) - int(0.5 * img.size[0]) - y1 = int(0.5 * im_bg.size[1]) - int(0.5 * img.size[1]) - x2 = int(0.5 * im_bg.size[0]) + int(0.5 * img.size[0]) - y2 = int(0.5 * im_bg.size[1]) + int(0.5 * img.size[1]) - img = img.convert("RGB") - im_bg.paste(img, box=(x1 - 5, y1, x2 - 5, y2)) - im_bg.save(imagefile, "PNG", quality=100) - image = Image.open(imagefile) - image = autocrop_image(image, 0) - image.save(imagefile, "PNG", quality=100) - - image = disnake.File(imagefile) - - print(f"Image {imagefile}") - embed = disnake.Embed( - title=f"Stocks: Zero-Lag-Moving-Average {ticker}", - description=plt_link, - colour=cfg.COLOR, - ) - embed.set_image(url=f"attachment://{imagefile}") - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - os.remove(imagefile) - - await ctx.send(embed=embed, file=image) - - except Exception as e: - embed = disnake.Embed( - title="ERROR Stocks: Zero-Lag-Moving-Average", - colour=cfg.COLOR, - description=e, - ) - embed.set_author( - name=cfg.AUTHOR_NAME, - icon_url=cfg.AUTHOR_ICON_URL, - ) - - await ctx.send(embed=embed, delete_after=30.0) diff --git a/discordbot/ticker b/discordbot/ticker deleted file mode 100644 index bd69cec423a8ea4fa031669ccfac58d4c4333f40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45 kcmZo*nX1G90kKmwdKgQl^stl^=Oj+)VNT3VMj=!602`eSQ2+n{