Trade records carrying pnl=None or a non-numeric value raised TypeError
while computing trade statistics. Skip those records instead, and — since
trade_count still reports every record — append a warning naming how many
were excluded so win_rate and profit_loss_ratio are never silently
derived from a subset.
compute_edge_density divided by n_pairs, which is zero for a zero- or
one-column frame. Correlation edge density is undefined without at least
one distinct asset pair, so return an index-aligned all-NaN series. The
timeline entrypoint already requires two fetched assets, so this does not
mask a user-facing error.
Supersedes the duplicate guard bundled in #912.
The input validator requires the original basket total to be positive but
permits individual zero weights, so the history filter can leave only
zero-weight survivors and the renormalization divided by zero. Raise a
clear ValueError instead: allocating capital to zero-weight survivors
would invent holdings the user does not own, and an all-zero x-ray would
contradict the fully-invested-basket contract.
* fix(channels): preserve leading indent when splitting messages
After a newline cut, lstrip() removed indentation on the next chunk, so
code blocks and nested markdown lost spaces across Discord/Slack/Telegram
splits. Drop only the separator character.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
* fix(channels): keep Signal style offsets aligned after indent-preserving splits
split_message no longer blanket-lstrips; mirror that in _partition_styles so
indented chunks keep UTF-16 style ranges.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
* fix(channels): advance style cursor on space separator preceding indented chunk
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
---------
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
* fix(skills): parse frontmatter when closing fence is at EOF
SKILL.md / memory files that end at the closing --- without a trailing
newline (common from editors and LLM skill saves) were treated as plain
text, so name/description were dropped. Empty ---/--- blocks had the
same miss. Accept EOF or empty meta between fences.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
* fix(skills): require newline before closing frontmatter fence
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
---------
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Concatenated pairs like BTCUSDT are alphabetic, so isalpha() labeled them
as US equities. Recognize USDT/USDC/BUSD suffixes before that branch.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Broker CSV cells like $1,234.56 or ¥1000 failed float() after only commas
were stripped, so price/fee silently became 0.0. Strip common currency
tokens and unicode minus too.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Notepad/Excel Unicode text exports start with a UTF-16 BOM; latin-1 never
raises so those bytes were accepted as mojibake. Detect BOM and decode as
utf-16 first.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
After loaders started accepting 1h/4h/1d, calc_bars_per_year still keyed
only 1H/1D, so hourly runs were annualised as daily (Sharpe/vol wrong).
Normalize interval case and alias yahoo→yfinance / binance→ccxt.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Non-ASCII skill names such as 动量策略 were scrubbed to "--", so distinct
Chinese names collided into one directory and overwrote each other.
Allow the same non-Latin ranges as persistent memory, and hash when the
slug would otherwise be only hyphens.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Excel "CSV UTF-16" / Unicode CSV writes a BOM that utf-8/gbk cannot
decode, so analyze_trade_journal rejected the file. Try utf-16 in the
encoding fallback before raising.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
str.strip('|') dropped leading/trailing empty cells in pipe tables, so
row-label columns and trailing blanks collapsed when rendering for Signal.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
str.strip('|') dropped leading/trailing empty cells in pipe tables, so
row-label columns and trailing blanks collapsed when rendering for Telegram.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Unknown periods such as 1H were silently served as daily candles. Fail
closed with an explicit unsupported-period error instead.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Project-style 1H fell through to daily K lines, and 4h incorrectly mapped
to K_60M instead of K_240M.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Generic CSV/XLSX loads stringify serial day numbers, which then failed
to parse and became NaT in the standardized journal frame.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
load_dataframe reads CSV/XLSX as strings, so Date serials never hit the
numeric Excel path and stayed as raw "45321.0".
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
load_dataframe reads CSV/XLSX as strings, so serial day numbers never
hit the numeric Excel path and stayed as raw "45321.375".
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
load_dataframe reads cells as strings, so day-count serials like
"44941.0" skipped the YYYYMMDD float path and stayed as raw date text.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Yahoo chart has no 4h interval; project 4H was lowercased to 4h and
sent unsupported to the API. Match yfinance's 4H → 1h approximation.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Docs say daily-only, but 1H/4H still logged in and returned frequency=d
bars. Reject non-daily intervals before login (tencent/fmp parity).
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
TIME_SERIES_DAILY is daily-only, but 1H/4H still hit the API and
returned day bars. Reject non-daily intervals before the request.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Finnhub always called resolution=D even for 1H/4H, so intraday runs
silently got day bars and stopped the fallback chain.
Reject unsupported intervals like the other daily-only loaders.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Tiingo always hit the daily EOD endpoint even for 1H/4H, so intraday
runs silently got day bars and stopped the fallback chain.
Reject unsupported intervals like the other daily-only loaders.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Stooq always fetched the daily CSV even for 1H/4H, so intraday runs
silently got day bars and stopped the fallback chain.
Reject unsupported intervals like tencent/fmp/india-broker.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Connector-style 1h/4h fell through the uppercase-only allowlist and were
substituted with 1D. Map the aliases like the OKX connector bar map, and
reject unknown intervals instead of silently fetching daily bars.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Lowercasing first left 1m/5m/15m/30m with empty wanted tokens, so daily
OHLCV capabilities outranked minute ones. Match minute tokens
case-sensitively and keep 1M as monthly.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
* fix(loaders): reject non-daily intervals for AKShare US/HK/ETF/forex
A-share already raised on unsupported intervals, but US/HK/ETF/forex
always called the daily endpoints, so interval=1H returned day bars
under a 1H cache key and stopped the fallback chain.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
* fix(loaders): tighten AKShare daily-only interval gate comment
---------
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Connector-style 1h/1d were rejected while project 1H/1D mapped to the
same broker tokens, so market_data and mixed-vocabulary callers got
empty frames for valid hour/day bars.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Connector-style hour/day tokens raised unsupported while 1H/4H/1D
already mapped to K_60M/K_240M/K_DAY, so mixed-vocabulary callers lost
intraday Futu history.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Lowercase 4h/1w fell through to .lower() as invalid Yahoo tokens while 4H/1W already mapped to 1h/1wk.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
iterrows yields np.int64 for int64 Date columns; use is_number so Excel
day serials convert instead of stringifying or ns-epoch parsing.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
iterrows yields np.int64 for int64 成交时间 columns; isinstance(int)
missed them and bare pd.to_datetime treated the value as ns-epoch.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
同花顺 exports opened via Excel leave 成交时间 as a float serial; stringifying
it produced values like "45321.375" instead of a real timestamp. Convert
Excel day serials with pd.to_datetime before storing datetime.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Futu exports opened via Excel leave Date/Time as float serials; stringifying
them produced values like "45321.0 0.375" instead of a real timestamp.
Convert Excel day serials with pd.to_datetime before building datetime.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
The MT5 loader only listed 1H/4H/1D, so trading-convention 1h fell through
to the daily fallback while the MT5 connector already maps 1h to H1.
Add the lowercase aliases.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Connector _candlestick_enums only listed lowercase 1h/4h, so runner-style
1H fell through to Day while the Longbridge loader already maps 1H to
Min_60. Align the connector map with the loader.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Documented connector periods 4h/1H fell through to daily D. Map them to
Noren 240/60 minute candles and return an error for unsupported tokens
such as 1w/1M instead of silently substituting daily bars.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
_BAR_MAP only listed lowercase 1h/4h, so runner-style 1H/4H fell
through to bar 1D and silently returned daily candles.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
_rest_timeframe only listed lowercase 1h/4h, so runner-style 1H/4H
fell through to 1Day and silently returned daily bars.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Unmapped intervals were lowercased, so project monthly token 1M became
yfinance 1m (one minute). Map 1M/1W explicitly like the yahoo loader.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Unknown intervals (including runner 4H) fell through to broker period 1d,
so hourly requests silently returned daily bars. Reject and return no data.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Blank holding_days become NaN under pandas and int(NaN) aborted the
validation CLI. Coerce non-numeric/blank values to 0 holding bars.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
When reported_value is 0 and fetched_value is not, verdict used float('inf')
and json.dumps emitted non-RFC Infinity tokens. Record null percents instead
and serialize with allow_nan=False.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Numeric CSV/Excel cells often stringify as 600519.0; the '.' made
_qualify_a_share treat them as already-qualified and skip .SH/.SZ.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Word/LLM paste often uses en/em/minus dashes in ranges like 1–10;
those were silently dropped because only ASCII '-' was recognized.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Shell-style export KEY=value lines were read as a literal key named export KEY, so Settings missed configured secrets and upserts appended a duplicate KEY= instead of updating the export line. Strip the export prefix on read/write like python-dotenv.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
One IC observation makes Series.std() (ddof=1) NaN, which leaked into ic_summary.json and the tool envelope. Treat single-sample std as 0.0 (same guard as metrics vol) and dump with allow_nan=False.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Explosive equity paths can set annual_return to +inf. The engine stdout
dump used default allow_nan=True and emitted bare Infinity tokens.
Sanitize non-finite scalars to null and dump with allow_nan=False,
matching options and run_card.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
A 100 → 0 → 50 equity path yields non-finite pct_change values that
poisoned Sharpe/Sortino/IR with NaN/inf. Skip those ratios (report 0)
when bar returns are non-finite, matching the options metrics path.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Halt-style NaN gaps can leave every polyfit window empty, so mean_slope
became NaN and leaked a bare NaN token into the tool JSON. Treat that as
0.0 and serialize with allow_nan=False, matching options_pricing.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
get_messages already skipped JSONDecodeError lines but still raised on
valid JSON that was not a Message mapping (null, extra keys). Skip those
too so later good lines remain readable.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
One unreadable or schema-invalid session.json raised during list_sessions
and aborted the whole listing; skip that entry and keep siblings.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
_to_yahoo_interval uppercased intervals before the map lookup, so project
interval 1m collided with monthly 1M and was sent to Yahoo as 1mo.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Planner/design artifacts can emit ma_window=0; build_indicator_series then divided by zero. Drop non-positive periods when inferring and when building series.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
Long or mid-size cuts inside an opening fence could land on the fence line and reopen the same chunk forever. Advance past the fence header and bail when reopen does not shrink the buffer.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
filter_expr like "2026-03 to 2026-01" silently emptied the frame and
ran analysis on zero rows. Raise ValueError, matching period inversion.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
pages like 10-5 produced an empty index list and silently returned no
text. Raise ValueError, matching alpha_bench period inversion.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>
When max_len is 0 or negative, the cut pointer never advances and split_message loops forever.
Return the content unsplit so outbound IM adapters cannot wedge.
Signed-off-by: santhreal <64453045+santhreal@users.noreply.github.com>