Momentum Investing îmbunătățit de modelul de limbă mare găzduit de Microsoft Foundry

URMĂREȘTE-NE
16,065FaniÎmi place
1,142CititoriConectați-vă

(Acest articol a fost publicat pentru prima dată pe DataGeeekși cu amabilitate a contribuit la R-bloggeri). (Puteți raporta problema legată de conținutul acestei pagini aici)


Doriți să vă distribuiți conținutul pe R-bloggeri? dați clic aici dacă aveți un blog, sau aici dacă nu aveți.

Investiții de impuls îmbunătățite de LLM combină semnalele tradiționale de impuls cu interpretarea în timp real a știrilor prin modele de limbaj mari (LLM). Ideea este simplă: acțiunile cu randamente anterioare puternice sunt candidate pentru portofoliile de impuls, dar includerea și ponderea lor sunt rafinate de scorurile de sentiment generate de LLM, derivate din știrile specifice companiei. Această abordare hibridă se îmbunătățește randamente ajustate la risc (Sharpe, Sortino) și este deosebit de eficient în portofoliile concentrate, cu convingeri ridicate.

Parametri cheie

1. Fereastra de retrospectivă (k)

  • Definiţie: Numărul de zile trecute de știri luate în considerare pentru analiza sentimentelor.
  • Rol: Determină câte informații recente folosește LLM pentru a evalua continuarea impulsului.
  • Exemplu: Dacă k = 5, modelul analizează ultimele 5 zile lucrătoare de titluri și rezumate pentru fiecare stoc.

2. Orizontul prognozat (l)

  • Definiţie: Perioada în care este prezisă continuarea impulsului.
  • Rol: Stabilește „ținta” pentru prognoza LLM – cât de departe în viitor modelul ar trebui să judece persistența impulsului.
  • Exemplu: Dacă l = 5, LLM prezice dacă impulsul va continua în următoarele 5 zile de tranzacționare.
  • Conexiune la reechilibrare: Orizontul de prognoză se aliniază de obicei cu ciclul de reechilibrare. Pentru reechilibrarea săptămânală orizontul este de 5 zile; pentru reechilibrarea lunară, este de ~21 de zile.

3. Dimensiunea portofoliului (m)

  • Definiţie: Numărul de acțiuni selectate după scorul LLM.
  • Rol: Controlează cât de concentrat sau diversificat este portofoliul.
  • Exemplu: Din cei mai buni 20 de performeri YTD, ați putea selecta primii 10 după scorul de sentiment.

4. Frecvența de reechilibrare (T)

  • Definiţie: Cât de des este actualizat portofoliul cu semnale noi.
  • Rol: Setează ritmul reîmprospătării portofoliului – săptămânal, lunar sau trimestrial.
  • Exemplu: Reechilibrarea săptămânală înseamnă recalcularea impulsului și a scorurilor de sentiment la fiecare 5 zile de tranzacționare.

Concept

Strategia începe cu a ecran de impuls clasic: selectați primele 20 de companii S&P 500 în funcție de performanța anuală (YTD).. În loc să se oprească aici, abordarea se integrează analiza sentimentelor modelului de limbaj mare (LLM). de știri specifice firmei. Prin analizarea ultimele 5 zile lucrătoare de titluri și rezumateLLM produce un scor care indică dacă impulsul este probabil să continue.

Aceste scoruri sunt apoi folosite reponderează portofoliulînclinând alocările către companii cu un sentiment de știri mai puternic. În cele din urmă, portofoliul este restrâns la primele 10 acțiuni de convingere.

Parametrii selectați

  • Fereastra de retrospectivă: 5 zile de știri specifice firmei.
  • Frecvența de reechilibrare: Actualizări săptămânale ale portofoliului.
  • Orizontul prognozei: 5 zile de tranzacționare (aliniate cu ciclul de reechilibrare).

Această configurație asigură că LLM este rugat să judece dacă impulsul va persista până la următoarea reechilibrare, făcând semnalele atât pe termen scurt și acționabil.

Ce face codul pas cu pas

  1. Preluarea datelor
    • The R scriptul trage mai întâi toate tickerele S&P 500.
    • Se calculează YTD revine pentru fiecare stoc.
    • The primele 20 de acțiuni în funcție de performanță sunt selectați ca candidați de impuls.
  2. Stiri Analiza sentimentelor cu LLM
    • Pentru fiecare dintre aceste 20 de acțiuni, codul solicită Bing News pentru titluri recente Servicii Azure AI.
    • Ultimul 5 zile lucrătoare de știri sunt colectate.
    • Aceste titluri și rezumate sunt trimise către a Microsoft Foundry-LLM găzduit.
    • Ieșirile LLM a scor (0–1) indicând dacă sentimentul sprijină continuarea impulsului sau semnalează inversarea.
  3. Înclinarea portofoliului
    • Scorurile LLM sunt normalizate la (-1, +1).
    • Greutățile egale de bază sunt înclinate în funcție de aceste scoruri.
    • The top 10 stocuri prin pondere ajustată din portofoliul final

4. Vizualizarea

  • Un tabel cu stil este creat folosind gt pachet.
  • Greutățile ajustate sunt codate pe culori (gradient roșu-verde).
  • Portofoliul final este salvat ca imagine (top10.png).

Perspectivă strategică

Această abordare reflectă metodologia din Lucrarea Swiss Finance Institute:

  • Clasamentul Momentum oferă linia de bază.
  • Punctajul sentimentului LLM rafinează selecția și ponderarea stocurilor.
  • Înclinarea portofoliului integrează semnale calitative de știri în alocarea cantitativă.
library(httr)
library(jsonlite)
library(tidyquant)
library(tidyverse)
library(lubridate)
library(gt)
library(gtExtras)
library(scales)
library(showtext)
library(webshot2)

# 1. Environment & Auth Setup
sysfonts::font_add_google("Roboto Slab", "roboto_slab")
showtext_auto()

# Azure & Bing Credentials
bing_key           <- ""
bing_endpoint      <- "" 
azure_llm_key      <- ""
azure_llm_endpoint <- ""

# R Part (first): S&P 500 Screening with Monthly Returns
sp500_tickers <- 
  tq_index("SP500") %>% 
  select(symbol, company)

# Calculate YTD change
momentum_df <- 
  sp500_tickers %>%
  tq_get(get = "stock.prices", from = floor_date(today(), "year")) %>%
  group_by(symbol) %>%
  arrange(date) %>% # Ensure chronological order for first/last functions
  summarize(
    total_return = (last(adjusted) / first(adjusted)) - 1, 
    .groups = "drop"
  ) %>%
  inner_join(sp500_tickers, by = "symbol") %>%
  slice_max(total_return, n = 20) %>%
  select(symbol, company)

# R Part (second): News Search and LLM Analysis
analyze_momentum_continuation <- function(ticker, company_name) {
  
  # Construct the Bing News Search query: ticker + ' stock'
  query_str <- paste0(ticker, " stock")
  news_url <- paste0(bing_endpoint, "v7.0/news/search")
  
  # Call Bing News Search API
  news_res <- GET(news_url, add_headers(`Ocp-Apim-Subscription-Key` = bing_key), 
                  query = list(q = query_str, count = 5, freshness = "Day"))
  
  # Short pause to throttle calls and avoid rate limit errors
  Sys.sleep(1)
  
  news_text <- ""
  if (status_code(news_res) == 200) {
    content <- fromJSON(content(news_res, "text", encoding = "UTF-8"))
    if (length(content$value) > 0) {
      news_text <- paste(content$value$name, content$value$description, collapse = " | ")
    }
  }
  
  # Construct the LLM payload for Azure AI Foundry
  prompt_payload <- list(
    messages = list(
      list(role = "system", content = "You are an LLM Enhanced Momentum Investing Agent."),
      list(role = "user", content = paste0(
        "Headlines + Summaries for ", company_name, " (", ticker, "): ", news_text,
        "nPerform sentiment analysis based on the last 5 days of news (lookback=5, horizon=5). ",
        "Infer whether sentiment supports momentum continuation or signals reversal. ",
        "Return a JSON object with two fields: 'subsector' (string) and 'llm_score' (string: probability 0-1)."))
    ),
    temperature = 0.1
  )
  
  llm_res <- POST(url = azure_llm_endpoint, 
                  add_headers(`api-key` = azure_llm_key, `Content-Type` = "application/json"),
                  body = prompt_payload, encode = "json")
  
  if (status_code(llm_res) == 200) {
    llm_out <- fromJSON(content(llm_res, "text", encoding = "UTF-8"))
    # Parse JSON response without using regex
    llm_json_data <- fromJSON(llm_out$choices$message$content)
    return(as.data.frame(llm_json_data))
  } else {
    return(data.frame(subsector = "N/A", llm_score = "0.5"))
  }
}

# Execute Analysis: Merge top 20 tickers with LLM scores
news_scores_df <- momentum_df %>%
  mutate(analysis = map2(symbol, company, analyze_momentum_continuation)) %>%
  unnest(analysis) %>%
  mutate(llm_score = as.numeric(llm_score))

# R Part (final): Portfolio Tilting and Visualization
# Normalize scores to (-1, +1) and tilt weights
tilted_portfolio <- news_scores_df %>%
  mutate(
    norm_score = rescale(llm_score, to = c(-1, 1), from = c(0, 1)),
    base_weight = 1 / n(),
    adj_weight = base_weight * (1 + norm_score)
  ) %>%
  mutate(adj_weight = adj_weight / sum(adj_weight)) %>%
  slice_max(adj_weight, n = 10)

# Create gt visualization using original column names
final_table <- tilted_portfolio %>%
  select(company, subsector, adj_weight) %>%
  gt() %>%
  tab_header(title = "Top 10 Tilted S&P 500 Momentum Stocks") %>%
  # Use cols_label for human-readable labels without renaming underlying columns
  cols_label(
    company = "Company", 
    subsector = "Subsector", 
    adj_weight = "Adjusted Weight "
  ) %>%
  # Apply color intensity with scales::col_numeric
  data_color(
    columns = adj_weight, 
    colors = col_numeric(palette = c("red", "green"), domain = NULL)
  ) %>%
  fmt_percent(
    columns = contains("adj_weight"), 
    decimals = 2,
    locale = "en" 
  ) %>% 
  cols_align(align = "center") %>%
  opt_table_font(font = google_font("Roboto Slab"))

# Save the visualization as top10.png using webshot
gtsave(final_table, "top10.png")

Observație finală

Privind portofoliul rezultat, o caracteristică izbitoare este dominaţia companiilor energetice şi petroliere. Firme precum ConocoPhillips, EOG Resources, ExxonMobil, Occidental Petroleum, Marathon Petroleum, Valero, Phillips 66, Chevron și Baker Hughes toate apar proeminent.

Această înclinare puternică spre energie nu este întâmplătoare – reflectă cum tensiuni geopolitice (dinamica războiului dintre Iran și SUA-Israel) au amplificat importanța petrolului și a gazelor pe piețele globale. Sentimentul știrilor în jurul acestor companii a susținut puternic impulsul continuu, împingându-le în primele locuri de alocare.

Dominic Botezariu
Dominic Botezariuhttps://www.noobz.ro/
Creator de site și redactor-șef.

Cele mai noi știri

Pe același subiect

LĂSAȚI UN MESAJ

Vă rugăm să introduceți comentariul dvs.!
Introduceți aici numele dvs.