Repository navigation
Simplify and fix: shared HTTP session, command table, Yahoo error handling, USD market caps, min_analysts, consolidation sweep - #37
Merged
Conversation
… push #35 was opened for a CI tweak, took in the 14 review fixes pushed to the work branch afterwards, and merged under its old title. The new section: before each push, check for an open PR from the work branch and update its title and description to cover every commit it holds; never make such a PR mergeable while they are out of date. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…ers, the ylim shim, unused imports - lib/discord.py (a pasted discord.py sample that could not even be imported) and lib/slack.py (getUser, never called) are gone; outbound Discord and Slack go through lib/webhook.py as before - util.categorise_tickers and util.get_currency_symbol had no callers; rows_chart no longer renames a ylim argument that nothing passes; webhook.strikethrough had no users (italics stays: reminder.py uses it) - shortman.fetch drops the locals it never read, and still parses every row's percentage - unused imports: json in reports, sharesight and shortman; io, json and lib.telegram in yahoo; util in webhook; shlex in jobs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…lder; the chart setup written once - flag_from_market and currency_from_market read one MARKETS table (flag, currency) instead of two 50-line if-chains walking the same groups; flag_from_ticker, transform_to_yahoo and the shared aliases of transform_to_google and exchange_human are tables too. Results are unchanged, checked against the old functions for every code either version knows (8,100 calls), quirks included (SGX still maps to the China flag and CNY, as the old chains' first match did) - yahoo_link and gfinance_link build their markup with link(); quote_link is the "Yahoo for what Google Finance has no page for" rule that charts.py and price.py each spelled out (identical links on 18,700 combinations of service, hyperlink setting and provider) - the chart palette, the 1280x800 figure and the PNG ending are written once (_figure, _png); the nine renderers give byte-identical PNGs - hbar_chart loses axis_fmt, which nothing could see (the value axis is hidden), and its never-read span, grid and inside-label state; the three chart specs that passed axis_fmt drop it - cache_path is the one place that says where cache and state files live - days_from_human_days tries each suffix in a loop (same results and errors on 300 inputs) - numpy and matplotlib.dates load inside the chart functions: a job process that draws no chart (trades.py every 20 minutes, reminder, cal, rating, shorts' text runs) no longer imports them. test_datadir imports matplotlib itself, so it still proves matplotlib starts without a home dir Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…s one thread at a time; fetch_each - fetch_detail reads its ~40 plain fields from DETAIL_FIELDS (path, conversion, and the extra error that also means "not there" where the old block caught one) instead of 55 copy-paste try blocks; the irregular ones (52-week low, net income, recommendations, trend, quarterly, insider) stay code. The duplicate currency block is gone. Checked against the old parser on the doc/yahoo samples and 6,000 single-point mutations of them (fields and modules deleted, nulled, given bad types): the same dict or the same exception every time - fetch() reads its optional quote fields from QUOTE_FIELDS (1,600 mutations, identical) - price_history drops a caption it built and never used, filters the days range once, and looks up the date-based periods (10Y to 1M, 7D) in one branch (identical tables and charts on 129 calls) - fetch_chart_json parses the response once and no longer fetches a cookie it never sends (getCrumb still renews it when due); getCookie measures the cache file's age with util.cache_age; rmCrumb moves the stale crumb aside with Path.replace - getCookie and getCrumb hold one lock: two threads renewing at once could cache a crumb for the cookie the other just replaced, for 14 days. prefetch_history already fetched 8 at a time - fetch_each(get, tickers) runs per-ticker fetches 8 at a time and returns results or exceptions in order; prefetch_history uses it - pandas loads in the functions that parse a history, not when the module is imported Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
- webhook: bold, italic and strike read one MARKUP table; payload_wrapper picks the size limit once (same "over 2,000/4,096 bytes" lines, same sends: checked on 180 calls); sendPhoto asks takes_charts once; pleaseHold loses its non-Telegram branch, which nothing could reach (TypingIndicator only runs on Telegram) and whose own Telegram arm was unreachable twice over - sharesight: _request and _check replace four copies of the request / status / error-field block in get_token, get_portfolios, get_trades and get_performance (same messages, same order: get_trades still prints the trade count before the error check); find_portfolio is the case-insensitive portfolio lookup that reports.py writes out twice - charts: the price chart's link is util.quote_link (price.py's copy of that rule too) and its marker util.get_emoji; the .history default view parses its 10-year history once instead of twice (history_payload hands back the chart it fetched); the history highlight comes from PERIODS rather than a second table of the same labels; one price.lambda_handler call for the price-list buttons - bot: the Telegram branch reads the message through two locals; the misspelt userRealname, never read, is gone (userRealName is the first name, as before: the fallback it was meant to set never ran); the worker thread runs worker.process_request directly; git_version defaults to REPO_DIR - jobs: the job process runs in REPO_DIR; commands() builds the hidden-script set once Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…; one portfolio lookup and holding line - .buy, .sell and the .peg lists fetched each ticker's Yahoo details one after another: tens of seconds on a cold cache for a hundred tickers, outlasting the typing indicator. They now go through yahoo.fetch_each, 8 at a time, and each list keeps its own error handling: ratings still skip a ticker that raised TypeError and fail on anything else, the PEG lists still print "value not found" for a KeyError, and the first failure in ticker order is the one reported - .compare downloads its 2 to 6 full histories at the same time (the first failure is still the one reported) - the .SYMBOL profile asks price_history for the percentages only: it rendered (or read) a chart it then dropped, up to 0.15 s of CPU on the bot's event loop for each cold ticker - simplywallst.get_url ran twice per profile (two identical lines); now once - holdings and .pie find the portfolio with sharesight.find_portfolio; holding_line formats a Sharesight holding for the holdings list (and .who, next); one module-level last_col replaces two nested copies; the buy and sell branches of the ratings share one condition; the four delta lines come from a loop - prepare_rating_payload is indented one tab, like every other function Checked against the previous module on 1,578 calls with canned data (every report, service, action and argument form, with tickers that raise TypeError, KeyError, ValueError or RuntimeError): the same payloads, chart rows and exceptions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…th block; no print-and-reraise wrappers - the 28 command patterns were all matched on every message up front, 100-odd lines above the if/elif chain that decides which one wins. Each pattern now sits in its own branch condition, in the same order (checked: identical patterns, identical order), and matching stops at the first hit. .buy and .sell, two identical branches, are one - working(service, chat_id, ...) starts the typing indicator, sends a command's "Fetching..." notice where there is no typing indicator, and stops the indicator however the block ends. It replaces 23 hand-written start/stop pairs and the per-request registry that stopped the ones a failed command left running: the error line still goes out after the typing has stopped - the 22 `except Exception as e: print(e); raise` wrappers are gone: process_request's report_error already logs the error line and the traceback (stderr loses only the duplicate bare message) - period_or_ticker, percent_or_ticker and period_and_portfolio parse the arguments that nine branches parsed by hand; the reply URL is webhook.chat_url; deliver() has one chart branch - .beta fetches its tickers' details at the same time (yahoo.fetch_each) and sorts its lines and chart rows once, together; .who formats its lines with reports.holding_line - dead locals gone: a doubled `threshold = threshold=` assignment, an unused specific_stock, a "DEBUG" print in .top, checks on match groups that cannot be empty Checked against the previous worker on 276 messages (every command and argument form, on Telegram DMs, Telegram groups and Slack) and on 1,748 more with Yahoo or Sharesight failing: the same sends, typing starts and stops, and error lines, in the same order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
… of the logs; a timeout on Yahoo's cookie - lib/net.py holds one requests.Session that webhook, telegram, yahoo, sharesight, shortman and podcasts all send through, so calls to the same host reuse a connection instead of a new TCP and TLS handshake each time (each typing notice and reply, each ticker of a Yahoo burst, each Sharesight report). The session keeps no cookies: Yahoo's cookie still travels only in the Cookie header finbot sets, and the request that asks fc.yahoo.com for a new cookie never carries the old one (a stored one would be sent back and Yahoo would set none). No retries are added: Telegram and Slack posts are not safe to resend, and the Yahoo calls already fall back to the second host - net.redact strips Telegram bot tokens, Slack and Discord webhook paths, bearer tokens and token, crumb, key, secret and auth parameters. Applied where secrets were printed: a failed webhook.write (it printed the URL with the bot token and Slack's Authorization header), failed sendPhoto, chart upload, message edits, typing notices and button answers (requests' errors quote the token URL), the traceback report_error and the WSGI handler print, Yahoo URLs (the crumb) and errors. The new Yahoo cookie and crumb, and Sharesight's token in debug, are no longer printed at all; print_headers hides Telegram's secret header and print_body Slack's verification token - getCookie's request to fc.yahoo.com has the same timeout as every other request (it had none) - tests patch lib.net where they patched requests.get/post; new tests: secrets stay out of a failed send's log and a reported traceback; the session keeps no cookies Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…null or garbled field is left out - fetch_chart_json raises RuntimeError (no secrets in it) instead of returning (error, None), and price_history and price_series pass it on: the nine places that checked for an error string are gone. Callers that skip one ticker now catch it: the price list and the profile leave that ticker's lines out, performance's benchmark line says "no price history", milestone skips the ticker, and prefetch logs it once. The ones that report it (.history, the price chart, .price TICKER, the chart buttons) let it reach report_error as before. historic_high raises on a history with no highs and lows rather than returning None for its caller to trip over. price_history raises on a history with no prices, uses the chart's last close when Yahoo gives no current price, and UTC when it gives no time zone. The "temporarily disabled" inject-latest block and its unused time are gone - fetch_detail: one dig() reads every path; a field Yahoo leaves out, sends as null or garbles (text or a list where a number belongs) is left out instead of failing the whole ticker or crashing. This also ends the crash on a 52-week low of 0 or less (now left out, as fetch() did) and the half-stored analyst estimates; the recommendation is stored whole or not at all. One percentage converter: a Yahoo 0 is 0.0 like every other percentage. Trailing P/E is rounded like fetch()'s, not truncated (56.7 showed as 56). 6,000 mutated responses: none raises - fetch(): the currency is the quote's own (a quote without one took the previous ticker's, or crashed on the first); a null name or price skips the ticker; the earnings date is the first announced date still to come among those Yahoo sends; percentages are floats - one query2-then-query1 fallback (_get_first) for the crumb, quotes and details - util: read_cache and read_binary_cache share one freshness check (_fresh_cache, ages from cache_age); its hit, miss and expired lines are all debug-only now (the PNG miss line printed always). getCookie reads its cache through read_cache, renewing 30 seconds early as before Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…lit into gather, report and delivery
Destinations
- webhook.default_chats() says where a scheduled report goes: Telegram's telegramChatID (left out
without one), and Slack and Discord through their incoming webhooks, with no chat id. It raises
"nowhere to post" when finbot.env names none. report_error, price, performance, shorts, cal,
trades, rating, milestone, reminder, podcasts and bot_send_stdin all use it, and
webhook.destinations(interactive, service, chat_id) adds the reply-to-the-asker case
- this fixes the crash with slack_webhook set, no slackBotToken and telegramChatID set: price,
shorts, rating and milestone sent Telegram's chat id to Slack's webhook, write() then needed the
bot token and raised TypeError before Telegram got its copy. A Slack reply without slackBotToken
now logs why and sends nothing instead of raising
- the ten `if not webhooks: sys.exit(1)` guards are gone: in a chat reply they killed the worker
thread outright (SystemExit gets past except Exception), e.g. on a Slack bot without slack_webhook
- webhook.send(service, url, chat, payload, image, buttons) sends the chart with the text as caption,
or the text: the scripts no longer each choose between sendPhoto and payload_wrapper
- cal's scheduled run uses its days argument (it used config_future_days, which is the default);
shortman.fetch hands the quotes back unchanged when Short Man is down (it returned {} and the
US short interest from Yahoo went too); shorts, cal and rating fetch Yahoo details 8 at a time and
skip a ticker whose fetch failed; trades finds its portfolio with sharesight.find_portfolio;
milestone loses parameters nothing passes (it never had an interactive path)
price.py and performance.py
- gather() fetches the data once; report(service, ...) builds (payload, chart) for one service;
lambda_handler sends it. The chart period buttons call report(): return_result and _rows_out go
- price: the volatile-stock second pass recursed with the demotion still on, so it re-failed the very
tickers it was meant to add back (and could add a second heading). Now, as its comment intended,
a leveraged or volatile ticker needs its higher bar (trigger_bar) only to send a scheduled report
on its own; once one goes out, every ticker past the threshold is listed, whatever their order.
Interactive reports are unchanged (identical on 39 cases), as are scheduled ones without demotion
- performance: benchmark() and report() at module level; util.get_emoji instead of a nested copy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…ices; .hello no longer grows its word list - COMMANDS lists (pattern, function) in match order; each command is a function taking the request and the match. - The "Fetching..." notices are gone, searchVerb with them; every command that fetches shows typing, a ticker lookup too. - .hello: the three struck-out verbs are local, so the shared list no longer grows and a tuple of them is never picked. - Percent arguments are numbers (2.5 included; nan and inf are tickers); thresholds print without a trailing .0. - .beta, .who and the one-ticker .marketcap are built in reports; .beta says so when no stock qualifies. - .pe, .fpe and .peg read a ticker as the other commands do; .performance and .trades take a portfolio name of several words. - .history and .compare on their own give their usage instead of the profile of a ticker named after them. - bot.py: a blank Telegram first name falls back to the handle; Slack's debug line goes to stderr. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…d SGX get their Yahoo suffixes, flags and currencies - TSE (Sharesight's Tokyo) becomes .T; TSX becomes .TO, NZX .NZ and SGX .SI. - SGX is Singapore (SGD), not China; NZX gets a flag and NZD; .SI, .T and .TA tickers get flags. - The Danish krone is DKK, not DEK. - Country suffixes for ADR primary listings: Ireland is IR (the stray dot made X..IR), Belgium BR (AS is Amsterdam), Saudi Arabia SR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…t has both new values - Gone: milestone's profit and cash-flow block with its four unused lists, the pinning stub in bot.py, the unpinChatMessage stub, and single lines in bot.py, reports, util, podcasts and trades. trades keeps the reason as a comment on the live line. - milestone wrote 0/0 for a ticker it saw for the first time before it had the ticker's 52-week values; when those were missing the zeros stayed, and the next run reported a record high for it. Now a ticker is recorded only with both values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…ashing; data files read in one place; unused imports gone - The .SYMBOL profile reads one data dict with each field defaulted once. It crashed when Yahoo left out marketState, profile_exchange, currency, market cap (with insider data present), regularMarketPrice or sharesOutstanding, on a zero shareholder equity, and on a missing percent_change it never used. - The PS ratio line carried over whatever warning an earlier line had set (forward P/E, insider selling): it now warns only above 15. - config.data_json reads a data file (the user's copy, else the shipped default) for the ADR table, simplywall.st and reminders. For simplywall.st the custom list now wins over the shipped ones, which it used to lose to (they never disagree as shipped). - Unused imports and two f-strings with nothing to fill removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…ments from two ordered tables - Pinning, button toasts, text and chart edits and the typing notice go through telegram.call; telegram.try_call is the same for calls whose failure is only logged. Pinning used to crash the earnings report on a non-JSON answer or a network error. - transform_title's ~90 replace lines are TITLE_TRIMS and TITLE_SHORTENINGS, in their old order. Fixed on the way: a title ending " Se" kept it (the check was for Se, the removal for SE), a trailing AG, SE, & or "and" removed every occurrence of it in the title rather than the one at the end, and runs of spaces left behind are collapsed. - The test fixture points lib.telegram at the fake Telegram too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
… same way everywhere; trades loses its dead lines - cal.lambda_handler(event='earnings' | 'ex-dividend') replaces the earnings and dividend flags (with neither set it crashed). An ADR whose primary listing Yahoo does not know is skipped instead of crashing the earnings report. "No events found" names the period as the heading does. - sharesight.pick_portfolios is the one lookup for .trades, .performance, .holdings and .pie. A name that matches no portfolio raises PortfolioNotFound, which the worker answers with the name and the portfolios there are. It used to be a ValueError, "no Sharesight data found", or one of two wordings of that answer. - trades: an interactive .trades whose trades are all neither buys nor sells says so instead of nothing; an unused start date, a second ticker and a portfolio filter that could not filter anything are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…ed items and keeps its state like the other scripts - rating wrote only the tickers it rated in a run: one that failed to fetch lost its last rating, and its next change went unreported (a one-ticker run wiped all the others). The record is now added to, and an interactive run records nothing, as with trades. - podcasts: a feed mixing dated and undated items crashed the sort (aware and naive times); a garbled date is now no date. The newest five are picked once, after reading the feed rather than at every item; a run with nothing new posts nothing (it used to post an empty report when the stored list was not in sorted order); the seen list is kept with util.json_write like the other state files (atomic); single-tab indentation like the rest of the code. - webhook.italics, an alias of italic, is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…ops did it; the ratings list sorts once - yahoo.fetch_details fetches the details of many tickers at once and leaves out (and logs) one whose fetch failed, so a list goes out without it; strict=True raises instead, for a report on the one ticker asked for. It replaces the loops in .beta, .buy/.sell, .peg, shorts, the calendar and rating.py. .buy/.sell used to stop at the first ticker that failed (other than with a TypeError), and .peg did too. - .buy/.sell sorts (rating, analysts, line, ticker) once instead of sorting the text lines and a chart list separately to the same key; the report and chart are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…r axis styling and heading; config errors print in one place - make_paragraphs dropped the words after its last paragraph break, so a short company description came out empty and a longer one lost its last sentences. - The price and compare charts share _style_time_axes, and those two and the pie share _heading; every chart renders byte for byte as before. - config.print_errors logs a ConfigError, where bot.py and config each had the loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
The text lines and the chart rows were two lists sorted separately to the same key; now one list of (percent, line, label) is sorted once. The report and chart are unchanged, but for the space after 🩳 in the "nothing found" lines, which every other line has. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…gone; a bare number on .price is a percent As decided: - milestone keeps the all-time record (the higher of the stored record and the new 52-week high, the lower for lows). After its first run it used to store the 52-week values, so a stock far below its peak posted a record high at each new 52-week high. Records kept before this (52-week, under 'high'/'low') are replaced by the ticker's history on the next run. - .buy and .sell list the strongest rating first (.sell used to start with the mildest), and on equal ratings the one more analysts give. - .top, undocumented and the same as .price top [period], is removed; .top alone looked up a ticker named TOP and .top10 read its 0 as a 0-day period. - .price 3 is 3%, as on .session, .premarket and .shorts and as the README has it; a period carries its unit (.price 3 7d is 3% over a week; it used to drop the 3). The README's price section names .session (not .midsession) and @botName price. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…on market cap no longer tops .marketcap
- .buy and .sell list only stocks rated by 7 or more analysts, and say so in the heading; with none, the reply says that.
Thinly covered ASX small caps (two to six brokers) were nearly all a perfect 1.0 and filled the top of .buy.
- yahoo.add_usd_market_caps converts each market cap, which Yahoo gives in the stock's own currency, to US dollars at Yahoo's
exchange rates (KRW=X and the like); a market quoted in pence, agorot or cents has its cap in pounds, shekels or rand.
.marketcap ranks and shows US dollars ("(USD)" in the heading; a stock with no rate is left out and logged); .marketcap SYMBOL
shows its own currency and the US dollar figure. .beta's "mkt cap under 2B" and demote_volatile's small-cap thresholds
(150M, 1B) are US dollars too: unconverted, a small cap quoted in won or yen was never small. For a stock quoted in Australian
dollars the thresholds move by the exchange rate. The profile labels a pence-quoted market cap GBP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…pe takes the spare place; buttons stay alphabetical - The rating command takes both words (.buy & sell, .buy sell, .buy/sell, .buy and sell) and sends each report once, in the order asked; .buy and .sell alone are unchanged. - The Telegram command keyboard has .buy & sell in place of .buy and .sell, and .forward pe in the place that frees, sorted alphabetically row by row. Its version changes, so chats get the new keyboard at startup and after their next DM reply. - A test checks that the keyboard is in order and that every button is a command, not a ticker for the .SYMBOL catch-all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
The forward P/E list already had a chart spec; the command now delivers it, with the list as its caption, where the service can take an image. The top, bottom and negative lists all chart; one ticker's ratios stay text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…s (default 7), documented - New preferences.conf key min_analysts (default 7, reloadable): .buy, .sell and the .forward pe lists (top, bottom, negative) take only stocks covered by at least that many analysts. A forward P/E is analysts' estimate, so with few of them it says as little as a perfect buy from two brokers. One ticker asked for (.fpe SYMBOL) is still shown. - The forward P/E headings say "(7+ analysts)", as .buy's does, and an empty list says why. - Documented in the README's preferences table (now 26 keys), the .buy/.sell and P/E sections, and the shipped defaults/preferences.conf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…0z2q Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A simplification pass over
bot.pyandlib/, the fixes from the review list, a consolidation sweep, and the report changes decided along the way: 25 commits.Changes you will notice in chat
.price 3is 3%, as the README documents and.session,.premarketand.shortsalready did. A period carries its unit (.price 3 7d). Decimals work everywhere (.session 2.5), and thresholds print as5%, not5.0%..marketcapranks in US dollars, converted at Yahoo's exchange rates, so a won market cap no longer tops the list..marketcap SYMBOLshows the stock's own currency and USD..beta's "under US$2B" anddemote_volatile's small-cap thresholds are US dollars too; for ASX stocks those thresholds move by the exchange rate..buy/.selland the.forward pelists take only stocks covered bymin_analystsanalysts (new preference, default 7). Ratings list the strongest first, and more analysts win a tie..forward pesends a bar chart. The Telegram keyboard has one.buy & sellbutton (it sends both reports) and a.forward pebutton, in alphabetical order..topis removed; use.price top [period]..historyand.comparealone give their usage..trades,.performance,.holdingsand.pie..tradeswith no buys or sells says so..T) and TSX is Toronto (.TO)..NZ) and SGX (.SI, SGD, 🇸🇬) are mapped.Fixes
.SYMBOLprofile when Yahoo leaves out any of several fields, or equity is zero.podcasts.pyon feeds mixing dated and undated items.milestone.pyno longer records 0/0, which caused fake record highs.rating.pykeeps the last rating of a ticker missing from a run..hellono longer grows the shared word list.transform_title's suffix rules are fixed.Consolidation
COMMANDStable, built around aRequestobject.webhook.destinations/default_chats/send).yahoo.fetch_details, which replaces six loopsyahoo.add_usd_market_capstelegram.call/try_callconfig.data_jsonconfig.print_errorssharesight.pick_portfoliosandPortfolioNotFoundutil.security_line_style_time_axes/_heading(the images are byte for byte as before)transform_titletablescal.lambda_handler(event='earnings' | 'ex-dividend')replaces the two flags. Schedule lines (cal.py earnings,cal.py ex-dividend) are unchanged.Config and docs
min_analysts(default 7, reloadable): documented in the README table and indefaults/preferences.conf. Existingpreferences.conffiles need no edit..sessionand@botname price, and documented USD market caps and the analyst filter.Testing
.venv/bin/python -m unittest discover -s tests). The regression test for each fixed bug fails on the old code.🤖 Generated with Claude Code
https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Generated by Claude Code