LLM¶
Local-model view generation routed through Black–Litterman. See the LLM strategies guide.
Strategies¶
strategies
¶
LLM-driven portfolio strategies (local models).
Three strategies, all with the standard method(returns, ...) -> PortfolioResult
shape so they drop into the backtester and plots like any built-in. Each elicits
per-asset views from a local LLM and routes them through the existing
:func:jaxfolio.optimizers.classical.black_litterman, so they inherit its
equilibrium prior, constraints, and diagnostics for free.
By default they construct an :class:~jaxfolio.llm.client.OllamaClient; pass
client=FakeLLM(...) (or any :class:~jaxfolio.llm.client.LLMClient) to run
offline or in tests. To use one inside :func:jaxfolio.backtest.compare, bind the
non-returns arguments with :func:functools.partial.
llm_black_litterman
¶
llm_black_litterman(
returns,
client: LLMClient | None = None,
*,
samples: int = 5,
extra_context: str | None = None,
tau: float = 0.05,
risk_aversion: float = 2.5,
config: OptimizerConfig | None = None,
) -> PortfolioResult
LLM-enhanced Black-Litterman (ICLR 2025).
Samples the local LLM samples times for per-asset return views, calibrates
the view confidence from the sampling dispersion, and feeds both into
Black-Litterman. Metadata records the raw views and confidence.
Source code in src/jaxfolio/llm/strategies.py
llm_sentiment_portfolio
¶
llm_sentiment_portfolio(
returns,
news: dict,
client: LLMClient | None = None,
*,
strength: float = 0.03,
view_confidence: float = 0.5,
tau: float = 0.05,
risk_aversion: float = 2.5,
config: OptimizerConfig | None = None,
) -> PortfolioResult
News-sentiment-tilted portfolio via a local LLM.
The LLM scores per-asset sentiment from news ({asset: text | [headlines]}),
which is converted to return-view tilts and combined with the market
equilibrium through Black-Litterman.
Source code in src/jaxfolio/llm/strategies.py
llm_agent_portfolio
¶
llm_agent_portfolio(
returns,
client: LLMClient | None = None,
*,
news: dict | None = None,
roles=DEFAULT_ROLES,
tau: float = 0.05,
risk_aversion: float = 2.5,
config: OptimizerConfig | None = None,
) -> PortfolioResult
Multi-agent-debate portfolio (bull / bear / risk agents).
Role-specialized LLM agents each argue per-asset views; the moderator aggregates them (with agreement-based confidence) and Black-Litterman turns the consensus into weights.
Source code in src/jaxfolio/llm/strategies.py
Client¶
client
¶
Local-LLM client abstraction.
The strategies in this package never talk to a cloud API — they drive a local
model. :class:OllamaClient speaks to a local Ollama <https://ollama.com>_
server (http://localhost:11434); because Ollama also exposes an
OpenAI-compatible endpoint, the same host serves llama.cpp / LM Studio / vLLM.
Everything is built around the small :class:LLMClient protocol, so strategies
accept an injected client. Tests and fully-offline runs pass :class:FakeLLM,
which returns canned responses with no network at all.
LLMClient
¶
OllamaClient
¶
OllamaClient(
model: str = DEFAULT_MODEL,
*,
host: str = DEFAULT_HOST,
timeout: float = 120.0,
options: dict | None = None,
)
Client for a local Ollama server (native /api/generate endpoint).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
str
|
Ollama model tag, e.g. |
DEFAULT_MODEL
|
host
|
str
|
Base URL of the local server. |
DEFAULT_HOST
|
timeout
|
float
|
Per-request timeout in seconds. |
120.0
|
Source code in src/jaxfolio/llm/client.py
FakeLLM
¶
A deterministic, offline stand-in for a local LLM.
Pass either a fixed list of responses (cycled), or a callable
responder(prompt, index) -> str for prompt-dependent behavior. Used in the
test suite and for running the examples without a model installed.
Source code in src/jaxfolio/llm/client.py
parse_json
¶
Extract the first JSON object from an LLM response, tolerating prose.
Handles ```json fenced blocks and leading/trailing commentary. Returns an empty dict if nothing parseable is found (callers treat that as "no view").
Source code in src/jaxfolio/llm/client.py
default_client
¶
default_client(
model: str = DEFAULT_MODEL, **kwargs
) -> OllamaClient
Views¶
views
¶
LLM-generated return views for the Black-Litterman model.
Implements the core of the ICLR-2025 "Integrating LLM-Generated Views into
Mean-Variance Optimization" method: prompt a local LLM with each asset's recent
return statistics, sample it k times, parse per-asset expected-return views,
and turn the dispersion across samples into a confidence — uncertain views are
automatically down-weighted. The output plugs straight into
:func:jaxfolio.optimizers.classical.black_litterman.
ViewSet
dataclass
¶
ViewSet(
views: dict[str, float],
confidence: float,
per_asset_std: dict[str, float] = dict(),
raw_samples: list[dict] = list(),
)
LLM-generated views and their calibrated confidences.
Attributes:
| Name | Type | Description |
|---|---|---|
views |
dict[str, float]
|
|
confidence |
float
|
Scalar in |
per_asset_std |
dict[str, float]
|
|
raw_samples |
list[dict]
|
The parsed per-sample view dicts (for inspection / debugging). |
calibrate_confidence
¶
Map sampling dispersion to a Black-Litterman view confidence in (0, 1].
Confidence is high when the samples agree tightly relative to the typical view magnitude — normalizing by magnitude (not by cross-asset spread) keeps it well-behaved for single-asset universes and when all views agree, where a cross-asset-spread denominator would collapse to the floor.
Source code in src/jaxfolio/llm/views.py
build_prompt
¶
Construct the view-elicitation prompt and return (prompt, asset_names).
Source code in src/jaxfolio/llm/views.py
llm_views
¶
llm_views(
returns,
client: LLMClient,
*,
samples: int = 5,
temperature: float = 0.8,
extra_context: str | None = None,
clip: tuple[float, float] = (-0.2, 0.2),
) -> ViewSet
Elicit per-asset return views from a local LLM and calibrate confidence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
LLMClient
|
A local LLM client ( |
required |
samples
|
int
|
Number of independent samples |
5
|
clip
|
tuple[float, float]
|
Per-period return views are clipped to this range to reject outliers / hallucinated magnitudes. |
(-0.2, 0.2)
|
Returns:
| Type | Description |
|---|---|
ViewSet
|
Mean views, a variance-derived confidence, and diagnostics. |
Source code in src/jaxfolio/llm/views.py
Sentiment¶
sentiment
¶
Local-LLM news-sentiment scoring and its conversion to return views.
A local model reads per-asset news/context text and emits a sentiment score in
[-1, 1]; :func:sentiment_to_views turns those scores into small
expected-return tilts suitable for the Black-Litterman views argument.
llm_sentiment
¶
llm_sentiment(
news: dict,
client: LLMClient,
*,
temperature: float = 0.3,
) -> dict[str, float]
Score per-asset news sentiment in [-1, 1] with a local LLM.
news maps asset -> a string or list of headlines. Assets whose score the
model omits or malforms are skipped (treated as neutral downstream).
Source code in src/jaxfolio/llm/sentiment.py
sentiment_to_views
¶
Convert sentiment scores to expected-return views.
Each score in [-1, 1] becomes a view of score * strength (so
strength is the per-period return magnitude a maximally bullish call
implies, e.g. 0.03 = +3%).
Source code in src/jaxfolio/llm/sentiment.py
Multi-agent debate¶
agents
¶
Multi-agent LLM debate for portfolio views (AlphaAgents / HARLF style).
Several role-specialized agents — a bull, a bear, and a risk manager by default — each argue a per-asset stance from the same data using a local LLM. A moderator step aggregates their (confidence-weighted) stances into a single set of expected-return views, which then flow into Black-Litterman.
The value over a single prompt is structured disagreement: the bull looks for upside, the bear for downside, the risk agent penalizes volatility, and the aggregation reflects where they converge.
DebateResult
dataclass
¶
DebateResult(
views: dict[str, float],
confidence: float,
agent_views: dict[str, dict[str, float]] = dict(),
)
Aggregated views plus each agent's individual stance.
debate
¶
debate(
returns,
client: LLMClient,
*,
roles=DEFAULT_ROLES,
news: dict | None = None,
temperature: float = 0.7,
clip: tuple[float, float] = (-0.2, 0.2),
) -> DebateResult
Run a one-round multi-agent debate and aggregate into views.
Each role produces per-asset return estimates; the moderator averages them per asset (equal weight across agents), and confidence reflects cross-agent agreement (assets the agents agree on carry more weight).