nothumanallowed 15.1.67 → 15.1.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "15.1.
|
|
3
|
+
"version": "15.1.69",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '15.1.
|
|
8
|
+
export const VERSION = '15.1.69';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -96,6 +96,8 @@ export const DESTRUCTIVE_ACTIONS = new Set([
|
|
|
96
96
|
'alexandria_send',
|
|
97
97
|
// Cron / scheduling
|
|
98
98
|
'cron_create', 'cron_delete',
|
|
99
|
+
// Portfolio (local file)
|
|
100
|
+
'portfolio_add', 'portfolio_remove',
|
|
99
101
|
]);
|
|
100
102
|
|
|
101
103
|
// ── Tool Definitions (for system prompt) ─────────────────────────────────────
|
|
@@ -661,6 +663,85 @@ TOOLS:
|
|
|
661
663
|
query: free-text like "Federal Reserve inflation", "AI chips", "earnings season".
|
|
662
664
|
Returns: headline, source, publish time, and URL for each article.
|
|
663
665
|
|
|
666
|
+
88. earnings_calendar(ticker: string, days?: number)
|
|
667
|
+
Next earnings date + EPS/revenue estimates + last 4 quarters surprise history + analyst trend for next quarter.
|
|
668
|
+
Use BEFORE any near-term trade idea — earnings move stocks more than fundamentals.
|
|
669
|
+
|
|
670
|
+
89. dividend_calendar(ticker: string)
|
|
671
|
+
Dividend yield, payout ratio, next ex-dividend date, next pay date, 5y average yield.
|
|
672
|
+
|
|
673
|
+
90. economic_calendar(country?: string, days?: number)
|
|
674
|
+
Upcoming macroeconomic releases (FOMC, ECB, CPI, NFP, etc.) for a country. Defaults: US, 7 days.
|
|
675
|
+
country codes: "US" | "EU" | "IT" | "DE" | "FR" | "UK" | "JP" | "CN". Returns time, importance ★/★★/★★★, forecast vs previous.
|
|
676
|
+
|
|
677
|
+
91. stock_screener(screen?: string, count?: number)
|
|
678
|
+
Pre-built Yahoo Finance screeners. screen values:
|
|
679
|
+
most_actives | day_gainers | day_losers | undervalued_growth_stocks | growth_technology_stocks |
|
|
680
|
+
aggressive_small_caps | small_cap_gainers | undervalued_large_caps | conservative_foreign_funds |
|
|
681
|
+
high_yield_bond | portfolio_anchors | top_mutual_funds.
|
|
682
|
+
Returns sym/name/price/change%/mcap/P/E for the top N matches.
|
|
683
|
+
|
|
684
|
+
92. peer_comparison(ticker: string)
|
|
685
|
+
Identify direct peers via Yahoo recommendations + side-by-side comparison of P/E, P/B, ROE, D/E, dividend yield.
|
|
686
|
+
Use to position a stock vs its industry, NOT just vs the broad market.
|
|
687
|
+
|
|
688
|
+
93. sec_filings(ticker: string, form?: string, limit?: number)
|
|
689
|
+
SEC EDGAR filings for a US-listed company. form filter: "10-K" | "10-Q" | "8-K" | "DEF 14A" | "4" (insider) | "13F-HR" etc.
|
|
690
|
+
Each row: date · form · description + direct URL to the filing document. Default: latest 10 of any form.
|
|
691
|
+
|
|
692
|
+
94. options_chain(ticker: string)
|
|
693
|
+
Options chain for nearest expiry: top 10 calls and puts by strike with bid/ask/last/IV/OI.
|
|
694
|
+
Lists all available expirations. Use for IV analysis, options strategy sizing, gamma proximity.
|
|
695
|
+
|
|
696
|
+
95. portfolio_add(ticker: string, qty: number, cost?: number)
|
|
697
|
+
Add (or average-down) a stock position to the local portfolio (~/.nha/portfolio.json). cost = price/share.
|
|
698
|
+
|
|
699
|
+
96. portfolio_remove(ticker: string)
|
|
700
|
+
Remove a position completely from the portfolio.
|
|
701
|
+
|
|
702
|
+
97. portfolio_summary()
|
|
703
|
+
Live snapshot of all positions: qty, cost, current price, value, P/L $, P/L %, plus aggregate totals.
|
|
704
|
+
|
|
705
|
+
98. portfolio_metrics(period?: string)
|
|
706
|
+
Quant metrics computed from historical prices: annualized return, volatility, Sharpe, Sortino, max drawdown, beta vs SPY.
|
|
707
|
+
period: "1mo" "3mo" "6mo" "1y" "2y" "5y" "10y". Default: "1y". Assumes 4% risk-free rate.
|
|
708
|
+
|
|
709
|
+
99. news_sentiment(ticker?: string, query?: string)
|
|
710
|
+
Fetch ~15 recent headlines and score each (positive/neutral/negative) via LLM. Returns aggregate verdict (🟢/🟡/🔴),
|
|
711
|
+
distribution, and the top 5 most signal-bearing headlines with one-line reasoning.
|
|
712
|
+
|
|
713
|
+
100. backtest_strategy(ticker?: string, period?: string, strategy?: string)
|
|
714
|
+
Run a parametric backtest. strategy values:
|
|
715
|
+
- "sma_crossover": go long when SMA-20 > SMA-50, flat otherwise
|
|
716
|
+
- "rsi_meanrev": buy oversold (<30), sell overbought (>70), hold otherwise
|
|
717
|
+
- "buy_hold": baseline
|
|
718
|
+
Returns total return, annualized return, vol, Sharpe, max drawdown. For custom strategies use execute_code directly.
|
|
719
|
+
|
|
720
|
+
101. italian_market(what?: string)
|
|
721
|
+
Italian market snapshot. what: "all" | "mib" (FTSE MIB index) | "constituents" (top 15 blue chips with live prices) | "spread" (BTP-Bund 10y).
|
|
722
|
+
|
|
723
|
+
102. portfolio_correlation(period?: string)
|
|
724
|
+
Pearson correlation matrix between all holdings + SPY benchmark over a period (default 1y).
|
|
725
|
+
Surfaces concentration risk (>0.7 correlation = redundant) and diversification opportunities (<0.3 = good).
|
|
726
|
+
|
|
727
|
+
103. portfolio_sector_breakdown()
|
|
728
|
+
Breakdown of portfolio exposure by sector, country, and currency. Computed from live prices × position size.
|
|
729
|
+
Use to spot over-concentration (e.g. 60% in tech) or unhedged currency exposure.
|
|
730
|
+
|
|
731
|
+
104. portfolio_var(period?: string, confidence?: number)
|
|
732
|
+
Historical-simulation Value at Risk and Expected Shortfall (CVaR). Default: 1y, 95% confidence.
|
|
733
|
+
Output: 1-day VaR % and $, 10-day VaR (sqrt-time scaled), Expected Shortfall (avg tail loss).
|
|
734
|
+
For stress testing pass confidence=0.99.
|
|
735
|
+
|
|
736
|
+
105. portfolio_rebalance(targets?: object)
|
|
737
|
+
Compare current weights vs target weights and suggest the exact share trades to rebalance.
|
|
738
|
+
targets: optional {AAPL: 0.20, MSFT: 0.15, ...} (sum to 1.0). If not provided, falls back to ~/.nha/portfolio.json
|
|
739
|
+
"targets" field, otherwise assumes equal-weight. Drift threshold for action: 1%.
|
|
740
|
+
|
|
741
|
+
106. insider_trading(ticker: string, limit?: number)
|
|
742
|
+
SEC Form 4 (insider/officer transactions) for a US-listed company. Returns last N filings with direct URLs.
|
|
743
|
+
Open each URL to see whether the insider BOUGHT or SOLD shares — strong signal when clustered.
|
|
744
|
+
|
|
664
745
|
--- CODE EXECUTION ---
|
|
665
746
|
|
|
666
747
|
81. execute_code(language: "python"|"javascript"|"typescript", code: string, files?: [{path: string, content: string}], packages?: string[], stdin?: string, timeout?: number)
|
|
@@ -3945,6 +4026,754 @@ export async function executeTool(action, params, config) {
|
|
|
3945
4026
|
}
|
|
3946
4027
|
}
|
|
3947
4028
|
|
|
4029
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
4030
|
+
// FINANCIAL ANALYSIS — extended tool suite
|
|
4031
|
+
// All providers are free / public APIs (Yahoo Finance unofficial,
|
|
4032
|
+
// SEC EDGAR, CoinGecko, FRED with optional key). Tools return
|
|
4033
|
+
// human-readable text so an LLM can synthesize a report on top.
|
|
4034
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
4035
|
+
|
|
4036
|
+
case 'earnings_calendar': {
|
|
4037
|
+
const ticker = String(params.ticker || '').toUpperCase();
|
|
4038
|
+
const days = Math.min(parseInt(params.days || '60', 10), 365);
|
|
4039
|
+
if (!ticker) return 'earnings_calendar: ticker required (e.g. "AAPL").';
|
|
4040
|
+
try {
|
|
4041
|
+
const url = `https://query2.finance.yahoo.com/v10/finance/quoteSummary/${encodeURIComponent(ticker)}?modules=earnings,calendarEvents,earningsHistory,earningsTrend`;
|
|
4042
|
+
const res = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4043
|
+
if (!res.ok) return `earnings_calendar: HTTP ${res.status}`;
|
|
4044
|
+
const d = await res.json();
|
|
4045
|
+
const summary = d?.quoteSummary?.result?.[0] || {};
|
|
4046
|
+
const cal = summary.calendarEvents?.earnings || {};
|
|
4047
|
+
const hist = summary.earningsHistory?.history || [];
|
|
4048
|
+
const trend = summary.earningsTrend?.trend || [];
|
|
4049
|
+
const lines = [`Earnings calendar — ${ticker}`];
|
|
4050
|
+
if (cal.earningsDate?.length) {
|
|
4051
|
+
const dates = cal.earningsDate.map(d => (d.fmt || d.raw)).filter(Boolean).join(' – ');
|
|
4052
|
+
lines.push(`Next earnings: ${dates}${cal.isEarningsDateEstimate ? ' (estimated)' : ''}`);
|
|
4053
|
+
if (cal.earningsAverage?.fmt) lines.push(` EPS estimate: ${cal.earningsAverage.fmt} (low ${cal.earningsLow?.fmt || '?'} / high ${cal.earningsHigh?.fmt || '?'})`);
|
|
4054
|
+
if (cal.revenueAverage?.fmt) lines.push(` Revenue estimate: ${cal.revenueAverage.fmt}`);
|
|
4055
|
+
}
|
|
4056
|
+
if (hist.length) {
|
|
4057
|
+
lines.push('\nHistory (last quarters): est → actual (surprise %)');
|
|
4058
|
+
hist.slice(0, 4).forEach(h => {
|
|
4059
|
+
const est = h.epsEstimate?.fmt ?? '?';
|
|
4060
|
+
const act = h.epsActual?.fmt ?? '?';
|
|
4061
|
+
const surp = h.surprisePercent?.fmt ?? '?';
|
|
4062
|
+
lines.push(` ${h.quarter?.fmt || '?'}: ${est} → ${act} (${surp})`);
|
|
4063
|
+
});
|
|
4064
|
+
}
|
|
4065
|
+
if (trend.length) {
|
|
4066
|
+
const next = trend.find(t => t.period === '+1q') || trend[0];
|
|
4067
|
+
if (next?.earningsEstimate?.avg?.fmt) {
|
|
4068
|
+
lines.push(`\nAnalyst trend next Q: avg EPS ${next.earningsEstimate.avg.fmt}, growth ${next.earningsEstimate.growth?.fmt || '?'} (${next.earningsEstimate.numberOfAnalysts?.fmt || '?'} analysts)`);
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
return lines.join('\n');
|
|
4072
|
+
} catch (e) { return `earnings_calendar error: ${e.message}`; }
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4075
|
+
case 'dividend_calendar': {
|
|
4076
|
+
const ticker = String(params.ticker || '').toUpperCase();
|
|
4077
|
+
if (!ticker) return 'dividend_calendar: ticker required.';
|
|
4078
|
+
try {
|
|
4079
|
+
const url = `https://query2.finance.yahoo.com/v10/finance/quoteSummary/${encodeURIComponent(ticker)}?modules=summaryDetail,calendarEvents`;
|
|
4080
|
+
const res = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4081
|
+
if (!res.ok) return `dividend_calendar: HTTP ${res.status}`;
|
|
4082
|
+
const d = await res.json();
|
|
4083
|
+
const sd = d?.quoteSummary?.result?.[0]?.summaryDetail || {};
|
|
4084
|
+
const cal = d?.quoteSummary?.result?.[0]?.calendarEvents || {};
|
|
4085
|
+
const lines = [`Dividend — ${ticker}`];
|
|
4086
|
+
lines.push(`Yield: ${sd.dividendYield?.fmt || 'n/a'}${sd.trailingAnnualDividendRate?.fmt ? ` (${sd.trailingAnnualDividendRate.fmt}/yr)` : ''}`);
|
|
4087
|
+
lines.push(`Payout ratio: ${sd.payoutRatio?.fmt || 'n/a'}`);
|
|
4088
|
+
if (cal.exDividendDate?.fmt) lines.push(`Next ex-dividend: ${cal.exDividendDate.fmt}`);
|
|
4089
|
+
if (cal.dividendDate?.fmt) lines.push(`Next pay date: ${cal.dividendDate.fmt}`);
|
|
4090
|
+
if (sd.fiveYearAvgDividendYield?.fmt) lines.push(`5y avg yield: ${sd.fiveYearAvgDividendYield.fmt}`);
|
|
4091
|
+
return lines.join('\n');
|
|
4092
|
+
} catch (e) { return `dividend_calendar error: ${e.message}`; }
|
|
4093
|
+
}
|
|
4094
|
+
|
|
4095
|
+
case 'economic_calendar': {
|
|
4096
|
+
const days = Math.min(parseInt(params.days || '7', 10), 30);
|
|
4097
|
+
const country = String(params.country || 'US').toUpperCase();
|
|
4098
|
+
try {
|
|
4099
|
+
// Use Trading Economics public calendar feed (free tier, JSON, no key).
|
|
4100
|
+
const today = new Date();
|
|
4101
|
+
const to = new Date(today.getTime() + days * 86400000);
|
|
4102
|
+
const fmt = (d) => d.toISOString().slice(0, 10);
|
|
4103
|
+
const url = `https://api.tradingeconomics.com/calendar/country/${encodeURIComponent(country)}?d1=${fmt(today)}&d2=${fmt(to)}&c=guest:guest&f=json`;
|
|
4104
|
+
const res = await fetch(url, { headers: { 'User-Agent': 'NHA/1.0', 'Accept': 'application/json' }, signal: AbortSignal.timeout(15000) });
|
|
4105
|
+
if (!res.ok) return `economic_calendar: HTTP ${res.status} — try country code like "US", "EU", "IT", "DE".`;
|
|
4106
|
+
const events = await res.json();
|
|
4107
|
+
if (!Array.isArray(events) || events.length === 0) return `economic_calendar: no events in next ${days} days for ${country}.`;
|
|
4108
|
+
const lines = [`Economic calendar — ${country}, next ${days} days (${events.length} events)`];
|
|
4109
|
+
events.slice(0, 30).forEach(e => {
|
|
4110
|
+
const when = (e.Date || '').slice(0, 16).replace('T', ' ');
|
|
4111
|
+
const imp = e.Importance === 3 ? '★★★' : e.Importance === 2 ? '★★' : '★';
|
|
4112
|
+
lines.push(` ${when} ${imp} ${e.Event || e.Category}: forecast ${e.Forecast ?? '?'} | previous ${e.Previous ?? '?'}${e.Actual != null ? ` | actual ${e.Actual}` : ''}`);
|
|
4113
|
+
});
|
|
4114
|
+
return lines.join('\n');
|
|
4115
|
+
} catch (e) { return `economic_calendar error: ${e.message}`; }
|
|
4116
|
+
}
|
|
4117
|
+
|
|
4118
|
+
case 'stock_screener': {
|
|
4119
|
+
// Yahoo Finance has a public "screener" endpoint. We use it via the
|
|
4120
|
+
// predefined-screener IDs (no key required). For complex filtering on
|
|
4121
|
+
// arbitrary criteria the LLM should chain calls (e.g. screener →
|
|
4122
|
+
// peer_comparison → market_indicators on each result).
|
|
4123
|
+
const screen = String(params.screen || 'most_actives').toLowerCase();
|
|
4124
|
+
const count = Math.min(parseInt(params.count || '20', 10), 50);
|
|
4125
|
+
// Known screen IDs: most_actives, day_gainers, day_losers, undervalued_growth_stocks,
|
|
4126
|
+
// growth_technology_stocks, aggressive_small_caps, small_cap_gainers, undervalued_large_caps,
|
|
4127
|
+
// conservative_foreign_funds, high_yield_bond, portfolio_anchors, solid_large_growth_funds,
|
|
4128
|
+
// solid_midcap_growth_funds, top_mutual_funds.
|
|
4129
|
+
try {
|
|
4130
|
+
const url = `https://query1.finance.yahoo.com/v1/finance/screener/predefined/saved?formatted=true&lang=en-US®ion=US&scrIds=${encodeURIComponent(screen)}&count=${count}`;
|
|
4131
|
+
const res = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)', 'Accept': 'application/json' }, signal: AbortSignal.timeout(15000) });
|
|
4132
|
+
if (!res.ok) return `stock_screener: HTTP ${res.status} — known screens: most_actives, day_gainers, day_losers, undervalued_growth_stocks, growth_technology_stocks, aggressive_small_caps, small_cap_gainers, undervalued_large_caps.`;
|
|
4133
|
+
const d = await res.json();
|
|
4134
|
+
const quotes = d?.finance?.result?.[0]?.quotes || [];
|
|
4135
|
+
if (!quotes.length) return `stock_screener: no results for "${screen}".`;
|
|
4136
|
+
const lines = [`Stock screener — ${screen} (${quotes.length} results)`];
|
|
4137
|
+
quotes.forEach((q, i) => {
|
|
4138
|
+
const sym = q.symbol;
|
|
4139
|
+
const name = (q.shortName || q.longName || '').slice(0, 40);
|
|
4140
|
+
const price = q.regularMarketPrice?.fmt || q.regularMarketPrice;
|
|
4141
|
+
const chg = q.regularMarketChangePercent?.fmt || `${q.regularMarketChangePercent?.toFixed?.(2)}%`;
|
|
4142
|
+
const mcap = q.marketCap?.fmt || '?';
|
|
4143
|
+
const pe = q.trailingPE?.fmt || q.forwardPE?.fmt || '?';
|
|
4144
|
+
lines.push(`${i + 1}. ${sym} (${name}) — $${price} ${chg} | mcap ${mcap} | P/E ${pe}`);
|
|
4145
|
+
});
|
|
4146
|
+
return lines.join('\n');
|
|
4147
|
+
} catch (e) { return `stock_screener error: ${e.message}`; }
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4150
|
+
case 'peer_comparison': {
|
|
4151
|
+
const ticker = String(params.ticker || '').toUpperCase();
|
|
4152
|
+
if (!ticker) return 'peer_comparison: ticker required.';
|
|
4153
|
+
try {
|
|
4154
|
+
const url = `https://query2.finance.yahoo.com/v10/finance/quoteSummary/${encodeURIComponent(ticker)}?modules=recommendationTrend,upgradeDowngradeHistory,assetProfile,summaryProfile`;
|
|
4155
|
+
const res = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4156
|
+
if (!res.ok) return `peer_comparison: HTTP ${res.status}`;
|
|
4157
|
+
const d = await res.json();
|
|
4158
|
+
const profile = d?.quoteSummary?.result?.[0]?.assetProfile || d?.quoteSummary?.result?.[0]?.summaryProfile || {};
|
|
4159
|
+
const peersUrl = `https://query1.finance.yahoo.com/v1/finance/recommendationsbysymbol/${encodeURIComponent(ticker)}`;
|
|
4160
|
+
const peerRes = await fetch(peersUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4161
|
+
const peerData = peerRes.ok ? await peerRes.json() : null;
|
|
4162
|
+
const peers = peerData?.finance?.result?.[0]?.recommendedSymbols?.map(s => s.symbol) || [];
|
|
4163
|
+
const lines = [`Peer comparison — ${ticker}`];
|
|
4164
|
+
if (profile.industry) lines.push(`Industry: ${profile.industry}${profile.sector ? ` · ${profile.sector}` : ''}`);
|
|
4165
|
+
if (peers.length === 0) return lines.join('\n') + '\nNo peer suggestions available.';
|
|
4166
|
+
lines.push(`\nPeers: ${peers.join(', ')}\n`);
|
|
4167
|
+
// Compare key metrics for ticker + first 5 peers.
|
|
4168
|
+
const symbols = [ticker, ...peers.slice(0, 5)];
|
|
4169
|
+
const batch = `https://query1.finance.yahoo.com/v7/finance/quote?symbols=${symbols.join(',')}&fields=regularMarketPrice,marketCap,trailingPE,forwardPE,priceToBook,returnOnEquity,profitMargins,debtToEquity,dividendYield`;
|
|
4170
|
+
const bres = await fetch(batch, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4171
|
+
if (!bres.ok) return lines.join('\n');
|
|
4172
|
+
const bd = await bres.json();
|
|
4173
|
+
const rows = bd?.quoteResponse?.result || [];
|
|
4174
|
+
const fmt = (v) => v == null ? '?' : (typeof v === 'number' ? v.toFixed(2) : v);
|
|
4175
|
+
const header = `${'Ticker'.padEnd(10)}${'Price'.padStart(10)}${'P/E (ttm)'.padStart(12)}${'P/E (fwd)'.padStart(12)}${'P/B'.padStart(10)}${'ROE %'.padStart(10)}${'D/E'.padStart(10)}${'DivYld %'.padStart(11)}`;
|
|
4176
|
+
lines.push(header);
|
|
4177
|
+
rows.forEach(r => {
|
|
4178
|
+
lines.push(
|
|
4179
|
+
`${r.symbol.padEnd(10)}${String(fmt(r.regularMarketPrice)).padStart(10)}${String(fmt(r.trailingPE)).padStart(12)}${String(fmt(r.forwardPE)).padStart(12)}${String(fmt(r.priceToBook)).padStart(10)}${String(fmt(r.returnOnEquity ? (r.returnOnEquity * 100) : null)).padStart(10)}${String(fmt(r.debtToEquity)).padStart(10)}${String(fmt(r.dividendYield ? (r.dividendYield * 100) : null)).padStart(11)}`
|
|
4180
|
+
);
|
|
4181
|
+
});
|
|
4182
|
+
return lines.join('\n');
|
|
4183
|
+
} catch (e) { return `peer_comparison error: ${e.message}`; }
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
case 'sec_filings': {
|
|
4187
|
+
const ticker = String(params.ticker || '').toUpperCase();
|
|
4188
|
+
const formType = String(params.form || '').toUpperCase();
|
|
4189
|
+
const limit = Math.min(parseInt(params.limit || '10', 10), 25);
|
|
4190
|
+
if (!ticker) return 'sec_filings: ticker required.';
|
|
4191
|
+
try {
|
|
4192
|
+
// SEC EDGAR requires a User-Agent with contact info per their TOS.
|
|
4193
|
+
const ua = 'NotHumanAllowed CLI hello@nothumanallowed.com';
|
|
4194
|
+
// Step 1: ticker → CIK
|
|
4195
|
+
const tickersRes = await fetch('https://www.sec.gov/files/company_tickers.json', { headers: { 'User-Agent': ua } });
|
|
4196
|
+
if (!tickersRes.ok) return `sec_filings: failed to map ticker (HTTP ${tickersRes.status})`;
|
|
4197
|
+
const map = await tickersRes.json();
|
|
4198
|
+
const entry = Object.values(map).find(e => e.ticker?.toUpperCase() === ticker);
|
|
4199
|
+
if (!entry) return `sec_filings: ticker ${ticker} not found in SEC EDGAR (only US-listed companies).`;
|
|
4200
|
+
const cik = String(entry.cik_str).padStart(10, '0');
|
|
4201
|
+
// Step 2: fetch filings list for that CIK
|
|
4202
|
+
const fres = await fetch(`https://data.sec.gov/submissions/CIK${cik}.json`, { headers: { 'User-Agent': ua } });
|
|
4203
|
+
if (!fres.ok) return `sec_filings: HTTP ${fres.status}`;
|
|
4204
|
+
const fd = await fres.json();
|
|
4205
|
+
const recent = fd.filings?.recent || {};
|
|
4206
|
+
const rows = [];
|
|
4207
|
+
for (let i = 0; i < (recent.form?.length || 0); i++) {
|
|
4208
|
+
const form = recent.form[i];
|
|
4209
|
+
if (formType && form !== formType) continue;
|
|
4210
|
+
rows.push({
|
|
4211
|
+
form,
|
|
4212
|
+
date: recent.filingDate[i],
|
|
4213
|
+
accession: recent.accessionNumber[i],
|
|
4214
|
+
primary: recent.primaryDocument[i],
|
|
4215
|
+
description: recent.primaryDocDescription[i] || '',
|
|
4216
|
+
});
|
|
4217
|
+
if (rows.length >= limit) break;
|
|
4218
|
+
}
|
|
4219
|
+
if (rows.length === 0) return `sec_filings: no ${formType || ''} filings for ${ticker}.`;
|
|
4220
|
+
const lines = [`SEC filings — ${entry.title} (${ticker}) — CIK ${cik}`];
|
|
4221
|
+
rows.forEach(r => {
|
|
4222
|
+
const accNoDash = r.accession.replace(/-/g, '');
|
|
4223
|
+
const url = `https://www.sec.gov/Archives/edgar/data/${parseInt(cik, 10)}/${accNoDash}/${r.primary}`;
|
|
4224
|
+
lines.push(` ${r.date} ${r.form.padEnd(6)} ${r.description || ''}\n ${url}`);
|
|
4225
|
+
});
|
|
4226
|
+
return lines.join('\n');
|
|
4227
|
+
} catch (e) { return `sec_filings error: ${e.message}`; }
|
|
4228
|
+
}
|
|
4229
|
+
|
|
4230
|
+
case 'options_chain': {
|
|
4231
|
+
const ticker = String(params.ticker || '').toUpperCase();
|
|
4232
|
+
if (!ticker) return 'options_chain: ticker required.';
|
|
4233
|
+
try {
|
|
4234
|
+
const url = `https://query2.finance.yahoo.com/v7/finance/options/${encodeURIComponent(ticker)}`;
|
|
4235
|
+
const res = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4236
|
+
if (!res.ok) return `options_chain: HTTP ${res.status}`;
|
|
4237
|
+
const d = await res.json();
|
|
4238
|
+
const result = d?.optionChain?.result?.[0];
|
|
4239
|
+
if (!result) return `options_chain: no options data for ${ticker}.`;
|
|
4240
|
+
const exps = (result.expirationDates || []).slice(0, 6);
|
|
4241
|
+
const quote = result.quote || {};
|
|
4242
|
+
const chain = result.options?.[0] || {};
|
|
4243
|
+
const lines = [`Options chain — ${ticker} @ $${quote.regularMarketPrice}`];
|
|
4244
|
+
lines.push(`Expirations available: ${exps.map(t => new Date(t * 1000).toISOString().slice(0, 10)).join(', ')}`);
|
|
4245
|
+
const calls = (chain.calls || []).slice(0, 10);
|
|
4246
|
+
const puts = (chain.puts || []).slice(0, 10);
|
|
4247
|
+
if (calls.length) {
|
|
4248
|
+
lines.push(`\nCalls (top ${calls.length} by strike, exp ${new Date(chain.expirationDate * 1000).toISOString().slice(0, 10)}):`);
|
|
4249
|
+
calls.forEach(c => lines.push(` K=$${c.strike} bid=${c.bid} ask=${c.ask} last=${c.lastPrice} IV=${(c.impliedVolatility * 100).toFixed(1)}% OI=${c.openInterest}`));
|
|
4250
|
+
}
|
|
4251
|
+
if (puts.length) {
|
|
4252
|
+
lines.push(`\nPuts (top ${puts.length} by strike):`);
|
|
4253
|
+
puts.forEach(p => lines.push(` K=$${p.strike} bid=${p.bid} ask=${p.ask} last=${p.lastPrice} IV=${(p.impliedVolatility * 100).toFixed(1)}% OI=${p.openInterest}`));
|
|
4254
|
+
}
|
|
4255
|
+
return lines.join('\n');
|
|
4256
|
+
} catch (e) { return `options_chain error: ${e.message}`; }
|
|
4257
|
+
}
|
|
4258
|
+
|
|
4259
|
+
case 'portfolio_add': {
|
|
4260
|
+
const ticker = String(params.ticker || '').toUpperCase();
|
|
4261
|
+
const qty = parseFloat(params.qty || params.quantity || '0');
|
|
4262
|
+
const cost = parseFloat(params.cost || params.price || '0');
|
|
4263
|
+
if (!ticker || qty <= 0) return 'portfolio_add: ticker and qty (>0) required.';
|
|
4264
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
4265
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
4266
|
+
let pf = { positions: [], cash: 0 };
|
|
4267
|
+
try { if (fs.default.existsSync(file)) pf = JSON.parse(fs.default.readFileSync(file, 'utf-8')); } catch {}
|
|
4268
|
+
pf.positions = pf.positions || [];
|
|
4269
|
+
const existing = pf.positions.find(p => p.ticker === ticker);
|
|
4270
|
+
if (existing) {
|
|
4271
|
+
const totalQty = existing.qty + qty;
|
|
4272
|
+
const avgCost = ((existing.qty * existing.cost) + (qty * cost)) / totalQty;
|
|
4273
|
+
existing.qty = totalQty;
|
|
4274
|
+
existing.cost = avgCost;
|
|
4275
|
+
} else {
|
|
4276
|
+
pf.positions.push({ ticker, qty, cost, addedAt: new Date().toISOString() });
|
|
4277
|
+
}
|
|
4278
|
+
fs.default.mkdirSync(path.default.dirname(file), { recursive: true });
|
|
4279
|
+
fs.default.writeFileSync(file, JSON.stringify(pf, null, 2));
|
|
4280
|
+
return `Portfolio updated: ${ticker} qty=${qty}${cost ? ` @ avg cost $${cost}` : ''}. Total positions: ${pf.positions.length}.`;
|
|
4281
|
+
}
|
|
4282
|
+
|
|
4283
|
+
case 'portfolio_remove': {
|
|
4284
|
+
const ticker = String(params.ticker || '').toUpperCase();
|
|
4285
|
+
if (!ticker) return 'portfolio_remove: ticker required.';
|
|
4286
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
4287
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
4288
|
+
let pf = { positions: [] };
|
|
4289
|
+
try { if (fs.default.existsSync(file)) pf = JSON.parse(fs.default.readFileSync(file, 'utf-8')); } catch {}
|
|
4290
|
+
const before = (pf.positions || []).length;
|
|
4291
|
+
pf.positions = (pf.positions || []).filter(p => p.ticker !== ticker);
|
|
4292
|
+
if (pf.positions.length === before) return `Portfolio: ${ticker} not found.`;
|
|
4293
|
+
fs.default.writeFileSync(file, JSON.stringify(pf, null, 2));
|
|
4294
|
+
return `Portfolio: ${ticker} removed.`;
|
|
4295
|
+
}
|
|
4296
|
+
|
|
4297
|
+
case 'portfolio_summary': {
|
|
4298
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
4299
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
4300
|
+
if (!fs.default.existsSync(file)) return 'Portfolio is empty. Add positions with portfolio_add.';
|
|
4301
|
+
const pf = JSON.parse(fs.default.readFileSync(file, 'utf-8'));
|
|
4302
|
+
const positions = pf.positions || [];
|
|
4303
|
+
if (positions.length === 0) return 'Portfolio is empty.';
|
|
4304
|
+
// Fetch live prices in batch
|
|
4305
|
+
const symbols = positions.map(p => p.ticker).join(',');
|
|
4306
|
+
const res = await fetch(`https://query1.finance.yahoo.com/v7/finance/quote?symbols=${symbols}`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4307
|
+
if (!res.ok) return `portfolio_summary: HTTP ${res.status}`;
|
|
4308
|
+
const d = await res.json();
|
|
4309
|
+
const quotes = d?.quoteResponse?.result || [];
|
|
4310
|
+
const priceMap = Object.fromEntries(quotes.map(q => [q.symbol, q.regularMarketPrice]));
|
|
4311
|
+
let totalValue = 0, totalCost = 0;
|
|
4312
|
+
const rows = positions.map(p => {
|
|
4313
|
+
const price = priceMap[p.ticker] || 0;
|
|
4314
|
+
const value = price * p.qty;
|
|
4315
|
+
const cost = (p.cost || 0) * p.qty;
|
|
4316
|
+
const pl = value - cost;
|
|
4317
|
+
const plPct = cost > 0 ? (pl / cost) * 100 : 0;
|
|
4318
|
+
totalValue += value; totalCost += cost;
|
|
4319
|
+
return { ticker: p.ticker, qty: p.qty, costBasis: p.cost, currentPrice: price, value, pl, plPct };
|
|
4320
|
+
});
|
|
4321
|
+
const totalPL = totalValue - totalCost;
|
|
4322
|
+
const totalPLPct = totalCost > 0 ? (totalPL / totalCost) * 100 : 0;
|
|
4323
|
+
const lines = [`Portfolio summary — ${positions.length} positions`];
|
|
4324
|
+
lines.push(`${'Ticker'.padEnd(10)}${'Qty'.padStart(10)}${'Cost'.padStart(12)}${'Price'.padStart(12)}${'Value'.padStart(14)}${'P/L'.padStart(14)}${'P/L %'.padStart(10)}`);
|
|
4325
|
+
rows.forEach(r => {
|
|
4326
|
+
lines.push(`${r.ticker.padEnd(10)}${String(r.qty).padStart(10)}${('$' + (r.costBasis || 0).toFixed(2)).padStart(12)}${('$' + r.currentPrice.toFixed(2)).padStart(12)}${('$' + r.value.toFixed(2)).padStart(14)}${((r.pl >= 0 ? '+' : '') + '$' + r.pl.toFixed(2)).padStart(14)}${(r.plPct.toFixed(2) + '%').padStart(10)}`);
|
|
4327
|
+
});
|
|
4328
|
+
lines.push(`\nTotal value: $${totalValue.toFixed(2)} | Cost basis: $${totalCost.toFixed(2)} | P/L: ${totalPL >= 0 ? '+' : ''}$${totalPL.toFixed(2)} (${totalPLPct.toFixed(2)}%)`);
|
|
4329
|
+
return lines.join('\n');
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
case 'portfolio_metrics': {
|
|
4333
|
+
const period = params.period || '1y';
|
|
4334
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
4335
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
4336
|
+
if (!fs.default.existsSync(file)) return 'Portfolio is empty.';
|
|
4337
|
+
const pf = JSON.parse(fs.default.readFileSync(file, 'utf-8'));
|
|
4338
|
+
const positions = pf.positions || [];
|
|
4339
|
+
if (positions.length === 0) return 'Portfolio is empty.';
|
|
4340
|
+
// Fetch historical returns for each ticker → compute weighted portfolio returns,
|
|
4341
|
+
// then Sharpe, Sortino, max drawdown, beta vs SPY.
|
|
4342
|
+
const symbols = [...positions.map(p => p.ticker), 'SPY'];
|
|
4343
|
+
const fetchHist = async (sym) => {
|
|
4344
|
+
const url = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(sym)}?range=${period}&interval=1d`;
|
|
4345
|
+
const r = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4346
|
+
if (!r.ok) return null;
|
|
4347
|
+
const d = await r.json();
|
|
4348
|
+
return d?.chart?.result?.[0]?.indicators?.quote?.[0]?.close || null;
|
|
4349
|
+
};
|
|
4350
|
+
const series = {};
|
|
4351
|
+
for (const s of symbols) series[s] = await fetchHist(s);
|
|
4352
|
+
const valid = Object.values(series).filter(Array.isArray);
|
|
4353
|
+
if (valid.length === 0) return 'portfolio_metrics: failed to fetch historical data.';
|
|
4354
|
+
const N = Math.min(...valid.map(a => a.length));
|
|
4355
|
+
// Weighted returns: weight = current_value / total_value
|
|
4356
|
+
const lastPrice = (s) => (series[s] || []).slice(-1)[0];
|
|
4357
|
+
const weights = positions.map(p => p.qty * (lastPrice(p.ticker) || 0));
|
|
4358
|
+
const totalW = weights.reduce((a, b) => a + b, 0) || 1;
|
|
4359
|
+
const wNorm = weights.map(w => w / totalW);
|
|
4360
|
+
const dailyRet = (arr) => arr.slice(1).map((v, i) => (v - arr[i]) / arr[i]).filter(r => Number.isFinite(r));
|
|
4361
|
+
const tickerRets = positions.map(p => dailyRet(series[p.ticker] || []).slice(-N + 1));
|
|
4362
|
+
const minRetLen = Math.min(...tickerRets.map(r => r.length), dailyRet(series.SPY).length);
|
|
4363
|
+
const portfRets = [];
|
|
4364
|
+
for (let i = 0; i < minRetLen; i++) {
|
|
4365
|
+
let r = 0;
|
|
4366
|
+
for (let j = 0; j < tickerRets.length; j++) r += (tickerRets[j][i] || 0) * wNorm[j];
|
|
4367
|
+
portfRets.push(r);
|
|
4368
|
+
}
|
|
4369
|
+
const spyRets = dailyRet(series.SPY).slice(-minRetLen);
|
|
4370
|
+
const mean = (a) => a.reduce((x, y) => x + y, 0) / a.length;
|
|
4371
|
+
const stdev = (a) => { const m = mean(a); return Math.sqrt(mean(a.map(x => (x - m) ** 2))); };
|
|
4372
|
+
const annRet = mean(portfRets) * 252 * 100;
|
|
4373
|
+
const annVol = stdev(portfRets) * Math.sqrt(252) * 100;
|
|
4374
|
+
const sharpe = annVol > 0 ? (annRet - 4) / annVol : 0; // assume 4% risk-free
|
|
4375
|
+
const downside = portfRets.filter(r => r < 0);
|
|
4376
|
+
const sortino = downside.length > 1 ? (annRet - 4) / (stdev(downside) * Math.sqrt(252) * 100) : 0;
|
|
4377
|
+
// Max drawdown
|
|
4378
|
+
const cum = []; let acc = 1;
|
|
4379
|
+
portfRets.forEach(r => { acc *= (1 + r); cum.push(acc); });
|
|
4380
|
+
let peak = -Infinity, maxDD = 0;
|
|
4381
|
+
cum.forEach(v => { peak = Math.max(peak, v); maxDD = Math.min(maxDD, (v - peak) / peak); });
|
|
4382
|
+
// Beta vs SPY
|
|
4383
|
+
const covMean = mean(portfRets.map((r, i) => (r - mean(portfRets)) * (spyRets[i] - mean(spyRets))));
|
|
4384
|
+
const varSpy = mean(spyRets.map(r => (r - mean(spyRets)) ** 2));
|
|
4385
|
+
const beta = varSpy > 0 ? covMean / varSpy : 0;
|
|
4386
|
+
return `Portfolio metrics (${period}, ${minRetLen} trading days):
|
|
4387
|
+
Annualized return: ${annRet.toFixed(2)}%
|
|
4388
|
+
Annualized volatility: ${annVol.toFixed(2)}%
|
|
4389
|
+
Sharpe ratio: ${sharpe.toFixed(2)} (assuming 4% RFR)
|
|
4390
|
+
Sortino ratio: ${sortino.toFixed(2)}
|
|
4391
|
+
Max drawdown: ${(maxDD * 100).toFixed(2)}%
|
|
4392
|
+
Beta vs SPY: ${beta.toFixed(2)}`;
|
|
4393
|
+
}
|
|
4394
|
+
|
|
4395
|
+
case 'news_sentiment': {
|
|
4396
|
+
const ticker = String(params.ticker || '').toUpperCase();
|
|
4397
|
+
const query = params.query || ticker;
|
|
4398
|
+
if (!ticker && !query) return 'news_sentiment: ticker or query required.';
|
|
4399
|
+
// 1. Pull recent news via the existing market_news tool
|
|
4400
|
+
const newsRes = await executeTool('market_news', { ticker, query, limit: 15 }, config);
|
|
4401
|
+
if (typeof newsRes !== 'string' || newsRes.startsWith('market_news error')) return newsRes;
|
|
4402
|
+
// 2. Sentiment-score each headline via LLM (single call, batched)
|
|
4403
|
+
const { callLLM } = await import('./llm.mjs');
|
|
4404
|
+
const sysPrompt = `You score financial-news headlines for sentiment. Output ONLY JSON array, no prose.\n` +
|
|
4405
|
+
`For each headline, output {"i": index, "s": "positive"|"neutral"|"negative", "c": confidence 0..1, "why": "<10 words"}.\n` +
|
|
4406
|
+
`Bullish/positive for the asset = "positive". Bearish/risk = "negative". Pure info = "neutral".`;
|
|
4407
|
+
const headlines = newsRes.split('\n').filter(l => /^\d+\./.test(l)).map((l, i) => `${i + 1}. ${l.replace(/^\d+\.\s*/, '').slice(0, 200)}`);
|
|
4408
|
+
const userMsg = headlines.join('\n');
|
|
4409
|
+
let scored;
|
|
4410
|
+
try {
|
|
4411
|
+
const raw = await callLLM(config, sysPrompt, userMsg, { temperature: 0, maxTokens: 800 });
|
|
4412
|
+
const m = raw.match(/\[[\s\S]*\]/);
|
|
4413
|
+
if (m) scored = JSON.parse(m[0]);
|
|
4414
|
+
} catch { /* fallthrough */ }
|
|
4415
|
+
if (!Array.isArray(scored)) return newsRes + '\n\n(sentiment scoring failed)';
|
|
4416
|
+
const counts = { positive: 0, neutral: 0, negative: 0 };
|
|
4417
|
+
const weightedSum = scored.reduce((acc, s) => { counts[s.s] = (counts[s.s] || 0) + 1; return acc + (s.s === 'positive' ? s.c : s.s === 'negative' ? -s.c : 0); }, 0);
|
|
4418
|
+
const avg = scored.length ? weightedSum / scored.length : 0;
|
|
4419
|
+
const verdict = avg > 0.2 ? '🟢 Bullish' : avg < -0.2 ? '🔴 Bearish' : '🟡 Mixed';
|
|
4420
|
+
const out = [`News sentiment — ${ticker || query} (${scored.length} headlines)`,
|
|
4421
|
+
`Aggregate: ${verdict} (score ${avg.toFixed(2)})`,
|
|
4422
|
+
`Distribution: ${counts.positive || 0} positive · ${counts.neutral || 0} neutral · ${counts.negative || 0} negative`,
|
|
4423
|
+
'',
|
|
4424
|
+
'Top signals:'];
|
|
4425
|
+
scored.slice(0, 5).forEach(s => out.push(` [${s.s}] ${headlines[s.i - 1] || '?'} — ${s.why}`));
|
|
4426
|
+
return out.join('\n');
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
case 'backtest_strategy': {
|
|
4430
|
+
// Thin wrapper around execute_code: produces a parametric backtest
|
|
4431
|
+
// script (pandas + numpy) and runs it. Useful as a one-liner from the
|
|
4432
|
+
// chat — for production-grade backtesting users should call execute_code
|
|
4433
|
+
// directly with their own strategy.
|
|
4434
|
+
const ticker = String(params.ticker || 'SPY').toUpperCase();
|
|
4435
|
+
const period = params.period || '5y';
|
|
4436
|
+
const strategy = params.strategy || 'sma_crossover'; // sma_crossover | rsi_meanrev | buy_hold
|
|
4437
|
+
const code = `
|
|
4438
|
+
import urllib.request, json, sys
|
|
4439
|
+
url = "https://query1.finance.yahoo.com/v8/finance/chart/${ticker}?range=${period}&interval=1d"
|
|
4440
|
+
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
|
4441
|
+
r = urllib.request.urlopen(req).read()
|
|
4442
|
+
d = json.loads(r)
|
|
4443
|
+
res = d["chart"]["result"][0]
|
|
4444
|
+
closes = [c for c in res["indicators"]["quote"][0]["close"] if c is not None]
|
|
4445
|
+
strategy = "${strategy}"
|
|
4446
|
+
signal = [0]*len(closes)
|
|
4447
|
+
if strategy == "sma_crossover":
|
|
4448
|
+
fast, slow = 20, 50
|
|
4449
|
+
for i in range(slow, len(closes)):
|
|
4450
|
+
f = sum(closes[i-fast+1:i+1])/fast
|
|
4451
|
+
s = sum(closes[i-slow+1:i+1])/slow
|
|
4452
|
+
signal[i] = 1 if f > s else 0
|
|
4453
|
+
elif strategy == "rsi_meanrev":
|
|
4454
|
+
gains = [max(0, closes[i]-closes[i-1]) for i in range(1, len(closes))]
|
|
4455
|
+
losses = [max(0, closes[i-1]-closes[i]) for i in range(1, len(closes))]
|
|
4456
|
+
p = 14
|
|
4457
|
+
for i in range(p, len(closes)-1):
|
|
4458
|
+
avg_g = sum(gains[i-p:i])/p; avg_l = sum(losses[i-p:i])/p
|
|
4459
|
+
rsi = 100 - (100 / (1 + (avg_g / (avg_l or 1e-9))))
|
|
4460
|
+
signal[i+1] = 1 if rsi < 30 else 0 if rsi > 70 else signal[i]
|
|
4461
|
+
else:
|
|
4462
|
+
signal = [1]*len(closes) # buy & hold
|
|
4463
|
+
|
|
4464
|
+
rets, eq = [], [1.0]
|
|
4465
|
+
for i in range(1, len(closes)):
|
|
4466
|
+
r = signal[i-1] * (closes[i]-closes[i-1]) / closes[i-1]
|
|
4467
|
+
rets.append(r); eq.append(eq[-1] * (1+r))
|
|
4468
|
+
total = (eq[-1] - 1) * 100
|
|
4469
|
+
days = len(rets)
|
|
4470
|
+
ann = ((eq[-1]) ** (252/days) - 1) * 100 if days else 0
|
|
4471
|
+
import statistics as st
|
|
4472
|
+
vol = (st.pstdev(rets) * (252**0.5) * 100) if len(rets) > 1 else 0
|
|
4473
|
+
sharpe = (ann - 4) / vol if vol else 0
|
|
4474
|
+
peak = max_dd = 0
|
|
4475
|
+
for v in eq:
|
|
4476
|
+
peak = max(peak, v); max_dd = min(max_dd, (v-peak)/peak)
|
|
4477
|
+
print(f"Backtest {strategy} on ${ticker} (${period}):")
|
|
4478
|
+
print(f" Total return: {total:.2f}% over {days} days")
|
|
4479
|
+
print(f" Annualized: {ann:.2f}%, vol {vol:.2f}%, Sharpe {sharpe:.2f}")
|
|
4480
|
+
print(f" Max drawdown: {max_dd*100:.2f}%")
|
|
4481
|
+
`;
|
|
4482
|
+
return executeTool('execute_code', { language: 'python', code, timeout: 60 }, config);
|
|
4483
|
+
}
|
|
4484
|
+
|
|
4485
|
+
case 'portfolio_correlation': {
|
|
4486
|
+
// Pearson correlation matrix of all portfolio holdings + SPY benchmark.
|
|
4487
|
+
const period = params.period || '1y';
|
|
4488
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
4489
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
4490
|
+
if (!fs.default.existsSync(file)) return 'Portfolio is empty.';
|
|
4491
|
+
const pf = JSON.parse(fs.default.readFileSync(file, 'utf-8'));
|
|
4492
|
+
const positions = pf.positions || [];
|
|
4493
|
+
if (positions.length < 2) return 'Need at least 2 positions for a correlation matrix.';
|
|
4494
|
+
const symbols = [...positions.map(p => p.ticker), 'SPY'];
|
|
4495
|
+
const series = {};
|
|
4496
|
+
for (const s of symbols) {
|
|
4497
|
+
try {
|
|
4498
|
+
const r = await fetch(`https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(s)}?range=${period}&interval=1d`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4499
|
+
if (!r.ok) continue;
|
|
4500
|
+
const d = await r.json();
|
|
4501
|
+
series[s] = d?.chart?.result?.[0]?.indicators?.quote?.[0]?.close?.filter(Number.isFinite) || [];
|
|
4502
|
+
} catch {}
|
|
4503
|
+
}
|
|
4504
|
+
const valid = Object.entries(series).filter(([_, a]) => a.length > 30);
|
|
4505
|
+
if (valid.length < 2) return 'portfolio_correlation: not enough historical data.';
|
|
4506
|
+
const N = Math.min(...valid.map(([_, a]) => a.length));
|
|
4507
|
+
const rets = Object.fromEntries(valid.map(([s, a]) => {
|
|
4508
|
+
const arr = a.slice(-N);
|
|
4509
|
+
return [s, arr.slice(1).map((v, i) => (v - arr[i]) / arr[i])];
|
|
4510
|
+
}));
|
|
4511
|
+
const mean = (a) => a.reduce((x, y) => x + y, 0) / a.length;
|
|
4512
|
+
const corr = (a, b) => {
|
|
4513
|
+
const ma = mean(a), mb = mean(b);
|
|
4514
|
+
let num = 0, dA = 0, dB = 0;
|
|
4515
|
+
for (let i = 0; i < a.length; i++) { const xa = a[i] - ma, xb = b[i] - mb; num += xa * xb; dA += xa * xa; dB += xb * xb; }
|
|
4516
|
+
return num / Math.sqrt(dA * dB || 1);
|
|
4517
|
+
};
|
|
4518
|
+
const syms = valid.map(([s]) => s);
|
|
4519
|
+
const matrix = syms.map(a => syms.map(b => corr(rets[a], rets[b])));
|
|
4520
|
+
const lines = [`Correlation matrix (${period}, ${N} days)`];
|
|
4521
|
+
lines.push(' ' + syms.map(s => s.padStart(8)).join(''));
|
|
4522
|
+
matrix.forEach((row, i) => {
|
|
4523
|
+
lines.push(syms[i].padEnd(9) + row.map(v => v.toFixed(2).padStart(8)).join(''));
|
|
4524
|
+
});
|
|
4525
|
+
// Find pairs with highest correlation (excluding self/SPY)
|
|
4526
|
+
const pairs = [];
|
|
4527
|
+
for (let i = 0; i < syms.length; i++) for (let j = i + 1; j < syms.length; j++) {
|
|
4528
|
+
if (syms[i] === 'SPY' || syms[j] === 'SPY') continue;
|
|
4529
|
+
pairs.push({ a: syms[i], b: syms[j], r: matrix[i][j] });
|
|
4530
|
+
}
|
|
4531
|
+
pairs.sort((a, b) => b.r - a.r);
|
|
4532
|
+
if (pairs.length) {
|
|
4533
|
+
lines.push('\nMost correlated pairs (concentration risk):');
|
|
4534
|
+
pairs.slice(0, 3).forEach(p => lines.push(` ${p.a} ↔ ${p.b}: ${p.r.toFixed(2)}${p.r > 0.7 ? ' ⚠ high' : ''}`));
|
|
4535
|
+
lines.push('\nLeast correlated (diversification value):');
|
|
4536
|
+
pairs.slice(-3).reverse().forEach(p => lines.push(` ${p.a} ↔ ${p.b}: ${p.r.toFixed(2)}`));
|
|
4537
|
+
}
|
|
4538
|
+
return lines.join('\n');
|
|
4539
|
+
}
|
|
4540
|
+
|
|
4541
|
+
case 'portfolio_sector_breakdown': {
|
|
4542
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
4543
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
4544
|
+
if (!fs.default.existsSync(file)) return 'Portfolio is empty.';
|
|
4545
|
+
const pf = JSON.parse(fs.default.readFileSync(file, 'utf-8'));
|
|
4546
|
+
const positions = pf.positions || [];
|
|
4547
|
+
if (positions.length === 0) return 'Portfolio is empty.';
|
|
4548
|
+
const symbols = positions.map(p => p.ticker);
|
|
4549
|
+
// Batch-fetch assetProfile + live price
|
|
4550
|
+
const summaries = {};
|
|
4551
|
+
for (const s of symbols) {
|
|
4552
|
+
try {
|
|
4553
|
+
const r = await fetch(`https://query2.finance.yahoo.com/v10/finance/quoteSummary/${encodeURIComponent(s)}?modules=assetProfile,summaryDetail,price`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4554
|
+
if (r.ok) {
|
|
4555
|
+
const d = await r.json();
|
|
4556
|
+
summaries[s] = d?.quoteSummary?.result?.[0] || {};
|
|
4557
|
+
}
|
|
4558
|
+
} catch {}
|
|
4559
|
+
}
|
|
4560
|
+
const rows = positions.map(p => {
|
|
4561
|
+
const sx = summaries[p.ticker] || {};
|
|
4562
|
+
const sector = sx.assetProfile?.sector || sx.assetProfile?.industry || 'Unknown';
|
|
4563
|
+
const industry = sx.assetProfile?.industry || '';
|
|
4564
|
+
const country = sx.assetProfile?.country || 'Unknown';
|
|
4565
|
+
const currency = sx.price?.currency || sx.summaryDetail?.currency || 'USD';
|
|
4566
|
+
const price = sx.price?.regularMarketPrice?.raw || 0;
|
|
4567
|
+
const value = price * p.qty;
|
|
4568
|
+
return { ticker: p.ticker, sector, industry, country, currency, value };
|
|
4569
|
+
});
|
|
4570
|
+
const total = rows.reduce((a, r) => a + r.value, 0) || 1;
|
|
4571
|
+
const bySector = {};
|
|
4572
|
+
const byCountry = {};
|
|
4573
|
+
const byCurrency = {};
|
|
4574
|
+
rows.forEach(r => {
|
|
4575
|
+
bySector[r.sector] = (bySector[r.sector] || 0) + r.value;
|
|
4576
|
+
byCountry[r.country] = (byCountry[r.country] || 0) + r.value;
|
|
4577
|
+
byCurrency[r.currency] = (byCurrency[r.currency] || 0) + r.value;
|
|
4578
|
+
});
|
|
4579
|
+
const fmt = (obj) => Object.entries(obj).sort((a, b) => b[1] - a[1])
|
|
4580
|
+
.map(([k, v]) => ` ${k.padEnd(28)} ${((v / total) * 100).toFixed(1).padStart(6)}% $${v.toFixed(0).padStart(10)}`).join('\n');
|
|
4581
|
+
return [
|
|
4582
|
+
`Portfolio breakdown — total value $${total.toFixed(0)}`,
|
|
4583
|
+
'',
|
|
4584
|
+
`By sector:\n${fmt(bySector)}`,
|
|
4585
|
+
'',
|
|
4586
|
+
`By country:\n${fmt(byCountry)}`,
|
|
4587
|
+
'',
|
|
4588
|
+
`By currency:\n${fmt(byCurrency)}`,
|
|
4589
|
+
].join('\n');
|
|
4590
|
+
}
|
|
4591
|
+
|
|
4592
|
+
case 'portfolio_var': {
|
|
4593
|
+
// Historical-simulation Value at Risk and Expected Shortfall.
|
|
4594
|
+
const period = params.period || '1y';
|
|
4595
|
+
const conf = parseFloat(params.confidence || '0.95'); // 95% default; pass 0.99 for stress
|
|
4596
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
4597
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
4598
|
+
if (!fs.default.existsSync(file)) return 'Portfolio is empty.';
|
|
4599
|
+
const pf = JSON.parse(fs.default.readFileSync(file, 'utf-8'));
|
|
4600
|
+
const positions = pf.positions || [];
|
|
4601
|
+
if (positions.length === 0) return 'Portfolio is empty.';
|
|
4602
|
+
const symbols = positions.map(p => p.ticker);
|
|
4603
|
+
const series = {};
|
|
4604
|
+
for (const s of symbols) {
|
|
4605
|
+
try {
|
|
4606
|
+
const r = await fetch(`https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(s)}?range=${period}&interval=1d`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4607
|
+
if (!r.ok) continue;
|
|
4608
|
+
const d = await r.json();
|
|
4609
|
+
series[s] = d?.chart?.result?.[0]?.indicators?.quote?.[0]?.close?.filter(Number.isFinite) || [];
|
|
4610
|
+
} catch {}
|
|
4611
|
+
}
|
|
4612
|
+
const valid = positions.filter(p => (series[p.ticker] || []).length > 30);
|
|
4613
|
+
if (valid.length === 0) return 'portfolio_var: not enough data.';
|
|
4614
|
+
const N = Math.min(...valid.map(p => series[p.ticker].length));
|
|
4615
|
+
const lastPrice = (p) => series[p.ticker][series[p.ticker].length - 1];
|
|
4616
|
+
const values = valid.map(p => p.qty * lastPrice(p));
|
|
4617
|
+
const totalValue = values.reduce((a, b) => a + b, 0);
|
|
4618
|
+
const weights = values.map(v => v / totalValue);
|
|
4619
|
+
const dailyRet = (a) => a.slice(1).map((v, i) => (v - a[i]) / a[i]);
|
|
4620
|
+
const tickerRets = valid.map(p => dailyRet(series[p.ticker]).slice(-N + 1));
|
|
4621
|
+
const minLen = Math.min(...tickerRets.map(r => r.length));
|
|
4622
|
+
const portfRets = [];
|
|
4623
|
+
for (let i = 0; i < minLen; i++) {
|
|
4624
|
+
let r = 0;
|
|
4625
|
+
for (let j = 0; j < tickerRets.length; j++) r += (tickerRets[j][i] || 0) * weights[j];
|
|
4626
|
+
portfRets.push(r);
|
|
4627
|
+
}
|
|
4628
|
+
// Sort returns ascending; VaR is the loss at the (1-conf) quantile.
|
|
4629
|
+
const sorted = [...portfRets].sort((a, b) => a - b);
|
|
4630
|
+
const idx = Math.floor((1 - conf) * sorted.length);
|
|
4631
|
+
const varRet = sorted[idx];
|
|
4632
|
+
const tailLosses = sorted.slice(0, idx + 1);
|
|
4633
|
+
const esRet = tailLosses.reduce((a, b) => a + b, 0) / (tailLosses.length || 1);
|
|
4634
|
+
const portValue = positions.reduce((a, p) => a + (lastPrice(p) || 0) * p.qty, 0);
|
|
4635
|
+
return [
|
|
4636
|
+
`Portfolio VaR (Historical Simulation, ${period}, ${minLen} days)`,
|
|
4637
|
+
`Confidence: ${(conf * 100).toFixed(0)}%`,
|
|
4638
|
+
`Portfolio value: $${portValue.toFixed(0)}`,
|
|
4639
|
+
`Daily VaR: ${(varRet * 100).toFixed(2)}% = $${(varRet * portValue).toFixed(0)} loss`,
|
|
4640
|
+
`Expected Shortfall (CVaR): ${(esRet * 100).toFixed(2)}% = $${(esRet * portValue).toFixed(0)} avg tail loss`,
|
|
4641
|
+
`10-day VaR (sqrt-time scaled): ${(varRet * Math.sqrt(10) * 100).toFixed(2)}% = $${(varRet * Math.sqrt(10) * portValue).toFixed(0)}`,
|
|
4642
|
+
'',
|
|
4643
|
+
`Interpretation: with ${(conf * 100).toFixed(0)}% confidence, daily losses won't exceed the VaR figure.`,
|
|
4644
|
+
`When they do (the worst ${((1 - conf) * 100).toFixed(0)}% of days), the average loss is the ES/CVaR.`,
|
|
4645
|
+
].join('\n');
|
|
4646
|
+
}
|
|
4647
|
+
|
|
4648
|
+
case 'portfolio_rebalance': {
|
|
4649
|
+
// Compare current weights vs target weights. Targets can be passed as
|
|
4650
|
+
// `params.targets = {AAPL: 0.20, MSFT: 0.15, ...}` or read from
|
|
4651
|
+
// `~/.nha/portfolio.json` field `targets`. Without targets we assume
|
|
4652
|
+
// equal-weight.
|
|
4653
|
+
const fs = await import('fs'); const path = await import('path'); const os = await import('os');
|
|
4654
|
+
const file = path.default.join(os.default.homedir(), '.nha', 'portfolio.json');
|
|
4655
|
+
if (!fs.default.existsSync(file)) return 'Portfolio is empty.';
|
|
4656
|
+
const pf = JSON.parse(fs.default.readFileSync(file, 'utf-8'));
|
|
4657
|
+
const positions = pf.positions || [];
|
|
4658
|
+
if (positions.length === 0) return 'Portfolio is empty.';
|
|
4659
|
+
let targets = params.targets || pf.targets || null;
|
|
4660
|
+
if (!targets) {
|
|
4661
|
+
const eq = 1 / positions.length;
|
|
4662
|
+
targets = Object.fromEntries(positions.map(p => [p.ticker, eq]));
|
|
4663
|
+
}
|
|
4664
|
+
const symbols = positions.map(p => p.ticker);
|
|
4665
|
+
const r = await fetch(`https://query1.finance.yahoo.com/v7/finance/quote?symbols=${symbols.join(',')}`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4666
|
+
if (!r.ok) return `portfolio_rebalance: HTTP ${r.status}`;
|
|
4667
|
+
const d = await r.json();
|
|
4668
|
+
const priceMap = Object.fromEntries((d?.quoteResponse?.result || []).map(q => [q.symbol, q.regularMarketPrice]));
|
|
4669
|
+
const rows = positions.map(p => {
|
|
4670
|
+
const px = priceMap[p.ticker] || 0;
|
|
4671
|
+
const value = px * p.qty;
|
|
4672
|
+
return { ticker: p.ticker, qty: p.qty, price: px, value };
|
|
4673
|
+
});
|
|
4674
|
+
const total = rows.reduce((a, r) => a + r.value, 0) || 1;
|
|
4675
|
+
const lines = [`Portfolio rebalance — total $${total.toFixed(0)}`];
|
|
4676
|
+
lines.push(`${'Ticker'.padEnd(10)}${'Current %'.padStart(12)}${'Target %'.padStart(12)}${'Drift'.padStart(10)}${'Action'.padStart(30)}`);
|
|
4677
|
+
let totalTrade = 0;
|
|
4678
|
+
rows.forEach(p => {
|
|
4679
|
+
const current = (p.value / total) * 100;
|
|
4680
|
+
const target = (targets[p.ticker] || 0) * 100;
|
|
4681
|
+
const drift = current - target;
|
|
4682
|
+
const tradeUsd = (target - current) / 100 * total;
|
|
4683
|
+
const shares = p.price > 0 ? tradeUsd / p.price : 0;
|
|
4684
|
+
const action = Math.abs(drift) < 1 ? '✓ ok'
|
|
4685
|
+
: drift > 0 ? `SELL ${Math.abs(shares).toFixed(2)} @ $${p.price.toFixed(2)}`
|
|
4686
|
+
: `BUY ${Math.abs(shares).toFixed(2)} @ $${p.price.toFixed(2)}`;
|
|
4687
|
+
totalTrade += Math.abs(tradeUsd);
|
|
4688
|
+
lines.push(`${p.ticker.padEnd(10)}${current.toFixed(2).padStart(11)}%${target.toFixed(2).padStart(11)}%${(drift >= 0 ? '+' : '') + drift.toFixed(2).padStart(8)}%${action.padStart(30)}`);
|
|
4689
|
+
});
|
|
4690
|
+
lines.push(`\nTotal trade volume to rebalance: $${totalTrade.toFixed(0)}`);
|
|
4691
|
+
lines.push(`Drift threshold ≥ 1% → trades suggested above. Below threshold → leave as is.`);
|
|
4692
|
+
return lines.join('\n');
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
case 'insider_trading': {
|
|
4696
|
+
const ticker = String(params.ticker || '').toUpperCase();
|
|
4697
|
+
const limit = Math.min(parseInt(params.limit || '15', 10), 30);
|
|
4698
|
+
if (!ticker) return 'insider_trading: ticker required.';
|
|
4699
|
+
try {
|
|
4700
|
+
const ua = 'NotHumanAllowed CLI hello@nothumanallowed.com';
|
|
4701
|
+
const tres = await fetch('https://www.sec.gov/files/company_tickers.json', { headers: { 'User-Agent': ua } });
|
|
4702
|
+
if (!tres.ok) return `insider_trading: ticker map HTTP ${tres.status}`;
|
|
4703
|
+
const map = await tres.json();
|
|
4704
|
+
const entry = Object.values(map).find(e => e.ticker?.toUpperCase() === ticker);
|
|
4705
|
+
if (!entry) return `insider_trading: ${ticker} not in SEC EDGAR (US-listed only).`;
|
|
4706
|
+
const cik = String(entry.cik_str).padStart(10, '0');
|
|
4707
|
+
const fres = await fetch(`https://data.sec.gov/submissions/CIK${cik}.json`, { headers: { 'User-Agent': ua } });
|
|
4708
|
+
if (!fres.ok) return `insider_trading: HTTP ${fres.status}`;
|
|
4709
|
+
const fd = await fres.json();
|
|
4710
|
+
const recent = fd.filings?.recent || {};
|
|
4711
|
+
const rows = [];
|
|
4712
|
+
for (let i = 0; i < (recent.form?.length || 0); i++) {
|
|
4713
|
+
if (recent.form[i] !== '4') continue;
|
|
4714
|
+
rows.push({
|
|
4715
|
+
date: recent.filingDate[i],
|
|
4716
|
+
accession: recent.accessionNumber[i],
|
|
4717
|
+
primary: recent.primaryDocument[i],
|
|
4718
|
+
});
|
|
4719
|
+
if (rows.length >= limit) break;
|
|
4720
|
+
}
|
|
4721
|
+
if (rows.length === 0) return `insider_trading: no Form 4 (insider) filings for ${ticker}.`;
|
|
4722
|
+
const lines = [`Insider trading (Form 4) — ${entry.title} (${ticker}) — last ${rows.length}`];
|
|
4723
|
+
rows.forEach(r => {
|
|
4724
|
+
const accNoDash = r.accession.replace(/-/g, '');
|
|
4725
|
+
const url = `https://www.sec.gov/Archives/edgar/data/${parseInt(cik, 10)}/${accNoDash}/${r.primary}`;
|
|
4726
|
+
lines.push(` ${r.date} Form 4 → ${url}`);
|
|
4727
|
+
});
|
|
4728
|
+
lines.push(`\nNote: Form 4 filings report officer/director/10%-owner transactions. Open URLs to see whether BUY (acquired) or SELL (disposed).`);
|
|
4729
|
+
return lines.join('\n');
|
|
4730
|
+
} catch (e) { return `insider_trading error: ${e.message}`; }
|
|
4731
|
+
}
|
|
4732
|
+
|
|
4733
|
+
case 'italian_market': {
|
|
4734
|
+
// FTSE MIB constituents and BTP-Bund spread (10y Italy minus 10y Germany).
|
|
4735
|
+
const which = String(params.what || 'all').toLowerCase();
|
|
4736
|
+
const lines = [];
|
|
4737
|
+
try {
|
|
4738
|
+
if (which === 'all' || which === 'mib' || which === 'index') {
|
|
4739
|
+
const r = await fetch('https://query1.finance.yahoo.com/v8/finance/chart/FTSEMIB.MI?range=5d&interval=1d', { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4740
|
+
if (r.ok) {
|
|
4741
|
+
const d = await r.json();
|
|
4742
|
+
const res = d.chart.result[0];
|
|
4743
|
+
const closes = res.indicators.quote[0].close.filter(Boolean);
|
|
4744
|
+
const last = closes[closes.length - 1];
|
|
4745
|
+
const prev = closes[closes.length - 2];
|
|
4746
|
+
const chg = prev ? ((last - prev) / prev * 100) : 0;
|
|
4747
|
+
lines.push(`FTSE MIB: ${last?.toFixed(0)} (${chg >= 0 ? '+' : ''}${chg.toFixed(2)}% giorn.)`);
|
|
4748
|
+
}
|
|
4749
|
+
}
|
|
4750
|
+
if (which === 'all' || which === 'spread' || which === 'btp') {
|
|
4751
|
+
const [it, de] = await Promise.all([
|
|
4752
|
+
fetch('https://query1.finance.yahoo.com/v8/finance/chart/^TNX-IT?range=1d&interval=1d', { headers: { 'User-Agent': 'Mozilla/5.0' } }).catch(() => null),
|
|
4753
|
+
fetch('https://query1.finance.yahoo.com/v8/finance/chart/^TNX-DE?range=1d&interval=1d', { headers: { 'User-Agent': 'Mozilla/5.0' } }).catch(() => null),
|
|
4754
|
+
]);
|
|
4755
|
+
// Yahoo doesn't expose BTP/Bund directly via simple ticker; use approx via futures or skip.
|
|
4756
|
+
lines.push('BTP-Bund spread: dato non disponibile via Yahoo gratis. Suggerisco fetch_url su https://www.borsaitaliana.it/borsa/obbligazioni.html');
|
|
4757
|
+
}
|
|
4758
|
+
// Top constituents — manual list of FTSE MIB blue chips, fetched as batch
|
|
4759
|
+
if (which === 'all' || which === 'constituents' || which === 'top') {
|
|
4760
|
+
const tickers = ['ENI.MI','ENEL.MI','UCG.MI','ISP.MI','STLAM.MI','RACE.MI','TIT.MI','G.MI','MB.MI','LDO.MI','PRY.MI','SPM.MI','PST.MI','BAMI.MI','FBK.MI'];
|
|
4761
|
+
const r = await fetch(`https://query1.finance.yahoo.com/v7/finance/quote?symbols=${tickers.join(',')}`, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; NHA/1.0)' } });
|
|
4762
|
+
if (r.ok) {
|
|
4763
|
+
const d = await r.json();
|
|
4764
|
+
const quotes = d?.quoteResponse?.result || [];
|
|
4765
|
+
lines.push('\nFTSE MIB — Top constituents:');
|
|
4766
|
+
quotes.forEach(q => {
|
|
4767
|
+
const chg = q.regularMarketChangePercent;
|
|
4768
|
+
lines.push(` ${q.symbol.padEnd(9)} ${(q.shortName || '').slice(0, 22).padEnd(23)} €${(q.regularMarketPrice || 0).toFixed(2).padStart(8)} ${(chg >= 0 ? '+' : '') + chg?.toFixed(2)}%`);
|
|
4769
|
+
});
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
if (lines.length === 0) return `italian_market: parametro "what" non riconosciuto. Usa "all" | "mib" | "constituents" | "spread".`;
|
|
4773
|
+
return lines.join('\n');
|
|
4774
|
+
} catch (e) { return `italian_market error: ${e.message}`; }
|
|
4775
|
+
}
|
|
4776
|
+
|
|
3948
4777
|
default:
|
|
3949
4778
|
return `Unknown action: ${action}`;
|
|
3950
4779
|
}
|
|
@@ -274,7 +274,57 @@ Provide specific, actionable levels. Use technical analysis (EMA, RSI, MACD, Fib
|
|
|
274
274
|
- Maximum drawdown and recovery expectations
|
|
275
275
|
- Strategy capacity (AUM limits before alpha decay)
|
|
276
276
|
- Implementation costs: slippage, market impact, transaction costs
|
|
277
|
-
- Key risks: overfitting, regime change, crowding`,agents:[{icon:`📊`,agent:`oracle`,label:`Oracle`,status:`waiting`,output:``},{icon:`💹`,agent:`mercury`,label:`Mercury`,status:`waiting`,output:``},{icon:`📈`,agent:`edi`,label:`Edi`,status:`waiting`,output:``},{icon:`⚠️`,agent:`cassandra`,label:`Cassandra`,status:`waiting`,output:``},{icon:`🔥`,agent:`prometheus`,label:`Prometheus`,status:`waiting`,output:``}]}],De=`trading strategy.fundamental analysis.portfolio risk.sector deep dive.crypto analysis.quant factor.macro regime.cross-asset.hedge.derivative.yield curve.market_price.market_chart.market_indicators.macro_data.crypto_data.valuation.equity.earnings.dividend.short interest.volatility.options.futures.bitcoin.ethereum`.split(`.`),Oe=[`Analyze my unread emails and create a priority action plan`,`Search the web for AI news today and summarize it in a canvas report`,`Check my calendar for this week and suggest how to optimize my schedule`,`Review my GitHub notifications and draft responses to open issues`,`Search for information about a topic, fact-check it, and write a report`],I={bg:`#0a0d14`,wall:`#0d1117`,wallStripe:`#0f1520`,baseboard:`#1e293b`,floorA:`#111827`,floorB:`#141c2b`,floorLine:`#1e293b`,ceiling:`#0b0f18`,lampBody:`#854d0e`,lampGlow:`#fef08a`,partition:`#1a2436`,partitionH:`#253347`,partitionT:`#2d4a6e`,deskTop:`#2d4263`,deskFace:`#1e3352`,deskSide:`#192b43`,deskLeg:`#0f1e30`,monBody:`#0f172a`,monFace:`#162032`,monScrIdle:`#060b12`,monScrOn:`#001208`,monScrDone:`#001a08`,monStand:`#0d1625`,keyboard:`#162032`,chairBack:`#1a3a5c`,chairSeat:`#1e4068`,chairLeg:`#0f1e30`,plant1:`#14532d`,plant2:`#166534`,pot:`#78350f`,cup:`#7c2d12`,paper:`#e2e8f0`,paperLine:`#94a3b8`,skin:[`#f5c97a`,`#fbbf24`,`#fca5a5`,`#86efac`,`#a5f3fc`,`#c4b5fd`,`#fde68a`,`#f9a8d4`],shirt:[`#1e40af`,`#065f46`,`#312e81`,`#be123c`,`#164e63`,`#7c3aed`,`#7f1d1d`,`#0e7490`],hair:[`#7c3aed`,`#92400e`,`#1e293b`,`#6b21a8`,`#78350f`,`#292524`,`#422006`,`#0c4a6e`],pants:`#1e293b`,shoes:`#0f172a`,cSkin:`#fbbf24`,cShirt:`#4f46e5`,cHair:`#111827`,cTie:`#dc2626`,cPants:`#1e293b`,cShoes:`#0f172a`,cBag:`#78350f`,green:`#22c55e`,greenDim:`#14532d`,cyan:`#67e8f9`,purple:`#a78bfa`,red:`#ef4444`,amber:`#fbbf24`,dim:`#334155`,white:`#f1f5f9`,bubbleBg:`#0f172a`,bubbleBdr:`#334155`},L=2,ke=1200,Ae=140,je=ke*L,Me=Ae*L,Ne=Math.round(Ae*.78),Pe=130,Fe=14,Ie=Ne-Fe-2,Le=8,Re=36;function R(e,t,n,r,i=1,a=1){e.fillStyle=t,e.fillRect(n*L,r*L,i*L,a*L)}function ze(e){R(e,I.ceiling,0,0,ke,7);for(let t=0;t<ke;t++)R(e,t%20<10?I.wall:I.wallStripe,t,7,1,Ne-7);R(e,I.baseboard,0,Ne-3,ke,3);for(let t=0;t<ke;t+=14)for(let n=Ne;n<Ae;n+=7)R(e,(Math.floor(t/14)+Math.floor((n-Ne)/7))%2==0?I.floorA:I.floorB,t,n,14,7),R(e,I.floorLine,t,n,14,1),R(e,I.floorLine,t,n,1,7);for(let t=40;t<ke;t+=120){R(e,I.lampBody,t,1,40,4),R(e,I.lampGlow,t+2,2,36,2);for(let n=0;n<18;n++)e.fillStyle=`rgba(254,240,138,${(.06-n*.003).toFixed(4)})`,e.fillRect((t+20-n*1.1)*L,(5+n)*L,n*2.2*L,L)}}function Be(e,t,n,r){let i=t.x,a=t.status===`running`,o=t.status===`done`,s=t.status===`error`,c=Math.abs(n-(i+Pe/2))<30;a&&(e.fillStyle=`rgba(99,102,241,0.09)`,e.fillRect(i*L,10*L,Pe*L,(Ae-10)*L)),o&&(e.fillStyle=`rgba(34,197,94,0.05)`,e.fillRect(i*L,10*L,Pe*L,(Ae-10)*L));let l=Ne-8;R(e,I.partition,i,8,3,l),R(e,I.partitionH,i+1,8,2,l),R(e,I.partition,i+Pe,8,3,l),R(e,a?I.partitionT:o?`#1a3a2e`:s?`#3a1a1a`:I.partitionH,i,8,Pe+3,4),R(e,`#0f172a`,i+5,14,16,10),R(e,`#162032`,i+6,15,14,3),a?(R(e,I.green,i+7,19,11,1),R(e,I.greenDim,i+7,21,12,1)):o?(R(e,I.green,i+7,18,12,1),R(e,I.green,i+7,20,9,1),R(e,I.green,i+7,22,11,1)):(R(e,I.dim,i+7,19,10,1),R(e,I.dim,i+7,21,7,1));let u=i+Pe-14;R(e,I.pot,u+2,Ie+Fe-5,7,5),R(e,I.plant1,u,Ie+Fe-13,10,8),R(e,I.plant2,u+2,Ie+Fe-17,6,5),R(e,I.plant2,u-2,Ie+Fe-12,5,4),R(e,I.plant2,u+7,Ie+Fe-13,5,4),R(e,I.deskTop,i+4,Ie,Pe-6,3),R(e,I.deskFace,i+4,Ie+3,Pe-6,Fe-3),R(e,I.deskSide,i+4,Ie+Fe,Pe-6,3);let d=Ne-Ie-Fe-3;R(e,I.deskLeg,i+8,Ie+Fe+3,4,d),R(e,I.deskLeg,i+Pe-10,Ie+Fe+3,4,d);let f=i+14,p=Ie-18;if(R(e,I.monBody,f,p,22,14),R(e,I.monFace,f+1,p+1,20,12),R(e,a?I.monScrOn:o?I.monScrDone:I.monScrIdle,f+2,p+2,18,8),a)for(let t=0;t<3;t++){let n=3+Math.floor((r/90+t*5)%12);R(e,I.green,f+3,p+3+t*2,n,1),R(e,I.greenDim,f+3+n,p+3+t*2,12-n,1)}else o?(R(e,I.green,f+3,p+3,10,1),R(e,I.green,f+3,p+5,8,1),R(e,I.green,f+3,p+7,9,1),R(e,I.green,f+13,p+3,2,4)):s?(R(e,I.red,f+8,p+2,4,6),R(e,I.red,f+8,p+9,4,2)):(R(e,I.dim,f+3,p+3,12,1),R(e,I.dim,f+3,p+5,8,1),R(e,I.dim,f+3,p+7,10,1));R(e,I.monStand,f+8,p+14,4,3),R(e,I.monStand,f+5,p+17,10,1),R(e,I.keyboard,f-1,Ie+1,16,4);for(let t=0;t<5;t++)R(e,I.monFace,f+t*3,Ie+2,2,2);R(e,I.cup,i+7,Ie+2,5,5),R(e,`#92400e`,i+12,Ie+4,2,3),R(e,I.paper,i+52,Ie+2,12,7),R(e,I.paper,i+54,Ie+1,12,7),R(e,I.paperLine,i+56,Ie+3,7,1),R(e,I.paperLine,i+56,Ie+5,5,1),R(e,I.chairBack,i+Pe/2-12,Ne-20,24,3),R(e,I.chairSeat,i+Pe/2-13,Ne-14,26,8),R(e,I.chairLeg,i+Pe/2-8,Ne-6,4,6),R(e,I.chairLeg,i+Pe/2+5,Ne-6,4,6),Ve(e,t,i+Pe/2-8,Ne-Re,a,c,r);let m=a?I.cyan:o?I.green:s?I.red:I.dim;e.font=`bold ${4*L}px monospace`,e.fillStyle=m,e.textBaseline=`alphabetic`;let h=t.label.length>14?t.label.slice(0,13)+`…`:t.label;e.fillText(h,(i+4)*L,8*L),R(e,m,i+Pe-4,9,4,4),a&&Math.floor(r/250)%2==0&&R(e,I.white,i+Pe-3,10,2,2)}function Ve(e,t,n,r,i,a,o){let s=I.skin[t.skinIdx],c=I.shirt[t.shirtIdx],l=I.hair[t.hairIdx],u=i&&!a?Math.floor(o/700)%2:0;R(e,l,n+1,r+u,12,3),R(e,l,n,r+1+u,14,2),R(e,l,n,r+3+u,2,5),R(e,l,n+12,r+3+u,2,5),R(e,s,n+1,r+3+u,12,9);let d=l;R(e,d,n+2,r+4+u,3,1),R(e,d,n+9,r+4+u,3,1),R(e,`#e2e8f0`,n+2,r+5+u,3,3),R(e,`#e2e8f0`,n+9,r+5+u,3,3);let f=t.skinIdx%2==0?`#1d4ed8`:`#065f46`;if(R(e,f,n+3,r+6+u,2,2),R(e,f,n+10,r+6+u,2,2),R(e,`#0f172a`,n+3,r+7+u,1,1),R(e,`#0f172a`,n+10,r+7+u,1,1),R(e,`#c97a4a`,n+7,r+9+u,1,2),t.status===`done`)R(e,`#991b1b`,n+4,r+11+u,6,1),R(e,`#991b1b`,n+3,r+10+u,1,1),R(e,`#991b1b`,n+10,r+10+u,1,1);else if(t.status===`error`)R(e,`#991b1b`,n+4,r+10+u,6,1),R(e,`#991b1b`,n+3,r+11+u,1,1),R(e,`#991b1b`,n+10,r+11+u,1,1);else if(t.status===`running`){let t=Math.floor(o/250)%2;R(e,`#991b1b`,n+5,r+10+u,4,t?2:1)}else R(e,`#7f1d1d`,n+5,r+11+u,4,1);if(R(e,s,n+4,r+12,6,2),R(e,c,n+1,r+14,14,11),R(e,`#f1f5f9`,n+4,r+14,3,4),R(e,`#f1f5f9`,n+9,r+14,3,4),e.font=`${4*L}px serif`,e.textBaseline=`middle`,e.textAlign=`center`,e.fillText(t.icon,(n+8)*L,(r+19)*L),e.textAlign=`left`,i&&!a){let t=Math.floor(o/120)%2;R(e,c,n-2,r+14,3,9),R(e,s,n-2,r+22-t,3,3),R(e,c,n+15,r+14,3,9),R(e,s,n+15,r+22-(1-t),3,3)}else if(a&&i){let t=Math.floor(o/180)%3;R(e,c,n-2,r+14,3,8),R(e,s,n-2,r+22,3,3),R(e,c,n+15,r+8+t,3,9),R(e,s,n+15,r+5+t,3,4)}else R(e,c,n-2,r+14,3,9),R(e,s,n-2,r+23,3,2),R(e,c,n+15,r+14,3,9),R(e,s,n+15,r+23,3,2);R(e,I.pants,n+2,r+25,5,7),R(e,I.pants,n+9,r+25,5,7),R(e,`#2d3f5f`,n+3,r+29,3,1),R(e,`#2d3f5f`,n+10,r+29,3,1),R(e,I.shoes,n,r+32,7,4),R(e,I.shoes,n+9,r+32,7,4),R(e,`#1e2a3a`,n+1,r+32,5,1),R(e,`#1e2a3a`,n+10,r+32,5,1),e.fillStyle=`rgba(0,0,0,0.35)`,e.beginPath(),e.ellipse((n+8)*L,(Ne-1)*L,9*L,2*L,0,0,Math.PI*2),e.fill()}function He(e){e.papers.length>=6||e.papers.push({x:e.x+20+Math.random()*(Pe-40),y:Ie-4,vx:(Math.random()-.5)*1.5,vy:-1.5-Math.random()*1,rot:Math.random()*360,vrot:(Math.random()-.5)*10,life:0,maxLife:70+Math.random()*40})}function Ue(e){e.papers=e.papers.filter(e=>e.life<e.maxLife);for(let t of e.papers)t.x+=t.vx,t.y+=t.vy,t.vy+=.06,t.rot+=t.vrot,t.life++}function We(e,t){for(let n of t.papers){let t=1-n.life/n.maxLife;e.save(),e.globalAlpha=t,e.translate(n.x*L,n.y*L),e.rotate(n.rot*Math.PI/180),e.fillStyle=I.paper,e.fillRect(-7*L,-5*L,14*L,10*L),e.fillStyle=I.paperLine,e.fillRect(-5*L,-2*L,8*L,L),e.fillRect(-5*L,0,6*L,L),e.restore()}e.globalAlpha=1}var Ge=`ABCDEFGHIJKLMNOPRSTUVWXYZ0123456789!?-_.:`;function Ke(e,t){return t<=0?e:Ge[Math.floor(Math.random()*41)]??e}function qe(e,t,n,r,i,a=110,o=`down`){if(t.length===0)return;e.fillStyle=I.bubbleBg,e.fillRect(r*L,i*L,a*L,36*L),e.strokeStyle=I.bubbleBdr,e.lineWidth=L,e.strokeRect(r*L,i*L,a*L,36*L),e.fillStyle=I.cyan,e.fillRect(r*L,i*L,a*L,L),e.fillStyle=I.bubbleBdr,e.fillRect(r*L,(i+13+5)*L,a*L,L);for(let o=0;o<2;o++){let s=t[(Math.floor(n)+o)%t.length];if(!s)continue;let c=i+5+o*14;e.fillStyle=o%2==0?`#0f172a`:`#111827`,e.fillRect(r*L,c*L,a*L,13*L);let l=n-Math.floor(n),u=s.text.split(``);e.font=`bold ${5*L}px monospace`,e.textBaseline=`middle`;let d=Math.floor(a/7)-1;for(let t=0;t<Math.min(u.length,d);t++){let n=l>.3&&t>u.length*(1-l)?Ke(u[t],3):u[t];e.globalAlpha=l>0&&t>u.length*(1-l)?.5+l*.5:1,e.fillStyle=s.color,e.fillText(n,(r+4+t*7)*L,(c+6)*L)}e.globalAlpha=1}let s=r+a/2;if(e.fillStyle=I.bubbleBdr,o===`down`){let t=i+36;e.fillRect((s-2)*L,t*L,4*L,4*L),e.fillRect((s-3)*L,(t+4)*L,6*L,3*L)}else e.fillRect((s-2)*L,(i-4)*L,4*L,4*L),e.fillRect((s-3)*L,(i-7)*L,6*L,3*L)}function Je(e,t,n,r){let i=Ne-Re-8,a=n%2,o=a===0?2:0,s=a===0?0:2;e.fillStyle=`rgba(0,0,0,0.4)`,e.beginPath(),e.ellipse(t*L,(Ne-1)*L,9*L,2*L,0,0,Math.PI*2),e.fill(),e.save(),e.translate(t*L,0),e.scale(r?1:-1,1),R(e,I.cHair,-4,i,9,3),R(e,I.cHair,-5,i+1,11,2),R(e,I.cHair,-5,i+3,2,3),R(e,I.cHair,5,i+3,2,3),R(e,I.cSkin,-4,i+3,8,7),R(e,`#0f172a`,-2,i+5,2,2),R(e,`#0f172a`,2,i+5,2,2),R(e,`#1d4ed8`,-1,i+6,1,1),R(e,`#1d4ed8`,3,i+6,1,1),R(e,`#7f1d1d`,-2,i+9,5,1),R(e,I.cSkin,-1,i+10,3,2),R(e,I.cShirt,-5,i+12,11,9),R(e,`#3730a3`,-5,i+12,3,5),R(e,`#3730a3`,3,i+12,3,5),R(e,I.cTie,-1,i+12,3,7),R(e,`#f1f5f9`,-2,i+12,1,3),R(e,`#f1f5f9`,2,i+12,1,3),R(e,I.cShirt,-7,i+13,3,9),R(e,I.cSkin,-7,i+21,3,3),R(e,I.cShirt,5,i+13,3,9),R(e,I.cSkin,5,i+21,3,3),R(e,I.cBag,6,i+24,6,4),R(e,`#92400e`,7,i+23,4,1),R(e,I.cPants,-3,i+21+o,5,10),R(e,I.cPants,1,i+21+s,5,10),R(e,I.cShoes,-5,i+29+o,6,3),R(e,I.cShoes,-1,i+29+s,6,3),e.restore()}var Ye=[[{text:`RUNNING WORKFLOW`,color:I.cyan},{text:`DISPATCHING...`,color:I.amber}],[{text:`ORCHESTRATING`,color:I.cyan},{text:`ALL SYSTEMS GO`,color:I.green}],[{text:`AGENT PIPELINE`,color:I.purple},{text:`EXECUTING...`,color:I.cyan}],[{text:`MONITORING ALL`,color:I.amber},{text:`ON SCHEDULE`,color:I.green}]];function Xe(e,t){let n=e.toUpperCase().slice(0,14);return t===`running`?[{text:n,color:I.cyan},{text:`WORKING...`,color:I.green}]:t===`done`?[{text:n,color:I.green},{text:`TASK DONE`,color:I.green}]:t===`error`?[{text:n,color:I.red},{text:`ERR`,color:I.red}]:[{text:n,color:I.dim},{text:`STANDBY`,color:I.dim}]}function Ze({nodes:e,running:t}){let n=(0,_.useRef)(null),r=(0,_.useRef)(null);return(0,_.useEffect)(()=>{if(e.length===0)return;let t=Math.min(e.length,8)*(Pe+Le)-Le,n=Math.max(6,Math.round((ke-t)/2));if(!r.current)r.current={agents:e.slice(0,8).map((e,t)=>({x:n+t*(Pe+Le),icon:e.icon,label:e.label,status:e.status,skinIdx:t%I.skin.length,shirtIdx:t%I.shirt.length,hairIdx:t%I.hair.length,papers:[],bubbleLines:Xe(e.label,e.status),bubbleScroll:0,bubbleTimer:0})),condX:n+Pe/2,condTargetX:n+Pe/2,condFacing:!0,condFrame:0,condFrameTimer:0,condIdleTimer:0,condBubble:Ye[0],condBubbleScroll:0,condBubbleTimer:0,rafId:0};else{let t=r.current;for(let r=0;r<Math.min(e.length,8);r++){let i=e[r],a=t.agents[r];a?(a.status!==i.status&&(a.status=i.status,a.bubbleLines=Xe(i.label,i.status),a.bubbleScroll=0),a.icon=i.icon,a.label=i.label):t.agents.push({x:n+r*(Pe+Le),icon:i.icon,label:i.label,status:i.status,skinIdx:r%I.skin.length,shirtIdx:r%I.shirt.length,hairIdx:r%I.hair.length,papers:[],bubbleLines:Xe(i.label,i.status),bubbleScroll:0,bubbleTimer:0})}}},[e]),(0,_.useEffect)(()=>{let e=n.current;if(!e)return;let i=e.getContext(`2d`);if(!i||(i.imageSmoothingEnabled=!1,!r.current))return;let a=0,o=0;function s(e){let n=r.current,c=Math.min(e-a,50);a=e;let l=e,u=n.agents.find(e=>e.status===`running`);if(u){let e=u.x+Pe+12,t=u.x-18;n.condTargetX=e<ke-30?e:t,n.condBubbleTimer+=c,n.condBubbleTimer>2e3&&(n.condBubbleTimer=0,n.condBubble=Ye[Math.floor(Math.random()*Ye.length)],n.condBubbleScroll=(n.condBubbleScroll+1)%4)}else if(n.condIdleTimer-=c,n.condIdleTimer<=0){let e=n.agents[Math.floor(Math.random()*n.agents.length)];e&&(n.condTargetX=e.x+Pe/2),n.condIdleTimer=2e3+Math.random()*3e3}let d=n.condTargetX-n.condX;if(Math.abs(d)>1&&(n.condX+=d*(t?.12:.04)*(c/16),n.condFacing=d>0,n.condFrameTimer+=c,n.condFrameTimer>100&&(n.condFrame++,n.condFrameTimer=0)),o+=c,o>200){o=0;for(let e of n.agents)e.status===`running`&&He(e)}for(let e of n.agents)Ue(e);for(let e of n.agents)e.bubbleTimer+=c,e.bubbleTimer>1800&&(e.bubbleTimer=0,e.bubbleScroll=(e.bubbleScroll+1)%4);i.clearRect(0,0,je,Me),ze(i);for(let e of n.agents)Be(i,e,n.condX,l),We(i,e);Je(i,n.condX,n.condFrame,n.condFacing);for(let e of n.agents){if(e.status===`waiting`)continue;let t=Math.min(e.label.length*6+16,90),n=Math.max(e.x+1,Math.min(e.x+(Pe-t)/2,ke-t-2)),r=Ne-Re-36;qe(i,e.bubbleLines,e.bubbleScroll,n,r,t,`down`)}let f=Ne-Re-8-38,p=Math.max(4,Math.min(n.condX-50,ke-106));qe(i,n.condBubble,n.condBubbleScroll,p,f,100,`down`);for(let e=0;e<Me;e+=L*3)i.fillStyle=`rgba(0,0,0,0.04)`,i.fillRect(0,e,je,L);n.rafId=requestAnimationFrame(s)}return r.current.rafId=requestAnimationFrame(s),()=>{r.current&&cancelAnimationFrame(r.current.rafId)}},[t]),e.length===0?null:(0,A.jsx)(`div`,{style:{width:`100%`,lineHeight:0},children:(0,A.jsx)(`canvas`,{ref:n,width:je,height:Me,style:{width:`100%`,height:`auto`,imageRendering:`pixelated`,display:`block`,background:I.bg,boxShadow:`0 0 40px rgba(99,102,241,0.15), inset 0 0 80px rgba(0,0,0,0.4)`}})})}var Qe=[`#f4d4b8`,`#e0a878`,`#c98a5b`,`#9c6b48`,`#7a5238`,`#5a3a26`,`#3d2818`,`#deba90`],$e=[`#dc2626`,`#2563eb`,`#16a34a`,`#7c3aed`,`#f59e0b`,`#0891b2`,`#db2777`,`#475569`],et=[`#1a1a1a`,`#3d2817`,`#6b4423`,`#8b6f47`,`#c9a55c`,`#e4d4a8`,`#6b7280`,`#000000`],tt=[{x:50,y:16},{x:78,y:28},{x:86,y:52},{x:78,y:76},{x:50,y:84},{x:22,y:76},{x:14,y:52},{x:22,y:28}];function nt({agents:e,council:t}){let[n,r]=(0,_.useState)(0);(0,_.useEffect)(()=>{if(!t||t.phase===`idle`||t.phase===`skipped`){r(0);return}let n=Math.min(e.length,tt.length),i=0;r(0);let a=()=>{i++,r(i),i<n&&setTimeout(a,250)},o=setTimeout(a,200);return()=>{clearTimeout(o)}},[t?.phase,e.length]);let[i,a]=(0,_.useState)(0);(0,_.useEffect)(()=>{if(!t||t.phase!==`r2`&&t.phase!==`r3`)return;let n=setInterval(()=>{a(t=>(t+1)%Math.max(1,e.length))},2e3);return()=>clearInterval(n)},[t?.phase,e.length]);let o=t?.phase===`r2`?`Round 2 — Cross-reading (${t.r2Nodes.length}/${e.length})`:t?.phase===`r3`?`Round 3 — HERALD mediazione`:t?.phase===`done`?`Sintesi completata · convergenza ${Math.round((t.r2Convergence||0)*100)}%`:`In attesa…`;return(0,A.jsxs)(`div`,{style:{width:`100%`,height:`100%`,background:`linear-gradient(180deg, #1a1530 0%, #0f0d22 100%)`,position:`relative`,overflow:`hidden`,color:`#fff`,fontFamily:`inherit`},children:[(0,A.jsxs)(`div`,{style:{position:`absolute`,top:8,left:12,right:12,display:`flex`,justifyContent:`space-between`,alignItems:`center`,fontSize:11,letterSpacing:`0.8px`,textTransform:`uppercase`,color:`#a78bfa`,zIndex:10},children:[(0,A.jsx)(`span`,{style:{fontWeight:800},children:`🏛️ Council Room`}),(0,A.jsx)(`span`,{style:{color:`#c4b5fd`},children:o})]}),(0,A.jsxs)(`svg`,{viewBox:`0 0 100 100`,preserveAspectRatio:`none`,style:{position:`absolute`,inset:0,width:`100%`,height:`100%`},children:[(0,A.jsxs)(`defs`,{children:[(0,A.jsxs)(`radialGradient`,{id:`tableGrad`,cx:`50%`,cy:`50%`,r:`50%`,children:[(0,A.jsx)(`stop`,{offset:`0%`,stopColor:`#5b3a8b`}),(0,A.jsx)(`stop`,{offset:`100%`,stopColor:`#2d1d54`})]}),(0,A.jsxs)(`radialGradient`,{id:`floorGrad`,cx:`50%`,cy:`100%`,r:`80%`,children:[(0,A.jsx)(`stop`,{offset:`0%`,stopColor:`#241a3a`}),(0,A.jsx)(`stop`,{offset:`100%`,stopColor:`#15102a`})]})]}),(0,A.jsx)(`rect`,{x:`0`,y:`0`,width:`100`,height:`100`,fill:`url(#floorGrad)`}),(0,A.jsx)(`ellipse`,{cx:`50`,cy:`52`,rx:`32`,ry:`20`,fill:`url(#tableGrad)`,stroke:`#7c3aed`,strokeWidth:`0.4`}),(0,A.jsx)(`ellipse`,{cx:`50`,cy:`49`,rx:`28`,ry:`17`,fill:`rgba(0,0,0,0.18)`}),(0,A.jsx)(`circle`,{cx:`50`,cy:`50`,r:`38`,fill:`none`,stroke:`rgba(167,139,250,0.12)`,strokeWidth:`0.3`})]}),e.slice(0,tt.length).map((e,r)=>{let a=tt[r],o=r<n,s=o&&r===i&&(t?.phase===`r2`||t?.phase===`r3`),c=$e[r%$e.length],l=Qe[r%Qe.length],u=et[r%et.length];return(0,A.jsxs)(`div`,{style:{position:`absolute`,left:`${a.x}%`,top:`${a.y}%`,transform:`translate(-50%, -50%) translateX(${o?0:a.x<50?`-80px`:`80px`})`,opacity:+!!o,transition:`transform 0.6s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.4s ease`,display:`flex`,flexDirection:`column`,alignItems:`center`,gap:2,pointerEvents:`none`,zIndex:5},children:[(0,A.jsxs)(`div`,{style:{fontSize:9,padding:`2px 6px`,background:`rgba(255,255,255,0.95)`,color:`#1e1b4b`,borderRadius:8,marginBottom:2,fontWeight:700,opacity:+!!s,transform:s?`translateY(0) scale(1)`:`translateY(4px) scale(0.85)`,transition:`opacity 0.25s, transform 0.25s`,whiteSpace:`nowrap`,maxWidth:110,overflow:`hidden`,textOverflow:`ellipsis`,boxShadow:`0 2px 6px rgba(0,0,0,0.4)`},children:[e.icon,` `,e.label.slice(0,12)]}),(0,A.jsxs)(`svg`,{width:`20`,height:`26`,viewBox:`0 0 20 26`,style:{filter:s?`drop-shadow(0 0 4px rgba(167,139,250,0.8))`:`none`,transition:`filter 0.25s`},children:[(0,A.jsx)(`rect`,{x:`6`,y:`2`,width:`8`,height:`3`,fill:u}),(0,A.jsx)(`rect`,{x:`6`,y:`4`,width:`8`,height:`6`,fill:l}),(0,A.jsx)(`rect`,{x:`8`,y:`7`,width:`1`,height:`1`,fill:`#000`}),(0,A.jsx)(`rect`,{x:`11`,y:`7`,width:`1`,height:`1`,fill:`#000`}),(0,A.jsx)(`rect`,{x:`5`,y:`10`,width:`10`,height:`9`,fill:c}),(0,A.jsx)(`rect`,{x:`9`,y:`10`,width:`2`,height:`2`,fill:l}),(0,A.jsx)(`rect`,{x:`3`,y:`11`,width:`2`,height:`7`,fill:c}),(0,A.jsx)(`rect`,{x:`15`,y:`11`,width:`2`,height:`7`,fill:c}),(0,A.jsx)(`rect`,{x:`3`,y:`18`,width:`2`,height:`2`,fill:l}),(0,A.jsx)(`rect`,{x:`15`,y:`18`,width:`2`,height:`2`,fill:l}),(0,A.jsx)(`rect`,{x:`6`,y:`19`,width:`3`,height:`4`,fill:`#1e293b`}),(0,A.jsx)(`rect`,{x:`11`,y:`19`,width:`3`,height:`4`,fill:`#1e293b`})]}),(0,A.jsx)(`div`,{style:{fontSize:9,color:`#c4b5fd`,fontWeight:600,maxWidth:80,textAlign:`center`,textShadow:`0 1px 2px rgba(0,0,0,0.6)`},children:e.label.slice(0,14)})]},e.agent+r)}),t?.phase===`done`&&(0,A.jsx)(`div`,{style:{position:`absolute`,bottom:10,left:0,right:0,textAlign:`center`,fontSize:10,color:`#c4b5fd`,letterSpacing:`0.5px`},children:t.converged?`✓ Consenso raggiunto`:`⚖ Mediazione completata`})]})}function rt(e){let t=e.toLowerCase();return De.some(e=>t.includes(e.toLowerCase()))}function it(e,t){let n=e=>e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`),r=t.map(e=>e.output).join(`
|
|
277
|
+
- Key risks: overfitting, regime change, crowding`,agents:[{icon:`📊`,agent:`oracle`,label:`Oracle`,status:`waiting`,output:``},{icon:`💹`,agent:`mercury`,label:`Mercury`,status:`waiting`,output:``},{icon:`📈`,agent:`edi`,label:`Edi`,status:`waiting`,output:``},{icon:`⚠️`,agent:`cassandra`,label:`Cassandra`,status:`waiting`,output:``},{icon:`🔥`,agent:`prometheus`,label:`Prometheus`,status:`waiting`,output:``}]},{label:`💼 Portfolio Health Check`,task:`You are a multi-asset risk manager and portfolio strategist. Produce a comprehensive weekly health check of the user's portfolio (the actual positions persisted in ~/.nha/portfolio.json). Use the dedicated tools:
|
|
278
|
+
- portfolio_summary: current positions, live P/L, cost basis, totals
|
|
279
|
+
- portfolio_metrics(period="1y"): annualized return, vol, Sharpe, Sortino, max drawdown, beta vs SPY
|
|
280
|
+
- portfolio_var(period="1y", confidence=0.95): daily VaR + Expected Shortfall + 10-day VaR
|
|
281
|
+
- portfolio_correlation(period="1y"): full correlation matrix + concentration pairs
|
|
282
|
+
- portfolio_sector_breakdown: % allocation by sector, country, currency
|
|
283
|
+
- portfolio_rebalance: current vs target weights drift + exact trades to bring back in line
|
|
284
|
+
- peer_comparison for the TOP 3 positions: side-by-side P/E, P/B, ROE, D/E, dividend yield
|
|
285
|
+
- news_sentiment for each position with weight > 10%: aggregate verdict + key headlines
|
|
286
|
+
- economic_calendar(country="US", days=7): macro events that could move the book
|
|
287
|
+
- market_news(query="Federal Reserve" OR "ECB"): central bank chatter
|
|
288
|
+
|
|
289
|
+
Then produce a structured report:
|
|
290
|
+
|
|
291
|
+
1. EXECUTIVE SUMMARY (3 bullets max) — overall health (green/yellow/red), biggest concentration risk, one concrete action this week.
|
|
292
|
+
2. PERFORMANCE & RISK — YTD vs SPY, Sharpe, max drawdown, beta. Flag Sharpe < 0.5 or beta > 1.3. VaR 95% in $ and %.
|
|
293
|
+
3. DIVERSIFICATION SCORECARD — sector concentration > 35%? unhedged FX > 20%? correlated pairs r > 0.7? effectively independent bets (1/sum(weight²)).
|
|
294
|
+
4. POSITION-BY-POSITION REVIEW — for each holding > 5%: fundamentals snapshot, technical signal, news sentiment, catalyst calendar, verdict HOLD/TRIM/ADD/EXIT with rationale.
|
|
295
|
+
5. REBALANCING PLAN — use portfolio_rebalance output. Sort by absolute drift. Flag tax implications if gain > 30%.
|
|
296
|
+
6. WEEK AHEAD WATCHLIST — macro events, earnings dates, technical levels to monitor.
|
|
297
|
+
7. ONE-LINE BOTTOM LINE — the sentence the user should remember in 30 seconds.
|
|
298
|
+
|
|
299
|
+
Quantitative. Numbers. No vague platitudes.`,agents:[{icon:`💼`,agent:`oracle`,label:`Oracle`,status:`waiting`,output:``},{icon:`⚠️`,agent:`cassandra`,label:`Cassandra`,status:`waiting`,output:``},{icon:`💹`,agent:`mercury`,label:`Mercury`,status:`waiting`,output:``},{icon:`📰`,agent:`herald`,label:`Herald`,status:`waiting`,output:``},{icon:`🔥`,agent:`prometheus`,label:`Prometheus`,status:`waiting`,output:``}]},{label:`📊 Earnings Preview — Week Ahead`,task:`You are an earnings strategist. For each ticker the user names (or the most-watched names of the week if none): pull earnings_calendar (date + EPS estimate + last 4 surprises), market_indicators (current valuation), peer_comparison (vs industry), options_chain (implied move = straddle / spot × 100), news_sentiment (pre-earnings tone), market_chart period="3mo" (technical setup, RSI, key levels).
|
|
300
|
+
|
|
301
|
+
Then produce:
|
|
302
|
+
1. CONSENSUS vs OPTIONS-IMPLIED MOVE — disagreement is the trade.
|
|
303
|
+
2. SETUP CATEGORIZATION — 🟢 HIGH-CONVICTION LONG (beats + cheap IV + bullish chart) | 🔴 SHORT INTO PRINT (deteriorating + expensive IV) | 🟡 STRADDLE (binary, no directional edge) | ⚪ SKIP.
|
|
304
|
+
3. SIZING & RISK — % of book, stop loss $ and %, hedging idea (peer offset, sector ETF, VIX call).
|
|
305
|
+
4. POST-EARNINGS PLAYBOOK — beats+raises: trail stop to BE at +5%. Misses+solid guidance: fade. Misses+lowers: short reversal. Beats+weak guidance: tactical short.
|
|
306
|
+
|
|
307
|
+
Specific prices. Specific levels.`,agents:[{icon:`💹`,agent:`mercury`,label:`Mercury`,status:`waiting`,output:``},{icon:`💼`,agent:`oracle`,label:`Oracle`,status:`waiting`,output:``},{icon:`📰`,agent:`herald`,label:`Herald`,status:`waiting`,output:``},{icon:`⚠️`,agent:`cassandra`,label:`Cassandra`,status:`waiting`,output:``}]},{label:`🔄 Sector Rotation Scan`,task:`You are a top-down sector strategist. Run a 4-week rotation scan.
|
|
308
|
+
|
|
309
|
+
Data: macro_data(indicator="all") for regime, stock_screener(day_gainers/day_losers, 20 each) for breadth, for each SPDR sector ETF (XLK, XLF, XLE, XLV, XLI, XLP, XLY, XLU, XLB, XLRE, XLC): market_chart period="3mo" + market_indicators. Plus market_news(query="sector rotation") and economic_calendar(country="US", days=14).
|
|
310
|
+
|
|
311
|
+
Report:
|
|
312
|
+
1. MACRO REGIME — cycle phase, risk-on/off, dollar regime, inflation surprise direction.
|
|
313
|
+
2. SECTOR LEADERSHIP TABLE — 11 sectors ranked by 1mo / 3mo / YTD performance, RSI, distance from 50d MA.
|
|
314
|
+
3. CROSS-SECTOR SIGNALS — cyclicals vs defensives (XLI+XLY+XLB vs XLP+XLV+XLU), banks vs gold (XLF vs GLD), energy vs tech (XLE vs XLK), discretionary vs staples (XLY vs XLP).
|
|
315
|
+
4. TACTICAL CALLS — 2 OVERWEIGHT + 2 UNDERWEIGHT with specific ETF + 1-3 leading names + entry zone + invalidation.
|
|
316
|
+
5. KEY RISK — the macro event that breaks the thesis + stop-loss conditions at the ETF level.`,agents:[{icon:`🌍`,agent:`oracle`,label:`Oracle`,status:`waiting`,output:``},{icon:`💹`,agent:`mercury`,label:`Mercury`,status:`waiting`,output:``},{icon:`📊`,agent:`cartographer`,label:`Cartographer`,status:`waiting`,output:``},{icon:`⚠️`,agent:`cassandra`,label:`Cassandra`,status:`waiting`,output:``}]},{label:`🇮🇹 Italian Market Daily Brief`,task:`Sei uno strategist del mercato italiano. Produci il brief giornaliero in italiano. Usa: italian_market(what="all"), macro_data(indicator="indices"), macro_data(indicator="commodities"), market_news(query="Borsa Italiana"), news_sentiment(query="MIB", limit=15), economic_calendar(country="IT", days=5), economic_calendar(country="EU", days=5), earnings_calendar per i top 5 MIB con earnings nei prossimi 14 giorni, peer_comparison per i 3 titoli più mossi.
|
|
317
|
+
|
|
318
|
+
Struttura:
|
|
319
|
+
1. APERTURA in 3 righe — FTSE MIB livello + delta %, tono, notizia dominante.
|
|
320
|
+
2. SETTORI MOTORI E FRENI — top 3 su / top 3 giù + ragione, performance vs DAX e CAC.
|
|
321
|
+
3. TITOLI DA SEGUIRE — 3 movimenti significativi (% + volume + news), 2 breakout/breakdown tecnici da 50d MA.
|
|
322
|
+
4. CONTESTO MACRO ITALIA — spread BTP-Bund, EUR/USD impatto su esportatrici (ENI, STM), BCE/Fed.
|
|
323
|
+
5. EVENTI SETTIMANA — earnings (UCG, ISP, ENI…), dati macro, decisioni politiche.
|
|
324
|
+
6. SENTIMENT — mood stampa, flussi, idea direzionale.
|
|
325
|
+
7. TRADE DEL GIORNO — 1 long + 1 short con entry, stop, target precisi. Sizing in % portafoglio.
|
|
326
|
+
|
|
327
|
+
Tutto quantitativo. Niente "il mercato è cauto" senza numeri.`,agents:[{icon:`🇮🇹`,agent:`oracle`,label:`Oracle`,status:`waiting`,output:``},{icon:`💹`,agent:`mercury`,label:`Mercury`,status:`waiting`,output:``},{icon:`📰`,agent:`herald`,label:`Herald`,status:`waiting`,output:``},{icon:`⚠️`,agent:`cassandra`,label:`Cassandra`,status:`waiting`,output:``}]}],De=`trading strategy.fundamental analysis.portfolio risk.sector deep dive.crypto analysis.quant factor.macro regime.cross-asset.hedge.derivative.yield curve.market_price.market_chart.market_indicators.macro_data.crypto_data.valuation.equity.earnings.dividend.short interest.volatility.options.futures.bitcoin.ethereum`.split(`.`),Oe=[`Analyze my unread emails and create a priority action plan`,`Search the web for AI news today and summarize it in a canvas report`,`Check my calendar for this week and suggest how to optimize my schedule`,`Review my GitHub notifications and draft responses to open issues`,`Search for information about a topic, fact-check it, and write a report`],I={bg:`#0a0d14`,wall:`#0d1117`,wallStripe:`#0f1520`,baseboard:`#1e293b`,floorA:`#111827`,floorB:`#141c2b`,floorLine:`#1e293b`,ceiling:`#0b0f18`,lampBody:`#854d0e`,lampGlow:`#fef08a`,partition:`#1a2436`,partitionH:`#253347`,partitionT:`#2d4a6e`,deskTop:`#2d4263`,deskFace:`#1e3352`,deskSide:`#192b43`,deskLeg:`#0f1e30`,monBody:`#0f172a`,monFace:`#162032`,monScrIdle:`#060b12`,monScrOn:`#001208`,monScrDone:`#001a08`,monStand:`#0d1625`,keyboard:`#162032`,chairBack:`#1a3a5c`,chairSeat:`#1e4068`,chairLeg:`#0f1e30`,plant1:`#14532d`,plant2:`#166534`,pot:`#78350f`,cup:`#7c2d12`,paper:`#e2e8f0`,paperLine:`#94a3b8`,skin:[`#f5c97a`,`#fbbf24`,`#fca5a5`,`#86efac`,`#a5f3fc`,`#c4b5fd`,`#fde68a`,`#f9a8d4`],shirt:[`#1e40af`,`#065f46`,`#312e81`,`#be123c`,`#164e63`,`#7c3aed`,`#7f1d1d`,`#0e7490`],hair:[`#7c3aed`,`#92400e`,`#1e293b`,`#6b21a8`,`#78350f`,`#292524`,`#422006`,`#0c4a6e`],pants:`#1e293b`,shoes:`#0f172a`,cSkin:`#fbbf24`,cShirt:`#4f46e5`,cHair:`#111827`,cTie:`#dc2626`,cPants:`#1e293b`,cShoes:`#0f172a`,cBag:`#78350f`,green:`#22c55e`,greenDim:`#14532d`,cyan:`#67e8f9`,purple:`#a78bfa`,red:`#ef4444`,amber:`#fbbf24`,dim:`#334155`,white:`#f1f5f9`,bubbleBg:`#0f172a`,bubbleBdr:`#334155`},L=2,ke=1200,Ae=140,je=ke*L,Me=Ae*L,Ne=Math.round(Ae*.78),Pe=130,Fe=14,Ie=Ne-Fe-2,Le=8,Re=36;function R(e,t,n,r,i=1,a=1){e.fillStyle=t,e.fillRect(n*L,r*L,i*L,a*L)}function ze(e){R(e,I.ceiling,0,0,ke,7);for(let t=0;t<ke;t++)R(e,t%20<10?I.wall:I.wallStripe,t,7,1,Ne-7);R(e,I.baseboard,0,Ne-3,ke,3);for(let t=0;t<ke;t+=14)for(let n=Ne;n<Ae;n+=7)R(e,(Math.floor(t/14)+Math.floor((n-Ne)/7))%2==0?I.floorA:I.floorB,t,n,14,7),R(e,I.floorLine,t,n,14,1),R(e,I.floorLine,t,n,1,7);for(let t=40;t<ke;t+=120){R(e,I.lampBody,t,1,40,4),R(e,I.lampGlow,t+2,2,36,2);for(let n=0;n<18;n++)e.fillStyle=`rgba(254,240,138,${(.06-n*.003).toFixed(4)})`,e.fillRect((t+20-n*1.1)*L,(5+n)*L,n*2.2*L,L)}}function Be(e,t,n,r){let i=t.x,a=t.status===`running`,o=t.status===`done`,s=t.status===`error`,c=Math.abs(n-(i+Pe/2))<30;a&&(e.fillStyle=`rgba(99,102,241,0.09)`,e.fillRect(i*L,10*L,Pe*L,(Ae-10)*L)),o&&(e.fillStyle=`rgba(34,197,94,0.05)`,e.fillRect(i*L,10*L,Pe*L,(Ae-10)*L));let l=Ne-8;R(e,I.partition,i,8,3,l),R(e,I.partitionH,i+1,8,2,l),R(e,I.partition,i+Pe,8,3,l),R(e,a?I.partitionT:o?`#1a3a2e`:s?`#3a1a1a`:I.partitionH,i,8,Pe+3,4),R(e,`#0f172a`,i+5,14,16,10),R(e,`#162032`,i+6,15,14,3),a?(R(e,I.green,i+7,19,11,1),R(e,I.greenDim,i+7,21,12,1)):o?(R(e,I.green,i+7,18,12,1),R(e,I.green,i+7,20,9,1),R(e,I.green,i+7,22,11,1)):(R(e,I.dim,i+7,19,10,1),R(e,I.dim,i+7,21,7,1));let u=i+Pe-14;R(e,I.pot,u+2,Ie+Fe-5,7,5),R(e,I.plant1,u,Ie+Fe-13,10,8),R(e,I.plant2,u+2,Ie+Fe-17,6,5),R(e,I.plant2,u-2,Ie+Fe-12,5,4),R(e,I.plant2,u+7,Ie+Fe-13,5,4),R(e,I.deskTop,i+4,Ie,Pe-6,3),R(e,I.deskFace,i+4,Ie+3,Pe-6,Fe-3),R(e,I.deskSide,i+4,Ie+Fe,Pe-6,3);let d=Ne-Ie-Fe-3;R(e,I.deskLeg,i+8,Ie+Fe+3,4,d),R(e,I.deskLeg,i+Pe-10,Ie+Fe+3,4,d);let f=i+14,p=Ie-18;if(R(e,I.monBody,f,p,22,14),R(e,I.monFace,f+1,p+1,20,12),R(e,a?I.monScrOn:o?I.monScrDone:I.monScrIdle,f+2,p+2,18,8),a)for(let t=0;t<3;t++){let n=3+Math.floor((r/90+t*5)%12);R(e,I.green,f+3,p+3+t*2,n,1),R(e,I.greenDim,f+3+n,p+3+t*2,12-n,1)}else o?(R(e,I.green,f+3,p+3,10,1),R(e,I.green,f+3,p+5,8,1),R(e,I.green,f+3,p+7,9,1),R(e,I.green,f+13,p+3,2,4)):s?(R(e,I.red,f+8,p+2,4,6),R(e,I.red,f+8,p+9,4,2)):(R(e,I.dim,f+3,p+3,12,1),R(e,I.dim,f+3,p+5,8,1),R(e,I.dim,f+3,p+7,10,1));R(e,I.monStand,f+8,p+14,4,3),R(e,I.monStand,f+5,p+17,10,1),R(e,I.keyboard,f-1,Ie+1,16,4);for(let t=0;t<5;t++)R(e,I.monFace,f+t*3,Ie+2,2,2);R(e,I.cup,i+7,Ie+2,5,5),R(e,`#92400e`,i+12,Ie+4,2,3),R(e,I.paper,i+52,Ie+2,12,7),R(e,I.paper,i+54,Ie+1,12,7),R(e,I.paperLine,i+56,Ie+3,7,1),R(e,I.paperLine,i+56,Ie+5,5,1),R(e,I.chairBack,i+Pe/2-12,Ne-20,24,3),R(e,I.chairSeat,i+Pe/2-13,Ne-14,26,8),R(e,I.chairLeg,i+Pe/2-8,Ne-6,4,6),R(e,I.chairLeg,i+Pe/2+5,Ne-6,4,6),Ve(e,t,i+Pe/2-8,Ne-Re,a,c,r);let m=a?I.cyan:o?I.green:s?I.red:I.dim;e.font=`bold ${4*L}px monospace`,e.fillStyle=m,e.textBaseline=`alphabetic`;let h=t.label.length>14?t.label.slice(0,13)+`…`:t.label;e.fillText(h,(i+4)*L,8*L),R(e,m,i+Pe-4,9,4,4),a&&Math.floor(r/250)%2==0&&R(e,I.white,i+Pe-3,10,2,2)}function Ve(e,t,n,r,i,a,o){let s=I.skin[t.skinIdx],c=I.shirt[t.shirtIdx],l=I.hair[t.hairIdx],u=i&&!a?Math.floor(o/700)%2:0;R(e,l,n+1,r+u,12,3),R(e,l,n,r+1+u,14,2),R(e,l,n,r+3+u,2,5),R(e,l,n+12,r+3+u,2,5),R(e,s,n+1,r+3+u,12,9);let d=l;R(e,d,n+2,r+4+u,3,1),R(e,d,n+9,r+4+u,3,1),R(e,`#e2e8f0`,n+2,r+5+u,3,3),R(e,`#e2e8f0`,n+9,r+5+u,3,3);let f=t.skinIdx%2==0?`#1d4ed8`:`#065f46`;if(R(e,f,n+3,r+6+u,2,2),R(e,f,n+10,r+6+u,2,2),R(e,`#0f172a`,n+3,r+7+u,1,1),R(e,`#0f172a`,n+10,r+7+u,1,1),R(e,`#c97a4a`,n+7,r+9+u,1,2),t.status===`done`)R(e,`#991b1b`,n+4,r+11+u,6,1),R(e,`#991b1b`,n+3,r+10+u,1,1),R(e,`#991b1b`,n+10,r+10+u,1,1);else if(t.status===`error`)R(e,`#991b1b`,n+4,r+10+u,6,1),R(e,`#991b1b`,n+3,r+11+u,1,1),R(e,`#991b1b`,n+10,r+11+u,1,1);else if(t.status===`running`){let t=Math.floor(o/250)%2;R(e,`#991b1b`,n+5,r+10+u,4,t?2:1)}else R(e,`#7f1d1d`,n+5,r+11+u,4,1);if(R(e,s,n+4,r+12,6,2),R(e,c,n+1,r+14,14,11),R(e,`#f1f5f9`,n+4,r+14,3,4),R(e,`#f1f5f9`,n+9,r+14,3,4),e.font=`${4*L}px serif`,e.textBaseline=`middle`,e.textAlign=`center`,e.fillText(t.icon,(n+8)*L,(r+19)*L),e.textAlign=`left`,i&&!a){let t=Math.floor(o/120)%2;R(e,c,n-2,r+14,3,9),R(e,s,n-2,r+22-t,3,3),R(e,c,n+15,r+14,3,9),R(e,s,n+15,r+22-(1-t),3,3)}else if(a&&i){let t=Math.floor(o/180)%3;R(e,c,n-2,r+14,3,8),R(e,s,n-2,r+22,3,3),R(e,c,n+15,r+8+t,3,9),R(e,s,n+15,r+5+t,3,4)}else R(e,c,n-2,r+14,3,9),R(e,s,n-2,r+23,3,2),R(e,c,n+15,r+14,3,9),R(e,s,n+15,r+23,3,2);R(e,I.pants,n+2,r+25,5,7),R(e,I.pants,n+9,r+25,5,7),R(e,`#2d3f5f`,n+3,r+29,3,1),R(e,`#2d3f5f`,n+10,r+29,3,1),R(e,I.shoes,n,r+32,7,4),R(e,I.shoes,n+9,r+32,7,4),R(e,`#1e2a3a`,n+1,r+32,5,1),R(e,`#1e2a3a`,n+10,r+32,5,1),e.fillStyle=`rgba(0,0,0,0.35)`,e.beginPath(),e.ellipse((n+8)*L,(Ne-1)*L,9*L,2*L,0,0,Math.PI*2),e.fill()}function He(e){e.papers.length>=6||e.papers.push({x:e.x+20+Math.random()*(Pe-40),y:Ie-4,vx:(Math.random()-.5)*1.5,vy:-1.5-Math.random()*1,rot:Math.random()*360,vrot:(Math.random()-.5)*10,life:0,maxLife:70+Math.random()*40})}function Ue(e){e.papers=e.papers.filter(e=>e.life<e.maxLife);for(let t of e.papers)t.x+=t.vx,t.y+=t.vy,t.vy+=.06,t.rot+=t.vrot,t.life++}function We(e,t){for(let n of t.papers){let t=1-n.life/n.maxLife;e.save(),e.globalAlpha=t,e.translate(n.x*L,n.y*L),e.rotate(n.rot*Math.PI/180),e.fillStyle=I.paper,e.fillRect(-7*L,-5*L,14*L,10*L),e.fillStyle=I.paperLine,e.fillRect(-5*L,-2*L,8*L,L),e.fillRect(-5*L,0,6*L,L),e.restore()}e.globalAlpha=1}var Ge=`ABCDEFGHIJKLMNOPRSTUVWXYZ0123456789!?-_.:`;function Ke(e,t){return t<=0?e:Ge[Math.floor(Math.random()*41)]??e}function qe(e,t,n,r,i,a=110,o=`down`){if(t.length===0)return;e.fillStyle=I.bubbleBg,e.fillRect(r*L,i*L,a*L,36*L),e.strokeStyle=I.bubbleBdr,e.lineWidth=L,e.strokeRect(r*L,i*L,a*L,36*L),e.fillStyle=I.cyan,e.fillRect(r*L,i*L,a*L,L),e.fillStyle=I.bubbleBdr,e.fillRect(r*L,(i+13+5)*L,a*L,L);for(let o=0;o<2;o++){let s=t[(Math.floor(n)+o)%t.length];if(!s)continue;let c=i+5+o*14;e.fillStyle=o%2==0?`#0f172a`:`#111827`,e.fillRect(r*L,c*L,a*L,13*L);let l=n-Math.floor(n),u=s.text.split(``);e.font=`bold ${5*L}px monospace`,e.textBaseline=`middle`;let d=Math.floor(a/7)-1;for(let t=0;t<Math.min(u.length,d);t++){let n=l>.3&&t>u.length*(1-l)?Ke(u[t],3):u[t];e.globalAlpha=l>0&&t>u.length*(1-l)?.5+l*.5:1,e.fillStyle=s.color,e.fillText(n,(r+4+t*7)*L,(c+6)*L)}e.globalAlpha=1}let s=r+a/2;if(e.fillStyle=I.bubbleBdr,o===`down`){let t=i+36;e.fillRect((s-2)*L,t*L,4*L,4*L),e.fillRect((s-3)*L,(t+4)*L,6*L,3*L)}else e.fillRect((s-2)*L,(i-4)*L,4*L,4*L),e.fillRect((s-3)*L,(i-7)*L,6*L,3*L)}function Je(e,t,n,r){let i=Ne-Re-8,a=n%2,o=a===0?2:0,s=a===0?0:2;e.fillStyle=`rgba(0,0,0,0.4)`,e.beginPath(),e.ellipse(t*L,(Ne-1)*L,9*L,2*L,0,0,Math.PI*2),e.fill(),e.save(),e.translate(t*L,0),e.scale(r?1:-1,1),R(e,I.cHair,-4,i,9,3),R(e,I.cHair,-5,i+1,11,2),R(e,I.cHair,-5,i+3,2,3),R(e,I.cHair,5,i+3,2,3),R(e,I.cSkin,-4,i+3,8,7),R(e,`#0f172a`,-2,i+5,2,2),R(e,`#0f172a`,2,i+5,2,2),R(e,`#1d4ed8`,-1,i+6,1,1),R(e,`#1d4ed8`,3,i+6,1,1),R(e,`#7f1d1d`,-2,i+9,5,1),R(e,I.cSkin,-1,i+10,3,2),R(e,I.cShirt,-5,i+12,11,9),R(e,`#3730a3`,-5,i+12,3,5),R(e,`#3730a3`,3,i+12,3,5),R(e,I.cTie,-1,i+12,3,7),R(e,`#f1f5f9`,-2,i+12,1,3),R(e,`#f1f5f9`,2,i+12,1,3),R(e,I.cShirt,-7,i+13,3,9),R(e,I.cSkin,-7,i+21,3,3),R(e,I.cShirt,5,i+13,3,9),R(e,I.cSkin,5,i+21,3,3),R(e,I.cBag,6,i+24,6,4),R(e,`#92400e`,7,i+23,4,1),R(e,I.cPants,-3,i+21+o,5,10),R(e,I.cPants,1,i+21+s,5,10),R(e,I.cShoes,-5,i+29+o,6,3),R(e,I.cShoes,-1,i+29+s,6,3),e.restore()}var Ye=[[{text:`RUNNING WORKFLOW`,color:I.cyan},{text:`DISPATCHING...`,color:I.amber}],[{text:`ORCHESTRATING`,color:I.cyan},{text:`ALL SYSTEMS GO`,color:I.green}],[{text:`AGENT PIPELINE`,color:I.purple},{text:`EXECUTING...`,color:I.cyan}],[{text:`MONITORING ALL`,color:I.amber},{text:`ON SCHEDULE`,color:I.green}]];function Xe(e,t){let n=e.toUpperCase().slice(0,14);return t===`running`?[{text:n,color:I.cyan},{text:`WORKING...`,color:I.green}]:t===`done`?[{text:n,color:I.green},{text:`TASK DONE`,color:I.green}]:t===`error`?[{text:n,color:I.red},{text:`ERR`,color:I.red}]:[{text:n,color:I.dim},{text:`STANDBY`,color:I.dim}]}function Ze({nodes:e,running:t}){let n=(0,_.useRef)(null),r=(0,_.useRef)(null);return(0,_.useEffect)(()=>{if(e.length===0)return;let t=Math.min(e.length,8)*(Pe+Le)-Le,n=Math.max(6,Math.round((ke-t)/2));if(!r.current)r.current={agents:e.slice(0,8).map((e,t)=>({x:n+t*(Pe+Le),icon:e.icon,label:e.label,status:e.status,skinIdx:t%I.skin.length,shirtIdx:t%I.shirt.length,hairIdx:t%I.hair.length,papers:[],bubbleLines:Xe(e.label,e.status),bubbleScroll:0,bubbleTimer:0})),condX:n+Pe/2,condTargetX:n+Pe/2,condFacing:!0,condFrame:0,condFrameTimer:0,condIdleTimer:0,condBubble:Ye[0],condBubbleScroll:0,condBubbleTimer:0,rafId:0};else{let t=r.current;for(let r=0;r<Math.min(e.length,8);r++){let i=e[r],a=t.agents[r];a?(a.status!==i.status&&(a.status=i.status,a.bubbleLines=Xe(i.label,i.status),a.bubbleScroll=0),a.icon=i.icon,a.label=i.label):t.agents.push({x:n+r*(Pe+Le),icon:i.icon,label:i.label,status:i.status,skinIdx:r%I.skin.length,shirtIdx:r%I.shirt.length,hairIdx:r%I.hair.length,papers:[],bubbleLines:Xe(i.label,i.status),bubbleScroll:0,bubbleTimer:0})}}},[e]),(0,_.useEffect)(()=>{let e=n.current;if(!e)return;let i=e.getContext(`2d`);if(!i||(i.imageSmoothingEnabled=!1,!r.current))return;let a=0,o=0;function s(e){let n=r.current,c=Math.min(e-a,50);a=e;let l=e,u=n.agents.find(e=>e.status===`running`);if(u){let e=u.x+Pe+12,t=u.x-18;n.condTargetX=e<ke-30?e:t,n.condBubbleTimer+=c,n.condBubbleTimer>2e3&&(n.condBubbleTimer=0,n.condBubble=Ye[Math.floor(Math.random()*Ye.length)],n.condBubbleScroll=(n.condBubbleScroll+1)%4)}else if(n.condIdleTimer-=c,n.condIdleTimer<=0){let e=n.agents[Math.floor(Math.random()*n.agents.length)];e&&(n.condTargetX=e.x+Pe/2),n.condIdleTimer=2e3+Math.random()*3e3}let d=n.condTargetX-n.condX;if(Math.abs(d)>1&&(n.condX+=d*(t?.12:.04)*(c/16),n.condFacing=d>0,n.condFrameTimer+=c,n.condFrameTimer>100&&(n.condFrame++,n.condFrameTimer=0)),o+=c,o>200){o=0;for(let e of n.agents)e.status===`running`&&He(e)}for(let e of n.agents)Ue(e);for(let e of n.agents)e.bubbleTimer+=c,e.bubbleTimer>1800&&(e.bubbleTimer=0,e.bubbleScroll=(e.bubbleScroll+1)%4);i.clearRect(0,0,je,Me),ze(i);for(let e of n.agents)Be(i,e,n.condX,l),We(i,e);Je(i,n.condX,n.condFrame,n.condFacing);for(let e of n.agents){if(e.status===`waiting`)continue;let t=Math.min(e.label.length*6+16,90),n=Math.max(e.x+1,Math.min(e.x+(Pe-t)/2,ke-t-2)),r=Ne-Re-36;qe(i,e.bubbleLines,e.bubbleScroll,n,r,t,`down`)}let f=Ne-Re-8-38,p=Math.max(4,Math.min(n.condX-50,ke-106));qe(i,n.condBubble,n.condBubbleScroll,p,f,100,`down`);for(let e=0;e<Me;e+=L*3)i.fillStyle=`rgba(0,0,0,0.04)`,i.fillRect(0,e,je,L);n.rafId=requestAnimationFrame(s)}return r.current.rafId=requestAnimationFrame(s),()=>{r.current&&cancelAnimationFrame(r.current.rafId)}},[t]),e.length===0?null:(0,A.jsx)(`div`,{style:{width:`100%`,lineHeight:0},children:(0,A.jsx)(`canvas`,{ref:n,width:je,height:Me,style:{width:`100%`,height:`auto`,imageRendering:`pixelated`,display:`block`,background:I.bg,boxShadow:`0 0 40px rgba(99,102,241,0.15), inset 0 0 80px rgba(0,0,0,0.4)`}})})}var Qe=[`#f4d4b8`,`#e0a878`,`#c98a5b`,`#9c6b48`,`#7a5238`,`#5a3a26`,`#3d2818`,`#deba90`],$e=[`#dc2626`,`#2563eb`,`#16a34a`,`#7c3aed`,`#f59e0b`,`#0891b2`,`#db2777`,`#475569`],et=[`#1a1a1a`,`#3d2817`,`#6b4423`,`#8b6f47`,`#c9a55c`,`#e4d4a8`,`#6b7280`,`#000000`],tt=[{x:50,y:16},{x:78,y:28},{x:86,y:52},{x:78,y:76},{x:50,y:84},{x:22,y:76},{x:14,y:52},{x:22,y:28}];function nt({agents:e,council:t}){let[n,r]=(0,_.useState)(0);(0,_.useEffect)(()=>{if(!t||t.phase===`idle`||t.phase===`skipped`){r(0);return}let n=Math.min(e.length,tt.length),i=0;r(0);let a=()=>{i++,r(i),i<n&&setTimeout(a,250)},o=setTimeout(a,200);return()=>{clearTimeout(o)}},[t?.phase,e.length]);let[i,a]=(0,_.useState)(0);(0,_.useEffect)(()=>{if(!t||t.phase!==`r2`&&t.phase!==`r3`)return;let n=setInterval(()=>{a(t=>(t+1)%Math.max(1,e.length))},2e3);return()=>clearInterval(n)},[t?.phase,e.length]);let o=t?.phase===`r2`?`Round 2 — Cross-reading (${t.r2Nodes.length}/${e.length})`:t?.phase===`r3`?`Round 3 — HERALD mediazione`:t?.phase===`done`?`Sintesi completata · convergenza ${Math.round((t.r2Convergence||0)*100)}%`:`In attesa…`;return(0,A.jsxs)(`div`,{style:{width:`100%`,height:`100%`,background:`linear-gradient(180deg, #1a1530 0%, #0f0d22 100%)`,position:`relative`,overflow:`hidden`,color:`#fff`,fontFamily:`inherit`},children:[(0,A.jsxs)(`div`,{style:{position:`absolute`,top:8,left:12,right:12,display:`flex`,justifyContent:`space-between`,alignItems:`center`,fontSize:11,letterSpacing:`0.8px`,textTransform:`uppercase`,color:`#a78bfa`,zIndex:10},children:[(0,A.jsx)(`span`,{style:{fontWeight:800},children:`🏛️ Council Room`}),(0,A.jsx)(`span`,{style:{color:`#c4b5fd`},children:o})]}),(0,A.jsxs)(`svg`,{viewBox:`0 0 100 100`,preserveAspectRatio:`none`,style:{position:`absolute`,inset:0,width:`100%`,height:`100%`},children:[(0,A.jsxs)(`defs`,{children:[(0,A.jsxs)(`radialGradient`,{id:`tableGrad`,cx:`50%`,cy:`50%`,r:`50%`,children:[(0,A.jsx)(`stop`,{offset:`0%`,stopColor:`#5b3a8b`}),(0,A.jsx)(`stop`,{offset:`100%`,stopColor:`#2d1d54`})]}),(0,A.jsxs)(`radialGradient`,{id:`floorGrad`,cx:`50%`,cy:`100%`,r:`80%`,children:[(0,A.jsx)(`stop`,{offset:`0%`,stopColor:`#241a3a`}),(0,A.jsx)(`stop`,{offset:`100%`,stopColor:`#15102a`})]})]}),(0,A.jsx)(`rect`,{x:`0`,y:`0`,width:`100`,height:`100`,fill:`url(#floorGrad)`}),(0,A.jsx)(`ellipse`,{cx:`50`,cy:`52`,rx:`32`,ry:`20`,fill:`url(#tableGrad)`,stroke:`#7c3aed`,strokeWidth:`0.4`}),(0,A.jsx)(`ellipse`,{cx:`50`,cy:`49`,rx:`28`,ry:`17`,fill:`rgba(0,0,0,0.18)`}),(0,A.jsx)(`circle`,{cx:`50`,cy:`50`,r:`38`,fill:`none`,stroke:`rgba(167,139,250,0.12)`,strokeWidth:`0.3`})]}),e.slice(0,tt.length).map((e,r)=>{let a=tt[r],o=r<n,s=o&&r===i&&(t?.phase===`r2`||t?.phase===`r3`),c=$e[r%$e.length],l=Qe[r%Qe.length],u=et[r%et.length];return(0,A.jsxs)(`div`,{style:{position:`absolute`,left:`${a.x}%`,top:`${a.y}%`,transform:`translate(-50%, -50%) translateX(${o?0:a.x<50?`-80px`:`80px`})`,opacity:+!!o,transition:`transform 0.6s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.4s ease`,display:`flex`,flexDirection:`column`,alignItems:`center`,gap:2,pointerEvents:`none`,zIndex:5},children:[(0,A.jsxs)(`div`,{style:{fontSize:9,padding:`2px 6px`,background:`rgba(255,255,255,0.95)`,color:`#1e1b4b`,borderRadius:8,marginBottom:2,fontWeight:700,opacity:+!!s,transform:s?`translateY(0) scale(1)`:`translateY(4px) scale(0.85)`,transition:`opacity 0.25s, transform 0.25s`,whiteSpace:`nowrap`,maxWidth:110,overflow:`hidden`,textOverflow:`ellipsis`,boxShadow:`0 2px 6px rgba(0,0,0,0.4)`},children:[e.icon,` `,e.label.slice(0,12)]}),(0,A.jsxs)(`svg`,{width:`20`,height:`26`,viewBox:`0 0 20 26`,style:{filter:s?`drop-shadow(0 0 4px rgba(167,139,250,0.8))`:`none`,transition:`filter 0.25s`},children:[(0,A.jsx)(`rect`,{x:`6`,y:`2`,width:`8`,height:`3`,fill:u}),(0,A.jsx)(`rect`,{x:`6`,y:`4`,width:`8`,height:`6`,fill:l}),(0,A.jsx)(`rect`,{x:`8`,y:`7`,width:`1`,height:`1`,fill:`#000`}),(0,A.jsx)(`rect`,{x:`11`,y:`7`,width:`1`,height:`1`,fill:`#000`}),(0,A.jsx)(`rect`,{x:`5`,y:`10`,width:`10`,height:`9`,fill:c}),(0,A.jsx)(`rect`,{x:`9`,y:`10`,width:`2`,height:`2`,fill:l}),(0,A.jsx)(`rect`,{x:`3`,y:`11`,width:`2`,height:`7`,fill:c}),(0,A.jsx)(`rect`,{x:`15`,y:`11`,width:`2`,height:`7`,fill:c}),(0,A.jsx)(`rect`,{x:`3`,y:`18`,width:`2`,height:`2`,fill:l}),(0,A.jsx)(`rect`,{x:`15`,y:`18`,width:`2`,height:`2`,fill:l}),(0,A.jsx)(`rect`,{x:`6`,y:`19`,width:`3`,height:`4`,fill:`#1e293b`}),(0,A.jsx)(`rect`,{x:`11`,y:`19`,width:`3`,height:`4`,fill:`#1e293b`})]}),(0,A.jsx)(`div`,{style:{fontSize:9,color:`#c4b5fd`,fontWeight:600,maxWidth:80,textAlign:`center`,textShadow:`0 1px 2px rgba(0,0,0,0.6)`},children:e.label.slice(0,14)})]},e.agent+r)}),t?.phase===`done`&&(0,A.jsx)(`div`,{style:{position:`absolute`,bottom:10,left:0,right:0,textAlign:`center`,fontSize:10,color:`#c4b5fd`,letterSpacing:`0.5px`},children:t.converged?`✓ Consenso raggiunto`:`⚖ Mediazione completata`})]})}function rt(e){let t=e.toLowerCase();return De.some(e=>t.includes(e.toLowerCase()))}function it(e,t){let n=e=>e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`),r=t.map(e=>e.output).join(`
|
|
278
328
|
|
|
279
329
|
`),i=/([+-]?\d+(?:\.\d+)?)\s*%/g,a=[],o;for(;(o=i.exec(r))!==null;){let e=parseFloat(o[1]);e>=-100&&e<=500&&a.push(e)}let s=t.filter(e=>e.output&&e.output!==`(no output)`).map(e=>{let t=xe(e.output);return`
|
|
280
330
|
<div class="section">
|
package/src/ui-dist/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
|
9
9
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
10
10
|
<title>NHA — NotHumanAllowed</title>
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-DKPyRmuw.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-IQn8QiFW.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|