MetaTrader5 SDK copy_rates_from_pos() returns numpy structured arrays
containing numpy.int64/float64 scalars. These types are not natively
JSON-serializable, causing TypeError when trading_history responses
are serialized downstream.
Add _int_or_none() and _float_or_none() helpers in reads.py and apply
them to all numeric fields in _rate_to_dict(). Add regression tests
covering numpy, plain-dict, and None input scenarios.
Closes#774
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Add P@5/MRR/NDCG@5 evaluation framework for memory retrieval A/B testing.
Benchmark compares Baseline (pure token overlap) vs Treatment (BM25 + importance
decay) on a 200-entry corpus with 50 annotated queries.
The benchmark suite is NOT intended for CI — it requires corpus files
(tmp/benchmark_corpus/) that are not version-controlled. The suite
automatically skips when corpus files are absent.
Corpus is derived from anonymized production agent sessions.
Usage:
python -m pytest agent/tests/memory/benchmarks/ -v
Refs: #732
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Evaluation framework (benchmarks, eval tests) moved to separate
contribution scope. Community discussion will determine whether
these are wanted as part of the memory lifecycle work.
Refs: #732
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Benchmark corpus lives outside version control (tmp/benchmark_corpus/).
CI runners do not have these files. Skip the entire benchmark module at
collection time rather than failing individual tests.
Refs: #732
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
These files are not part of the community contribution scope.
Benchmark results are kept locally for validation only.
Refs: #732
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Document VT_MEMORY_QUALITY, VT_MEMORY_GC, and VT_MEMORY_DECAY
environment variables with descriptions and default values.
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Move VT_MEMORY_QUALITY, VT_MEMORY_GC, VT_MEMORY_DECAY reads from raw
os.environ.get() to src.config.env_schema.MemoryConfig, satisfying the
CI environment-variable encapsulation gate.
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Update test_related_memories_filters_invalid_ids to verify that
non-hex 6-char IDs (e.g. 'ok33ff') are correctly rejected by the
hex charset validator introduced in the previous commit.
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
- Move memory_lock to persistent.py; guard add/remove/rebuild with lock
- Compute importance dynamically in _scan_entries via Ebbinghaus decay
- Add exception handling in GC delete path and gc.log writes
- Conditional fcntl import for Windows compatibility
- Validate hex charset for related_memories field
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Design target remains 15-35ms on dev machines. The 50ms gate still
guarantees >40x improvement over the pre-optimization 2-2.5s baseline
while accommodating GitHub Actions shared-runner variability.
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
AI-Contributed/Feature: 0/0
AI-Contributed/UT: 0/18
Add TestAlignGoldStandard class that runs both the old pandas scalar
path and new vectorized path with identical inputs, asserting
element-wise output equivalence.
Covers: basic sizes (100-2000 bars), high NaN gaps (20%),
cross-market (ffill_limit=10), all-NaN column drops, and optimizer
callback scenarios.
Ref: reviewer request on PR #698
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
AI-Contributed/Feature: 0/0
AI-Contributed/UT: 0/232
The numpy arrays extracted from target_pos and close_df must be
reindexed by the codes parameter to ensure _code_to_col mapping
is consistent regardless of DataFrame internal column order.
Fixes test_rotation_is_independent_of_close_open_symbol_order
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
AI-Contributed/Feature: 0/6
AI-Contributed/UT: 0/0
The cache key for MCP tool discovery results must include the
enabled_tools configuration. Without it, calls to the same server
with different enabledTools filters incorrectly return cached specs
from a prior call with a different filter, bypassing tool filtering.
This fixes the CI failures in test_mcp_stdio_integration,
test_mcp_sse_integration, and test_mcp_streamable_http_integration
where enabledTools=["echo"] was not being respected.
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
AI-Contributed/Feature: 0/1
AI-Contributed/UT: 0/0
Add a module-level thread-safe cache in src/tools/mcp.py that stores
MCPRemoteToolSpec lists keyed by (server_name, command, args, env).
Within a single Swarm run, identical MCP servers are discovered only
once regardless of how many workers or layers execute.
SwarmRuntime._execute_run() calls invalidate_mcp_specs_cache() at the
start of each run to ensure fresh discovery when operator config may
have changed between runs.
Reduces tool discovery overhead from O(Workers x Layers x Servers) to
O(Servers), yielding ~6x speedup in typical multi-layer DAG scenarios.
Closes#703
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
The CI env var gate checks the AST node's start line for the noqa
comment. Move markers from the closing parenthesis line to the
os.getenv( call line so the gate recognizes the exemption.
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Replace manual credential resolution in _build_native_deepseek() with
the centralized get_llm_credentials() helper, completing the
consolidation goal of #553.
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
- Track ocr_attempted_pages to distinguish "OCR attempted but empty"
from "no OCR needed"; map former to quality_flag="degraded"
- Promote inline magic numbers to named module constants with rationale
comments (_OCR_MAX_TOKENS, _OCR_TEMPERATURE, _OCR_JPEG_QUALITY)
- Replace no-op round(len(text) / 1, 1) with float(len(text))
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
AI-Contributed/Feature: 0/40
AI-Contributed/UT: 0/0
Two related provider architecture improvements:
1. Keep explicit provider choice when it is a gateway (OpenRouter/Requesty)
(#549): The existing explicit-provider check already protects gateway
users from silent misrouting — provider="openrouter" returns
openrouter capabilities regardless of model name. Added regression
tests covering all gateway + model-name-prefix combinations.
2. Centralize credential resolution (#553): Extract the duplicated
provider→env_name→os.getenv→credential chain that was copy-pasted
across _sync_provider_env() and provider_diagnostics() into a
single get_llm_credentials() in capabilities.py. The new function
inherits the existing provider resolution logic, so every consumer
benefits from consistent behavior.
Additional fixes from code review:
- Remove unused base_env variable in provider_diagnostics() — use
caps.api_key_env directly, eliminating the redundant
provider_env_names() call.
- Add 3 base_url resolution tests covering the fallback chain
(provider-specific → OPENAI_BASE_URL → OPENAI_API_BASE).
- Run black to fix formatting violations on import lines.
Note: _sync_runtime_env() in settings_routes.py is a WRITE operation
(saves Web UI settings to env vars) and follows a fundamentally
different pattern from the READ path above — intentionally left out
of this refactor.
Backward compatibility: The default provider="openai" + model-name
inference (e.g. model="glm-4.6" → zhipu) is preserved. Users who
rely on this convenience behavior are unaffected.
Closes#549, closes#553
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Implement the backward compatibility layer mandated by Issue #547
(design-decision table):
- engine.py: add _CHOICE_ALIASES table ("qwen-vl" -> "llm-vision") applied
in get_ocr_engine() before engine lookup, emitting a deprecation warning.
- env_schema.py: add Pydantic model_validator(mode="before") on OcrConfig
that reads legacy VIBE_TRADING_OCR_QWEN_MODEL, forwards to the new
VIBE_TRADING_OCR_LLM_MODEL field, and emits a deprecation warning.
New env var takes precedence when both are set.
- llm_vision_ocr.py: restrict the "ollama" placeholder api_key to the
ollama provider only. Unknown providers now get empty string so they
no longer silently misconfigure (previously any unknown provider
without OPENAI_API_KEY was given "ollama" as api_key, corrupting the
OpenAI client for non-ollama providers).
- test_ocr_engine.py: TestBackwardCompatAliases (6 new tests): alias
mapping, deprecation warning emission, env-var precedence,
new-takes-precedence over legacy, ollama-only fallback, and
unknown-provider empty-key behavior.
Tests: 30 passed, 1 skipped (rapidocr_onnxruntime not installed).
Full suite: 5412 passed, 4 failed (pre-existing tushare E2E auth, unrelated).
Part of #547.
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Replace hard-coded QwenVisionOCR with a plugin-based engine that discovers
OCR backends via Python entry_points. Ships with:
- LLMDocumentOCR: multi-modal LLM vision (Qwen/OpenAI/Gemini)
- RapidDocumentOCR: offline RapidOCR adapter
- Extensible base class for custom engines
Includes comprehensive test coverage, env-schema config for engine selection,
and updated doc-reader skill documentation.
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Replace hardcoded RapidOCR dependency with a pluggable OcrEngine Protocol:
- OcrEngine Protocol (engine.py): abstract interface with recognize(image) method
- RapidOcrEngine (rapid_ocr.py): local ONNX-based OCR (existing capability)
- QwenVisionOcrEngine (qwen_vision_ocr.py): cloud vision via DashScope API
using qwen-vl-plus model with OpenAI-compatible endpoint
Engine selection via VIBE_TRADING_OCR_ENGINE env var:
auto (default) -> try rapid first, then qwen-vl
rapid -> local RapidOCR only
qwen-vl -> Qwen-VL cloud vision only
none -> disable OCR entirely
doc_reader_tool.py refactored to use the pluggable interface.
Fail-close behavior preserved: scanned pages error out with install hints
when no OCR engine is available.
E2E verified:
- Text PDF (21-page Chinese broker report): 28,130 chars extracted, 0 OCR pages
- Synthetic scanned PDF (3 image-only pages): all 3 pages OCR'd via Qwen-VL
- 60 existing unit tests pass
Co-Authored-By: OpenCode <noreply@opencode.ai>
AI-Model: qwen3.7-max
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Co-Authored-By: opencode <noreply@ai-tool.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
AI-Contributed/Feature: 323/323
AI-Contributed/UT: 0/0
Previously, when rapidocr_onnxruntime was not installed and a scanned/image
page was encountered, the tool silently returned empty text. Users had no
indication that content was lost.
Now:
- PDF pages with <50 chars of extractable text return an error with clear
install instructions (pip install rapidocr_onnxruntime)
- Standalone image files return the same error when OCR is unavailable
- Text-based PDFs and text files are unaffected (no OCR needed)
- _ocr_available() caches the import check for performance
Co-Authored-By: OpenCode <noreply@opencode.ai>
AI-Model: qwen3.7-max
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Co-Authored-By: opencode <noreply@ai-tool.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
AI-Contributed/Feature: 42/42
AI-Contributed/UT: 0/0
The host-check logic in _get_channel_runtime/_get_session_service used
_host_attr(name, None) which returns None when the host module sets the
attribute to None (e.g. monkeypatch reset). The elif branch checking the
local singleton was unreachable because api_server always exists and
hasattr is True for re-exported symbols, so stale local instances from
prior tests were returned instead of re-initializing.
Replace _host_attr with direct sys.modules getattr so that an explicit
None on the host falls through to the initialization path, matching the
original api_server.py behavior before extraction.
Co-Authored-By: OpenCode <noreply@opencode.ai>
AI-Model: qwen3.7-max
Co-Authored-By: opencode <noreply@ai-tool.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
AI-Contributed/Feature: 23/23
AI-Contributed/UT: 0/0
Move runs, scheduled research, swarm, sessions, goals, and live routes from api_server.py into focused src/api route modules.\n\nKeep compatibility re-exports for existing tests and preserve route behavior while reducing api_server.py to the app shell and shared wiring.
The batch_fetch_daily function in a_mootdx_fetcher.py used a bare
'except:' clause that caught KeyboardInterrupt and SystemExit in
addition to ordinary exceptions. This prevented users from interrupting
long-running A-share data pulls with Ctrl+C and silently swallowed
OOM/SIGTERM signals.
Replace with 'except Exception as e:' and log a warning so that:
- KeyboardInterrupt and SystemExit propagate to the caller
- fetch failures are visible in logs instead of being silent
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Move LLM and data-source settings routes plus their exclusive helpers into src/api/settings_routes.py, preserving existing api_server re-exports for test monkeypatch compatibility.
Local verification: 63 focused settings/security tests passed; compileall, diff check, settings route registration, and monkeypatch target checks passed.
Move the IM channel HTTP routes and channel runtime lifecycle wrappers from api_server.py into src/api/channels_routes.py while preserving the existing registration path and test monkeypatch compatibility.
Local verification: 85 focused API/channel tests passed; compileall, diff check, and route duplicate checks passed.
Closes#371.
- Add channel-agnostic /new, /reset, and /newsession handling in ChannelRuntime so IM users can start a fresh session without leaving the chat.
- Preserve existing session history while clearing only the channel session mapping.
- Document in-chat slash commands across README locales.
- Normalize pairing slash-command parsing so the documented case-insensitive command behavior matches runtime behavior.
Validation:
- PYTHONPATH=agent pytest agent/tests/test_channels_runtime.py -q
- python -m compileall -q agent/src/channels/runtime.py agent/src/channels/pairing/store.py agent/tests/test_channels_runtime.py
- git diff --check
- GitHub Actions CI: test
The frontend dev service published port 5899 on all interfaces
(0.0.0.0) while the backend was correctly bound to 127.0.0.1:8899.
On a shared/LAN network, this exposed the Vite dev server (source,
module graph, HMR websocket) to other hosts unnecessarily.
Add the 127.0.0.1 prefix to the frontend port mapping so both
services share the same loopback-only exposure discipline.
Closes#334
Co-Authored-By: OpenCode <noreply@opencode.ai>
AI-Model: qwen3.7-max
Co-Authored-By: opencode <noreply@ai-tool.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
AI-Contributed/Feature: 2/2
AI-Contributed/UT: 0/0
Address review findings from PR #328:
- Add module-level provider registry (register/clear/get_advisory_providers)
replacing the hardcoded AdvisoryOrchestrator([]) dead-code path
- Add public aliases in enforcement.py for 3 private helpers used by advisory
- Replace monkeypatch __init__ in test #9 with registry-based injection
- Sanitize exception messages: str(exc) → type(exc).__name__ in audit dicts
- Add exc_info=True to logger.warning for full traceback in logs only
- Add logger.info when advisory enabled but no providers registered
- Rename account_drawdown → utilization_ratio (semantic accuracy)
Co-Authored-By: OpenCode <noreply@opencode.ai>
AI-Model: qwen3.7-max
Co-Authored-By: opencode <noreply@ai-tool.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
AI-Contributed/Feature: 85/85
AI-Contributed/UT: 57/57
Add advisory review hook to LiveOrderGuardTool._allow() behind an opt-in
environment variable (VIBE_TRADING_ENABLE_ADVISORY). Advisory verdicts are
embedded in gate_decision['advisory'] for audit visibility but never block
or alter order execution.
- _advisory_review() builds AdvisoryContext from intent/positions/balance
- Fail-open: any exception → REVIEW_UNAVAILABLE, order proceeds
- Default off: env var unset = no advisory field in gate_decision
- _allow() accepts positions/balance kwargs threaded from execute()
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Configurable in-memory advisory provider for test suites. Supports verdict
injection, optional delay, forced failure, and call-history recording —
everything needed to exercise the orchestrator and gate integration without
any network dependency.
Signed-off-by: shadowinlife <shadowinlife@gmail.com>
Tushare loader now routes ETF/LOF to fund_daily(), index to index_daily(), HK to hk_daily() instead of always calling daily() (empty for non-stocks). Adds per-symbol empty warnings + runner partial-fetch warning. Closes#310. Thanks @shadowinlife.
Adds a 4-layer post-backtest attribution framework to the agent system prompt (trade attribution, beta regression, regime analysis, Monte Carlo permutation test) with strategy routing (At-risk / Sub-optimal / Healthy) and self-check rules. Prompt-only change to the Backtest task routing — no engine or tool code. Attribution is gated by data availability and explicitly marked secondary to strategy correctness.
Verified against live artifact schemas (trades.csv pnl!=0 exit rows with holding_days/return_pct, validation.json monte_carlo section, config.validation wiring) and prompt .format() brace integrity; 11 new tests pass.
Closes#279
Lets swarm workers call tools from operator-approved external MCP
servers in addition to the 31 local Vibe-Trading tools. Useful for
internal knowledge bases, compliance services, or any extra MCP server
the operator wants to attach.
Trust model is preserved at the schema level: MCP server URLs, commands,
env, and allowlists are resolved from the operator's config file
(VIBE_TRADING_SWARM_AGENT_CONFIG / ~/.vibe-trading/swarm-agent.json /
~/.vibe-trading/agent.json), never from `variables` or any other
caller-supplied input. The `run_swarm` MCP entry point cannot inject
server config.
Per-worker registries are built by intersecting each agent's `tools:`
whitelist with the resolved local + remote tool pool. Whitelist entries
the operator hasn't surfaced are dropped with an operator-facing
warning rather than failing the worker. Discovery is narrowed to the
servers actually referenced by the whitelist, so local-only workers
incur no MCP spawn cost.
MCP server-name collisions (e.g. `foo-bar` / `foo_bar`) keep their hash
suffixes after pruning — `_prune_agent_config_for_swarm_tools` resolves
segments against the full config first, then passes the preserved
mapping into `build_registry` so the worker whitelist still matches.
Adds `server` + `remote_tool` metadata to `tool_call` / `tool_result`
events for audit clarity, with nested redaction of sensitive argument
and result values.
Wired into MCP server, API server, CLI live runner, and in-process
SwarmTool. 27 new tests across M1-M5 (config plumbing, registry
assembly, boot wiring, e2e, trust model); 288 broader swarm/mcp/tool
tests pass.
Adds the ashare-pre-st-filter skill, Sina regulatory penalty scraper, and regression tests. Follow-up hardening will refine penalty-record relevance filtering.