Agent Strategies for Financial Applications with Fin-R1
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:
- The agent gets a task: βSummarize this 10-K filingβ
- It chunks the document and calls MCP to build a prompt for each chunk
- MCP structures:
- System prompt
- chunk
- Model instructions
- 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
Section 5: Future Trends & DIY Experiments in Financial Agent Systems
5.1 Trends
- 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.