Repository navigation
Treemap, bare-command pickers, jobs menu and schedules, background chart priming, 6 h cache, CI on Node 24 - #41
Merged
Merged
Conversation
…hats Each chat has had its own jobs since jobs per chat, which left the home chats two things, the errors that belong to no chat and the first jobs. Both go: - An error that belongs to no chat (a system job's, a webhook request's, a pulled update's) is logged and posted nowhere; one that belongs to a chat still goes there. No more "Failed to report error: nowhere to post". - A report runs for a chat, the one that asked or whose job it is: without one it raises "nowhere to post" (delivery.targets; default_chats is gone). - The three keys are retired: accepted in finbot.env with a note, at start and from check_config, to delete the line; gone from the template, Env and the startup warnings. - An upgrade from before jobs per chat still needs them once: lib/migrate.py reads them from the environment, for that alone, to give those chats the old schedule.cron, watchlist, plans and records. - util/send_stdin.py takes the chat's key: echo hi | util/send_stdin.py telegram:-1001234567890 - The tests run scheduled reports for a chat (common.scheduled()), and record_outputs gives a tree without home chats each network's chat. util/record_outputs.py: 268 steps, identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…s own ARM and CLBT are held on US markets, so their own details come first, as before. Only when those have no earnings or ex-dividend date does the calendar try another listing of the company, where the date may be: the one finbot_adr.json names, the NZX's for an ASX ticker reporting in NZD, or, again, the home market's for a foreign company's US listing (markets.YAHOO_COUNTRY: ASML.AS for ASML). The last two are guesses: one that Yahoo does not know (ARM.L, CLBT.TA: there are none) is logged at DEBUG, not WARN (yahoo.fetch_detail(guess=True)), and the stock left out. Only the earnings and ex-dividend calendar (/earnings, /dividend and the scheduled cal job) uses these fallbacks. .exdividend and .ex-dividend are .dividend; they were taken as a ticker. util/record_outputs.py: 268 steps, identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…rt, slash commands first - Pull mode is the default, and HTTP mode the alternative for those who prefer to run an HTTPS gateway; the docs no longer say "no server": finbot runs all the same, it only makes outgoing connections. Each guide's table says whether finbot listens on the internet. - Discord's gateway is its gateway API, and the mode gateway API mode. - README: the new features list; beside Docker's quick start, one with Python (git clone and a venv, on Linux, macOS or WSL), both installing every optional library; Issues in place of Suggestions. - Slash commands come first (/price, /jobs add), the dot form (.price) the plain message, quicker to type on a phone; the command list and each report's examples are slash commands, without the @botName copies. - No home chats: the guides lose the chat id steps, each chat schedules its own reports with /jobs add, and configuration.md lists the retired keys. - install.md: a Python venv anywhere else, and what Docker, a venv and a Debian host each install. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
A schedule is which days (daily, weekdays, some weekdays, a day of some
months, or a date once), which times on them (a time of day, or a repeat
within a window of hours), or a span across days such as Mon 9am to Sat
3pm, which cron cannot hold as one line; and the zone it is in.
- parse() reads the English a chat types ("weekly at 9am", "hourly at :01
weekdays from 10am to 9pm", "every 31 mar", "on 28 oct 2026"), with a
zone last as a name, a place or what people call it (EST, AEST).
- describe() says it back by its zone's clock, with the local time beside
it where the clocks differ.
- next_after() gives the next run: a skipped time runs at the end of the
gap, a repeated one runs once.
Nothing uses it yet; the scheduler moves onto it next.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…jobs
Each chat's jobs now run on schedules of finbot's own (lib/when.py) and
whitelisted actions; no cron line is shown, typed or stored, and croniter
is gone.
- Default jobs (lib/catalog.py): 35 items, every one switched on in each
chat finbot knows. That is every chat in the old finbot_schedule.json,
every old home chat, and any chat at its first command. Its old cron
jobs are not carried over.
- Trades runs as one span, Monday 9am to Saturday 3pm Sydney.
- Market reports run in their market's zone.
- Performance and price cover the days between their runs.
- Message jobs replace the reminder report and reminder.json: their texts
are default jobs now, AU ones only where country_code is AU.
- A job is an action from a fixed table of report modules, with arguments
its own parser takes. A stored job naming anything else is logged and
left out, never run.
- .jobs lists the jobs a page at a time, with buttons: Enable/disable,
Delete (jobs added here only), Add (report, how often, day, hour; Back
and Cancel), Run now and Next. A stale or forged press acts on nothing.
- .jobs add takes English ("price close weekdays at 4:30pm New York",
"message every 31 mar "text""); .jobs next and .jobs system are new.
- Each list marks a job that has not run yet with *; a one-off past its
time is deleted as the list is shown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…eminder.json
- doc/scheduling.md is rewritten:
- the menu and what to type;
- schedules in English, with zones and daylight saving;
- message jobs;
- the 35 default jobs as a Melbourne chat sees them;
- the whitelist of actions;
- what an upgrade leaves behind (finbot_schedule.json's cron jobs,
reminder.json).
- The job examples in commands.md now use English.
- configuration.md: the country_code and timezone rows, and reminder.json.
- install.md drops croniter; docker.md drops reminder.json.
- The network guides describe the menu's buttons and say every chat has
the default jobs.
- CLAUDE.md maps lib/when.py, lib/catalog.py, the new lib/jobs.py, the
message report and the menu.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
- In every list, a default job's name carries †, in place of the Delete screen's "(default)". Each page ends with a key to the marks it shows: "* not run yet, † default (no delete, only disable)", or whichever half applies. - Enable/disable has an All button: - "All off" when every job of the chat is on, switching them all off; - otherwise "All on", switching them all on. It says what it did, and a press drawn for a list that has since changed switches nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…r log
Add:
- Every frequency's screens go down to the minute:
- one-off: the date, the hour, the minute;
- daily and weekdays: the hour, the minute;
- weekly: the day of the week, the hour, the minute;
- monthly, quarterly and bi-annual: the day of the month, the hour, the
minute (two pages on Discord);
- annual: the month, the day, the hour, the minute;
- repeating: the interval, then the days.
- Each screen marks its default with † and says "† indicates default".
Nothing is skipped.
Direct messages:
- A direct message with the bot starts with its default jobs off. That is
a person's chat on Telegram, a D channel on Slack, and on Discord a
message from no server, which its updates now say (inbound.Message.dm).
- Once, at the next start, every job of each direct message chat finbot
knows is switched off. Discord's are found by asking Discord, and the
old schedule file's DMs are made known with their jobs off. It is
recorded in finbot_upgrades.json, so it runs at one start only, and is
tried again at the next start if Discord cannot be reached.
Log:
- These go to DEBUG: a button press, its acknowledgement, the message
edited in place (logged='debug' on every network), and a success
answered to a network.
- The tickers asked of Yahoo for trades are logged sorted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…rts by name
Enable/disable, Delete, Run now, Next and Add's first screen say Cancel, back to the
list. Add's report buttons drop their numbers ("price", not "5 price"), and its message
no longer lists them; Add's later screens keep Back, a step back, beside Cancel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
The menu's Next button reads Upcoming, its screen "Upcoming runs:", and the typed form is .jobs upcoming (help, autocomplete, the slash command's description and the docs); .jobs next still works. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
A request's log line (its method and URL) was cut at a fixed 73 bytes. The log_width preference now sets it, in characters, for any line that can run long: log.cut() keeps a line within it, its last character an ellipsis, and 0 cuts none. The default, 71, gives the same lines as before, and none longer (a line of 72 or 73 characters was shown whole). In Prefs, defaults/preferences.conf and doc/configuration.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
when.cycle(start, count) gives a cycle's months from its start month (quarterly
from Feb: Feb/May/Aug/Nov), and month_kind() reads any such cycle as quarterly or
bi-annual, so Schedule.frequency, days_words ("quarterly, 15 Feb/May/Aug/Nov",
"bi-annual, 1 Mar and 1 Sep") and the period of performance and price follow it.
Typed: "quarterly from feb on the 15th", "bi-annual starting in march", "annual
from aug"; a bare quarterly or bi-annual still starts in January.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…, repeats to the minute
- How often: one-off, hourly, daily, weekly, monthly, quarterly, bi-annual, annual,
every... ("weekdays" moves under every... as "weekday", hourly out of it).
- one-off: year (this one and the nine after), month, day (from today, tomorrow
the default), hour, minute.
- quarterly, bi-annual and annual: "start month?", then the day of the month (an
annual's only the days its month has), hour and minute.
- hourly and every 5 mins ... 12 hours: which days, start hour, start minute, then
the end time among the runs after the start, or the end of the day.
- every... weekday: hour and minute.
- Each level's choices come from one place (offered), which also checks a press:
a choice no screen offered (forged, or an older finbot's) is no step of Add.
- "† indicates default" is in italics.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…bs like any other
- The top level is Add, Remove, Enable, Disable, Run now and Upcoming..., two to a row,
and the list ends with how Add is typed, in italics.
- Enable has a button for each job that is off and Enable all; Disable for each one
that is on and Disable all; each says what it did ("Enabled 3. trades"), and with
nothing to switch the screen says so. A press switches one way only, so a stale one
cannot flip a job back.
- Remove has a button for every job, the default ones too, and Remove all, which asks
"Remove all N jobs here?" first. A removed default job is kept in the chat's record
('deleted') and comes back when the same job is added again.
- No "Default jobs:"/"Added here:" headings, no † on job names; the key is "* not run
yet" alone.
- Typed: .jobs enable|disable|remove|run NUMBER (toggle and del still work); alone,
each draws its screen. /jobs suggests the new words, the numbers of jobs off after
enable and on after disable.
- A slash command's description takes the usage lines that fit Discord's 100
characters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
The command keyboard of a direct message has .schedule (the jobs) where .beta was, sorted with the rest; its version changes, so each DM gets the new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
- sharesight.configured(): whether its keys are in finbot.env (yahoo_warm and sharesight_warm ask it too). - Without them, sharesight.tickers() is empty, so a chat's reports cover its watchlist alone and Sharesight is never asked; sharesight.portfolios() raises NotSetUp, an answer, so .holdings, .trades, .performance, .who and .pie say "Sharesight is not set up: its keys go in finbot.env (doc/configuration.md)". - The trades and performance jobs are default jobs only with Sharesight; a chat cannot add a job that reports on it, and Add does not offer one. - README: "If you don't have Sharesight, you can still add your holdings manually via the watchlist"; doc/configuration.md says what changes without it. Before, every report that covers the watchlist failed without Sharesight's keys, and the trades job posted an error every 20 minutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
… buy & sell, history, compare...
A job may run any command of .help (worker.COMMANDS, the table .help is made from)
but those that change something (watchlist, plan, jobs; Command.job false) and
those a scheduled report already runs (price, performance, trades, shorts,
earnings/dividend, session/premarket). That leaves holdings, pie, who, marketcap,
stock, beta, compare, history, buy/sell, pe, forward pe and peg; a new command is a
job too unless it says otherwise.
- worker.CommandJob stands in for a report module: its usage is what it takes,
job() reads its words (none where it takes none, at least one SYMBOL or
PORTFOLIO where it needs one), and run() answers in the job's chat as the
command would, through a Request aimed at that chat (Request takes a dest).
Nothing is evaluated: the job names a command of the table, its arguments stay
words, and a stored job naming anything else is still left out.
- jobs.actions() is the reports and the commands; .jobs add takes a command by
its name, two-word ones too (forward pe), the words before the schedule
(".jobs add pe top weekly", ".jobs add compare AAPL MSFT 1y weekly at 9am");
another of its names (fpe, a typo) stands for it, but buy, sell or both say which.
- Add lists the commands after the reports (buy & sell as one); one that needs a
stock or a portfolio says how to type it. On Discord the list comes on two pages.
- .jobs add alone lists what each takes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
"schedule: 85 jobs of 1 group chat and 1 DM, and 3 system jobs". A chat's record
marks a direct message ('dm': True) from when finbot first knows it; a one-off
upgrade (dm flag) marks the DMs it knew already, Telegram's and Slack's by key,
Discord's as Discord says, switching nothing. A DM's record kept 'defaults': 'off'
only until All on, and All off set it in a group too, so it could not tell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
… Sharesight needed - doc/scheduling.md: the list with how Add is typed under it; the six buttons (Add, Remove, Enable, Disable, Run now, Upcoming...) with Remove all, Enable all and Disable all; Add step by step, a table of every frequency and its levels down to the minute; typed enable/disable/remove; quarterly and bi-annual from a start month; the commands of /help a job can run; a removed default job comes back when added again; trades and performance only with Sharesight; the DM mark. - doc/commands.md: the /jobs lines. - CLAUDE.md: the code map (job_commands and CommandJob, actions(), 'deleted' and 'dm', delete_all and switch, when.cycle, mark_dms, sharesight.configured and NotSetUp) and a new command's job=False. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…legram counts it Scheduled price reports (close, midsession) went out as text without their chart. Telegram counts a caption's 1,024 characters in UTF-16 code units, after its HTML is read: each 🔼 or 🔻 counts 2. finbot counted code points, so a list of about thirty moves, cut to 1,012 of them, came to about 1,042 by Telegram's count. Telegram refused the chart and the report went as text. The shorter names (30 characters) fit more lines, and so more emoji, into a caption, which is why it showed up now. - dialects.Telegram.visible() counts as Telegram does: the text the HTML shows (no tags, & one character), in UTF-16 code units. - deliver(): a chart the network refuses goes once more with its heading (the first line) alone as its caption and its buttons, and the report follows as text. If that is refused too, the report goes as text alone, as before. A chart that timed out (it may have arrived) is not sent again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
A chat message's log line ("[Telegram]: @rob /price ...") went out as
it was typed, over as many lines as the message had, at any length.
- log.cut() puts a line on one line (each run of line breaks and spaces
one space), then cuts it to log_width as before. At log_width=0 the
line is whole, still on one line.
- The message line and the line for a message sent twice within a second
(now "sent it again within 1 s, not run again: TEXT", the text last so
a cut takes only the text) go through it, as each request's line does.
- The log_width docs (preferences.conf, configuration.md, the Prefs
comment) name chat messages too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
The list and its chart were sorted by gain, best first, so a portfolio's place changed from one report to the next. Now the portfolios come by name (ignoring case), then the S&P 500 and NASDAQ 100 benchmarks, in the same order in the list and the chart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…sector Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…lden image Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…ke the height it freed Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…ings .history .compare A picker is one message edited as buttons are pressed (a 1x1 image where the answer is a chart), .compare ticks up to 6 tickers with an alert on a seventh, .treemap toggles Sector/Industry, tickers sit behind a sub menu. The Telegram keyboard now has .compare, .pie and .treemap in place of .dividend, .marketcap and .pe. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…Group by after, buttons kept under the answer A picker's buttons stay under its answer (the item shown marked), so another item, the Sector/Industry toggle or a period redoes it in place; a Slack chart message keeps its button rows; the log prefix reads 'Telegram @user' not '[Telegram]: @user'. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
… it has a job to act on Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…d keep the picker under it The first portfolio (pie, treemap), the first ticker (history) or the first two (compare), alphabetically, with the picker's buttons under the chart; a press redoes the answer in place from what is shown; the page buttons are only under the tickers menu; a pie or treemap draws its other charts in a background thread to fill the cache. The 1x1 placeholder image is gone. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…are gets the tickers menu Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…tay inside it Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…s share Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
…the pie a little smaller to make room Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
… deprecated) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX
Co-Authored-By: Claude Sonnet 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.
What this brings in
40 commits since the last merge (#40), grouped by what they change.
.treemapand the pie/treemap PORTFOLIO [sector]: the holdings as tiles sized by value, grouped and coloured by industry (default) or sector, with group headings that fit their block, 2px edges and the subtitle beside the title./pielegend text is bigger (13.5pt), shrunk only where a name would run into its share; the pie is a little smaller to make room.Pickers for a bare command
/pie,/treemap,/historyand/comparetyped with nothing answer at once with the first portfolio / first ticker / first two tickers (alphabetical;/comparestarts on 7D), with the picker's buttons kept under the chart./holdingsshows its portfolio buttons./treemap's Sector/Industry toggle after them; tickers behind a menu (Tickers ▶,Select... (n)for compare) with 32 items a page and<>(fewer on Discord's five rows of five). Compare ticks up to six, refuses a seventh with an alert toast; Compare and picking a history ticker go back to the top level, which has the period row./compare T1 T2 [period]typed with tickers gets the same buttons when the tickers are among those held or watched..compare,.pie,.treemapin place of.dividend,.marketcap,.pe.Background priming
.jobsand scheduleslib/when.pyschedules, default jobs in every chat, message jobs, the.jobsmenu with Add down to the minute, Upcoming..., jobs that run.helpcommands, finbot without Sharesight)..jobs delete N;removeanddelstill accepted), and a top-level button shows only where it has a job to act on.Cache
cache_secondsdefault is now 6 hours (was 12); theyahoo_warmsystem job runs every 4 hours (was 8) to stay inside it.CI and packaging
actions/checkoutv4 to v5 andactions/setup-pythonv5 to v6, which run on Node 24, to clear "Node.js 20 is deprecated".finbot.service: the wrapped comments are one short line each.Other
telegramChatID,slackChannelID,discordChannelID; calendar home-market fallback;.performancesorted by name;log_widthfor chat lines; log lines readTelegram @user text; charts attach again to long reports (Telegram's caption counting); docs for all of the above.Testing
.venv/bin/python -m unittest discover -s tests -b), after mergingorigin/main.🤖 Generated with Claude Code
https://claude.ai/code/session_01JVEkZofdPjQ4jtXFKipeUX