Skip to content

Sharesight portfolio names, /future, holding-news search, yahoo-finance2 MCP, DARKMODE hours - #50

Merged
robdevops merged 40 commits into
mainfrom
claude/compassionate-heisenberg-pz23nq
Nov 6, 2026
Merged

robdevops merged 40 commits into
mainfrom
claude/compassionate-heisenberg-pz23nq

Conversation

@robdevops

@robdevops robdevops commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner

Portfolio names and holdings from Sharesight itself: drop PORTFOLIO_NAMES

PORTFOLIO_NAMES fed the tool gate and the known-ticker holdings; both now come from Sharesight's list_portfolios (every portfolio except consolidated views), refreshed daily and kept in kv. The env var is gone.

/future TICKER

Analysts' low/average/high revenue and EPS for this and next fiscal year, as two line charts drawn without the model.

Holding news: search allowance

One combined request with a larger web-search allowance (60 results), ETFs and funds skipped, and the model's notes about its search dropped.

Move to the yahoo-finance2 MCP server

  • Ticker charts and their period buttons use chart, in the exchange's own time zone. 1D/5D show the last trading days. A new internal _raw flag (no slimming or cap, stripped from the model's calls) keeps the bot's own fetch at full resolution.
  • /future reads quoteSummary earningsTrend. Fiscal-year labels come from its end dates.
  • Quotes are trimmed to the fields a model uses, so a ten-stock quote fits in one result.
  • mcp_servers.json:
    • The new command.
    • allowed_tools (quote, quoteSummary, chart, fundamentalsTimeSeries, search).
    • moduleOptions is hidden from the model.
    • The Yahoo part of the prompt drops from about 1,120 to 725 tokens.
  • Fixtures are real output from the new server.

MCP connection failures in one log line

A bad URL, a refused or unauthorised connection, a missing command or an MCP error is logged as one line, e.g. MCP server yahoo failed: HTTP 401 Unauthorized from http://127.0.0.1:3000/mcp. Anything else keeps its traceback.

DARKMODE takes the dark hours

off (default), on, or a range in TZ: 18:00-6:00, 18-6, 6pm-6am. A range may run past midnight. auto is removed: use 21-7 for the old behaviour.


🤖 Generated with Claude Code

https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw

claude added 30 commits November 2, 2026 23:43
… plain stand-ins

Pillow's default font has no ² ³ − → € etc., so they drew as boxes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
…ws the share)

Falls back to shares when a value would round to zero.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
…hes value legend

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
…ks none

The url_citation annotations were only counted; now they are kept on the Step and Answer
and ask() appends a Sources line (max 3) to chat answers that carry no link of their own.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
Positions from the url_citation annotations are applied to the raw reply before any
formatting; the Sources line stays as the fallback when no marker could be placed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
…CHAT; log user input as two words

- LOG_WIDTH removed: tool-name lists are logged in full.
- BOT_TZ -> TZ, HISTORY_LIMIT -> HISTORY_MAX (default 10, and a real maximum: the
  stepped window now holds max/2+1 .. max messages), TELEGRAM_PRELOAD_CHAT ->
  TELEGRAM_IMAGE_PRELOAD_CHATID. The old names are not read any more.
- User input in the log is cut to its first two words and one ellipsis character; the
  other log-line truncations (error snippets, failed HTML) are gone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
…Default column

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
…rs were placed

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
OpenRouter's url_citation positions are 0 and the search runs in an earlier round than the
answer, so the position-based marker is replaced by matching the answer's sentences to each
source's snippet (figures weigh most, words every snippet shares count for less).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
The chat system prompt (OpenRouter only) tells the model to cite a sentence that rests on a search
result as [<a href=URL>n</a>]. Its links are kept only for pages the search returned and renumbered;
without them the snippet-overlap markers apply. The trailing Sources line is gone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
…ere dropped

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
…n order

The model cited 3 of ~10 pages the search returned; the overlap pass now runs for the rest even when
the model wrote markers of its own.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
Bare capitalised words (GAAP, EBITDA, AFR, GM, UPS) kept getting Yahoo links and the stoplist never
ended; ASX:NXT linked to the US NXT. Replies now link only $NVDA, NXT.AX, ASX:NXT (mapped to
Yahoo's suffix), Name (TICKER), Sharesight's NXT (ASX), ^GSPC and BTC-USD, plus bare words in a
known set: the S&P 500 and NASDAQ-100 (from Wikipedia) and the US Sharesight holdings, refreshed
daily in the background and kept in kv, never fetched while answering. A bare code in brackets
links only if known, since it names no exchange. Holding news links the holdings' codes and a
movers explanation the listed codes with the list's exchange. The prompt asks for these forms.
The NOT_TICKERS env var is gone; the built-in list now only serves the tool gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
- An exchange suffix links on its own: ALL.AX (Aristocrat), ASX.AX, AI.PA were left plain because
  the code is also on the acronym list.
- A bracket that isn't a ticker ("NVDA (Q3 FY26)") no longer swallows the ticker before it.
- Share classes: BRK.B as the model writes it links to Yahoo's BRK-B.
- "AT&amp;T (T)" and "Procter &amp; Gamble (PG)" are bolded whole.
- Numeric holding codes (700 on HKEX) never link bare numbers, and keep their unsubscribe button.
- Index tickers that are also finance or tech acronyms (GM, UPS, IR, HD, USB, RF...) link only in a
  marked form, as single letters already did.
- A refresh part that fails (a page down, Sharesight not up yet) is tried again in 30 minutes,
  not 24 hours; a footnote in the table header no longer hides the ticker column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
…the ASX 200

Wikipedia answered 403 to the page fetches: its policy refuses programs whose User-Agent names no
contact. The lists now come through the action API (action=parse) with the repo URL in the
User-Agent, and a refusal logs Wikimedia's reason. The S&P/ASX 200 is added: its codes link bare
with .AX. Holdings on any known exchange count (NXT -> NXT.AX). A code in a US index and in the
ASX 200 isn't linked bare unless a holding settles it. ASX codes that are everyday words or
acronyms (GPT, WOW, ZIP, CAR...) need a marked form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
The "Nasdaq-100" article has no constituents table any more (0 tickers in production).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
…gle Finance

Google takes CODE:EXCHANGE (NXT.AX -> NXT:ASX, 0700.HK -> 700:HKG); a US ticker names no exchange
(NASDAQ or NYSE), so it opens a Google Finance search. History compaction and the holding-news
mute codes read either provider's links back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
Google Finance quotes are CODE:EXCHANGE (JPM:NYSE, SPCX:NASDAQ, BRK.A:NYSE). A US ticker's exchange
now comes from the text (NYSE:JPM, JPM (NYSE)), the Sharesight holdings, the NASDAQ-100 (all
NASDAQ) or the S&P 500 list, whose ticker links point at nyse.com or nasdaq.com; kept in kv with
the lists. Only a ticker none of these place opens a Google Finance search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
TradingView pages are EXCHANGE-CODE (ASX-NXT, NYSE-BRK.A, NASDAQ-SPCX), with the US exchange from
the same sources as Google's; a code it can't place opens TradingView's page for the bare code.
The three sites' URL builders leave tickers.py (near its size limit) for their own module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
Shows what the daily holding-news check (and movers, DM buttons, chat commands, filings) costs
next to chat; every request was already recorded with its kind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
/tokens and /credits still work. Telegram shows the menu commands in autocomplete, so admins now
see /usage there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
Each recipient used to get a separate searched request, so a stock two people hold was searched
twice and every request paid its own overhead (holding news was the biggest cost in /usage).
The daily check now asks once for the union of everyone's unmuted holdings, with everyone's recent
items as the don't-repeat list, then gives each person the items about their own holdings; an item
naming no holding goes to everyone. /holdingnews still checks just the asker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
… articles

SHARESIGHT_HOLDING_NEWS_SKIP_DAYS (e.g. sun,mon) skips the daily check on those weekdays. Each
check now searches back to the last one that got an answer (kv; at least 24 h, at most 7 days),
so skipped days, a busy provider or an outage leave no gap; the prompt and the message header
say how far (past 72h). Repeats: the don't-repeat list always reaches past the last check, and
an item citing an article already sent is dropped whatever the model says. The feature becomes a
package (holding_news/ with text.py) to stay under the module size limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
The old name is no longer read: rename it in the service's environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
…AMES

The daily known-ticker refresh now reads every portfolio list_portfolios returns (skipping
consolidated views, which repeat the others): their holdings feed the known tickers and their names
route portfolio questions in the gate. Both are kept in kv, so a restart has them at once. The
PORTFOLIO_NAMES setting is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
…arts

Two charts from Yahoo's get_analyst_estimates (revenue, EPS), each with Low, Average and High lines
fanning out from last fiscal year's actual through this and next fiscal year; the axis is labelled
with real fiscal years from the latest annual statement. Drawn with the existing make_chart, no
model. History uses the estimates' own year-ago figures, so EPS stays on the analysts' (adjusted)
basis rather than mixing in GAAP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
…earch

The combined daily check searches every holding in one request, and OpenRouter's search tool capped
the whole request at 20 results, so it ran out part-way (and said so in a "Note:" bullet sent to
everyone). Requests can now ask for a larger allowance (Request.search_results; OpenRouter's
max_total_results) and the daily check asks for 60. ETFs and funds, found by name, are left out
of the search. The prompt asks for news items only, and lines about the search are dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
The Yahoo server is now yahoo-finance2's own (npx -y -p yahoo-finance2 yahoo-finance-mcp): generic tools (chart,
quote, quoteSummary, fundamentalsTimeSeries, search, ...) returning raw yahoo-finance2 JSON. The old tool names are
gone, so what called them by name moves over:

- Ticker charts and their period buttons fetch prices with `chart` (period1 from the period; times in the exchange's
  own time zone; 1D and 5D keep the last trading days, so a weekend isn't empty). The fetch passes `_raw`, a new
  internal flag (no slimming, no size cap, stripped from the model's calls like `_keep_sales`), so a day of 5-minute
  bars isn't thinned to 60 points.
- /future reads quoteSummary's earningsTrend; the fiscal-year labels come from its end dates (a June year is right
  for ASX names), so the statements call is gone.
- Quotes are trimmed to the fields a model uses (about 870 characters each instead of 3,700), so a ten-stock quote
  fits one result instead of being cut after three.
- mcp_servers.json: the new command, an allowed_tools list (quote, quoteSummary, chart, fundamentalsTimeSeries,
  search: names that don't look read-only, and the rest left out), moduleOptions hidden, and a description naming
  the queryOptions the model needs. The Yahoo part of the prompt drops from about 1,120 to 725 tokens.
- Fixtures are real output from the new server; tools/capture.py and the docs follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
@robdevops robdevops changed the title Portfolio names and holdings from Sharesight itself: drop PORTFOLIO_NAMES Sharesight portfolio names, /future, holding-news search fix, yahoo-finance2 MCP Nov 6, 2026
A wrong URL, a refused, failed or unauthorised connection, a missing command or an MCP error is logged as
"MCP server yahoo failed: HTTP 401 Unauthorized from http://127.0.0.1:3000/mcp", without the SDK's nested
ExceptionGroup traceback (httpx's "For more information" line and a local server's stderr are folded into the same
line). Anything else still gets its traceback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
Besides off, on and auto (9 pm to 7 am), DARKMODE can now be a range in TZ, in 24-hour or am/pm form, with or
without minutes; it may run past midnight and ends just before its last time. Settings.darkmode is now that range as
minutes of the day (None = off), and the chart checks the minute, not just the hour. A value that isn't a range
(18, 25-6, 6-6) stops startup with the accepted forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
The dark hours are now a range you set (21-7 is what auto was); auto is refused at startup with the accepted forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014k4kywyN5dAo39izUPvxbw
@robdevops robdevops changed the title Sharesight portfolio names, /future, holding-news search fix, yahoo-finance2 MCP Sharesight portfolio names, /future, holding-news search, yahoo-finance2 MCP, DARKMODE hours Nov 6, 2026
@robdevops
robdevops merged commit 1270c6b into main Nov 6, 2026
1 check passed
robdevops pushed a commit that referenced this pull request Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants