MAF (Multi Agent Financial Assistant) is a Spring Boot backend that provides account management, portfolio/dashboard data, stock price fetching, trade execution endpoints, and a multi-agent chat subsystem with optional direct LLM integration.
This README documents the project's purpose, configuration, authentication, all HTTP endpoints (only authentication endpoints are documented as public), error behaviors, observability (Prometheus/Grafana), performance testing guidance (JMeter + performance_data.xlsx), and step-by-step run instructions for Windows (cmd.exe) including optional services.
- Project overview
- Key features (recent changes)
- Configuration (properties & environment variables)
- Authentication & token lifecycle
- Endpoints (overview; only auth endpoints are public)
- Chat subsystem & Gemini LLM integration
- Observability: Prometheus, Grafana, metrics
- Performance testing: JMeter and
performance_data.xlsx - Running the app (Windows cmd.exe) and Docker services (Redis, Prometheus, Grafana)
- Troubleshooting & notes
MAF is a backend service offering:
- Signup / Login (JWT access token + refresh token in HttpOnly cookie)
- Profile endpoints for user info, balance and holdings
- Dashboard and transaction endpoints
- Stock price fetching and daily prices
- Execution endpoints to buy / sell -> creates Transaction records and updates holdings
- Multi-agent chat endpoints: general, execution-assistant (EA), market-research-assistant (MRA)
- Chat queries are persisted per user and can be forwarded to agent services or an LLM when configured
The project uses Spring Boot (Java 17+), Spring Security, Spring Data JPA / Hibernate, Micrometer for metrics, and the Java HTTP client for outward requests.
- Chat endpoints now accept a compact request body:
{ "query": "..." }. general-chatcan call Gemini LLM whengemini.api.keyis present; otherwise requests are forwarded to configured agent endpoints.- Chat requests are persisted with the authenticated
userIdso the service can remember users and maintain chat history. - Chat endpoint responses are returned as plain
Stringand are also saved as chat history. - Redis cache option has been disabled in this codebase (the cache wiring was commented out). Redis is no longer required for normal operation; configuration and Docker snippets remain for optional setups but are not necessary.
- Observability: Micrometer instrumentation with Prometheus exposition via Spring Boot Actuator (
/actuator/prometheus) is included. - New transaction and stock daily price filters: endpoints support optional query parameters for date range (
startDate/endDate),beforeDate, andafterDate— any combination is allowed and all are optional. - Performance testing artifacts:
performance_data.xlsxat repo root contains prior benchmark results and guidance.
Place properties in src/main/resources/application.properties or provide them as environment variables.
Important properties:
jwt.secret(required) - secret used to sign access tokens (HS256); must be sufficiently strong.allowed_origins- Comma-separated list for CORS (e.g.,http://localhost:3000).
Optional / integrations
gemini.api.key- API key used to call the Gemini LLM (if you want chat to hit Gemini directly).gemini.endpoint- Gemini API endpoint (optional; a sensible default is used in code when absent).agents_endpoint- Base URL for agent services (forwards chat queries when Gemini is not used).- Redis config (deprecated/disabled by default in the repo):
spring.redis.host,spring.redis.port,spring.redis.password— note the project currently comments out caching code and does not rely on Redis. management.endpoints.web.exposure.include- includeprometheusandhealthfor the actuator (recommended:health,info,prometheus).
Example snippet to add to application.properties:
# Security
jwt.secret=YOUR_BASE64_SECRET_HERE
allowed_origins=http://localhost:3000
# Agents & LLM
agents_endpoint=http://localhost:8081/agents/
# Optional Gemini LLM
gemini.api.key=YOUR_GEMINI_API_KEY
# gemini.endpoint=https://api.gemini.example/v1/complete
# Redis (deprecated/disabled) - not required for normal operation
# spring.redis.host=localhost
# spring.redis.port=6379
# spring.redis.password=
# Actuator / Prometheus
management.endpoints.web.exposure.include=health,info,prometheus
management.endpoint.prometheus.enabled=true
Secrets such as gemini.api.key should be provided via environment variables on production or CI.
Auth flow summary:
POST /auth/signup— create account, publicPOST /auth/login— returnsApiResponsewithdatacontainingaccessTokenanduserfields; also setsrefresh_tokencookie (HttpOnly)- Protected endpoints require header:
Authorization: Bearer <accessToken> POST /auth/refresh— uses therefresh_tokencookie to issue a new access tokenPOST /auth/logout— clears the refresh cookie
Security rules (from SecurityConfig) - typical mapping in this project:
- Public (no auth required):
/auth/**,/(home/health), andOPTIONS /**. - All other endpoints require a valid access token.
Token details:
- Access token: short-lived JWT in response; send in Authorization header.
- Refresh token: long-lived, stored as HttpOnly cookie with name
refresh_token.
Important: Only authentication endpoints are public. All other endpoints require a valid JWT access token passed in the header:
Authorization: Bearer <token>
-
POST /auth/signup- Description: Creates a new user account.
- Expects (JSON):
{ "email": "user@example.com", "password": "securePassword", "firstName": "John", "lastName": "Doe" } - Returns (201 Created):
{ "success": true, "message": "User signed up successfully", "data": { "id": 1, "email": "user@example.com", "firstName": "John", "lastName": "Doe" } }
-
POST /auth/login- Description: Authenticates a user and issues tokens. Sets a
refresh_tokenHttpOnly cookie. - Expects (JSON):
{ "email": "user@example.com", "password": "securePassword" } - Returns (200 OK):
{ "success": true, "message": "Login successful", "data": { "accessToken": "eyJhbGciOiJIUzI1NiIsIn...", "user": { "id": 1, "email": "user@example.com" } } }
- Description: Authenticates a user and issues tokens. Sets a
-
POST /auth/refresh- Description: Exchanges a valid refresh cookie for a fresh access token.
- Expects: A valid
refresh_tokenHttpOnly cookie attached to the request. - Returns (200 OK):
{ "success": true, "message": "Token refreshed successfully", "data": { "accessToken": "eyJhbGciOiJIUzI1...new" } }
-
POST /auth/logout- Description: Revokes the refresh cookie to log out the user.
- Expects: A valid
refresh_tokenHttpOnly cookie. - Returns (200 OK):
{ "success": true, "message": "Logged out successfully", "data": null }
-
GET /profile/me- Description: Retrieves the authenticated user's profile details.
- Returns (200 OK):
{ "success": true, "message": "Profile retrieved", "data": { "id": 1, "email": "user@example.com", "firstName": "John", "lastName": "Doe" } }
-
PUT /profile/me- Description: Updates the user's profile information.
- Expects (JSON):
{ "firstName": "Johnny", "lastName": "Doe" } - Returns (200 OK):
{ "success": true, "message": "Profile updated", "data": { "id": 1, "email": "user@example.com", "firstName": "Johnny", "lastName": "Doe" } }
-
GET /profile/balance- Description: Retrieves the user's available cash balance.
- Returns (200 OK):
{ "success": true, "message": "Balance retrieved", "data": { "balance": 15000.50 } }
-
GET /profile/holdings- Description: Retrieves the user's current asset holdings (portfolio).
- Returns (200 OK):
{ "success": true, "message": "Holdings retrieved", "data": [ { "symbol": "AAPL", "quantity": 15, "averagePrice": 145.20 } ] }
-
GET /alert- Description: Gets all active price alerts for the user.
- Returns (200 OK):
[ { "id": 10, "symbol": "AAPL", "targetPrice": 160.00, "condition": "ABOVE", "active": true } ]
-
POST /alert- Description: Creates a new price alert.
- Expects (JSON):
{ "symbol": "TSLA", "targetPrice": 200.00, "condition": "BELOW" } - Returns (201 Created):
{ "id": 11, "symbol": "TSLA", "targetPrice": 200.00, "condition": "BELOW", "active": true }
-
PUT /alert/{id}- Description: Updates an existing active price alert.
- Expects (JSON):
{ "targetPrice": 190.00, "condition": "BELOW", "active": true } - Returns (200 OK):
{ "id": 11, "symbol": "TSLA", "targetPrice": 190.00, "condition": "BELOW", "active": true }
-
DELETE /alert/{id}- Description: Deletes a specific alert by ID.
- Returns (200 OK):
{ "success": true, "message": "Alert deleted successfully" }
-
GET /strategy- Description: Returns the user's active trading strategy details.
- Returns (200 OK):
{ "id": 5, "name": "Moving Average Crossover", "parameters": { "shortWindow": 10, "longWindow": 50 }, "active": true }
-
GET /strategy/history- Description: Returns a history of past strategy executions and performance data.
- Returns (200 OK):
[ { "executionId": 1001, "strategyName": "Moving Average Crossover", "executedAt": "2026-04-20T10:30:00Z", "pnl": 120.50 } ]
-
POST /strategy- Description: Saves a new trading strategy configuration.
- Expects (JSON):
{ "name": "Momentum", "parameters": { "rsiThreshold": 70 } } - Returns (201 Created):
{ "id": 6, "name": "Momentum", "parameters": { "rsiThreshold": 70 }, "active": true }
-
PUT /strategy/{id}- Description: Updates the parameters of an existing trading strategy.
- Expects (JSON):
{ "parameters": { "rsiThreshold": 75 }, "active": false } - Returns (200 OK):
{ "id": 6, "name": "Momentum", "parameters": { "rsiThreshold": 75 }, "active": false }
GET /portfolio- Description: Returns high-level portfolio metrics.
- Returns (200 OK):
{ "totalValue": 250000.00, "totalInvested": 200000.00, "overallProfit": 50000.00, "percentageChange": 25.0 }
GET /master-data- Description: Fetches application metadata (like supported stocks) for frontend usage.
- Returns (200 OK):
{ "supportedStocks": ["AAPL", "GOOGL", "MSFT", "TSLA"], "tradingHours": "09:30-16:00 EST" }
-
GET /transactions- Description: Lists user transactions. Supports optional date filters.
- Query Params (Optional):
startDate,endDate,beforeDate,afterDate(Format:YYYY-MM-DD). - Returns (200 OK):
{ "success": true, "data": [ { "id": 101, "symbol": "AAPL", "type": "BUY", "quantity": 5, "price": 150.00, "date": "2026-04-10T14:20:00Z" } ] }
-
GET /dashboard- Description: Returns aggregated overview data for the user dashboard.
- Returns (200 OK):
{ "success": true, "data": { "userBalance": 10500.00, "recentActivity": [], "portfolioHealth": "GOOD" } }
-
GET /stockprice?symbol={SYMBOL}- Description: Fetches the real-time or latest available price for a stock.
- Returns (200 OK):
{ "success": true, "data": { "symbol": "AAPL", "currentPrice": 155.00, "timestamp": "2026-04-25T10:00:00Z" } }
-
GET /stockdailyprices?symbol={SYMBOL}- Description: Fetches historical daily prices. Supports optional date filters.
- Query Params:
symbol(required). Optional:startDate,endDate,beforeDate,afterDate(Format:YYYY-MM-DD). - Returns (200 OK):
{ "success": true, "data": [ { "date": "2026-04-24", "closePrice": 154.50, "volume": 1200000 } ] }
-
GET /stockchange?symbol={SYMBOL}- Description: Returns recent price variations for the specified stock.
- Returns (200 OK):
{ "success": true, "data": { "symbol": "AAPL", "absoluteChange": 2.50, "percentChange": 1.63 } }
-
POST /execute/buy- Description: Executes a buy order, deducting balance and adding to holdings.
- Expects (JSON):
{ "symbol": "AAPL", "quantity": 10, "orderType": "MARKET" } - Returns (200 OK):
{ "success": true, "message": "Buy order executed successfully", "data": { "transactionId": 102, "symbol": "AAPL", "quantity": 10, "price": 155.00 } }
-
POST /execute/sell- Description: Executes a sell order, adding to balance and reducing holdings.
- Expects (JSON):
{ "symbol": "AAPL", "quantity": 5, "orderType": "MARKET" } - Returns (200 OK):
{ "success": true, "message": "Sell order executed successfully", "data": { "transactionId": 103, "symbol": "AAPL", "quantity": 5, "price": 155.50 } }
-
POST /general-chat,POST /ea-chat,POST /mra-chat- Description: Send queries to the multi-agent chat system (or Gemini LLM).
- Expects (JSON):
{ "query": "Should I buy more AAPL stock?" } - Returns (200 OK - plain text):
"Based on current market conditions and your portfolio, increasing your AAPL position could be beneficial but watch out for upcoming earnings reports."
-
GET /chats- Description: Retrieves the chat history corresponding to the authenticated user.
- Returns (200 OK):
[ { "id": 50, "userId": 1, "query": "Should I buy more AAPL stock?", "response": "Based on current market conditions...", "timestamp": "2026-04-25T11:00:00Z" } ]
Errors and conventions:
- Most endpoints return
ApiResponse<T>withsuccess,message, anddataexcept chat POST endpoints that return plainString. - Standard HTTP codes used: 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found), 409 (Conflict), 500 (Internal Server Error).
Both the transactions list and stock daily prices endpoints now accept three optional query params that can be used together or separately:
startDateandendDate— define an inclusive date range. Use ISO-8601 date format:YYYY-MM-DD.beforeDate— returns records strictly before this date (exclusive). Format:YYYY-MM-DD.afterDate— returns records strictly after this date (exclusive). Format:YYYY-MM-DD.
Behavior notes:
- All parameters are optional. If none provided, the endpoint returns the default (usually recent or paginated) results for the authenticated user.
- If both
startDate/endDateandbeforeDate/afterDateare present, they are combined logically (the repository/service layer applies filters cumulatively). - Examples:
- Date range:
GET /transactions?startDate=2026-01-01&endDate=2026-01-31 - Before a date:
GET /transactions?beforeDate=2026-01-01 - After a date:
GET /transactions?afterDate=2026-02-01 - Combined:
GET /stockdailyprices?symbol=AAPL&afterDate=2026-01-01&beforeDate=2026-03-01
- Date range:
Make sure to pass the access token header for these endpoints:
Authorization: Bearer <accessToken>
Behavior summary:
- Request shape: All chat POST endpoints expect a compact JSON body:
{ "query": "..." }. - The authenticated user is inferred from the access token; the service saves the incoming user message in the chat history table with the
userIdand timestamp. general-chatbehavior:- If
gemini.api.keyis present in configuration, ChatService will POST to the Gemini endpoint with a JSON body containing the user query and theuserId(example body:{ "query": "...", "userId": 123 }) and an authorization header using the configured key. The exact header format depends on the Gemini provider; the default implementation usesAuthorization: Bearer <GEMINI_KEY>. - The service expects Gemini's response to include assistant text; the helper extracts that text and returns it as a plain
Stringto the caller and persists it in chat history. - If Gemini is not configured, ChatService forwards the request to
agents_endpoint + "general-agent"and forwards the user's JWT so the agent can authorize/recognize the user. - If the agent/LLM is unreachable or returns an error, the service responds with a friendly fallback message such as:
Our chat assistant is under active development and temporarily unavailable — please try again shortly.
- If
Security and persistence:
- Chats are stored per
userId, enabling the service to reference a user's prior messages for future context. - The service only returns the assistant message as a String to simplify client usage; clients wanting more structure can retrieve chat history via
GET /chats.
Example request (Windows cmd.exe curl style):
curl -X POST http://localhost:8080/general-chat -H "Authorization: Bearer <accessToken>" -H "Content-Type: application/json" -d "{\"query\":\"Hello assistant\"}"
Example improved generic fallback response used by the application when LLM/agent is unavailable:
"Thanks for your message — our chat assistant is being actively improved and is currently in a limited-access phase. We appreciate your patience; please try again in a few moments or use the dashboard actions for critical requests."
Notes on Gemini integration:
- The code expects Gemini to return JSON with a field containing assistant text. Adjust parsing logic in
ChatServiceif your Gemini endpoint returns a different schema. - Keep
gemini.api.keysecret — provide via environment variables in production.
The project includes Micrometer + Spring Boot Actuator support to expose runtime metrics that Prometheus can scrape. Typical setup steps:
- Enable prometheus endpoint in
application.properties(see Configuration section):
management.endpoints.web.exposure.include=health,info,prometheus
management.endpoint.prometheus.enabled=true
- Minimal Prometheus config (
prometheus.yml):
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'maf'
static_configs:
- targets: ['host.docker.internal:8080']
- Run Grafana and add Prometheus as a data source. Useful dashboards:
- JVM memory and GC
- HTTP request count, errors, latency percentiles (p50/p95/p99)
- Custom app metrics (chat request counts, agent latency)
Docker example (Windows cmd.exe):
REM Start Prometheus (uses default prometheus.yml on cwd)
docker run -p 9090:9090 -v %cd%\prometheus.yml:/etc/prometheus/prometheus.yml --name prometheus -d prom/prometheus
REM Start Grafana
docker run -d -p 3000:3000 --name grafana grafana/grafana
Prometheus will scrape /actuator/prometheus if the JVM app is reachable by the Prometheus container or host.
Suggested metrics during performance testing:
- request rate per-endpoint
- latency percentiles (p50/p95/p99)
- error rate
- JVM memory and GC metrics
- DB connection pool usage
The repository includes performance_data.xlsx at the project root. It contains previous benchmark results and per-endpoint latency/throughput breakdowns. Use it as a baseline for comparison.
How to reproduce tests and update the spreadsheet:
- Start the application and any required services (database). Redis is not required by default because caching was disabled in the codebase.
- Use JMeter (recommended),
wrk, orheyto generate load and collect latency/throughput. - Export JMeter summary and copy p50/p95/p99, throughput, and error counts into
performance_data.xlsx.
JMeter quick-start (Windows cmd.exe):
REM Run JMeter non-GUI on Windows cmd.exe
jmeter -n -t test-plan.jmx -l results.jtl -e -o report-folder
JMeter test plan notes:
- Add an HTTP Header Manager with
Authorization: Bearer <token>for protected endpoints. - Use
{ "query": "load test message" }as the JSON body when testing chat endpoints.
Prereqs:
- Java 17+
- Maven (or use the included
mvnw.cmd) - Docker (recommended for optional services like Prometheus/Grafana; Redis is optional and not required by default)
- (Optional) Start Redis if you still want it for experimentation (the project's cache layer is commented out; Redis is not required):
REM Start a Redis container (optional)
docker run -p 6379:6379 --name redis -d redis:7
- Set environment variables (cmd.exe example):
set JWT_SECRET=your-base64-secret
set GEMINI_API_KEY=your_gemini_key
set AGENTS_ENDPOINT=http://localhost:8081/agents/
- Run the app with Maven wrapper in Windows cmd.exe:
mvnw.cmd spring-boot:run
Or build and run the JAR:
mvnw.cmd package
java -jar target/MAFA-0.0.1-SNAPSHOT.jar
- Visit health and metrics endpoints:
- Health: http://localhost:8080/actuator/health
- Prometheus metrics: http://localhost:8080/actuator/prometheus
Login and receive tokens (public):
curl -X POST http://localhost:8080/auth/login -H "Content-Type: application/json" -d '{"email":"user@example.com","password":"pass"}'
Call a protected chat endpoint after receiving an access token (Windows cmd.exe curl example):
curl -X POST http://localhost:8080/general-chat -H "Authorization: Bearer <accessToken>" -H "Content-Type: application/json" -d "{\"query\":\"Hello assistant\"}"
Transactions with date filters examples:
REM Date range
curl -X GET "http://localhost:8080/transactions?startDate=2026-01-01&endDate=2026-01-31" -H "Authorization: Bearer <token>"
REM Before date
curl -X GET "http://localhost:8080/transactions?beforeDate=2026-01-01" -H "Authorization: Bearer <token>"
REM After date
curl -X GET "http://localhost:8080/transactions?afterDate=2026-02-01" -H "Authorization: Bearer <token>"
Stock daily prices with filters:
curl -X GET "http://localhost:8080/stockdailyprices?symbol=AAPL&startDate=2026-01-01&endDate=2026-01-31" -H "Authorization: Bearer <token>"
If token is missing or expired you'll receive 401 Unauthorized. If refresh token is expired, POST /auth/refresh will return 403.
- Authentication failing: ensure
jwt.secretis set and the token is signed using that secret. If tokens are rejected, check system time and token expiry. - Chat requests failing: increase logging for
majorproject.maf.service.ChatServiceandmajorproject.maf.controller.ChatControllerto see outgoing HTTP requests and responses. - Prometheus not scraping: check
prometheus.ymltarget address and network connectivity. - If agent forwarding fails (503), the fallback response will be returned; check
agents_endpointand network connectivity.
majorproject.maf.controller— controllers (AuthController, ChatController, DashboardController, PriceFetchController, ExecutionController, ProfileController)majorproject.maf.service— business logic (AuthService, ChatService, ExecutionService, PriceFetch)majorproject.maf.model— JPA entities (User, Chat, Transaction, Stock, StockPrice)majorproject.maf.exception— global exception handling and custom exceptionssrc/main/resources/application.properties— example properties referenced above
File location: performance_data.xlsx at the project root.
Contents and how to use:
- The workbook contains sheets summarizing previous benchmark runs: latency percentiles, throughput, error counts per endpoint, and resource usage across concurrency levels.
- Use it as a baseline and paste new results from JMeter or other tools into the sheets for comparisons.
If you want, I can also:
- Add an example
prometheus.ymland a minimal Grafana dashboard JSON to the repository. - Create a sample JMeter test plan (
.jmx) for the/general-chatendpoint and add a short script to run non-GUI tests and export results to CSV.
If you want any of those additions, tell me which and I'll add them to the repo.
Last updated: 2026-04-24