Agent Strategies for Financial Applications with Fin-R1

Page content

Summary

The rise of LLM-based agents marks a new era in financial AI. While Fin-R1 excels at financial reasoning, advisory, and code generation, wrapping it in agent-based systems unlocks planning, memory, tool use, and complianceβ€”all critical for real-world finance workflows. This post explores modern agent strategies, how they complement Fin-R1, and how to build intelligent financial systems using Python.

In this blog post we are going to start integrating some of the work we have done so far to generate a comprehensive approach to AI applications.

  • Fin-R1 currently the SOTA model for finance
    • We use the GGUF model created in the previous post and Ollama to interact with the model in this solution.
  • Agents currently the SOTA model for finance
  • MCP this is the best way to interact with models.
  • pgvector use the awesome PostgresSQL to manage and search for vectors.
  • smolagents an agent framework we can leverage

🧠 First, Define the Components

πŸ€– Agent

An agent is typically a system that:

  • Has goals or tasks
  • Makes decisions over time
  • Can use tools, memory, documents, or models
  • Often delegates LLM calls for reasoning or planning

Think of it as a smart coordinator or orchestrator.

πŸ“¦ Model Context Protocol (MCP)

The Model Context Protocol is about structuring the inputs to a model, often used to:

  • Maintain consistent context windows across steps
  • Manage multi-turn memory or scratchpad chains
  • Integrate documents, tools, user instructions, and prior outputs in a unified prompt

So it’s more of a prompt engineering and memory abstraction layer.


🧩 How They Fit Together

Think of this like layers in a system:

             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
             β”‚       Agent         β”‚
             β”‚  ─ Planning logic   β”‚
             β”‚  ─ Task switching   β”‚
             β”‚  ─ Tool calls       β”‚
             β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚ calls...
                      β–Ό
             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
             β”‚  ModelContextProtocolβ”‚
             β”‚  ─ Prompt structure  β”‚
             β”‚  ─ Memory layout     β”‚
             β”‚  ─ Context builder   β”‚
             β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚ builds prompt for
                      β–Ό
             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
             β”‚     Fin R1 Call     β”‚
             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”§ Should the Agent Use MCP?

βœ… Yes β€” the agent should wrap or use MCP as a tool for organizing prompts, memory, or documents.

Example Use Case:

You build an agent that does financial report review:

  1. The agent gets a task: β€œSummarize this 10-K filing”
  2. It chunks the document and calls MCP to build a prompt for each chunk
  3. MCP structures:
    • System prompt
    • chunk
    • Model instructions
  4. The agent then calls the model with MCP’s output, collects results, maybe passes them to another tool

You can think of MCP like LangChain’s prompt + memory wrapper β€” but without all the bloat.


🧠 Bonus Tip: When to Separate Them

If you’re building:

  • A reusable agent framework β†’ MCP is a dependency
  • A notebook/one-off use β†’ you might skip MCP
  • A document-aware chatbot β†’ MCP can be its own module for memory + context control

βœ… TL;DR

  • Agents = brains (planning + decision-making)
  • Model Context Protocol = memory + formatting (prompt management)
  • Your agents should use or call MCP, not the other way around
  • Keep them modular so you can reuse MCP across agents or apps

Section 1: Financial Statements tool

We are goint to build a financial statemets too. This will download and store to a database table the

  • income statement
  • balence sheet
  • cash flow statment for a stock from EDGAR.

We will the feed this data into the Fin-R1 model to detemine information about a stock.

This function will allow us to get the url for our data

1. Get the data url

This will allow us to get the latest data. We can easily modify this to get a series of results.

from sec_api import QueryApi
from sec_api import ExtractorApi
from dotenv import load_dotenv
import os
load_dotenv()

def get_latest_filing_url(ticker: str, form_type: str = "10-K") -> str:
    api_key = os.getenv("SEC_API_KEY")
    query = {
        "query": {You so support you can get the data take care A multiple assumptions
            "query_string": {
                "query": f"formType:\"{form_type}\" AND ticker:{ticker}"
            }
        },
        "from": "0",
        "size": "1",
        "sort": [{ "filedAt": { "order": "desc" } }]
    }    
    queryApi = QueryApi(api_key=api_key)
    filings = queryApi.get_filings(query)
    accession_number = filings['filings'][0]['accessionNo']
    logger.info(f"Accession number: {accession_number}")    
    cik = filings['filings'][0]['cik']
    logger.info(f"CIK: {cik}")
    url = filings['filings'][0]['linkToHtml']
    return cik, accession_number, url

2. Convert the url to xbrl

import requests
import json

_, _, url = get_latest_filing_url("TSLA")

xbrl_converter_api_endpoint = "https://api.sec-api.io/xbrl-to-json"
api_key = os.getenv("SEC_API_KEY")
final_url = xbrl_converter_api_endpoint + "?htm-url=" + url + "&token=" + api_key

# make request to the API
response = requests.get(final_url)

# load JSON into memory
xbrl_json = json.loads(response.text)

3. Get income statement

import pandas as pd
# convert XBRL-JSON of income statement to pandas dataframe
def get_income_statement(xbrl_json):
    income_statement_store = {}

    # iterate over each US GAAP item in the income statement
    for usGaapItem in xbrl_json['StatementsOfIncome']:
        values = []
        indicies = []

        for fact in xbrl_json['StatementsOfIncome'][usGaapItem]:
            # only consider items without segment. not required for our analysis.
            if 'segment' not in fact:
                index = fact['period']['startDate'] + '-' + fact['period']['endDate']
                # ensure no index duplicates are created
                if index not in indicies:
                    values.append(fact['value'])
                    indicies.append(index)                    

        income_statement_store[usGaapItem] = pd.Series(values, index=indicies) 
    income_statement = pd.DataFrame(income_statement_store)
    return income_statement

4. Get the balance sheet

# convert XBRL-JSON of balance sheet to pandas dataframe
def get_balance_sheet(xbrl_json):
    balance_sheet_store = {}

    for usGaapItem in xbrl_json['BalanceSheets']:
        values = []
        indicies = []

        for fact in xbrl_json['BalanceSheets'][usGaapItem]:
            # only consider items without segment.
            if 'segment' not in fact:
                index = fact['period']['instant']

                # avoid duplicate indicies with same values
                if index in indicies:
                    continue
                    
                # add 0 if value is nil
                if "value" not in fact:
                    values.append(0)
                else:
                    values.append(fact['value'])

                indicies.append(index)                    

            balance_sheet_store[usGaapItem] = pd.Series(values, index=indicies) 

    balance_sheet = pd.DataFrame(balance_sheet_store)
    return balance_sheet

5. Get the cash flow statement

def get_cash_flow_statement(xbrl_json):
    cash_flows_store = {}

    for usGaapItem in xbrl_json['StatementsOfCashFlows']:
        values = []
        indicies = []

        for fact in xbrl_json['StatementsOfCashFlows'][usGaapItem]:        
            # only consider items without segment.
            if 'segment' not in fact:
                # check if date instant or date range is present
                if "instant" in fact['period']:
                    index = fact['period']['instant']
                else:
                    index = fact['period']['startDate'] + '-' + fact['period']['endDate']

                # avoid duplicate indicies with same values
                if index in indicies:
                    continue

                if "value" not in fact:
                    values.append(0)
                else:
                    values.append(fact['value'])

                indicies.append(index)                    

        cash_flows_store[usGaapItem] = pd.Series(values, index=indicies) 


    cash_flows = pd.DataFrame(cash_flows_store)
    return cash_flows

6. Clean the results

Need to do a little formatting on the results before we can use them for analysis


def clean_financial_df(df):
    """
    Cleans a DataFrame where:
    - The index represents a 'date' column so we merge this back to the dataframe as data.
    - The date colum is the start and end date merged so we split to two new columns
    - Drop the date
    - Reorder
    - Fill the NaN with 0
      Returns a cleaned, ready-to-use DataFrame.
    """
    # Step 1: Move index to a column
    df = df.reset_index()
    df = df.rename(columns={"index": "date"})
    # Step 3: Parse date column
    df['start_date'] = df['date'].str[:10]
    df['end_date'] = df['date'].str[-10:]
    df = df.drop(columns=['date'])
    # List the columns you want first
    first_cols = ['start_date', 'end_date']
    # Add the rest of the columns (excluding the first ones to avoid duplication)
    remaining_cols = [col for col in df.columns if col not in first_cols]

    # Reorder the DataFrame
    df = df[first_cols + remaining_cols]
    return df.fillna(0)  

Example usage:

import sqlite3

income_statement = get_income_statement(xbrl_json)
income_statement = clean_financial_df(income_statement)
income_statement
...

# Connect to SQLite database (or create one)
conn = sqlite3.connect("financial_data.db")

# Write the dataframe to a new table
income_statement.to_sql("income_statement", conn, if_exists="replace", index=False)
balance_sheet.to_sql("balance_sheet", conn, if_exists="replace", index=False)
cash_flow.to_sql("cash_flow", conn, if_exists="replace", index=False)

# Close the connection
conn.close()

1.1 Planning & Multi-Step Reasoning

Use case: Build a retirement plan based on income, expenses, and goals.

task = "Create a retirement plan for a 35-year-old earning €80,000/year."

1.2 Tool Use

Use case: Calculate compound interest, risk scores, or tax.

agent.call_tool("compound_interest", principal=10000, rate=0.05, years=10)

1.3 Self-Reflection

Use case: Review financial advice given by the agent for compliance or correctness.

feedback = agent.get_feedback("Recommended investing all savings into crypto.")

1.4 Memory

Use case: Remember client financial profile across sessions (risk tolerance, income, etc.)

agent.memory.remember("risk_profile", "moderate")

Section 2: Application-Specific Agents – Finance Edition

2.1 Web Agent

Task: Automate checking of stock prices or scraping regulatory filings from the SEC.

agent.run_task("Find latest 10-K for Tesla from sec.gov")

2.2 SWE Agent

Task: Write a Python script to compute financial ratios or build a backtester.

agent.generate_code("Build a Python function that computes Sharpe Ratio")

2.3 Scientific Agent

Task: Design and test a hypothesis like “How do interest rates impact tech stock returns?”

agent.design_experiment("Test correlation between FED rates and NASDAQ performance")

2.4 Conversational Agent

Task: Serve as a virtual financial advisor with access to client data and investment tools.

User: "I want to retire by 60 with €1M saved. Can you help me plan?"

Section 3: Generalist Agent – Financial Analyst Bot

Scenario: Build a financial agent that can:

  • Pull EDGAR data
  • Run risk calculations
  • Answer investor questions
  • Summarize quarterly reports

Build a class like GeneralistAgent that integrates Fin-R1 and modular tools.


Section 4: Evaluation Frameworks for Financial Agents

4.1 What to Evaluate?

Evaluation Dimension Example Metric
Task Completion Success rate
Tool Use Correct tool call rate
Compliance Violation check pass rate
Memory Use Recall precision
Cost Tokens, API hits

4.2 Judging Fin-R1 Outputs in Agent Context

Use LLM-as-a-judge to score:

  • Accuracy
  • Clarity
  • Compliance
  • Risk alignment

  • LLM-as-Core, Agent-as-Orchestrator
  • Compliance-Aware Agents
  • Financial Copilots (e.g., Robo-Advisors)
  • Modular Multi-Agent Systems

5.2 DIY Project Ideas

Project Strategy Fin-R1 Role
SEC Filing Summarizer Web Agent Extract + summarize
Portfolio Simulator Scientific + Planner Generate + analyze risk
Multilingual Advisor Conversational Agent Q&A + translate
Compliance Copilot Reflection Validate advice

5.3 Example: Fin-R1 Compliance Wrapper

class ComplianceAgent:
    def __init__(self, fin_r1, compliance_rules):
        self.fin_r1 = fin_r1
        self.rules = compliance_rules

    def review_advice(self, user_profile, query):
        advice = self.fin_r1.query(user_profile, query)
        is_compliant = self.evaluate_against_rules(advice, user_profile)
        return {
            "advice": advice,
            "compliance": "βœ…" if is_compliant else "⚠️ Not suitable"
        }

    def evaluate_against_rules(self, advice, profile):
        if profile['risk'] == "conservative" and "crypto" in advice.lower():
            return False
        return True

5.4 What’s Coming Next

  • Agent self-play for better planning
  • Simulation gyms for financial scenarios
  • Open FinOps copilots
  • Autonomous portfolio managers

Final Thoughts

Fin-R1 is the financial brain. Agents are the nervous system that moves, senses, adapts, and reflects. Together, they form intelligent, compliant, adaptive financial systems for the modern age.

Ready to build your own? Let’s code it next.

References

Survey on Evaluation of LLM-based Agents