FMP

FMP

Build an AI Market Trend Forecaster Using Multi-Agents + Deep Learning (LSTM + FMP Data)

Forecasting market trends is often approached as a modeling problem. In practice, it is primarily a data and system design problem. Many forecasting pipelines fail not because of the model itself, but because data preparation is fragile, feature logic is tightly coupled to training code, and swapping models requires rewriting large parts of the workflow. These issues make experimentation slow and results difficult to reproduce.

In this article, we build a market trend forecasting pipeline using Financial Modeling Prep (FMP) as the core data layer. FMP provides structured market datasets—such as historical prices, technical indicators, and market news—that are well suited for time-series workflows. Using these datasets allows the system to focus on organizing data and computation cleanly, rather than spending effort on sourcing, cleaning, and normalizing raw market inputs.

The pipeline is organized using a lightweight multi-agent structure to separate responsibilities across the major stages of forecasting. One agent handles market data collection from FMP, another prepares features for time-series modeling, and a final agent generates the forecast output. The agents execute in a simple linear sequence, keeping the system easy to inspect, modify, and debug.

For the forecasting component, the pipeline uses an LSTM-based model to capture temporal patterns in historical market data. The model is treated as a replaceable component rather than a fixed dependency. This separation makes it easier to experiment with alternative modeling approaches, iterate safely on features, and evolve the system without destabilizing the rest of the workflow.

By the end of this article, you will have a clear view of how to:

  • Use FMP datasets as a stable foundation for market trend forecasting
  • Structure a forecasting pipeline that supports safer iteration and easier debugging
  • Combine historical prices, derived indicators, and contextual data into a single, maintainable workflow

Data Sources Behind the Article

  • Stock Price and Volume Data API: This dataset provides the core time-ordered signal used for forecasting, including open, high, low, close, and volume values that define how the market evolves over time.

  • Relative Strength Index (RSI) API: The RSI dataset supplies a derived momentum signal that summarizes recent price behavior and complements raw price movements without requiring custom indicator calculations.

  • Stock News API: Market news provides contextual information that helps interpret forecast outputs, surfacing recent events or narratives associated with the symbol without being used directly as a model input.

Taken together, these datasets are sufficient for a first-pass market trend forecaster: historical prices establish the primary signal, technical indicators add structured behavioral context, and news data supports post-forecast interpretation. This combination keeps the pipeline focused and manageable while covering the essential inputs needed for an end-to-end forecasting workflow.

System Architecture: A Linear Multi-Agent Forecasting Pipeline

The forecasting system is organized as a linear multi-agent pipeline. The goal of this structure is not orchestration complexity, but clear separation of responsibilities across the major stages of the workflow. Each agent owns a well-defined task and passes its output forward through a shared state object.

At a high level, the pipeline moves through three stages:

  1. Market data collection from Financial Modeling Prep
  2. Feature preparation for time-series modeling
  3. Trend forecasting and contextual assembly

The agents execute sequentially in a fixed order. This keeps control flow explicit and avoids introducing routing logic that is unnecessary for a single-symbol forecasting task.

Why a Multi-Agent Structure?

Using agents in this pipeline is a deliberate design choice to enforce clear boundaries between data access, feature preparation, and forecasting logic. Each agent represents a stable responsibility that maps directly to a stage in the forecasting workflow.

  • The data agent focuses exclusively on retrieving and structuring market datasets from FMP.
  • The feature agent transforms those datasets into model-ready sequences without assuming anything about how the model is implemented.
  • The forecasting agent consumes prepared features and produces a trend estimate, remaining isolated from both data retrieval and feature engineering concerns.

This separation allows each part of the system to evolve independently. New datasets can be added without changing feature logic, feature pipelines can be modified without retraining code, and forecasting models can be swapped or extended without touching data access. As a result, the pipeline remains easier to experiment with, safer to iterate on, and simpler to maintain as requirements change.

Linear Routing by Design

Routing between agents is intentionally simple. The system executes agents in a predefined order:

Data Agent → Feature Agent → Forecast Agent

Each agent receives a shared state object, updates it with its output, and passes it forward. This avoids hidden control flow and makes debugging straightforward.

While graph-based orchestration frameworks can be introduced later, a linear pipeline is sufficient for demonstrating how FMP data can be integrated into an agent-based forecasting workflow without additional abstraction layers.

Shared State as the Integration Point

Instead of separate state or routing files, agents communicate through a single shared state object that moves sequentially through the pipeline. This state holds the target symbol, retrieved market data, prepared feature matrices, and forecasting outputs.

Using a shared state makes data lineage explicit and keeps intermediate results easy to inspect at each stage of execution. When something breaks or produces unexpected output, developers can examine the state after any agent to identify where assumptions diverged or data changed shape. This also simplifies experimentation, since features, models, or datasets can be modified in isolation while preserving visibility into how those changes propagate through the workflow.

With this architecture in place, the next step is to examine which FMP datasets fit naturally into the pipeline and how they support market trend forecasting from an engineering perspective.

Selecting Market Data from FMP for Trend Forecasting

Before introducing models or agents, it is important to clarify which market datasets are required and how they map to a forecasting workflow. Trend forecasting relies on observing how prices evolve over time, augmenting those observations with derived signals, and optionally adding contextual information that helps interpret model outputs. Financial Modeling Prep (FMP) provides datasets that align naturally with each of these needs.

Historical Price Data as the Primary Signal

The foundation of the forecasting pipeline is historical price data. This dataset provides a time-ordered record of market behavior, typically including open, high, low, close, and volume values.

In the pipeline, historical prices serve as:

  • The base time series for modeling
  • The source for return calculations
  • The reference timeline for aligning all other features

This data is consumed by the data agent and passed forward in a structured tabular form. From a workflow perspective, it represents the raw signal the forecasting model attempts to learn from.

Technical Indicators as Derived Features

While raw prices capture market movement, forecasting models often benefit from derived indicators that summarize recent behavior. Examples include momentum-based indicators, volatility measures, or trend-following signals.

FMP provides precomputed technical indicators that can be retrieved alongside price data. In the pipeline:

  • These indicators are treated as additional input features
  • They are aligned by date with the price series
  • They reduce the need for custom indicator calculations inside the pipeline

Using indicators in this way keeps feature preparation focused on data alignment and transformation rather than indicator math.

Market News as Contextual Information

Market news does not directly replace quantitative signals, but it provides context for interpreting forecasts. Headlines can help explain why a model-generated trend aligns with recent market events or shifts in sentiment.

Within the pipeline:

  • News data is fetched for the same symbol being forecasted
  • It is kept separate from numeric model inputs
  • It is surfaced alongside the forecast output as contextual information

This separation ensures that the forecasting model remains grounded in time-series data, while the final output benefits from additional market context.

Mapping Datasets to the Forecasting Workflow

Each dataset from FMP maps cleanly to a stage in the system:

  • Historical prices → primary time-series input
  • Technical indicators → supplementary features for modeling
  • Market news → interpretability and context at the output stage

This separation keeps modeling inputs and contextual data isolated, which helps avoid feature leakage and prevents accidental coupling between the forecasting logic and post-hoc interpretation signals.

With the data foundation defined, the next step is to show how these datasets are organized and processed using a multi-agent design.

Agent Design: Data Collection, Feature Preparation, and Forecasting

With the data foundation in place, the next step is to define how responsibilities are divided across the system. Instead of treating the forecasting pipeline as a single monolithic script, we organize it into a small set of agents, each responsible for a clearly scoped task.

This design keeps the system readable and makes it easier to evolve individual stages without affecting the rest of the pipeline.

Data Collection Agent

The data collection agent is responsible for interacting with FMP datasets. Its role is limited to:

  • Retrieving historical price data
  • Retrieving selected technical indicators
  • Returning these datasets in a structured, time-aligned format

This agent does not perform feature engineering or modeling. By keeping it focused only on data access, changes to data sources or datasets remain isolated from the rest of the system.

From a workflow perspective, this agent converts external financial data into an internal representation that downstream agents can consume reliably.

Feature Preparation Agent

The feature preparation agent operates entirely on structured data produced by the data agent. Its responsibilities include:

  • Aligning price and indicator time series
  • Generating lagged values and returns
  • Converting the data into fixed-length sequences suitable for time-series models

This agent treats feature preparation as a pure transformation step. It does not make assumptions about the forecasting model beyond the shape of inputs it needs to produce. As a result, the same feature pipeline can support different models without modification.

Forecasting Agent

The forecasting agent consumes prepared feature sequences and produces a market trend estimate. In this article, the agent uses an LSTM-based model to capture temporal dependencies in the data.

Importantly, the forecasting agent:

  • Does not handle raw data access
  • Does not perform feature alignment
  • Focuses solely on training, inference, and output interpretation

This separation allows the forecasting logic to be swapped or extended independently, while the rest of the pipeline remains unchanged.

Shared State and Execution Flow

All agents communicate through a shared state object that moves sequentially through the pipeline. Each agent reads the inputs it needs from the state, appends its outputs, and passes the state forward.

Execution follows a fixed sequence:

Data Collection → Feature Preparation → Forecasting

This linear execution model keeps control flow explicit and easy to trace. While more advanced routing strategies can be introduced later, a sequential design is sufficient for building and understanding a market trend forecaster on top of FMP data.

With agent responsibilities clearly defined, we can now move from design to implementation, starting with how the data collection agent retrieves market data using FMP APIs.

Adapting the Forecasting Pipeline for Individual and Enterprise Use

The forecasting pipeline described in this article can be used at different scales, depending on who is running it and for what purpose. Individual analysts and developers often start with a single symbol and a limited historical window to explore model behavior, test feature ideas, or prototype forecasting logic. In these cases, lightweight usage of FMP market datasets is sufficient to support experimentation and learning.

At the enterprise level, the same structure can be extended to support broader workflows. Research teams may run the pipeline across many symbols, automate recurring forecasts, or integrate outputs into internal dashboards and monitoring systems. As usage scales, considerations such as request volume, data coverage, and operational reliability become more important, which is where different FMP plans and pricing tiers align with varying research and production needs.

Implementing the Data Agent with FMP APIs

This section implements the Data Collection Agent, whose responsibility is to retrieve time-ordered market datasets from FMP and return them in a consistent, analysis-ready form.

The agent focuses strictly on data access and basic normalization, leaving feature engineering and modeling to downstream stages. By the end of this section, you will have a clean, reusable data layer that reliably feeds structured market data into the rest of the forecasting pipeline.

1) Data Agent contract (shared state in, shared state out)

from dataclasses import dataclass

from typing import Dict, Any

import pandas as pd


@dataclass

class DataAgent:

api_key: str


def run(self, state: Dict[str, Any]) -> Dict[str, Any]:

symbol = state["symbol"]


price_df = fetch_price_history(symbol, self.api_key)

rsi_df = fetch_technical_indicator(

symbol,

self.api_key,

indicator="rsi",

period=14

)

news_df = fetch_stock_news(symbol, self.api_key, limit=5)


# Merge later in FeatureAgent; DataAgent just returns datasets.

state["price_df"] = price_df

state["indicator_df"] = rsi_df

state["news_df"] = news_df


return state




What's happening here:

  • run() reads symbol from the shared state.
  • It fetches three datasets and stores them back into state under clear keys.
  • The agent does not engineer features or train models—those belong to later agents.

2) Fetch historical prices (training signal)

import requests


def fetch_price_history(symbol: str, api_key: str) -> pd.DataFrame:


# Example endpoint pattern used for historical EOD price series

url = f"https://financialmodelingprep.com/api/v3/historical-price-full/{symbol}"


resp = requests.get(url, params={"apikey": api_key})

resp.raise_for_status()


payload = resp.json()

rows = payload.get("historical", [])

df = pd.DataFrame(rows)


if df.empty:

return df


df["date"] = pd.to_datetime(df["date"])

df = df.sort_values("date")


return df[["date", "open", "high", "low", "close", "volume"]]




One workflow line: This call returns a time-ordered historical price series, which becomes the core signal used for returns, windows, and LSTM training.

Key lines:

  • params={"apikey": api_key} keeps auth clean and consistent across calls.
  • payload.get("historical", []) avoids crashing if the response shape changes or data is missing.
  • sort_values("date") ensures the sequence is correctly ordered for time-series modeling.

3) Fetch a technical indicator (derived feature)

def fetch_technical_indicator(

symbol: str,

api_key: str,

indicator: str = "rsi",

period: int = 14

) -> pd.DataFrame:


# Example endpoint pattern for daily technical indicators

url = f"https://financialmodelingprep.com/api/v3/technical_indicator/daily/{symbol}"


resp = requests.get(

url,

params={

"type": indicator,

"period": period,

"apikey": api_key,

},

)

resp.raise_for_status()


df = pd.DataFrame(resp.json())

if df.empty:

return df


df["date"] = pd.to_datetime(df["date"])

df = df.sort_values("date")


# Column name typically matches indicator type (e.g., "rsi"), but we guard defensively.

value_col = indicator if indicator in df.columns else [

c for c in df.columns if c != "date"

][0]


return df[["date", value_col]].rename(columns={value_col: indicator})



One workflow line: This call returns a dated indicator series that we align with prices to provide the model an additional signal beyond raw returns.

Key lines:

  • type + period let you swap indicators without changing pipeline logic.
  • value_col selection is defensive to avoid hard assumptions about the returned field name.

4) Fetch stock news (context for the final output)

def fetch_stock_news(symbol: str, api_key: str, limit: int = 5) -> pd.DataFrame:


# Example endpoint pattern for ticker-filtered news

url = "https://financialmodelingprep.com/api/v3/stock_news"


resp = requests.get(

url,

params={

"tickers": symbol,

"limit": limit,

"apikey": api_key,

},

)

resp.raise_for_status()


items = resp.json()

df = pd.DataFrame(items)


# Keep only fields we'll use for context in the final response

keep = [c for c in ["publishedDate", "title", "site", "url"] if c in df.columns]


return df[keep] if keep else df




One workflow line: This call returns recent news items for the symbol, which we attach to the forecast output as context—not as a model input.

Key lines:

  • keep filters to only what we actually display later, keeping state lightweight.

Now that state holds:

  • price_df (historical prices)
  • indicator_df (RSI or another indicator)
  • news_df (recent headlines)

The next step is to align these series and convert them into LSTM-ready windows.

Feature Preparation: Converting Market Data into Forecasting Signals

The Data Agent returns structured market datasets—prices, technical indicators, and contextual information—that are ready for transformation. The Feature Preparation stage focuses exclusively on aligning numeric time series, generating targets, and converting the data into fixed-length sequences suitable for time-series modeling. This stage is intentionally model-agnostic: the same feature pipeline can support LSTM models, other neural sequence models, or classical time-series approaches without modification, as long as they consume windowed numeric inputs.

1) Merge and align time series (prices + indicators)

import numpy as np

import pandas as pd


def build_model_table(

price_df: pd.DataFrame,

indicator_df: pd.DataFrame

) -> pd.DataFrame:


# Join on date so every row represents one trading day

df = price_df.merge(indicator_df, on="date", how="left")


# Basic sanity: ensure chronological order for time-series operations

df = df.sort_values("date").reset_index(drop=True)


# Compute daily return from close prices (common forecasting signal)

df["ret_1d"] = df["close"].pct_change()


# Define the prediction target: next-day return

df["target"] = df["ret_1d"].shift(-1)


# Drop rows with missing values introduced by pct_change/shift or indicator gaps

df = df.dropna().reset_index(drop=True)


return df




What's happening in these lines:

  • merge(..., on="date") aligns indicator values to the same trading day as the price row.
  • pct_change() creates a normalized signal (ret_1d) instead of using raw prices.
  • shift(-1) creates a forward-looking label: “predict tomorrow based on past days”.
  • dropna() removes the first row (no previous return), the last row (no next-day target), and any indicator gaps.

2) Select features and build fixed-length windows

LSTM expects a 3D tensor:

  • X shape: (num_samples, lookback_days, num_features)
  • y shape: (num_samples,)

from sklearn.preprocessing import StandardScaler


def make_lstm_windows(df: pd.DataFrame, lookback: int = 30):


# Choose a small feature set that maps cleanly to market behavior

# - ret_1d: recent movement

# - rsi: momentum/overbought-oversold style signal

# - volume: activity / participation proxy


feature_cols = ["ret_1d", "rsi", "volume"]


X_raw = df[feature_cols].values

y = df["target"].values


# Scale numeric features to stabilize training

scaler = StandardScaler()

X_scaled = scaler.fit_transform(X_raw)


X, Y = [], []

for i in range(lookback, len(df)):

X.append(X_scaled[i - lookback:i])

Y.append(y[i])


return np.array(X), np.array(Y), scaler

What's happening in these lines:

  • feature_cols defines exactly what the model sees. You can expand later without changing the agent boundary.
  • StandardScaler() keeps feature magnitudes comparable (returns vs volume can differ a lot).
  • The loop builds rolling windows: each sample is the last lookback days of features.
  • Y.append(y[i]) aligns the label to the day immediately after the window ends.

3) A small helper to validate shapes before training

This is a quick guardrail that prevents “silent shape bugs.”

def validate_windows(X: np.ndarray, Y: np.ndarray):


if X.ndim != 3:

raise ValueError(f"Expected X to be 3D, got shape {X.shape}")

if Y.ndim != 1:

raise ValueError(f"Expected Y to be 1D, got shape {Y.shape}")

if X.shape[0] != Y.shape[0]:

raise ValueError(f"X and Y sample count mismatch: {X.shape[0]} vs {Y.shape[0]}")



Why this matters:

  • Most LSTM training issues come from incorrect shapes, especially after merging and dropping rows.
  • These checks fail early and keep debugging straightforward.

How this maps to the overall workflow

At this point, the pipeline has converted raw market data into:

  • a clean modeling table (date, features, target)
  • LSTM-ready sequences (X) and labels (Y)

The next step is to train and run the model.

Forecasting Market Trends Using an LSTM Model

At this stage, the pipeline has already done the heavy lifting on the data side. We have:

  • X: fixed-length sequences of engineered market signals
  • Y: the next-step target (next-day return in this setup)

Now the forecasting stage trains an LSTM model and produces a trend estimate from the most recent window.

1) Train the LSTM model

from tensorflow.keras import Sequential

from tensorflow.keras.layers import LSTM, Dense, Dropout

from tensorflow.keras.callbacks import EarlyStopping


def train_lstm_model(X, Y):

# X shape: (samples, lookback, features)

# Y shape: (samples,)


model = Sequential([

LSTM(64, input_shape=(X.shape[1], X.shape[2])),

Dropout(0.2),

Dense(1) # regression output: predicted return

])


model.compile(optimizer="adam", loss="mse")


early_stop = EarlyStopping(

monitor="val_loss",

patience=3,

restore_best_weights=True

)


model.fit(

X,

Y,

validation_split=0.2,

epochs=20,

batch_size=32,

callbacks=[early_stop],

verbose=0

)


return model



What's happening in these lines:

  • input_shape=(X.shape[1], X.shape[2]) tells the LSTM: lookback days × features per sample.
  • Dense(1) produces a single numeric value (a return estimate in this workflow).
  • EarlyStopping(...) prevents unnecessary training once validation loss stops improving.
  • validation_split=0.2 provides a quick internal validation slice without extra dataset plumbing.

2) Generate a trend estimate for the latest window

import numpy as np


def predict_trend(model, X):

# Use the most recent window as the "current market state"

last_window = X[-1:].copy() # shape: (1, lookback, features)


pred_return = model.predict(last_window, verbose=0)[0][0]

pred_return = float(pred_return)


# Convert numeric forecast into a simple trend label

trend = "UP" if pred_return > 0 else "DOWN"


return {

"pred_return": pred_return,

"trend": trend

}



What's happening in these lines:

  • X[-1:] selects the most recent window while keeping the 3D shape required by Keras.
  • pred_return is the model's estimate of the next-day return (based on prior window definition).
  • The trend label is a thin interpretation layer: it translates a numeric output into a directional signal.

3) Wrap it into a Forecast Agent

This agent consumes the prepared windows and updates the shared state with a forecast.

from dataclasses import dataclass

from typing import Dict, Any


@dataclass

class ForecastAgent:

def run(self, state: Dict[str, Any]) -> Dict[str, Any]:

X = state["X"]

Y = state["Y"]


model = train_lstm_model(X, Y)

out = predict_trend(model, X)


state["model"] = model

state["forecast"] = out

return state


What's happening:

  • The agent reads X and Y from shared state.
  • It trains the model, generates the forecast, and writes results back into state.
  • Downstream output formatting can now use state["forecast"] and state["news_df"].

Producing a Unified Forecast with Market Context

At this point, the pipeline has two types of outputs:

  • Numeric forecast output from the LSTM (predicted return + UP/DOWN label)
  • Market context from FMP news (recent headlines for the same symbol)

The key engineering decision here is to keep these concerns separate:

  • The model consumes only numeric time-series features
  • News is attached at the end to make the output easier to interpret in real workflows

1) A small formatter that assembles the final response

def build_forecast_report(state):

symbol = state["symbol"]

forecast = state["forecast"] # {"pred_return": ..., "trend": ...}

news_df = state.get("news_df")


pred_return = forecast["pred_return"]

trend = forecast["trend"]


# Short, workflow-friendly summary

report = {

"symbol": symbol,

"trend": trend,

"predicted_next_return": round(pred_return, 6),

"notes": (

"The trend label is derived from the sign of the predicted next-step return "

"based on recent price/indicator windows."

),

"recent_headlines": []

}


# Attach context: keep it lightweight and deterministic

if news_df is not None and not news_df.empty:

cols = news_df.columns.tolist()

title_col = "title" if "title" in cols else None

date_col = "publishedDate" if "publishedDate" in cols else None


for _, row in news_df.head(5).iterrows():

item = {}

if date_col:

item["publishedDate"] = row.get(date_col)

if title_col:

item["title"] = row.get(title_col)

report["recent_headlines"].append(item)


return report

What's happening in these lines:

  • We read the forecast from state["forecast"] and news from state["news_df"].
  • We keep the interpretation rule explicit: trend = sign(predicted return).
  • We attach a few headlines as context. This mirrors real forecasting workflows where you want a quick “what changed recently?” view next to model output.

One workflow line (FMP mapping): The news dataset provides recent symbol-linked headlines that we display alongside the forecast as context for interpretation.

2) End-to-end run function (linear router)

def run_pipeline(symbol: str, api_key: str):

state = {"symbol": symbol}


# Agents (linear execution)

state = DataAgent(api_key=api_key).run(state)


model_df = build_model_table(state["price_df"], state["indicator_df"])

X, Y, scaler = make_lstm_windows(model_df, lookback=30)

validate_windows(X, Y)


state["model_df"] = model_df

state["X"] = X

state["Y"] = Y

state["scaler"] = scaler


state = ForecastAgent().run(state)


return build_forecast_report(state)

What's happening:

  • This is the “router”: a fixed sequence that keeps execution easy to follow.
  • The state object remains the single integration point across steps.
  • You can extend this later by inserting a new agent (e.g., volatility features) without rewriting the whole pipeline.

Quick visual: what the final output looks like

The final output is intentionally compact, combining a clear directional signal with the minimum amount of supporting context needed for interpretation. The forecasted trend and predicted return provide a concise quantitative summary, while recent headlines anchor the signal in observable market events.

After the pipeline completes, it produces a single, consolidated forecasting result for the requested symbol. The output contains:

  • The symbol being analyzed
  • A trend label (UP/DOWN) derived from the model's next-step return estimate
  • The predicted next return as a numeric value
  • A short notes field that documents how the trend label was derived
  • A small set of recent headlines pulled from FMP, attached as context for interpretation.



{

"symbol": "AMD",

"trend": "UP",

"predicted_next_return": 0.002341,

"notes": "The trend label is derived from the sign of the predicted next-step return based on recent price/indicator windows.",

"recent_headlines": [

{

"publishedDate": "",

"title": ""

},

{

"publishedDate": "",

"title": ""

}

]

}

Final Words

This article showed how a market trend forecaster can be built by treating forecasting as a data and system design problem first, and a modeling problem second. By anchoring the pipeline on structured datasets from Financial Modeling Prep and organizing the workflow into clearly defined agents, the system remains transparent, extensible, and easier to reason about.

The multi-agent structure keeps responsibilities separated across data collection, feature preparation, and forecasting, while the shared state and linear execution model make the pipeline straightforward to debug and evolve. The LSTM-based model fits naturally into this design as a replaceable forecasting component rather than a tightly coupled dependency.

Taken together, this approach produces a forecasting workflow that prioritizes reliable data handling and clean system boundaries, allowing models to be iterated on safely without destabilizing the rest of the pipeline.