Gemini-Powered Stock Analysis: Parsing Financial News for Automated Trading Decisions


Over the past weeks I built a small Go service that reads Italian finance RSS feeds, extracts the article body, and asks Gemini for a buy/sell/hold view for the tickers it finds. Below I show the pieces that make it work and the trade‑offs I hit along the way.

This runs inside a broader trading setup that talks to a broker API. I’m working on software to automate trading on a well‑known Italian broker; if you’re interested, please leave a comment below or get in touch via the contact form.

The Challenge: Processing Italian Financial News at Scale

Financial news moves markets, but manually processing hundreds of articles daily is impractical for algorithmic trading. The challenge becomes even more complex when dealing with Italian financial news sources, which often use specific terminology and reference stocks with various ticker formats (e.g., “STLAM” for Stellantis, “LDO.MI” for Leonardo).

My solution needed to:

  1. Parse multiple RSS feeds from Italian financial sources
  2. Extract full article content from various HTML formats
  3. Analyze articles using AI to identify mentioned stocks and trading signals
  4. Route recommendations to appropriate trading strategies
  5. Handle multilingual content (Italian and English)

Architecture Overview

The system consists of several key components working together:

// Core components of the news analysis system
type Stream struct {
    feeds             []string
    client            *http.Client
    newsChan          chan *NewsItem
    stopChan          chan struct{}
    fetchedItems      map[string]bool
    vertexAiModelName string
    analysisEnabled   bool
    genaiClient       *genai.Client
}

type AIAnalysisService struct {
    engine    *engine.Engine
    longChan  chan *AIRecommendation
    shortChan chan *AIRecommendation
    stopChan  chan struct{}
}

The architecture follows a producer-consumer pattern where the news stream fetches and analyzes articles, while the AI analysis service routes recommendations to trading strategies.

Italian News Sources and Content Extraction

The system monitors several Italian financial news sources:

func NewStream() *Stream {
    // Initialize NewsStream with default RSS feeds
    defaultNewsFeeds := []string{
        "https://news.teleborsa.it/NewsFeed.ashx",
        "https://investire.biz/feed/analisi/azioni",
    }
    // ... initialization code
}

Handling Different HTML Formats

One of the most challenging aspects was extracting clean article content from different Italian news websites. Each source has its own HTML structure:

// ExtractArticleFromHTML supports both Teleborsa and Investire.biz formats
func ExtractArticleFromHTML(htmlContent string) string {
    // First, try to extract from Investire.biz format
    if content := extractInvestireBizArticle(htmlContent); content != "" {
        return content
    }

    // If not found, try Teleborsa format
    if content := extractTeleborsaArticle(htmlContent); content != "" {
        return content
    }

    return ""
}

func extractTeleborsaArticle(htmlContent string) string {
    // Find the start of the article content
    startMarker := "(Teleborsa) - "
    startIndex := strings.Index(htmlContent, startMarker)
    if startIndex == -1 {
        return ""
    }

    // Extract and clean the content
    articleHTML := htmlContent[startIndex:endIndex]
    cleanText := RemoveHTMLTags(articleHTML)
    cleanText = html.UnescapeString(cleanText)
    cleanText = NormalizeWhitespace(cleanText)

    return cleanText
}

The extraction process handles the specific formatting patterns used by Italian financial news sites. For example, Teleborsa articles always start with “(Teleborsa) - “ followed by the actual content, while Investire.biz uses a specific <div id="articleText"> container.

Note on robustness: parsing articles by matching specific HTML patterns is inherently brittle—publishers can change their markup at any time, breaking custom extractors. An alternative is to feed the entire page HTML to the LLM and let it identify the relevant content. That approach is often robust but more expensive in tokens due to boilerplate HTML. Extracting the article body upfront helps reduce token usage by avoiding irrelevant markup while accepting the maintenance cost if page structures change.

Gemini Integration for Stock Analysis

The heart of the system is the Gemini-powered analysis that processes the extracted article content and generates trading recommendations.

Authentication and client setup are covered in a separate article: From Vertex AI SDK to Google Gen AI SDK: Service Account Authentication for Python and Go.

The Analysis Prompt

The key to effective AI-powered stock analysis lies in crafting a precise prompt that handles the multilingual nature of Italian financial news:

func (s *Stream) analyzeArticleWithGemini(articleText string) (*NewsAnalysis, error) {
    prompt := fmt.Sprintf(`Analyze the following news article and provide a JSON response with an array of stock analyses.
The article can be in Italian or English..
For each stock mentioned, include the ticker symbol (e.g., for "Stellantis" it should be "STLAM"), a suggested action (buy, sell, or hold), and a brief reason for the suggestion in English.
It's not mandatory to include all the stocks mentioned in the article, only the ones that are relevant to the article.
If there are no stocks mentioned in the article, return an empty array.
The suggested action should be based on the article content and the stock's performance. The suggested action will be used for day trading.

Article:
%s

IMPORTANT: 
- Your response must contain ONLY valid JSON, no explanatory text before or after
- Do not include any markdown formatting or code blocks
- Return exactly this JSON structure and nothing else:

{
  "items": [
    {
      "ticker": "STOCK_TICKER",
      "action": "buy|sell|hold", 
      "reason": "Brief explanation in English."
    }
  ]
}`, articleText)

    config := &genai.GenerateContentConfig{
        Temperature: genai.Ptr[float32](0.1),
    }

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    resp, err := s.genaiClient.Models.GenerateContent(ctx, s.vertexAiModelName, genai.Text(prompt), config)
    // ... error handling and JSON parsing
}

The prompt is carefully designed to:

  • Handle both Italian and English content
  • Request specific ticker formats used in Italian markets
  • Provide structured JSON output for easy parsing
  • Focus on actionable trading recommendations

Real-World Example: Analyzing Italian Financial News

Let’s look at how the system processes a typical Italian financial news article:

// Sample Italian article from Teleborsa
articleText := `(Teleborsa) - Seduta in ribasso per Stellantis, che mostra un calo dell'1,26%. A pesare sulle azioni è la notizia di un richiamo di oltre un milione di veicoli negli Stati Uniti per un difetto alla telecamera posteriore. Brilla invece Leonardo, che avanza del 2,5% grazie a nuove commesse nel settore della difesa.`

When processed by Gemini, this article generates the following analysis:

{
  "items": [
    {
      "ticker": "STLAM",
      "action": "sell",
      "reason": "Stock declining 1.26% due to recall of over 1 million vehicles in US for rear camera defect."
    },
    {
      "ticker": "LDO.MI",
      "action": "buy", 
      "reason": "Stock rising 2.5% on new defense sector contracts."
    }
  ]
}

Routing Recommendations to Trading Strategies

Once Gemini analyzes the articles, the system routes recommendations to appropriate trading channels:

func (s *AIAnalysisService) Start() error {
    go func() {
        for {
            select {
            case newsItem := <-newsChan:
                if newsItem.Analysis == nil || len(newsItem.Analysis.Items) == 0 {
                    continue
                }

                for _, analysis := range newsItem.Analysis.Items {
                    // Generate ticker variations for different markets
                    allTickers := []string{analysis.Ticker}
                    
                    // Handle Italian stocks (.MI suffix)
                    if strings.Contains(analysis.Ticker, ".MI") {
                        miRemovedTicker := strings.Replace(analysis.Ticker, ".MI", "", 1)
                        allTickers = append(allTickers, miRemovedTicker)
                    }

                    // Lookup actual tradeable instruments
                    titles, err := s.engine.ListTitles(allTickers)
                    if err != nil {
                        continue
                    }

                    // Route to appropriate channels
                    for _, titleList := range titles {
                        for _, title := range titleList {
                            recommendation := &AIRecommendation{
                                Title:    title,
                                Analysis: analysis,
                                Source:   newsItem.Source,
                            }

                            switch analysis.Action {
                            case news.BuyAction:
                                s.longChan <- recommendation
                            case news.SellAction:
                                s.shortChan <- recommendation
                            }
                        }
                    }
                }
            }
        }
    }()
}

The system intelligently handles ticker symbol variations common in Italian markets, where stocks might be referenced as “STLAM”, “STLA.MI”, or other formats depending on the exchange.

Performance and Reliability Considerations

Concurrent Processing

The system uses Go’s concurrency features to handle multiple news sources simultaneously:

func (s *Stream) Start() {
    s.wg.Add(1)
    go func() {
        defer s.wg.Done()
        ticker := time.NewTicker(s.fetchInterval)
        defer ticker.Stop()

        for {
            select {
            case <-ticker.C:
                for _, feed := range s.feeds {
                    go s.fetchFeedWithRetry(feed)
                }
            case <-s.stopChan:
                return
            }
        }
    }()
}

Error Handling and Retries

Given the critical nature of financial data, the system implements robust retry mechanisms:

func (s *Stream) fetchFeedWithRetry(feedURL string) error {
    var lastErr error
    delay := s.retryConfig.InitialDelay

    for attempt := 0; attempt <= s.retryConfig.MaxRetries; attempt++ {
        if attempt > 0 {
            time.Sleep(delay)
            delay = time.Duration(float64(delay) * s.retryConfig.BackoffFactor)
            if delay > s.retryConfig.MaxDelay {
                delay = s.retryConfig.MaxDelay
            }
        }

        if err := s.fetchFeed(feedURL); err != nil {
            lastErr = err
            continue
        }
        return nil
    }
    
    return fmt.Errorf("failed to fetch feed after %d attempts: %w", 
        s.retryConfig.MaxRetries+1, lastErr)
}

Lessons Learned and Challenges

Prompt Engineering for Financial Analysis

Crafting effective prompts for financial analysis required several iterations. Key learnings:

  1. Be explicit about output format: Gemini can be verbose, so explicitly requesting JSON-only responses is crucial
  2. Handle multilingual content: Italian financial news often mixes Italian and English terms
  3. Specify ticker formats: Different markets use different conventions (STLAM vs STLA.MI vs STLA)
  4. Set appropriate temperature: Low temperature (0.1) provides more consistent, factual analysis

Managing API Costs

Vertex AI costs can add up quickly with frequent news analysis. Optimization strategies:

  1. Content filtering: Only analyze articles that pass initial relevance filters
  2. Batch processing: Aggregate similar articles when possible
  3. Caching: Avoid re-analyzing identical content
  4. Timeout management: Set reasonable timeouts to prevent hanging requests

Integration with Trading Strategies

The AI analysis service integrates seamlessly with various trading strategies:

// Example: Trend following strategy with AI recommendations
longStocks, err := stockSelector.SelectStocksLong(strategy.SelectFromAll)
if err != nil {
    log.Error("Error starting SelectStocks: %s", err)
    return
}

// Process AI recommendations
go func() {
    for recommendation := range aiService.GetLongChannel() {
        // Create trend following strategy for recommended stock
        trendStrategy := strategy.NewTrendFollowing(
            tradingEngine, 
            recommendation.Title,
            strategy.LongDirection,
        )
        
        if err := trendStrategy.Start(); err != nil {
            log.Error("Failed to start trend strategy: %s", err)
            continue
        }
        
        log.Info("Started AI-recommended long strategy for %s: %s", 
            recommendation.Title.GetPriceCode(), 
            recommendation.Analysis.Reason)
    }
}()

Future Enhancements

Several improvements are planned for the system:

  1. Sentiment scoring: Add numerical sentiment scores alongside buy/sell/hold recommendations
  2. Multi-model analysis: Compare recommendations from different AI models
  3. Historical backtesting: Evaluate AI recommendation accuracy over time
  4. Real-time alerts: Push critical news analysis to mobile devices
  5. Portfolio integration: Consider existing positions when generating recommendations

Conclusion

Key takeaways:

  • Extract only the article body to save tokens, but expect HTML to change.
  • Keep the prompt strict (JSON-only, low temperature).
  • Normalize tickers for local markets before routing to strategies.
  • Concurrency and retries matter more than clever code.

I’ll keep hardening the extractors and add backtesting and alerts. If you want to try it, share feedback, or chat about the broker integration, leave a comment or reach out via the contact form.

Don't you want to miss the next article? Do you want to be kept updated?
Subscribe to the newsletter!

Related Posts

From Vertex AI SDK to Google Gen AI SDK: Service Account Authentication for Python and Go

Complete migration guide from Vertex AI SDK to Google Gen AI SDK for Python and Go developers. Covers service account authentication, OAuth2 scope limitations, and the critical implementation details missing from Google's official documentation.

Getting back to the EU: from Google Cloud to Self-Hosted EU Infrastructure

A detailed walkthrough of migrating a web service from Google Cloud to OVH, covering PostgreSQL database migration, CI/CD pipeline setup on Github Actions, and significant cost savings by migrating from Cloud to a self hosted solution. This migration represents a first step toward reducing dependency on US cloud providers while maintaining service quality.

Using AI for Coding: My Journey with Cline and Large Language Models

How I leveraged AI tools like Cline to enhance the UI/UX of a website and streamline backend tasks. From redesigning pages and translating content to navigating the benefits and challenges of AI-assisted development, this blog post highlights the potential of using large language models to boost productivity while sharing key lessons learned.

Fixing the code signing and notarization issues of Unreal Engine (5.3+) projects

Starting from Unreal Engine 5.3, Epic Games added support for the so-called modern Xcode workflow. This workflow allows the Unreal Build Tool (UBT) to be more consistent with the standard Xcode app projects, and to be compliant with the Apple requirements for distributing applications... In theory! 😅 In practice this workflow is flawed: both the code signing and the framework supports are not correctly implemented, making the creation of working apps and their distribution impossible. In this article, we'll go through the problems faced during the packaging, code signing, and notarization of an Unreal Engine application on macOS and end up with the step-by-step process to solve them all.

The (Hidden?) Costs of Vertex AI Resource Pools: A Cautionary Tale

In the article "Custom model training & deployment on Google Cloud using Vertex AI in Go" we explored how to leverage Go to create a resource pool and train a machine learning model using Vertex AI's allocated resources. While this approach offers flexibility, there's a crucial aspect to consider: the cost implications of resource pools. This article details my experience with a sudden price increase in Vertex AI and the hidden culprit – a seemingly innocuous resource pool.

Building a RAG for tabular data in Go with PostgreSQL & Gemini

In this article we explore how to combine a large language model (LLM) with a relational database to allow users to ask questions about their data in a natural way. It demonstrates a Retrieval-Augmented Generation (RAG) system built with Go that utilizes PostgreSQL and pgvector for data storage and retrieval. The provided code showcases the core functionalities. This is an overview of how the "chat with your data" feature of fitsleepinsights.app is being developed.

Using Gemini in a Go application: limits and details

This article explores using Gemini within Go applications via Vertex AI. We'll delve into the limitations encountered, including the model's context window size and regional restrictions. We'll also explore various methods for feeding data to Gemini, highlighting the challenges faced due to these limitations. Finally, we'll briefly introduce RAG (Retrieval-Augmented Generation) as a potential solution, but leave its implementation details for future exploration.

Custom model training & deployment on Google Cloud using Vertex AI in Go

This article shows a different approach to solving the same problem presented in the article AutoML pipeline for tabular data on VertexAI in Go. This time, instead of relying on AutoML we will define the model and the training job ourselves. This is a more advanced usage that allows the experienced machine learning practitioner to have full control on the pipeline from the model definition to the hardware to use for training and deploying. At the end of the article, we will also see how to use the deployed model. All of this, in Go and with the help of Python and Docker for the custom training job definition.

Integrating third-party libraries as Unreal Engine plugins: solving the ABI compatibility issues on Linux when the source code is available

In this article, we will discuss the challenges and potential issues that may arise during the integration process of a third-party library when the source code is available. It will provide guidance on how to handle the compilation and linking of the third-party library, manage dependencies, and resolve compatibility issues. We'll realize a plugin for redis plus plus as a real use case scenario, and we'll see how tough can it be to correctly compile the library for Unreal Engine - we'll solve every problem step by step.

AutoML pipeline for tabular data on VertexAI in Go

In this article, we delve into the development and deployment of tabular models using VertexAI and AutoML with Go, showcasing the actual Go code and sharing insights gained through trial & error and extensive Google research to overcome documentation limitations.