AMCAnalysis / app.py
singhn9's picture
Update app.py
3706982 verified
raw
history blame
13 kB
import gradio as gr
import pandas as pd
import networkx as nx
import plotly.graph_objects as go
import numpy as np
from collections import defaultdict
# ============================================================
# DATA
# ============================================================
AMCS = [
"SBI MF", "ICICI Pru MF", "HDFC MF", "Nippon India MF", "Kotak MF",
"UTI MF", "Axis MF", "Aditya Birla SL MF", "Mirae MF", "DSP MF"
]
COMPANIES = [
"HDFC Bank", "ICICI Bank", "Bajaj Finance", "Bajaj Finserv", "Adani Ports",
"Tata Motors", "Shriram Finance", "HAL", "TCS", "AU Small Finance Bank",
"Pearl Global", "Hindalco", "Tata Elxsi", "Cummins India", "Vedanta"
]
BUY_MAP = {
"SBI MF": ["Bajaj Finance", "AU Small Finance Bank"],
"ICICI Pru MF": ["HDFC Bank"],
"HDFC MF": ["Tata Elxsi", "TCS"],
"Nippon India MF": ["Hindalco"],
"Kotak MF": ["Bajaj Finance"],
"UTI MF": ["Adani Ports", "Shriram Finance"],
"Axis MF": ["Tata Motors", "Shriram Finance"],
"Aditya Birla SL MF": ["AU Small Finance Bank"],
"Mirae MF": ["Bajaj Finance", "HAL"],
"DSP MF": ["Tata Motors", "Bajaj Finserv"]
}
SELL_MAP = {
"SBI MF": ["Tata Motors"],
"ICICI Pru MF": ["Bajaj Finance", "Adani Ports"],
"HDFC MF": ["HDFC Bank"],
"Nippon India MF": ["Hindalco"],
"Kotak MF": ["AU Small Finance Bank"],
"UTI MF": ["Hindalco", "TCS"],
"Axis MF": ["TCS"],
"Aditya Birla SL MF": ["Adani Ports"],
"Mirae MF": ["TCS"],
"DSP MF": ["HAL", "Shriram Finance"]
}
COMPLETE_EXIT = {
"DSP MF": ["Shriram Finance"]
}
FRESH_BUY = {
"HDFC MF": ["Tata Elxsi"],
"UTI MF": ["Adani Ports"],
"Mirae MF": ["HAL"]
}
def sanitize_map(m):
out = {}
for k, vals in m.items():
out[k] = [v for v in vals if v in COMPANIES]
return out
BUY_MAP = sanitize_map(BUY_MAP)
SELL_MAP = sanitize_map(SELL_MAP)
COMPLETE_EXIT = sanitize_map(COMPLETE_EXIT)
FRESH_BUY = sanitize_map(FRESH_BUY)
# ============================================================
# GRAPH BUILDING
# ============================================================
company_edges = []
for amc, comps in BUY_MAP.items():
for c in comps:
company_edges.append((amc, c, {"action": "buy", "weight": 1}))
for amc, comps in SELL_MAP.items():
for c in comps:
company_edges.append((amc, c, {"action": "sell", "weight": 1}))
for amc, comps in COMPLETE_EXIT.items():
for c in comps:
company_edges.append((amc, c, {"action": "complete_exit", "weight": 3}))
for amc, comps in FRESH_BUY.items():
for c in comps:
company_edges.append((amc, c, {"action": "fresh_buy", "weight": 3}))
def infer_amc_transfers(buy_map, sell_map):
transfers = defaultdict(int)
company_to_sellers = defaultdict(list)
company_to_buyers = defaultdict(list)
for amc, comps in sell_map.items():
for c in comps:
company_to_sellers[c].append(amc)
for amc, comps in buy_map.items():
for c in comps:
company_to_buyers[c].append(amc)
for c in set(company_to_sellers.keys()) | set(company_to_buyers.keys()):
sellers = company_to_sellers[c]
buyers = company_to_buyers[c]
for s in sellers:
for b in buyers:
transfers[(s, b)] += 1
edge_list = []
for (s, b), w in transfers.items():
edge_list.append((s, b, {"action": "transfer", "weight": w}))
return edge_list
transfer_edges = infer_amc_transfers(BUY_MAP, SELL_MAP)
def build_graph(include_transfers=True):
G = nx.DiGraph()
for a in AMCS:
G.add_node(a, type="amc")
for c in COMPANIES:
G.add_node(c, type="company")
for u, v, attr in company_edges:
if u in G.nodes and v in G.nodes:
if G.has_edge(u, v):
G[u][v]["weight"] += attr["weight"]
G[u][v]["actions"].append(attr["action"])
else:
G.add_edge(u, v, weight=attr["weight"], actions=[attr["action"]])
if include_transfers:
for s, b, attr in transfer_edges:
if s in G.nodes and b in G.nodes:
if G.has_edge(s, b):
G[s][b]["weight"] += attr["weight"]
G[s][b]["actions"].append("transfer")
else:
G.add_edge(s, b, weight=attr["weight"], actions=["transfer"])
return G
# ============================================================
# PLOTLY NETWORK DRAWING
# ============================================================
def graph_to_plotly(
G,
node_color_amc="#9EC5FF",
node_color_company="#FFCF9E",
node_shape_amc="circle",
node_shape_company="circle",
edge_color_buy="#2ca02c",
edge_color_sell="#d62728",
edge_color_transfer="#888888",
edge_thickness_base=1.4,
show_labels=True
):
pos = nx.spring_layout(G, seed=42, k=1.2)
node_x, node_y, node_text, node_color, node_size = [], [], [], [], []
for n, d in G.nodes(data=True):
x, y = pos[n]
node_x.append(x)
node_y.append(y)
node_text.append(n)
if d["type"] == "amc":
node_color.append(node_color_amc)
node_size.append(40)
else:
node_color.append(node_color_company)
node_size.append(60)
node_trace = go.Scatter(
x=node_x, y=node_y,
mode="markers+text" if show_labels else "markers",
marker=dict(
color=node_color,
size=node_size,
line=dict(width=2, color="#222")
),
text=node_text if show_labels else None,
textposition="top center"
)
edge_traces = []
for u, v, attrs in G.edges(data=True):
acts = attrs.get("actions", [])
weight = attrs.get("weight", 1)
x0, y0 = pos[u]
x1, y1 = pos[v]
if "complete_exit" in acts:
color = edge_color_sell
dash = "solid"
width = edge_thickness_base * 3
elif "fresh_buy" in acts:
color = edge_color_buy
dash = "solid"
width = edge_thickness_base * 3
elif "transfer" in acts:
color = edge_color_transfer
dash = "dash"
width = edge_thickness_base * (1 + np.log1p(weight))
elif "sell" in acts:
color = edge_color_sell
dash = "dot"
width = edge_thickness_base * (1 + np.log1p(weight))
else:
color = edge_color_buy
dash = "solid"
width = edge_thickness_base * (1 + np.log1p(weight))
edge_traces.append(
go.Scatter(
x=[x0, x1, None],
y=[y0, y1, None],
mode="lines",
line=dict(color=color, width=width, dash=dash),
hoverinfo="none"
)
)
fig = go.Figure(data=edge_traces + [node_trace])
fig.update_layout(
showlegend=False,
height=900,
width=1400,
margin=dict(l=5, r=5, t=40, b=20),
xaxis=dict(visible=False),
yaxis=dict(visible=False)
)
return fig
# ============================================================
# COMPANY & AMC INSPECTION
# ============================================================
def company_trade_summary(company_name):
buyers = [a for a, comps in BUY_MAP.items() if company_name in comps]
sellers = [a for a, comps in SELL_MAP.items() if company_name in comps]
fresh = [a for a, comps in FRESH_BUY.items() if company_name in comps]
exits = [a for a, comps in COMPLETE_EXIT.items() if company_name in comps]
df = pd.DataFrame({
"Role": ["Buyer"] * len(buyers) + ["Seller"] * len(sellers)
+ ["Fresh buy"] * len(fresh) + ["Complete exit"] * len(exits),
"AMC": buyers + sellers + fresh + exits
})
if df.empty:
return None, pd.DataFrame([], columns=["Role", "AMC"])
counts = df.groupby("Role").size().reset_index(name="Count")
fig = go.Figure(go.Bar(
x=counts["Role"],
y=counts["Count"],
marker_color=["green", "red", "orange", "black"][:len(counts)]
))
fig.update_layout(
title_text=f"Trade summary for {company_name}",
height=300
)
return fig, df
def amc_transfer_summary(amc_name):
sold = SELL_MAP.get(amc_name, [])
transfers = []
for s in sold:
buyers = [a for a, comps in BUY_MAP.items() if s in comps]
for b in buyers:
transfers.append({"security": s, "buyer_amc": b})
df = pd.DataFrame(transfers)
if df.empty:
return None, pd.DataFrame([], columns=["security", "buyer_amc"])
counts = df["buyer_amc"].value_counts().reset_index()
counts.columns = ["Buyer AMC", "Count"]
fig = go.Figure(go.Bar(
x=counts["Buyer AMC"],
y=counts["Count"],
marker_color="lightslategray"
))
fig.update_layout(
title_text=f"Inferred transfers from {amc_name}",
height=300
)
return fig, df
# ============================================================
# INITIAL GRAPH
# ============================================================
initial_graph = build_graph(include_transfers=True)
initial_fig = graph_to_plotly(initial_graph)
# ============================================================
# GRADIO UI — CLEAN, FULL-WIDTH LAYOUT
# ============================================================
with gr.Blocks() as demo:
gr.Markdown("## Mutual Fund Churn Explorer — Full Network & Transfer Analysis")
# === FULL-WIDTH NETWORK GRAPH AT THE TOP ===
network_plot = gr.Plot(
value=initial_fig,
label="Network graph (drag to zoom)"
)
# === SETTINGS BELOW THE GRAPH ===
with gr.Accordion("Network Customization", open=True):
node_color_company = gr.ColorPicker("#FFCF9E", label="Company node color")
node_color_amc = gr.ColorPicker("#9EC5FF", label="AMC node color")
node_shape_company = gr.Dropdown(["circle", "square", "diamond"], value="circle",
label="Company node shape")
node_shape_amc = gr.Dropdown(["circle", "square", "diamond"], value="circle",
label="AMC node shape")
edge_color_buy = gr.ColorPicker("#2ca02c", label="BUY edge color")
edge_color_sell = gr.ColorPicker("#d62728", label="SELL edge color")
edge_color_transfer = gr.ColorPicker("#888888", label="Transfer edge color")
edge_thickness = gr.Slider(0.5, 6.0, value=1.4, step=0.1, label="Edge thickness base")
include_transfers = gr.Checkbox(value=True, label="Show AMC→AMC inferred transfers")
update_button = gr.Button("Update Network Graph")
gr.Markdown("### Inspect a Company (buyers / sellers)")
select_company = gr.Dropdown(choices=COMPANIES, label="Select company")
company_out_plot = gr.Plot(label="Company trade summary")
company_out_table = gr.DataFrame(label="Company table")
gr.Markdown("### Inspect an AMC (transfer analysis)")
select_amc = gr.Dropdown(choices=AMCS, label="Select AMC")
amc_out_plot = gr.Plot(label="AMC transfer summary")
amc_out_table = gr.DataFrame(label="AMC transfer table")
# === CALLBACKS ===
def update_network(node_color_company_val, node_color_amc_val,
node_shape_company_val, node_shape_amc_val,
edge_color_buy_val, edge_color_sell_val, edge_color_transfer_val,
edge_thickness_val, include_transfers_val):
G = build_graph(include_transfers=include_transfers_val)
fig = graph_to_plotly(
G,
node_color_amc=node_color_amc_val,
node_color_company=node_color_company_val,
node_shape_amc=node_shape_amc_val,
node_shape_company=node_shape_company_val,
edge_color_buy=edge_color_buy_val,
edge_color_sell=edge_color_sell_val,
edge_color_transfer=edge_color_transfer_val,
edge_thickness_base=edge_thickness_val,
)
return fig
update_button.click(
update_network,
[
node_color_company,
node_color_amc,
node_shape_company,
node_shape_amc,
edge_color_buy,
edge_color_sell,
edge_color_transfer,
edge_thickness,
include_transfers,
],
[network_plot]
)
def handle_company(company):
fig, df = company_trade_summary(company)
return fig, df
def handle_amc(amc):
fig, df = amc_transfer_summary(amc)
return fig, df
select_company.change(handle_company, select_company, [company_out_plot, company_out_table])
select_amc.change(handle_amc, select_amc, [amc_out_plot, amc_out_table])
if __name__ == "__main__":
demo.launch()