singhn9 commited on
Commit
8617b14
·
verified ·
1 Parent(s): 3da8a5e

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +377 -0
app.py ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import gradio as gr
3
+ import pandas as pd
4
+ import networkx as nx
5
+ import plotly.graph_objects as go
6
+ import numpy as np
7
+ from collections import defaultdict, Counter
8
+
9
+ # ---------------------------
10
+ # Sample dataset (editable)
11
+ # ---------------------------
12
+ # 10 AMCs (from your table) and ~15 companies extracted earlier.
13
+ AMCS = [
14
+ "SBI MF", "ICICI Pru MF", "HDFC MF", "Nippon India MF", "Kotak MF",
15
+ "UTI MF", "Axis MF", "Aditya Birla SL MF", "Mirae MF", "DSP MF"
16
+ ]
17
+
18
+ COMPANIES = [
19
+ "HDFC Bank", "ICICI Bank", "Bajaj Finance", "Bajaj Finserv", "Adani Ports",
20
+ "Tata Motors", "Shriram Finance", "HAL", "TCS", "AU Small Finance Bank",
21
+ "Pearl Global", "Hindalco", "Tata Elxsi", "Cummins India", "Vedanta"
22
+ ]
23
+
24
+ # These are best-effort imputations from the newspaper table you provided.
25
+ # BUY: AMC -> company
26
+ BUY_MAP = {
27
+ "SBI MF": ["Bajaj Finance", "AU Small Finance Bank"],
28
+ "ICICI Pru MF": ["HDFC Bank", "NTPC"] if False else ["HDFC Bank"], # NTPC not in COMPANIES list here
29
+ "HDFC MF": ["Tata Elxsi", "TCS"],
30
+ "Nippon India MF": ["Colgate-Palmolive (India)"] if False else ["Hindalco"],
31
+ "Kotak MF": ["Bajaj Finance", "Power Finance Corporation"] if False else ["Bajaj Finance"],
32
+ "UTI MF": ["Adani Ports", "Shriram Finance"],
33
+ "Axis MF": ["Tata Motors", "Shriram Finance"],
34
+ "Aditya Birla SL MF": ["AU Small Finance Bank", "Tata Steel"] if False else ["AU Small Finance Bank"],
35
+ "Mirae MF": ["Bajaj Finance", "HAL"],
36
+ "DSP MF": ["Tata Motors", "Bajaj Finserv"]
37
+ }
38
+
39
+ # SELL: AMC -> company
40
+ SELL_MAP = {
41
+ "SBI MF": ["Tata Motors"],
42
+ "ICICI Pru MF": ["Bajaj Finance", "Adani Ports"],
43
+ "HDFC MF": ["HDFC Bank"],
44
+ "Nippon India MF": ["Hindalco"],
45
+ "Kotak MF": ["AU Small Finance Bank"],
46
+ "UTI MF": ["Hindalco", "TCS"],
47
+ "Axis MF": ["TCS"],
48
+ "Aditya Birla SL MF": ["Adani Ports"],
49
+ "Mirae MF": ["TCS"],
50
+ "DSP MF": ["HAL", "Shriram Finance"]
51
+ }
52
+
53
+ # COMPLETE EXITs (one-way big exit)
54
+ COMPLETE_EXIT = {
55
+ "DSP MF": ["Shriram Finance"], # DSP completed exit of Shriram (example)
56
+ }
57
+
58
+ # FRESH_BUY (first time or notable fresh buy)
59
+ FRESH_BUY = {
60
+ "HDFC MF": ["Tata Elxsi"],
61
+ "UTI MF": ["Adani Ports"],
62
+ "Mirae MF": ["HAL"]
63
+ }
64
+
65
+ # sanitize maps (remove any items not in COMPANIES)
66
+ def sanitize_map(m):
67
+ out = {}
68
+ for k, vals in m.items():
69
+ out[k] = [v for v in vals if v in COMPANIES]
70
+ return out
71
+
72
+ BUY_MAP = sanitize_map(BUY_MAP)
73
+ SELL_MAP = sanitize_map(SELL_MAP)
74
+ COMPLETE_EXIT = sanitize_map(COMPLETE_EXIT)
75
+ FRESH_BUY = sanitize_map(FRESH_BUY)
76
+
77
+ # Build edge lists (AMC -> company) with attributes
78
+ company_edges = []
79
+ for amc, comps in BUY_MAP.items():
80
+ for c in comps:
81
+ company_edges.append((amc, c, {"action": "buy", "weight": 1}))
82
+ for amc, comps in SELL_MAP.items():
83
+ for c in comps:
84
+ company_edges.append((amc, c, {"action": "sell", "weight": 1}))
85
+ for amc, comps in COMPLETE_EXIT.items():
86
+ for c in comps:
87
+ company_edges.append((amc, c, {"action": "complete_exit", "weight": 3}))
88
+ for amc, comps in FRESH_BUY.items():
89
+ for c in comps:
90
+ company_edges.append((amc, c, {"action": "fresh_buy", "weight": 3}))
91
+
92
+ # Inferred AMC->AMC transfers: if AMC A sells company X and AMC B buys company X,
93
+ # infer A -> B transfer (transfer volume increments with multiple shared tickers)
94
+ def infer_amc_transfers(buy_map, sell_map):
95
+ transfers = defaultdict(int)
96
+ # for each company, find sellers and buyers
97
+ company_to_sellers = defaultdict(list)
98
+ company_to_buyers = defaultdict(list)
99
+ for amc, comps in sell_map.items():
100
+ for c in comps:
101
+ company_to_sellers[c].append(amc)
102
+ for amc, comps in buy_map.items():
103
+ for c in comps:
104
+ company_to_buyers[c].append(amc)
105
+ for c in set(list(company_to_sellers.keys()) + list(company_to_buyers.keys())):
106
+ sellers = company_to_sellers.get(c, [])
107
+ buyers = company_to_buyers.get(c, [])
108
+ for s in sellers:
109
+ for b in buyers:
110
+ # infer s -> b transfer for this company
111
+ transfers[(s,b)] += 1
112
+ # convert to list of edges
113
+ edge_list = []
114
+ for (s,b), w in transfers.items():
115
+ edge_list.append((s,b, {"action": "transfer", "weight": w, "company_count": w}))
116
+ return edge_list
117
+
118
+ transfer_edges = infer_amc_transfers(BUY_MAP, SELL_MAP)
119
+
120
+ # Combined graph builder
121
+ def build_graph(include_transfers=True):
122
+ G = nx.DiGraph()
123
+ # add AMC nodes
124
+ for a in AMCS:
125
+ G.add_node(a, type="amc", label=a)
126
+ # add company nodes
127
+ for c in COMPANIES:
128
+ G.add_node(c, type="company", label=c)
129
+ # add company edges (amc->company)
130
+ for a, c, attrs in company_edges:
131
+ if not G.has_node(a) or not G.has_node(c):
132
+ continue
133
+ # use unique key if multiple edges to same target: accumulate weight
134
+ if G.has_edge(a,c):
135
+ G[a][c]["weight"] += attrs.get("weight",1)
136
+ G[a][c]["actions"].append(attrs["action"])
137
+ else:
138
+ G.add_edge(a, c, weight=attrs.get("weight",1), actions=[attrs["action"]])
139
+ # add transfers
140
+ if include_transfers:
141
+ for s,b,attrs in transfer_edges:
142
+ if not G.has_node(s) or not G.has_node(b):
143
+ continue
144
+ if G.has_edge(s,b):
145
+ G[s][b]["weight"] += attrs.get("weight",1)
146
+ G[s][b]["actions"].append("transfer")
147
+ else:
148
+ G.add_edge(s, b, weight=attrs.get("weight",1), actions=["transfer"])
149
+ return G
150
+
151
+ # ---------------------------
152
+ # Visualization helpers
153
+ # ---------------------------
154
+ def graph_to_plotly(G,
155
+ node_color_amc="#9EC5FF",
156
+ node_color_company="#FFCF9E",
157
+ node_shape_amc="circle",
158
+ node_shape_company="circle",
159
+ edge_color_buy="#2ca02c",
160
+ edge_color_sell="#d62728",
161
+ edge_color_transfer="#888888",
162
+ edge_thickness_base=1.2,
163
+ show_labels=True):
164
+ # Layout
165
+ pos = nx.spring_layout(G, seed=42, k=1.2)
166
+ # Build traces
167
+ node_x = []
168
+ node_y = []
169
+ node_text = []
170
+ node_color = []
171
+ node_size = []
172
+ marker_symbols = []
173
+ for n, d in G.nodes(data=True):
174
+ x, y = pos[n]
175
+ node_x.append(x)
176
+ node_y.append(y)
177
+ node_text.append(n)
178
+ if d["type"] == "amc":
179
+ node_color.append(node_color_amc)
180
+ node_size.append(40)
181
+ marker_symbols.append(node_shape_amc)
182
+ else:
183
+ node_color.append(node_color_company)
184
+ node_size.append(60)
185
+ marker_symbols.append(node_shape_company)
186
+
187
+ node_trace = go.Scatter(
188
+ x=node_x, y=node_y,
189
+ mode='markers+text' if show_labels else 'markers',
190
+ marker=dict(
191
+ color=node_color,
192
+ size=node_size,
193
+ line=dict(width=2, color="#222")
194
+ ),
195
+ text=node_text if show_labels else None,
196
+ textposition="top center",
197
+ hoverinfo='text'
198
+ )
199
+
200
+ # Edge traces (separate traces for buy, sell, transfers for color/thickness control)
201
+ edge_traces = []
202
+ for u, v, attrs in G.edges(data=True):
203
+ # get style by action mix - priority: complete_exit/fresh_buy > transfer > sell > buy
204
+ actions = attrs.get("actions",[])
205
+ weight = attrs.get("weight",1)
206
+ x0, y0 = pos[u]
207
+ x1, y1 = pos[v]
208
+ # choose color & dash & width
209
+ if "complete_exit" in actions:
210
+ color = edge_color_sell
211
+ dash = "solid"
212
+ width = max(edge_thickness_base * 3, 3)
213
+ elif "fresh_buy" in actions:
214
+ color = edge_color_buy
215
+ dash = "solid"
216
+ width = max(edge_thickness_base * 3, 3)
217
+ elif "transfer" in actions:
218
+ color = edge_color_transfer
219
+ dash = "dash"
220
+ width = max(edge_thickness_base * (1 + np.log1p(weight)), 1.5)
221
+ elif "sell" in actions:
222
+ color = edge_color_sell
223
+ dash = "dot"
224
+ width = max(edge_thickness_base * (1 + np.log1p(weight)), 1)
225
+ else: # buy
226
+ color = edge_color_buy
227
+ dash = "solid"
228
+ width = max(edge_thickness_base * (1 + np.log1p(weight)), 1)
229
+ edge_trace = go.Scatter(
230
+ x=[x0, x1, None],
231
+ y=[y0, y1, None],
232
+ line=dict(width=width, color=color, dash=dash),
233
+ hoverinfo='none',
234
+ mode='lines'
235
+ )
236
+ edge_traces.append(edge_trace)
237
+
238
+ # Create figure
239
+ fig = go.Figure(data=edge_traces + [node_trace],
240
+ layout=go.Layout(
241
+ showlegend=False,
242
+ margin=dict(b=20,l=5,r=5,t=40),
243
+ xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
244
+ yaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
245
+ height=1000,
246
+ width=1400,
247
+ ))
248
+ return fig
249
+
250
+ # ---------------------------
251
+ # Analysis helpers (what user requested)
252
+ # ---------------------------
253
+ def company_trade_summary(company_name):
254
+ """Return a small bar chart / dataframe of who bought vs sold the given company."""
255
+ # build counts
256
+ buyers = []
257
+ sellers = []
258
+ for amc, comps in BUY_MAP.items():
259
+ if company_name in comps:
260
+ buyers.append(amc)
261
+ for amc, comps in SELL_MAP.items():
262
+ if company_name in comps:
263
+ sellers.append(amc)
264
+ # include complete exits and fresh buys
265
+ fresh = [amc for amc, comps in FRESH_BUY.items() if company_name in comps]
266
+ exits = [amc for amc, comps in COMPLETE_EXIT.items() if company_name in comps]
267
+ df = pd.DataFrame({
268
+ "Role": ["Buyer"]*len(buyers) + ["Seller"]*len(sellers) + ["Fresh buy"]*len(fresh) + ["Complete exit"]*len(exits),
269
+ "AMC": buyers + sellers + fresh + exits
270
+ })
271
+ if df.empty:
272
+ return "No visible trades for this company in dataset."
273
+ # make simple counts bar chart
274
+ counts = df.groupby("Role").size().reset_index(name="Count")
275
+ fig = go.Figure(go.Bar(x=counts["Role"], y=counts["Count"], marker_color=["green","red","orange","black"][:len(counts)]))
276
+ fig.update_layout(title_text=f"Trade summary for {company_name}", height=350, width=600)
277
+ return fig, df
278
+
279
+ def amc_transfer_summary(amc_name):
280
+ """For a selected AMC, show which securities were sold to which other AMC (inferred)"""
281
+ # securities sold by this AMC
282
+ sold = SELL_MAP.get(amc_name, [])
283
+ # who bought those securities
284
+ transfers = []
285
+ for s in sold:
286
+ buyers = [amc for amc, comps in BUY_MAP.items() if s in comps]
287
+ for b in buyers:
288
+ transfers.append({"security": s, "buyer_amc": b})
289
+ df = pd.DataFrame(transfers)
290
+ if df.empty:
291
+ return "No inferred transfers for this AMC in dataset."
292
+ # return table and a simple count chart (buyers count)
293
+ counts = df['buyer_amc'].value_counts().reset_index()
294
+ counts.columns = ['Buyer AMC', 'Count']
295
+ fig = go.Figure(go.Bar(x=counts['Buyer AMC'], y=counts['Count'], marker_color='lightslategray'))
296
+ fig.update_layout(title_text=f"Inferred transfers from {amc_name} (sold securities -> buyer AMCs)", height=350, width=600)
297
+ return fig, df
298
+
299
+ # ---------------------------
300
+ # Gradio UI
301
+ # ---------------------------
302
+ with gr.Blocks() as demo:
303
+ gr.Markdown("## Mutual Fund Churn explorer — interactive network + transfer analysis")
304
+ with gr.Row():
305
+ with gr.Column(scale=3):
306
+ # Graph controls
307
+ gr.Markdown("### Network customization")
308
+ node_color_company = gr.ColorPicker(value="#FFCF9E", label="Company node color")
309
+ node_color_amc = gr.ColorPicker(value="#9EC5FF", label="AMC node color")
310
+ node_shape_company = gr.Dropdown(choices=["circle","square","diamond"], value="circle", label="Company node shape")
311
+ node_shape_amc = gr.Dropdown(choices=["circle","square","diamond"], value="circle", label="AMC node shape")
312
+ edge_color_buy = gr.ColorPicker(value="#2ca02c", label="BUY edge color")
313
+ edge_color_sell = gr.ColorPicker(value="#d62728", label="SELL edge color")
314
+ edge_color_transfer = gr.ColorPicker(value="#888888", label="Transfer edge color")
315
+ edge_thickness = gr.Slider(minimum=0.5, maximum=6.0, value=1.4, step=0.1, label="Edge thickness base")
316
+ include_transfers = gr.Checkbox(value=True, label="Infer AMC → AMC transfers (yes = show direct loops)")
317
+ update_button = gr.Button("Update network")
318
+
319
+ gr.Markdown("### Inspect specific node")
320
+ select_company = gr.Dropdown(choices=COMPANIES, label="Select company (show buyers/sellers)")
321
+ select_amc = gr.Dropdown(choices=AMCS, label="Select AMC (show inferred transfers)")
322
+ with gr.Column(scale=7):
323
+ network_plot = gr.Plot(label="Network graph (drag to zoom)")
324
+
325
+ # outputs for selections
326
+ company_out_plot = gr.Plot(label="Company trade summary")
327
+ company_out_table = gr.DataFrame(label="Trades (company)")
328
+ amc_out_plot = gr.Plot(label="AMC inferred transfers")
329
+ amc_out_table = gr.DataFrame(label="Inferred transfers (AMC)")
330
+
331
+ def update_network(node_color_company_val, node_color_amc_val, node_shape_company_val, node_shape_amc_val,
332
+ edge_color_buy_val, edge_color_sell_val, edge_color_transfer_val, edge_thickness_val,
333
+ include_transfers_val):
334
+ G = build_graph(include_transfers=include_transfers_val)
335
+ fig = graph_to_plotly(G,
336
+ node_color_amc=node_color_amc_val,
337
+ node_color_company=node_color_company_val,
338
+ node_shape_amc=node_shape_amc_val,
339
+ node_shape_company=node_shape_company_val,
340
+ edge_color_buy=edge_color_buy_val,
341
+ edge_color_sell=edge_color_sell_val,
342
+ edge_color_transfer=edge_color_transfer_val,
343
+ edge_thickness_base=edge_thickness_val,
344
+ show_labels=True)
345
+ return fig
346
+
347
+ def on_company_select(cname):
348
+ res = company_trade_summary(cname)
349
+ if isinstance(res, tuple):
350
+ fig, df = res
351
+ return fig, df
352
+ else:
353
+ return None, pd.DataFrame([], columns=["Role","AMC"])
354
+
355
+ def on_amc_select(aname):
356
+ res = amc_transfer_summary(aname)
357
+ if isinstance(res, tuple):
358
+ fig, df = res
359
+ return fig, df
360
+ else:
361
+ return None, pd.DataFrame([], columns=["security","buyer_amc"])
362
+
363
+ update_button.click(fn=update_network,
364
+ inputs=[node_color_company, node_color_amc, node_shape_company, node_shape_amc,
365
+ edge_color_buy, edge_color_sell, edge_color_transfer, edge_thickness, include_transfers],
366
+ outputs=[network_plot])
367
+ select_company.change(fn=on_company_select, inputs=[select_company], outputs=[company_out_plot, company_out_table])
368
+ select_amc.change(fn=on_amc_select, inputs=[select_amc], outputs=[amc_out_plot, amc_out_table])
369
+
370
+ # initial network
371
+ network_plot.update(value=update_network(node_color_company.value, node_color_amc.value,
372
+ node_shape_company.value, node_shape_amc.value,
373
+ edge_color_buy.value, edge_color_sell.value, edge_color_transfer.value,
374
+ edge_thickness.value, include_transfers.value))
375
+
376
+ if __name__ == "__main__":
377
+ demo.launch()