← All projects
Personal project

Stock Portfolio Tracker

A desktop app I built for stock research, portfolio monitoring, interactive charts, and quick answers through a natural-language chatbot.

Last updated October 2026

Overview

I wanted to offer free alternatives to paid features in stock research services like Yahoo Finance. I built a desktop app for stock research and portfolio tracking, using Java for the interface and Python for data processing and the chatbot. It normalizes data from multiple financial APIs and provides interactive charts, holding alerts, and a natural-language assistant with 25+ intents.

Portfolio Tracker JavaFX dashboard showing holdings, portfolio performance, market sections, and the assistant panel.
Take a closer look
JavaFX portfolio dashboard.

What it is

Users can look up tickers, review portfolio performance in interactive charts, and receive alerts when holdings change. A chatbot answers common research questions without requiring users to navigate menus.

The desktop interface is written in Java, with Python components for visualization, data processing, and chatbot logic.

Why I built it

I was interested in finance and wanted to make useful stock research and portfolio features available without relying on paid offerings from services like Yahoo Finance. Building a desktop app gave me a way to work with real market data from external APIs, including their rate limits, inconsistent formats, and edge cases.

Adding a chatbot forced me to think about how users phrase questions and how to map messy natural language to structured queries.

Data and integrations

The app pulls from multiple financial data providers, each with different response shapes and constraints. A big part of the work was normalizing that data into something the rest of the app could rely on.

Handling API failures gracefully, caching where appropriate, and keeping the UI responsive while data loads were all recurring challenges.

Architecture diagram: financial data APIs feed source adapters, which normalize and cache market data. Holdings and watchlists join normalized prices in portfolio analysis, then flow to charts and alerts.
Take a closer look
Multi-source normalization and portfolio analysis feed the research dashboard and alerts.

Making data useful

Raw numbers are not enough—you need charts and summaries that help you decide what to look at next. I built interactive visualizations for trends and portfolio performance.

The chatbot covers 25+ common intents so users can ask things like current price, performance summaries, or holding details in plain language instead of navigating the full interface.

Lessons learned

Working with financial APIs taught me that data integration is mostly normalization. Every provider returns different field names, units, response shapes, and error codes—building a reliable app meant abstracting those differences behind a consistent internal model rather than passing raw API responses to the UI.

The chatbot work changed how I think about user input. Natural language is messy: people phrase the same question dozens of ways, abbreviate freely, and expect the system to fill in gaps. Mapping that ambiguity to structured queries without making users feel constrained was the hardest design problem in the project, and it taught me more about interface design than any textbook example.

Highlights

  • Integrated 3+ external financial APIs and normalized heterogeneous response data into a consistent application model
  • Combined Java and Python in a single desktop application, using each language where it fit best
  • Built interactive data visualizations to speed up portfolio analysis and market research workflows
  • Developed a natural-language chatbot with 25+ intents that maps user questions to structured data queries
  • Implemented automated alerts and performance summaries so portfolio changes surface without manual checking

Code

Live Quote Lookup: Async Fetch with Defensive Parsing

java
/**
 * Fetches a real-time quote from the market-data script on a
 * background executor, then swaps the result onto the UI thread.
 */
private void fetchQuotePreview() {
    String ticker = tickerField.getText().trim().toUpperCase();
    if (ticker.isEmpty()) return;

    // Show loading state
    quotePrice.setText("...");
    quoteChange.setText("");

    CompletableFuture.supplyAsync(() -> {
        List<String> args = List.of("python3",
            PythonRunner.scriptPath(), "--quotes", ticker);
        return PythonRunner.execute(args);
    }, executor).thenAccept(output -> Platform.runLater(() -> {
        if (output != null && !output.isEmpty()) {
            parseQuoteResponse(output, ticker);
        } else {
            showQuoteError();
        }
    })).exceptionally(e -> {
        Platform.runLater(this::showQuoteError);
        return null;
    });
}

private void parseQuoteResponse(String output, String ticker) {
    try {
        Type mapType = new TypeToken<Map<String, Object>>(){}.getType();
        Map<String, Object> response = gson.fromJson(output, mapType);

        if (response != null && response.containsKey("data")) {
            @SuppressWarnings("unchecked")
            Map<String, Map<String, Object>> data =
                (Map<String, Map<String, Object>>) response.get("data");

            if (data != null && data.containsKey(ticker)) {
                Map<String, Object> quote = data.get(ticker);

                String companyName = (String) quote.getOrDefault("name", ticker);
                Object priceObj = quote.get("price");
                Object changeObj = quote.get("change");
                Object changePercentObj = quote.get("changePercent");
                String marketState = (String) quote.getOrDefault("marketState", "");

                String priceStr = "$--";
                if (priceObj != null) {
                    double price = ((Number) priceObj).doubleValue();
                    priceStr = String.format("$%.2f", price);
                }

                String changeStr = "";
                if (changeObj != null) {
                    double change = ((Number) changeObj).doubleValue();
                    String sign = change >= 0 ? "+" : "";
                    if (changePercentObj != null) {
                        double changePercent = ((Number) changePercentObj).doubleValue();
                        changeStr = String.format("%s%.2f (%.2f%%)",
                            sign, change, changePercent);
                    } else {
                        changeStr = String.format("%s%.2f", sign, change);
                    }
                }

                String stateStr = switch (marketState) {
                    case "PRE_MARKET"                         -> "Pre-Market";
                    case "POST_MARKET", "POSTPOST"            -> "Post-Market";
                    case "CLOSED"                             -> "Market Closed";
                    case "REGULAR", "OPEN"                    -> "Market Open";
                    default -> marketState.isEmpty() ? "" : marketState;
                };

                quoteCompanyName.setText(companyName);
                quotePrice.setText(priceStr);
                quoteChange.setText(changeStr);
                quoteMarketState.setText(stateStr);
                return;
            }
        }
        showQuoteError();
    } catch (Exception e) {
        showQuoteError();
    }
}

A live-quote lookup that runs the market-data call off the UI thread. The fetch happens in a CompletableFuture on a dedicated executor, and the result—or a failure—is marshaled back onto the JavaFX thread via Platform.runLater so the interface never blocks. The response is parsed defensively: raw Gson maps instead of typed models, getOrDefault fallbacks for missing fields, and a switch that folds the API's raw market-state strings (PRE_MARKET, POSTPOST, ...) into one readable label. Every missing field degrades gracefully to a placeholder instead of throwing.

Skills & Technologies

JavaPythonJavaFXMavenSQLiteNumPyNLPData VisualizationFinancial APIsJUnit 5

Glossary

JavaFX
A Java toolkit for building desktop interfaces, including windows, controls, and charts.
JUnit 5
A testing framework used to write and run automated tests for Java code.
Maven
A tool that manages Java project dependencies and automates building and testing.
NLP
Natural language processing: techniques that help software interpret human language, such as chatbot questions.
NumPy
A Python library for efficient calculations on arrays of numerical data.
SQLite
A database stored in a local file that does not need a separate database server.
See more glossary terms