-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy path.env.example
More file actions
541 lines (461 loc) · 24.3 KB
/
Copy path.env.example
File metadata and controls
541 lines (461 loc) · 24.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
# =============================================================================
# Multi-Agent Investment Analysis System — environment configuration
# =============================================================================
#
# Copy to .env and fill in your keys: cp .env.example .env
#
# Precedence (canonical, both entry points):
# CLI flag > shell environment variable > .env file > built-in default
#
# A shell variable that disagrees with .env wins and logs
# "SHELL ENVIRONMENT OVERRIDE DETECTED". That is expected, not a bug.
#
# .env is read by pydantic-settings into the Settings model; it does NOT
# populate os.environ. Only keys this file documents are actually read — an
# invented name is silently ignored. (LangSmith/Langfuse keys are the exception:
# src/config.py re-exports those to os.environ for the vendor SDKs.)
#
# Sections here follow the README's order, so the two read side by side.
# =============================================================================
# 1. REQUIRED API KEYS
# =============================================================================
# Google Gemini — required only when a reachable chat or embedding binding uses
# Google. An all-OpenAI configuration does not need it.
# https://aistudio.google.com/app/apikey
GOOGLE_API_KEY=your_google_api_key_here
# Finnhub — market data. https://finnhub.io/register
FINNHUB_API_KEY=your_finnhub_api_key_here
# Tavily — web-search fallback for data gaps. https://tavily.com/
TAVILY_API_KEY=your_tavily_api_key_here
# =============================================================================
# 2. LLM PROVIDER BINDINGS
# =============================================================================
#
# Provider identity is bound per *seat group*, not per call site. Swapping a
# group re-points every seat in it. See README "LLM Provider Architecture" and
# docs/LLM_PROVIDERS.md for which providers are qualified for which group.
#
# base the 17 analysis seats, including BOTH Bull and Bear. They are
# role-adversarial, not vendor-adversarial: they share memory,
# one debate barrier, and one Research Manager, so splitting them
# across vendors would confound "the bear case was stronger" with
# "vendor A writes more forcefully".
# review Consultant / Forensic Auditor / Editor — the vendor-adversary
# plane. Vendor diversity belongs at *verification* boundaries.
# regional APAC regional specialist.
# writer article prose.
# operational macro context, retrospective lessons, health check, inspector.
# judge eval judge. Kept separate so an A/B of base vs review is not
# graded by a judge that moved with the arm under test.
LLM_BASE_PROVIDER=google
LLM_REVIEW_PROVIDER=openai
LLM_REGIONAL_PROVIDER=deepseek
LLM_WRITER_PROVIDER=anthropic
LLM_OPERATIONAL_PROVIDER=google
LLM_JUDGE_PROVIDER=google
# Independence is enforced at startup and compares BOTH vendor and model
# lineage, so two compatible proxies fronting one model cannot pass. To collapse
# a boundary, set the flag false AND record a non-empty reason. A reason left in
# place while enforcement is on is inactive and harmless.
LLM_REQUIRE_REVIEW_INDEPENDENCE=true
LLM_REVIEW_INDEPENDENCE_WAIVER_REASON=
LLM_REQUIRE_REGIONAL_INDEPENDENCE=true
LLM_REGIONAL_INDEPENDENCE_WAIVER_REASON=
# Optional seats. Modes are independent of each other:
# required fail startup without a usable binding
# auto disable honestly, with one log line, when unavailable
# off never construct it
LLM_CONSULTANT_MODE=auto
LLM_AUDITOR_MODE=auto
LLM_EDITOR_MODE=auto
LLM_APAC_MODE=off
# =============================================================================
# 3. PROVIDER MODELS, CREDENTIALS, AND ENDPOINTS
# =============================================================================
#
# Model IDs are plain vendor IDs — this project deliberately does not use
# LangChain's "provider:model" notation. A model with no reviewed profile fails
# closed at startup rather than running on guessed capabilities.
#
# Intents, not seats: each seat asks for fast / reasoning / critical / prose /
# escalation, and the binding decides which vendor answers.
# --- Google ------------------------------------------------------------------
GOOGLE_LLM_FAST_MODEL=gemini-3.1-flash-lite
GOOGLE_LLM_REASONING_MODEL=gemini-3-pro-preview
GOOGLE_LLM_CRITICAL_MODEL=gemini-3.1-pro-preview
# --- OpenAI ------------------------------------------------------------------
OPENAI_API_KEY=your_openai_api_key_here
OPENAI_LLM_FAST_MODEL=gpt-5.4-mini
OPENAI_LLM_REASONING_MODEL=gpt-5.4
OPENAI_LLM_CRITICAL_MODEL=gpt-5.4
OPENAI_LLM_ESCALATION_MODEL=gpt-5.6-sol
# Optional. Only an OpenAI-owned host is accepted here — point a compatible
# vendor at its own *_API_BASE below instead. Never affects embeddings.
# OPENAI_API_BASE=
# --- Anthropic (writer plane) -------------------------------------------------
# CLAUDE_KEY is an accepted alias. Missing key => the writer falls back through
# the review plane to the Gemini floor, and run_summary records that it did.
# https://console.anthropic.com/settings/keys
ANTHROPIC_API_KEY=sk-ant-your_key_here
ANTHROPIC_LLM_PROSE_MODEL=claude-opus-4-6
# Alternatives: claude-sonnet-4-6, claude-haiku-4-5
# --- Moonshot / Kimi (review plane only) --------------------------------------
# Reviewed for REVIEW only. This is not a claim that arbitrary OpenAI-compatible
# models can run the base fleet.
MOONSHOT_API_KEY=
MOONSHOT_API_BASE=https://api.moonshot.ai/v1
MOONSHOT_LLM_FAST_MODEL=kimi-k3
MOONSHOT_LLM_REASONING_MODEL=kimi-k3
MOONSHOT_LLM_CRITICAL_MODEL=kimi-k3
MOONSHOT_LLM_ESCALATION_MODEL=kimi-k3
# --- xAI / Grok (review plane only) -------------------------------------------
# Only grok-4.6 has a reviewed profile: 4.5 documents no "xhigh" and fails
# closed rather than inheriting 4.6's reasoning ladder. There is no
# XAI_LLM_ESCALATION_MODEL — escalation resolves to the critical model for
# every non-OpenAI provider.
XAI_API_KEY=
XAI_API_BASE=https://api.x.ai/v1
XAI_LLM_FAST_MODEL=grok-4.6
XAI_LLM_REASONING_MODEL=grok-4.6
XAI_LLM_CRITICAL_MODEL=grok-4.6
# --- DeepSeek / Z.AI (regional plane) -----------------------------------------
DEEPSEEK_API_KEY=
DEEPSEEK_API_BASE=https://api.deepseek.com
DEEPSEEK_LLM_REASONING_MODEL=deepseek-v4-pro
ZAI_API_KEY=
ZAI_API_BASE=https://api.z.ai/api/paas/v4/
ZAI_LLM_REASONING_MODEL=glm-5.2
# =============================================================================
# 4. PROVIDER RUNTIME LIMITS AND SERVICE TIERS
# =============================================================================
# Application-side request ceilings, one runtime bucket per provider. These are
# conservative; raise only after checking your account quota.
# Google free tier is 15 RPM; paid Tier 1 is 360.
GOOGLE_RPM_LIMIT=15
OPENAI_RPM_LIMIT=120
ANTHROPIC_RPM_LIMIT=60
MOONSHOT_RPM_LIMIT=60
XAI_RPM_LIMIT=60
DEEPSEEK_RPM_LIMIT=30
ZAI_RPM_LIMIT=30
# Flex tiers cost ~50% per token in exchange for variable latency (calls may
# queue 1–15 minutes) and best-effort capacity. Good for unattended batches,
# bad when you are waiting on a result.
#
# GOOGLE_SERVICE_TIER governs both what is sent to the API and whether the
# runtime applies the flex timeout floors and the degradation cache below —
# one key, resolved in one place (service_tiers.resolve_google_service_tier).
# Its legacy predecessor GEMINI_SERVICE_TIER is ignored whenever section 2 is
# active, mirroring GOOGLE_RPM_LIMIT vs GEMINI_RPM_LIMIT.
GOOGLE_SERVICE_TIER=standard
# OpenAI's own tier: "auto" (standard) or "flex". This is an OpenAI pricing and
# queueing product — it is ignored by compatible vendors reached through their
# own *_API_BASE. Do not reach for flex to buy a compatible vendor more time;
# that is OPENAI_COMPATIBLE_CLIENT_TIMEOUT_SECONDS.
OPENAI_SERVICE_TIER=auto
# On a flex capacity or latency failure, re-issue once at the standard tier
# rather than failing the call. Worst case you pay standard price for a few
# calls and the run still completes.
FLEX_FALLBACK_TO_STANDARD=true
# Minimum per-call allowance for flex calls. Floors every timeout that could
# otherwise kill a legitimately queued call. Vendor guidance is >= 900s.
FLEX_LLM_TIMEOUT_SECONDS=900
# A degraded flex pool costs time AND money: each queued call can burn the full
# floor above before falling back, and the fallback bills at the standard rate —
# the discount inverts exactly when the queue is worst. Past the threshold the
# provider is asked for the standard tier until the cool-off ends, then probed.
# Keyed per provider (models share a vendor's queue), in-process only.
FLEX_DEGRADE_ENABLED=true
FLEX_DEGRADE_THRESHOLD=2
FLEX_DEGRADE_WINDOW_SECONDS=900
FLEX_DEGRADE_COOL_OFF_SECONDS=1800
# How long an OpenAI-*compatible* client may wait. ~4x the slowest kimi-k3 call
# measured to date. Applies to both configuration schemas so they agree.
OPENAI_COMPATIBLE_CLIENT_TIMEOUT_SECONDS=300
# =============================================================================
# 5. ADVANCED SEAT PINS (normally leave empty)
# =============================================================================
# Exact per-seat overrides, as JSON objects keyed by seat id. Use these to pin
# the two gate-critical seats — fundamentals_analyst (the DATA_BLOCK rubric
# arithmetic feeding the hard <50% health/growth gates) and portfolio_manager
# (gate checks, override logic, PM_BLOCK contract). Those carry the largest,
# most rule-dense prompts and fail flash-tier models at a steady rate, so a
# quick-mode pin one notch above the quick fleet is cheap insurance (2 calls
# per run). Example:
# LLM_SEAT_QUICK_MODEL_OVERRIDES={"fundamentals_analyst": "gemini-3.5-flash"}
LLM_SEAT_MODEL_OVERRIDES={}
LLM_SEAT_QUICK_MODEL_OVERRIDES={}
LLM_SEAT_REASONING_OVERRIDES={}
LLM_SEAT_QUICK_REASONING_OVERRIDES={}
# Output budget shared by every agent; per-agent fractions scale from it.
# LLM_BASE_OUTPUT_TOKENS=32768
# Completion tokens reserved for the visible answer on reasoning models, so
# hidden reasoning cannot consume the whole budget. Deep applies to high/xhigh
# /max effort. Raising these is safe (a cap, not an allocation).
# LLM_DEFAULT_REASONING_RESERVE_TOKENS=2048
# LLM_DEEP_REASONING_RESERVE_TOKENS=8192
# =============================================================================
# 6. EMBEDDINGS
# =============================================================================
# Independent of chat bindings. Changing ANY component below fingerprints a new
# Chroma collection — the old one is never deleted, and old memories are not
# migrated. Inspect collections with scripts/embedding_collections.py.
EMBEDDING_PROVIDER=google
EMBEDDING_MODEL=gemini-embedding-001
EMBEDDING_DIMENSION=768
EMBEDDING_SCHEMA_VERSION=1
# =============================================================================
# 7. OPTIONAL DATA SOURCES
# =============================================================================
# Sources are fetched in parallel and merged by quality. More keys mean better
# ex-US coverage; none of them are required.
# EODHD — the strongest international fundamentals. Needs the plan that
# includes fundamentals data.
EODHD_API_KEY=your_eodhd_key_here
# Financial Modeling Prep — fallback when Yahoo is missing data, and the
# Consultant's independent spot-check source. Coverage of ex-US listings is
# systematically partial; see README "MCP Consultant Checks".
FMP_API_KEY=your_fmp_key_here
# Alpha Vantage — free tier is 25 requests/day.
# https://www.alphavantage.co/support/#api-key
ALPHAVANTAGE_API_KEY=your_alphavantage_api_key_here
# Japan EDINET — free registration. Supplies major shareholders, segment
# breakdowns, and filing-level cash flow used as ground truth against
# aggregator data. https://disclosure2.edinet-fsa.go.jp
EDINET_API_KEY=
# =============================================================================
# 8. ANALYSIS BEHAVIOR
# =============================================================================
# Bull/Bear debate rounds in full mode: 1 or 2. --quick always forces 1.
MAX_DEBATE_ROUNDS=2
# Live web/API tools for agents. Off makes runs deterministic but data-starved.
ONLINE_TOOLS=true
# Ticker-isolated ChromaDB memory plus cross-run "lessons learned". Disable per
# run with --no-memory.
ENABLE_MEMORY=true
# Withhold a marginal BUY when recent runs of the same ticker disagreed, or
# when quality flags could not be established. Applies to portfolio
# reconciliation, not to the analysis itself.
# BUY_STABILITY_ENABLED=true
# BUY_STABILITY_LOOKBACK_DAYS=7
# BUY_STABILITY_MARGIN_TALLY=0.5
# =============================================================================
# 9. CROSS-CHECK BUDGETS (Consultant and Forensic Auditor)
# =============================================================================
# Both seats live on the REVIEW plane above and are optional — a failure
# degrades the run rather than losing it, and the console says so.
# Full-mode Consultant wall clock. Ceilings, not waits: unused headroom costs
# nothing, while a cap crossed mid-loop discards the whole review and the
# Portfolio Manager decides with no cross-check. Slowest single call measured
# 2026-08-14 was 87s (grok-4.6) and 77s (kimi-k3) — re-measure before lowering.
CONSULTANT_CALL_TIMEOUT_SECONDS=120
CONSULTANT_TOTAL_TIMEOUT_SECONDS=300
# Consultant tool loop. The prompt mandates plan-then-batch and a turn runs
# concurrently, so the whole verification set should land in one round: 2 rounds
# leaves a follow-up plus synthesis. The executed-tool ceiling is the product of
# these two (12) — raising BOTH grows per-ticker cost. The per-turn value is the
# Consultant's only fan-out guard (unlike the Auditor it has no per-tool
# budget), so raise it, do not remove it.
CONSULTANT_MAX_TOOL_ITERATIONS=2
CONSULTANT_MAX_TOOL_CALLS_PER_TURN=6
# --quick screening budgets. The Consultant's 35s total is a deliberate cost
# choice: on a slow vendor it cannot finish a single call, and the seat is
# skipped rather than allowed to stall the batch.
CONSULTANT_TOOLS_IN_QUICK=false
CONSULTANT_QUICK_TOTAL_TIMEOUT_SECONDS=35
CONSULTANT_QUICK_MAX_COMPLETION_TOKENS=4096
# AUDITOR_QUICK_TOTAL_TIMEOUT_SECONDS=300
# Bounded forensic retrieval. All are hard caps; raise deliberately.
AUDITOR_SEARCH_CALL_BUDGET=3
AUDITOR_DOCUMENT_BUDGET=2
AUDITOR_MAX_DOCUMENT_BYTES=40000000
AUDITOR_MAX_DOCUMENT_PAGES=250
AUDITOR_MAX_SELECTED_PAGES=12
AUDITOR_MAX_EVIDENCE_CHARS=35000
AUDITOR_MAX_TOOL_ITERATIONS=2
AUDITOR_MAX_LLM_CALLS=4
# Extra issuer/IR domains the auditor may fetch primary documents from,
# comma-separated exact hosts (for example: issuer.com,ir.issuer.co.jp).
# Check auditor_budget.rejected_hosts in saved artifacts before adding any.
AUDITOR_OFFICIAL_DOCUMENT_HOSTS=
# =============================================================================
# 10. MCP CONSULTANT CHECKS (Optional)
# =============================================================================
# Narrow, allowlisted vendor tools for Consultant spot-checks, run through the
# same hook chain as every other tool. See README "MCP Consultant Checks" for
# which switch to throw when turning a server off.
# cp config/mcp_servers.example.json config/mcp_servers.json
MCP_ENABLED=false
CONSULTANT_MCP_ENABLED=false
MCP_SERVERS_PATH=./config/mcp_servers.json
MCP_USAGE_DB_PATH=./runtime/mcp_usage.db
# =============================================================================
# 11. UNTRUSTED CONTENT INSPECTION (Optional — prompt-injection hardening)
# =============================================================================
# Inspects search / social / memory / cached content before it re-enters a
# prompt. Recommended first posture:
# ENABLED=true, BACKEND=python, MODE=warn, FAIL_POLICY=fail_open
UNTRUSTED_CONTENT_INSPECTION_ENABLED=false
# null | python (in-process heuristics) | composite (heuristics + LLM judge)
UNTRUSTED_CONTENT_BACKEND=null
# warn (log only) | sanitize (use the safe rewrite) | block (replace content)
UNTRUSTED_CONTENT_INSPECTION_MODE=warn
# fail_open (keep original on backend error) | fail_closed (block on error)
UNTRUSTED_CONTENT_FAIL_POLICY=fail_open
# =============================================================================
# 12. TRACING (Optional — Langfuse and LangSmith can run together)
# =============================================================================
# --- Langfuse (open source, self-hostable) ------------------------------------
# Enable per run with --trace-langfuse instead of setting this globally.
LANGFUSE_ENABLED=false
LANGFUSE_PUBLIC_KEY=pk-lf-your_public_key_here
LANGFUSE_SECRET_KEY=sk-lf-your_secret_key_here
# EU (default) https://cloud.langfuse.com | US https://us.cloud.langfuse.com
LANGFUSE_BASE_URL=https://cloud.langfuse.com
LANGFUSE_SAMPLE_RATE=1.0
LANGFUSE_TRACING_ENVIRONMENT=development
LANGFUSE_DEBUG=false
# Group several CLI invocations under one session (useful for batch runs).
# LANGFUSE_SESSION_ID=
# Fetch prompts from Langfuse instead of prompts/*.json. Off by default: the
# on-disk prompts are what the drift harness and parity tests check.
# LANGFUSE_PROMPT_FETCH_ENABLED=false
# LANGFUSE_PROMPT_LABEL=production
# LANGFUSE_PROMPT_CACHE_TTL_SECONDS=300
# --- LangSmith ----------------------------------------------------------------
# https://smith.langchain.com/
LANGSMITH_API_KEY=your_langsmith_api_key_here
LANGSMITH_TRACING=false
LANGSMITH_PROJECT=multi-agent-trading
# LANGSMITH_ENDPOINT=https://api.smith.langchain.com
# =============================================================================
# 13. TIMEOUTS, RETRIES, AND CIRCUIT BREAKERS
# =============================================================================
# Provider SDK timeout per call.
API_TIMEOUT=300
# Quick-mode SDK timeout, deliberately below API_TIMEOUT so screening batches
# fail and retry in ~2 minutes rather than waiting out the generic 5 minutes.
QUICK_LLM_API_TIMEOUT_SECONDS=120
# SDK-level retries per call. Keep small: the outer retry loop already retries
# with backoff, and high values multiply worst-case latency (10 x API_TIMEOUT
# parks a worker for ~50 minutes before raising).
API_RETRY_ATTEMPTS=2
# Hard wall-clock cap on any single LLM call, enforced above the SDK. This is
# the load-bearing safety net against a hung provider call.
LLM_CALL_HARD_TIMEOUT_SECONDS=120
# Per-call caps in --quick mode. The flat quick cap is fine for cheap data
# agents, but two families need more: the gate-critical APEX seats (largest
# prompts, and losing them fails the whole ticker) and the cross-check seats
# (a reasoning vendor on the review plane can exceed 60s outright).
# QUICK_LLM_CALL_HARD_TIMEOUT_SECONDS=60
# APEX_QUICK_LLM_CALL_HARD_TIMEOUT_SECONDS=180
# CROSS_CHECK_QUICK_LLM_CALL_HARD_TIMEOUT_SECONDS=180
# Fast-fail a (agent, provider, model) after repeated hard timeouts, so one sick
# model does not stall every sibling agent. Content refusals and flex queueing
# are excluded — neither is a provider-health fault.
LLM_CIRCUIT_BREAKER_ENABLED=true
LLM_CIRCUIT_BREAKER_THRESHOLD=3
LLM_CIRCUIT_BREAKER_WINDOW_SECONDS=300
LLM_CIRCUIT_BREAKER_COOL_OFF_SECONDS=60
# Same idea for outbound data/network calls.
# NETWORK_BREAKER_ENABLED=true
# NETWORK_BREAKER_THRESHOLD=4
# NETWORK_BREAKER_WINDOW_SECONDS=30
# NETWORK_BREAKER_COOL_OFF_SECONDS=45
# Grace period for in-flight work on Ctrl-C / SIGTERM before a hard exit.
# SHUTDOWN_HARD_TIMEOUT_SECONDS=15
# Max characters kept per Tavily result (~1750 tokens at the default).
TAVILY_MAX_CHARS=7000
# =============================================================================
# 14. PATHS, LOGGING, AND OUTPUT
# =============================================================================
# Shared by the analyzer (writes) and portfolio_manager (reads). The dashboard
# keeps its own IBKR_DASHBOARD_RESULTS_DIR for process isolation.
RESULTS_DIR=./results
DATA_CACHE_DIR=./data_cache
CHROMA_PERSIST_DIR=./chroma_db
PROMPTS_DIR=./prompts
# Chart output; --imagedir overrides per run. Relative to the report directory.
# IMAGES_DIR=images
# DEBUG | INFO | WARNING | ERROR | CRITICAL
LOG_LEVEL=INFO
QUIET_MODE=false
# 'dev' or 'prod'.
ENVIRONMENT=dev
# =============================================================================
# 15. IBKR PORTFOLIO MANAGEMENT (Optional — poetry install -E ibkr)
# =============================================================================
# Read-only reconciliation needs only the OAuth credentials; the 24-hour
# brokerage session is required for --execute.
# IBKR_ACCOUNT_ID=U1234567
# OAuth 1.0a, from IBKR Web API settings:
# https://ndcdyn.interactivebrokers.com/oauth/?loginType=1&action=OAUTH&clt=0#/configuration
# Consumer key is 9 characters, A-Z and 0-9, and you choose it yourself.
# IBKR_OAUTH_CONSUMER_KEY=YOURKEY9
# IBKR_OAUTH_ACCESS_TOKEN=your_access_token
# The token secret is shown ONCE when you generate it — copy it immediately.
# It does not expire. If blank, portfolio_manager.py prompts each run.
# IBKR_OAUTH_ACCESS_TOKEN_SECRET=your_access_token_secret
# Paths to your RSA *private* keys (PEM). The matching PUBLIC keys are the ones
# uploaded to the Client Portal. Files must begin with
# "-----BEGIN RSA PRIVATE KEY-----" or "-----BEGIN PRIVATE KEY-----".
# IBKR_OAUTH_SIGNATURE_KEY_FP=/path/to/private_signature_key.pem
# IBKR_OAUTH_ENCRYPTION_KEY_FP=/path/to/private_encryption_key.pem
# Diffie-Hellman parameters — required, ibind has no default. Generate with:
# openssl dhparam -outform PEM -out dhparam.pem 2048
# then upload to the portal and point at the file (or paste the Base64 body).
# IBKR_OAUTH_DH_PRIME_FP=/path/to/dhparam.pem
# IBKR_OAUTH_DH_PRIME=
# Reconciliation knobs. Canonical defaults: src/ibkr/portfolio_defaults.py
# IBKR_MAX_ANALYSIS_AGE_DAYS=14
# IBKR_DRIFT_THRESHOLD_PCT=15.0
# IBKR_CASH_BUFFER_PCT=0.03
# Use the IBKR market-data snapshot as an ADVISORY 6th source in the analysis
# merge — point-in-time ratios and price only, never statements. It overrides
# Yahoo/FMP point-in-time values, defers to EODHD and filings, and records any
# large divergence. Off by default; partial entitlement is fine.
# IBKR_DATA_SOURCE_ENABLED=false
# =============================================================================
# 16. PLATFORM AND TELEMETRY
# =============================================================================
DISABLE_CHROMA_TELEMETRY=true
# macOS: a fork() from a process holding the gRPC/Gemini stack can SIGSEGV in
# Apple's Network.framework atfork handler and pop a crash dialog. Harmless but
# noisy; this suppresses the common triggers.
# MACOS_FORK_SAFETY_MITIGATION=true
# gRPC settings for Apple Silicon / macOS compatibility.
GRPC_ENABLE_FORK_SUPPORT=true
GRPC_POLL_STRATEGY=poll
# =============================================================================
# 17. LEGACY CONFIGURATION SCHEMA (pre-multi-provider)
# =============================================================================
#
# Before providers were bound per seat group, models were named directly:
# QUICK_MODEL, DEEP_MODEL, APEX_MODEL, APEX_QUICK_MODEL, APEX_THINKING_LEVEL,
# CONSULTANT_MODEL, CONSULTANT_QUICK_MODEL, AUDITOR_MODEL, AUDITOR_QUICK_MODEL,
# AUDITOR_ESCALATION_MODEL, EDITOR_MODEL, WRITER_MODEL, ENABLE_CONSULTANT,
# ENABLE_APAC_SPECIALIST, APAC_SPECIALIST_MODEL / _API_KEY / _BASE_URL,
# GEMINI_RPM_LIMIT, and the metadata-only LLM_PROVIDER.
#
# That schema still works and is fully supported — but the two are MUTUALLY
# EXCLUSIVE. Setting any LLM_*_PROVIDER key above activates the new schema, and
# mixing in a legacy model key is rejected at startup naming both sets. Leave
# them all unset unless you are deliberately running the old schema, in which
# case comment out section 2 first.
#
# To migrate an existing legacy .env non-destructively:
# poetry run python scripts/llm_env_migrate.py --check .env
#
# Retired entirely (ignored, with a startup warning):
# SENIOR_FUNDAMENTALS_MODEL -> use LLM_SEAT_MODEL_OVERRIDES
# SENIOR_FUNDAMENTALS_THINKING_LEVEL -> use LLM_SEAT_REASONING_OVERRIDES
# =============================================================================
# NOTES
# =============================================================================
# 1. Never commit .env or any file with a name beginning .env-to version control.
# 2. Rotate keys periodically, and immediately if one is ever printed or pasted.
# 3. Watch cost: token_usage.by_provider / by_model / by_tier in every saved
# analysis JSON reconciles to total_cost_usd. scripts/cost_report.py ranks
# and diffs runs.
# 4. A new model family must be added to MODEL_PRICING_PER_1M in
# src/token_tracker.py or its spend silently prices at $0.