Overview
SpendWisely George tracks your mutual fund portfolio by fetching real-time NAV (Net Asset Value) data from MFapi.in , a free public API for Indian mutual funds. Users manually add holdings (scheme code + units), and the system calculates current portfolio value automatically.
How It Works
User adds holdings
Enter scheme code and units in Settings panel
Saved to JSON file
Holdings stored in holdings.json on server
Fetch latest NAV
Backend calls MFapi.in for each scheme’s current NAV
Calculate value
Portfolio value = Σ(units × NAV) for all holdings
Display in UI
Total portfolio value shown in dashboard card
Adding Holdings
Frontend UI
Settings panel provides input for scheme code and units:
< div class = "p-4 bg-green-50 rounded-2xl border border-green-100" >
< h3 class = "font-bold text-green-900 mb-2" > Mutual Fund Holdings </ h3 >
< div id = "mfList" class = "space-y-2 mb-2" ></ div >
< div class = "flex gap-2" >
< input type = "text" id = "mfCode" placeholder = "Scheme Code"
class = "flex-1 p-3 bg-white rounded-xl text-sm" >
< input type = "number" id = "mfUnits" placeholder = "Units"
class = "w-20 p-3 bg-white rounded-xl text-sm" >
</ div >
< button onclick = " addHolding ()"
class = "w-full mt-2 bg-green-600 text-white p-3 rounded-xl font-bold text-sm" >
Add Holding
</ button >
< button onclick = " saveHoldings ()"
class = "w-full mt-2 bg-slate-900 text-white p-3 rounded-xl font-bold text-sm" >
Save & Update
</ button >
</ div >
JavaScript Implementation
let currentHoldings = [];
// Fetch existing holdings from backend
async function fetchHoldings () {
try {
const res = await fetch ( '/api/holdings' );
currentHoldings = await res . json ();
renderHoldings ();
} catch ( e ) { }
}
// Render holdings list
function renderHoldings () {
const list = document . getElementById ( 'mfList' );
list . innerHTML = currentHoldings . map (( h , i ) =>
`<div class="flex justify-between text-xs bg-white p-2 rounded border">
<span class="font-mono"> ${ h . scheme_code } </span>
<span> ${ h . units } units</span>
<button onclick="removeHolding( ${ i } )" class="text-red-500">x</button>
</div>`
). join ( '' );
}
// Add new holding to local array
function addHolding () {
const code = document . getElementById ( 'mfCode' ). value ;
const units = parseFloat ( document . getElementById ( 'mfUnits' ). value );
if ( code && units ) {
currentHoldings . push ({ scheme_code: code , units: units });
renderHoldings ();
document . getElementById ( 'mfCode' ). value = '' ;
document . getElementById ( 'mfUnits' ). value = '' ;
}
}
// Remove holding from array
function removeHolding ( i ) {
currentHoldings . splice ( i , 1 );
renderHoldings ();
}
// Save all holdings to backend
async function saveHoldings () {
await fetch ( '/api/holdings' , {
method: 'POST' ,
body: JSON . stringify ( currentHoldings ),
headers: { 'Content-Type' : 'application/json' }
});
alert ( "Holdings Saved!" );
fetchPortfolio (); // Recalculate portfolio value
}
// Initialize on page load
fetchHoldings ();
Backend Storage
Holdings Data Model
from pydantic import BaseModel
from typing import List
import json
import os
HOLDINGS_FILE = "./holdings.json"
class Holding ( BaseModel ):
scheme_code: str
units: float
def load_holdings ():
if os.path.exists( HOLDINGS_FILE ):
try :
with open ( HOLDINGS_FILE , "r" ) as f:
return json.load(f)
except :
return []
return []
def save_holdings ( holdings ):
with open ( HOLDINGS_FILE , "w" ) as f:
json.dump(holdings, f)
API Endpoints
@app.get ( "/api/holdings" )
def get_holdings_api ():
return load_holdings()
@app.post ( "/api/holdings" )
def set_holdings_api ( holdings : List[Holding]):
data = [{ "scheme_code" : h.scheme_code, "units" : h.units} for h in holdings]
save_holdings(data)
return { "status" : "updated" }
NAV Fetching & Calculation
Portfolio Endpoint
Fetches NAV for each holding and calculates total value:
import requests
@app.get ( "/api/portfolio" )
def get_portfolio ():
total_value = 0
portfolio = []
holdings = load_holdings()
for holding in holdings:
code = holding[ "scheme_code" ]
units = float (holding[ "units" ])
try :
# Call MFapi.in for this scheme
resp = requests.get( f "https://api.mfapi.in/mf/ { code } " )
data = resp.json()
if data and "data" in data and len (data[ "data" ]) > 0 :
nav = float (data[ "data" ][ 0 ][ "nav" ]) # Latest NAV
fund_name = data[ "meta" ][ "scheme_name" ]
value = units * nav
portfolio.append({
"scheme_code" : code,
"scheme_name" : fund_name,
"units" : units,
"nav" : nav,
"current_value" : round (value, 2 )
})
total_value += value
except Exception as e:
print ( f "Error fetching { code } : { e } " )
return {
"portfolio" : portfolio,
"total_value" : round (total_value, 2 )
}
Example Response
Parsing Logic
{
"meta" : {
"fund_house" : "Axis Mutual Fund" ,
"scheme_type" : "Open Ended Schemes" ,
"scheme_category" : "Equity Scheme - Flexi Cap" ,
"scheme_code" : 120503 ,
"scheme_name" : "Axis Bluechip Fund - Direct Plan - Growth"
},
"data" : [
{
"date" : "05-03-2026" ,
"nav" : "84.5231"
},
{
"date" : "04-03-2026" ,
"nav" : "84.1234"
}
],
"status" : "SUCCESS"
}
Frontend Display
Portfolio Card
< div class = "card p-4 bg-white" >
< p class = "text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1" >
Investments
</ p >
< h3 id = "uiPortfolio" class = "text-xl font-black text-slate-800" > ₹0 </ h3 >
< p class = "text-[10px] text-green-500 font-bold mt-1" > MFapi.in Linked </ p >
</ div >
Fetching Portfolio Value
async function fetchPortfolio () {
try {
const res = await fetch ( '/api/portfolio' );
const data = await res . json ();
if ( data . total_value ) {
appData . portfolioValue = data . total_value ;
renderUI (); // Updates the card display
}
} catch ( e ) {
console . error ( "Portfolio Fetch Error" , e );
}
}
// Called on page load and after saving holdings
fetchPortfolio ();
Total Balance Integration
function renderUI () {
const showBal = appData . showBalance || ! appData . privacy ;
// Total balance includes sheet balance + bank + portfolio
const totalBal = appData . balance +
( appData . bankBalance || 0 ) +
( appData . portfolioValue || 0 );
const updateDisplay = ( id , val ) => {
const el = document . getElementById ( id );
if ( showBal && el )
el . innerText = `₹ ${ Number ( val ). toLocaleString ( 'en-IN' ) } ` ;
else if ( el )
el . innerText = "••••••" ;
};
updateDisplay ( 'uiBalance' , totalBal );
updateDisplay ( 'uiPortfolio' , appData . portfolioValue || 0 );
}
Finding Scheme Codes
Where to find scheme codes : Visit MFapi.in and search for your fund. The scheme code is shown in the URL and API response.
Example Scheme Codes
Axis Bluechip - Direct Growth Code: 120503
SBI Bluechip - Regular Growth Code: 100042
HDFC Flexi Cap - Direct Growth Code: 119591
Parag Parikh Flexi Cap - Direct Growth Code: 122639
Searching for Codes
# Search by fund name
curl "https://api.mfapi.in/mf/search?q=axis%20bluechip"
# Get details by code
curl "https://api.mfapi.in/mf/120503"
Data Flow Diagram
Advanced Features
Portfolio Breakdown
While the current UI only shows total value, the backend returns detailed portfolio data:
{
"portfolio" : [
{
"scheme_code" : "120503" ,
"scheme_name" : "Axis Bluechip Fund - Direct Plan - Growth" ,
"units" : 150.5 ,
"nav" : 84.5231 ,
"current_value" : 12720.73
},
{
"scheme_code" : "119591" ,
"scheme_name" : "HDFC Flexi Cap Fund - Direct Plan - Growth" ,
"units" : 200.0 ,
"nav" : 125.45 ,
"current_value" : 25090.00
}
],
"total_value" : 37810.73
}
This can be used to build:
Individual fund cards
Pie charts showing allocation
Gains/loss tracking (if purchase NAV is stored)
Limitations
Manual Updates Required : Unlike bank sync, holdings are not auto-updated. Users must manually add new purchases or update units after redemptions.
No Purchase Tracking : The system doesn’t track purchase date or NAV, so gains/losses cannot be calculated. Only current value is shown.
NAV Freshness : MFapi.in updates NAV data after market close (~6 PM IST). Intraday values are not available.
Future Enhancements
Potential improvements to portfolio tracking:
Store purchase date and NAV to calculate:
Absolute gains/losses
XIRR (annualized returns)
Days held
Auto-sync from CAMS/Karvy
Integrate with CAS (Consolidated Account Statement) to automatically fetch holdings
Allocation pie chart (equity vs debt)
Value over time graph
Fund-wise performance comparison
Associate holdings with financial goals (retirement, house, etc.)
Best Practices
Update after SIP Add new units immediately after each SIP investment
Use Direct Plans Direct plan codes have lower expense ratios than regular plans
Review quarterly Check portfolio allocation and rebalance if needed
Backup holdings Keep a copy of holdings.json as backup
Troubleshooting
Check:
Holdings are saved (look for holdings.json file)
Scheme codes are valid (test on mfapi.in)
Internet connection is active
Debug: curl http://localhost:8000/api/portfolio
MFapi.in returns the latest available NAV , which might be T-1 (previous day) if today’s NAV isn’t published yet.
Verify code: curl "https://api.mfapi.in/mf/120503"
If it returns an error, the code is invalid. Search for the correct code on mfapi.in.
Ensure holdings.json has write permissions: ls -la holdings.json
chmod 644 holdings.json