#!/usr/bin/env python3 """Audit of the VQuant description against the platform code (vquant.git, commit e2a148a, 2026-10-02). Usage: python3 audit_vquant_code.py /path/to/vquant-clone Writes: tool_families.json, code_facts.json and ../../paper/tables/tools.tex (generated table). Run on 2026-10-17 for the editorial-board revision of UQO WP 13. """ import re, json, sys, os, collections, pathlib root = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else '/tmp/vquant') here = pathlib.Path(__file__).resolve().parent paper_tables = here.parent.parent / 'paper' / 'tables' src = (root / 'server/services/claude/toolDefinitions.ts').read_text() names = sorted(set(re.findall(r"name: '([a-zA-Z0-9_]+)'", src))) rules = [ ('Global market data (EODHD)', r'eodhd'), ('Fundamentals, valuation and classification', r'(income_statement|balance_sheet|cash_?flow|key_metrics|ratios|financial_(growth|scores|statement)|enterprise_values|owner_earnings|dcf|revenue_|market_capitalization|shares_float|company_profile|company_outlook|key_executives|profile_by_cik|latest_financial_statements|peers|stock_screener|stock_price_change|industry_pe|sector_pe|industry_performance|sector_performance|industry_classification|available_|exchange|sec_company_full_profile)'), ('SEC filings, transcripts and ownership', r'(sec_filings|8k|cik|company_notes|transcript|form_13f|institutional|mutual_fund_disclosure|holder|positions_summary|beneficial_ownership|crowdfunding|equity_offering|ipo_prospectus|ipo_disclosures|filings_extract)'), ('Events, calendars and corporate actions', r'(calendar|dividend|split|ipo|earnings_report|mergers_acquisitions|symbol_change|delisted|market_hours|holidays)'), ('Forex, crypto and commodities', r'(forex|crypto|commodit)'), ('Web research (Firecrawl, Tavily, Exa)', r'^(firecrawl_|tavily_|web_search_exa|get_contents_exa)'), ('Insider and congressional trading', r'(insider|congressional|senate|house_)'), ('Indices, ETFs and funds', r'(constituent|etf_|index_list|sp500|nasdaq|dow_jones|mutualfund)'), ('Quotes, prices and market movers', r'(quote|historical_price|intraday|gainers|losers|actives|actively_trading|stock_list|batch_|aftermarket)'), ('Technical indicators', r'^get_(rsi|macd|ema|sma|wma|dema|tema|adx|cci|stochastic|williams_r|standard_deviation)$'), ('News and press releases', r'(news|press_release|fmp_articles)'), ('Analysts, ratings and price targets', r'(analyst|price_target|grades|ratings_snapshot|historical_ratings|upgrades_downgrades|earnings_surprises)'), ('ESG, compensation and alternative data', r'(esg|social_sentiment|employee|executive_comp)'), ('Economic and macro', r'(treasury|economic|cot_|market_risk_premium)'), ('Search and identifiers', r'^(search_|get_cik_list)'), ('Options', r'option'), ('Advanced analysis', r'^(download_fmp_data|execute_custom_python_analysis|extract_sec_filings)$'), ] fam = collections.OrderedDict((r[0], []) for r in rules); other = [] for n in names: for lab, pat in rules: if re.search(pat, n): fam[lab].append(n); break else: other.append(n) assert not other, other assert sum(len(v) for v in fam.values()) == len(names) fmp = (root / 'server/services/fmpService.ts').read_text() fmp_funcs = len(re.findall(r'^export (?:async )?function [a-zA-Z0-9_]+', fmp, re.M)) fmp_paths = len(set(re.findall(r"'/[a-z0-9-]+(?:/[a-z0-9-]+)*'", fmp))) py = (root / 'server/services/python/customPythonExecutor.py').read_text() std = {'sys','json','io','base64','traceback','os','re','time','tempfile','contextlib','datetime','warnings','typing','math','collections','functools','itertools','random','string','pathlib','subprocess'} pk = sorted({m.group(1) for m in re.finditer(r'^\s*(?:import|from)\s+([a-zA-Z_][a-zA-Z0-9_]*)', py, re.M)} - std) routes = (root / 'server/routes.ts').read_text() handlers = sorted(set(re.findall(r"toolCall\.name === '([a-zA-Z0-9_]+)'", routes))) unreachable = sorted(set(handlers) - set(names)) facts = { 'commit': 'e2a148a (2026-10-02)', 'tools_defined': len(names), 'families': {k: len(v) for k, v in fam.items()}, 'fmp_wrapper_functions': fmp_funcs, 'fmp_distinct_endpoint_paths': fmp_paths, 'sandbox_packages_preimported': pk, 'sandbox_packages_count': len(pk), 'handlers_in_routes_not_in_catalogue': unreachable, 'batch_size': int(re.search(r'MAX_TOOLS_PER_BATCH = (\d+)', routes).group(1)), 'inter_batch_delay_ms': 500, 'per_tool_truncation_chars': [3000, 15000], 'resend_cap_chars': 5000, 'token_threshold': 100000, 'recent_messages_kept': 5, 'summary_input_cap_chars': 10000, 'summary_max_tokens': 2000, 'summary_temperature': 0.5, 'main_max_tokens': 64000, 'main_temperature': 1, 'sandbox_timeout_s': 90, 'figure_dpi': 150, 'openai_tool_cap': 128, } json.dump({'families': fam, 'total': len(names)}, open(here / 'tool_families.json', 'w'), indent=1) json.dump(facts, open(here / 'code_facts.json', 'w'), indent=1) ex = { 'Fundamentals, valuation and classification': 'profile, income statement, balance sheet, cash flow, key metrics, DCF, screener', 'SEC filings, transcripts and ownership': 'filings by form/CIK/symbol, 8-K, call transcripts, 13F, institutional holders', 'Events, calendars and corporate actions': 'earnings, dividends, splits, IPO calendars, M\\&A, market hours', 'Forex, crypto and commodities': 'FX and crypto quotes and lists, commodity quotes, batch quotes', 'Web research (Firecrawl, Tavily, Exa)': 'scrape, crawl, map, extract, agent; search, research; neural search', 'Insider and congressional trading': 'insider trades and statistics, House and Senate disclosures', 'Indices, ETFs and funds': 'S\\&P 500 / Nasdaq / Dow constituents, ETF holdings and weightings', 'Quotes, prices and market movers': 'quote, historical and intraday prices, gainers, losers, actives', 'Technical indicators': 'RSI, MACD, SMA/EMA/WMA/DEMA/TEMA, ADX, CCI, stochastic, Williams \\%R', 'News and press releases': 'stock, forex, crypto and general news; press releases', 'Analysts, ratings and price targets': 'estimates, price targets, grades, ratings, upgrades/downgrades', 'ESG, compensation and alternative data': 'ESG scores and benchmarks, social sentiment, headcount, executive pay', 'Global market data (EODHD)': 'symbol search, EOD and intraday bars, fundamentals, news (70+ exchanges)', 'Economic and macro': 'treasury rates, indicators, economic calendar, COT reports, risk premium', 'Search and identifiers': 'company, symbol, CUSIP, ISIN and CIK lookup', 'Options': 'option chain, contract prices, Greeks (EODHD); Black--Scholes pricer', 'Advanced analysis': 'custom Python execution, bulk data download, SEC filing extraction', } rows = sorted(fam.items(), key=lambda kv: -len(kv[1])) with open(paper_tables / 'tools.tex', 'w') as f: f.write("% Generated by docs/verify/audit_vquant_code.py from vquant.git commit e2a148a (2026-10-17). Do not edit by hand.\n") f.write("\\begin{table}[htbp]\n\\centering\n") f.write("\\caption{Tool catalogue exposed to the model, by family. Counts are exact and were extracted programmatically from the tool-definition module of the audited code (vquant.git, commit e2a148a, 2 October 2026): %d schema-defined tools. The financial families wrap two commercial market-data providers (%d typed wrapper functions over %d distinct endpoint paths of the primary provider, plus the EODHD family); the web-research family wraps three services. Models served through the OpenAI-compatible adapter receive only the first %d tools of the catalogue because of that interface's limit.}\n" % (len(names), fmp_funcs, fmp_paths, facts['openai_tool_cap'])) f.write("\\label{tab:tools}\n\\begin{threeparttable}\n\\small\n\\begin{tabular}{@{}lrl@{}}\n\\toprule\nFamily & Tools & Representative tools \\\\\n\\midrule\n") for lab, v in rows: f.write("%s & %d & %s \\\\\n" % (lab, len(v), ex[lab])) f.write("\\midrule\nTotal & %d & \\\\\n\\bottomrule\n\\end{tabular}\n" % len(names)) f.write("\\begin{tablenotes}[flushleft]\n\\footnotesize\n\\item \\textit{Notes.} Family assignment follows keyword rules on tool names (\\texttt{docs/verify/audit\\_vquant\\_code.py}); a tool belongs to exactly one family. The system prompt (Section~\\ref{subsec:routing}) describes only a subset of these tools explicitly; the full catalogue is transmitted as JSON schemas with every model call. Six server-side handlers for pre-built numerical routines (Monte Carlo, GARCH, VaR, portfolio optimisation, risk metrics, plotting) exist in the route module but are not in the catalogue at the audited commit and are therefore unreachable by the model (Section~\\ref{subsec:numerical}).\n\\end{tablenotes}\n\\end{threeparttable}\n\\end{table}\n") print(json.dumps(facts, indent=1)[:1500])