diff --git a/repair_challenge/carmignac repair.py b/repair_challenge/carmignac repair.py new file mode 100644 index 0000000..f8dd91c --- /dev/null +++ b/repair_challenge/carmignac repair.py @@ -0,0 +1,1037 @@ +""" +Carmignac Data Challenge — Pipeline Results Analysis +===================================================== +Analyses the CSV outputs produced by carmignac_repair.py: + - carmignac_scores.csv (post-surgery score history) + - carmignac_mapping.csv (reg_id mapping history) + - carmignac_surgery_log.csv (surgery operations) + +Produces a self-contained HTML report with interactive charts. + +Usage: + python carmignac_analysis.py + python carmignac_analysis.py --scores path/to/scores.csv \ + --mapping path/to/mapping.csv \ + --surgery path/to/surgery_log.csv \ + --out report.html +""" + +import argparse +import json +import os +import sys +import numpy as np +import pandas as pd + +# ───────────────────────────────────────────────────────────── +# 1. LOAD & VALIDATE +# ───────────────────────────────────────────────────────────── + +def load_outputs(scores_path, mapping_path, surgery_path): + scores = pd.read_csv(scores_path, parse_dates=["date"]) + mapping = pd.read_csv(mapping_path, parse_dates=["date"]) + surgery = pd.read_csv(surgery_path, parse_dates=["date"]) + + # Normalise dtypes + scores["reg_id"] = scores["reg_id"].astype(str) + mapping["reg_orig"] = mapping["reg_orig"].astype(str) + mapping["reg_used"] = mapping["reg_used"].astype(str) + mapping["changed"] = mapping["changed"].astype(bool) + surgery["reg_orig"] = surgery["reg_orig"].astype(str) + surgery["reg_from"] = surgery["reg_from"].astype(str) + surgery["reg_to"] = surgery["reg_to"].astype(str) + + return scores, mapping, surgery + + +# ───────────────────────────────────────────────────────────── +# 2. COMPUTE ANALYTICS +# ───────────────────────────────────────────────────────────── + +def compute_analytics(scores, mapping, surgery): + dates = sorted(scores["date"].unique()) + + # ── 2.1 Sum of scores per date (post-surgery) ────────────── + sum_post = (scores.groupby("date")["score"] + .sum() + .reindex(dates) + .rename("sum_post")) + + # ── 2.2 Reconstruct pre-surgery (counterfactual) ─────────── + # Without surgery, every reg_id that had a hard break would score 0 + # from that date backwards. We propagate the surgery "gain" as a + # cumulative deficit going back in time. + gain_by_date = surgery.groupby("date")["gain_vs_no_surgery"].sum() + # cumulative deficit = sum of gains for all surgeries at or after date t + cumulative_deficit = pd.Series(0.0, index=dates) + for d in dates: + cumulative_deficit[d] = gain_by_date[gain_by_date.index >= d].sum() + sum_pre = (sum_post - cumulative_deficit).clip(lower=0).rename("sum_pre") + + timeline = pd.DataFrame({"sum_post": sum_post, "sum_pre": sum_pre}) + timeline.index = pd.to_datetime(timeline.index) + timeline["recovery_pct"] = np.where( + sum_pre < sum_post, + (sum_post - sum_pre) / sum_post.clip(lower=1e-9) * 100, + 0.0, + ) + + # ── 2.3 Per-date surgery stats ───────────────────────────── + surgery_stats = ( + surgery.groupby("date") + .agg( + n_surgeries = ("reg_orig", "count"), + total_gain = ("gain_vs_no_surgery", "sum"), + avg_gain = ("gain_vs_no_surgery", "mean"), + avg_jaccard = ("jaccard_composite", "mean"), + avg_score_before = ("score_before", "mean"), + avg_score_after = ("score_after", "mean"), + ) + .reindex(dates, fill_value=0) + ) + + # ── 2.4 Score distribution over time ─────────────────────── + # Wide format: rows=dates, cols=reg_ids + pivot = scores.pivot_table(index="date", columns="reg_id", + values="score", aggfunc="last") + pivot = pivot.reindex(dates) + pivot.index = pd.to_datetime(pivot.index) + + # ── 2.5 Mapping churn ────────────────────────────────────── + # For each date, how many reg_ids are remapped (not using their original code)? + churn = (mapping.groupby("date")["changed"] + .sum() + .reindex(dates, fill_value=0) + .rename("n_remapped")) + + # ── 2.6 Score entropy (distribution spread) ──────────────── + def entropy(row): + p = row.dropna() + p = p[p > 0] + if len(p) == 0: + return np.nan + p = p / p.sum() + return -(p * np.log(p)).sum() + + timeline["entropy"] = pivot.apply(entropy, axis=1).values + + # ── 2.7 Individual score trajectories ────────────────────── + # Identify which reg_ids were ever remapped + ever_remapped = set(mapping.loc[mapping["changed"], "reg_orig"].unique()) + + # ── 2.8 Surgery detail table ─────────────────────────────── + surgery_detail = surgery.copy() + surgery_detail["gain_pct_of_score"] = ( + surgery_detail["gain_vs_no_surgery"] + / surgery_detail["score_before"].clip(lower=1e-9) * 100 + ).round(2) + + return { + "timeline": timeline, + "surgery_stats": surgery_stats, + "pivot": pivot, + "churn": churn, + "ever_remapped": ever_remapped, + "surgery_detail": surgery_detail, + "dates": [d.strftime("%Y-%m-%d") for d in dates], + } + + +# ───────────────────────────────────────────────────────────── +# 3. PRINT CONSOLE SUMMARY +# ───────────────────────────────────────────────────────────── + +def print_summary(analytics, surgery): + tl = analytics["timeline"] + ss = analytics["surgery_stats"] + + print("\n" + "=" * 65) + print(" CARMIGNAC PIPELINE — RESULTS SUMMARY") + print("=" * 65) + + print(f"\n Date range : {tl.index.min().date()} → {tl.index.max().date()}") + print(f" Total months : {len(tl)}") + print(f" Reg IDs : {analytics['pivot'].shape[1]}") + + print(f"\n ── Score (Σ) ──────────────────────────────────────────") + print(f" At t_ref (latest) : {tl['sum_post'].iloc[-1]:.6f}") + print(f" At t_min (earliest): {tl['sum_post'].iloc[0]:.6f}") + print(f" Min (post-surgery) : {tl['sum_post'].min():.6f} " + f"({tl['sum_post'].idxmin().date()})") + print(f" Min (pre-surgery) : {tl['sum_pre'].min():.6f} " + f"({tl['sum_pre'].idxmin().date()})") + print(f" Max recovery (pct) : {tl['recovery_pct'].max():.2f}%") + + print(f"\n ── Surgeries ─────────────────────────────────────────") + if len(surgery) == 0: + print(" No surgeries performed.") + else: + print(f" Total operations : {len(surgery)}") + print(f" Total score gained : {surgery['gain_vs_no_surgery'].sum():.6f}") + print(f" Avg Jaccard : {surgery['jaccard_composite'].mean():.4f}") + print(f" Avg gain / surgery : {surgery['gain_vs_no_surgery'].mean():.6f}") + print() + print(f" {'Date':12s} {'Reg orig':12s} {'From':15s} {'To':15s} " + f"{'Jaccard':>8s} {'Gain':>10s}") + print(" " + "-" * 78) + for _, row in surgery.sort_values("date").iterrows(): + print(f" {str(row['date'].date()):12s} {row['reg_orig']:12s} " + f"{row['reg_from']:15s} {row['reg_to']:15s} " + f"{row['jaccard_composite']:8.4f} {row['gain_vs_no_surgery']:10.6f}") + + print(f"\n ── Mapping churn ─────────────────────────────────────") + ch = analytics["churn"] + print(f" Max remapped at one date : {int(ch.max())} ({ch.idxmax().date() if ch.max()>0 else 'N/A'})") + print(f" Reg IDs ever remapped : {len(analytics['ever_remapped'])}") + + print(f"\n ── Score entropy (distribution spread) ───────────────") + ent = analytics["timeline"]["entropy"] + print(f" Mean entropy : {ent.mean():.4f}") + print(f" Std entropy : {ent.std():.4f}") + print() + + +# ───────────────────────────────────────────────────────────── +# 4. BUILD HTML REPORT +# ───────────────────────────────────────────────────────────── + +def build_html(analytics, surgery, scores, mapping): + tl = analytics["timeline"] + ss = analytics["surgery_stats"] + piv = analytics["pivot"] + ch = analytics["churn"] + dates_str = analytics["dates"] + + # ── helpers to serialise for JS ───────────────────────────── + def jf(arr, decimals=6): + return json.dumps([round(float(v), decimals) if not np.isnan(v) else None + for v in arr]) + + def js(arr): + return json.dumps(list(arr)) + + # ── colour palette ─────────────────────────────────────────── + REG_COLORS = [ + "#2563eb","#16a34a","#dc2626","#d97706","#7c3aed", + "#0891b2","#db2777","#65a30d","#ea580c","#6366f1", + "#059669","#b45309","#9333ea","#0284c7","#e11d48", + ] + + # ── 4.1 Surgery sparkline data ────────────────────────────── + surg_dates = [d.strftime("%Y-%m-%d") for d in ss.index] + n_surg = jf(ss["n_surgeries"].values, 0) + total_gain = jf(ss["total_gain"].values) + avg_gain = jf(ss["avg_gain"].values) + avg_jaccard = jf(ss["avg_jaccard"].values) + + # ── 4.2 Individual trajectories ──────────────────────────── + reg_ids = list(piv.columns) + traj_datasets = [] + for idx, rid in enumerate(reg_ids): + col = analytics["ever_remapped"] + dashed = rid in col + traj_datasets.append({ + "label": rid, + "data": [round(float(v), 6) if not np.isnan(v) else None + for v in piv[rid].values], + "borderColor": REG_COLORS[idx % len(REG_COLORS)], + "backgroundColor": REG_COLORS[idx % len(REG_COLORS)] + "22", + "borderWidth": 2 if not dashed else 2, + "borderDash": [] if not dashed else [6, 3], + "pointRadius": 0, + "tension": 0.3, + "fill": False, + }) + + traj_json = json.dumps(traj_datasets) + + # ── 4.3 Surgery detail table rows ────────────────────────── + sd = analytics["surgery_detail"].sort_values("date") + surg_rows_html = "" + if len(sd) == 0: + surg_rows_html = "No surgeries performed" + else: + for _, r in sd.iterrows(): + gain_class = "gain-high" if r["gain_vs_no_surgery"] > 0.05 else "gain-low" + surg_rows_html += f""" + + {r['date'].date()} + {r['reg_orig']} + {r['reg_from']} + → + {r['reg_to']} + {r['jaccard_composite']:.4f} + +{r['gain_vs_no_surgery']:.6f} + {r['gain_pct_of_score']:.1f}% + """ + + # ── 4.4 Top accounts table ────────────────────────────────── + last_date = piv.index.max() + top_accounts = piv.loc[last_date].dropna().sort_values(ascending=False) + top_rows_html = "" + for rank, (rid, sc) in enumerate(top_accounts.items(), 1): + remapped = "✓" if rid in analytics["ever_remapped"] else "" + bar_w = int(sc / top_accounts.max() * 100) + color = REG_COLORS[(rank - 1) % len(REG_COLORS)] + top_rows_html += f""" + + #{rank} + {rid} + {sc:.6f} + +
+ + {remapped} + """ + + # ───────────────────────────────────────────────────────────── + # HTML TEMPLATE + # ───────────────────────────────────────────────────────────── + html = f""" + + + + +Carmignac Pipeline — Analysis Report + + + + + + +
+
Carmignac × ENSAE · Data Challenge 2025
+

Pipeline Results — Analysis Report

+
Registrar ID repair · Score propagation · Surgery audit
+
+ + +
+
+ Σ score at t_ref + {tl['sum_post'].iloc[-1]:.4f} + post-surgery +
+
+ Σ score at t_min + {tl['sum_post'].iloc[0]:.4f} + post-surgery +
+
+ Max recovery + {tl['recovery_pct'].max():.1f}% + score rescued by surgery +
+
+ Total surgeries + {len(surgery)} + operations performed +
+
+ Reg IDs universe + {piv.shape[1]} + at reference date +
+
+ Ever remapped + {len(analytics['ever_remapped'])} + reg IDs w/ code change +
+
+ + +
+ +
01 · Score Integrity Over Time
+ + +
+
+ Sum of scores — pre vs post surgery + + Post-surgery (solid) shows the corrected score after code repairs. + Pre-surgery (dashed) is the counterfactual without any remapping. + Gap = score rescued. + +
+
+
+ +
+
+
+ + +
+
+
+ Score recovered by surgery + Difference post − pre at each date +
+
+
+ +
+
+
+ +
+
+ Portfolio concentration (entropy) + Shannon entropy of score distribution — higher = more spread +
+
+
+ +
+
+
+
+ +
02 · Individual Score Trajectories
+ +
+
+ Score per Registrar Account — full history + + Dashed lines = accounts that were remapped at some point (surgery applied). + Solid lines = stable codes throughout. + +
+
+
+ +
+
+
+ +
03 · Surgery Operations
+ +
+
+
+ Surgeries per time step + Number of code remappings performed at each month +
+
+
+ +
+
+
+ +
+
+ Score gain per surgery + Average gain in Σ score from surgery at each month +
+
+
+ +
+
+
+
+ +
+
+ Jaccard similarity of surgery matches + + Composite Jaccard score of the matched code pair — closer to 1.0 = stronger portfolio overlap. + Low values may indicate uncertain matches. + +
+
+
+ +
+
+
+ +
04 · Surgery Detail Log
+ +
+
+ All surgery operations +
+
+ {'
No surgeries were performed on this dataset.
' if len(surgery) == 0 else f""" + + + + + + + + + + + + + + {surg_rows_html} +
DateReg origCode fromCode toJaccardScore gain% of score
"""} +
+
+ +
05 · Score Ranking at t_ref
+ +
+
+ Accounts ranked by weight at reference date + ✓ in last column = account was remapped at some point in history +
+
+ + + + + + + + + + + {top_rows_html} +
RankRegistrar IDScore (weight)Relative sizeRemapped
+
+
+ +
+ + + + + + +""" + + return html + + +# ───────────────────────────────────────────────────────────── +# 5. MAIN +# ───────────────────────────────────────────────────────────── + +def main(): + parser = argparse.ArgumentParser(description="Carmignac pipeline results analyser") + parser.add_argument("--scores", default="repair_results/carmignac_scores.csv") + parser.add_argument("--mapping", default="repair_results/carmignac_mapping.csv") + parser.add_argument("--surgery", default="repair_results/carmignac_surgery_log.csv") + parser.add_argument("--out", default="repair_results/carmignac_report.html") + args = parser.parse_args() + + # Resolve paths relative to this script's directory if files not found + base = os.path.dirname(os.path.abspath(__file__)) + def resolve(p): + if os.path.exists(p): + return p + alt = os.path.join(base, p) + if os.path.exists(alt): + return alt + sys.exit(f"[ERROR] File not found: {p}") + + scores_path = resolve(args.scores) + mapping_path = resolve(args.mapping) + surgery_path = resolve(args.surgery) + + print(f"[Load] scores : {scores_path}") + print(f"[Load] mapping : {mapping_path}") + print(f"[Load] surgery : {surgery_path}") + + scores, mapping, surgery = load_outputs(scores_path, mapping_path, surgery_path) + analytics = compute_analytics(scores, mapping, surgery) + + print_summary(analytics, surgery) + + html = build_html(analytics, surgery, scores, mapping) + + out_path = args.out + with open(out_path, "w", encoding="utf-8") as f: + f.write(html) + print(f"\n[Report] Written to → {out_path}") + + +if __name__ == "__main__": + main() diff --git a/repair_challenge/carmignac_analysis.py b/repair_challenge/carmignac_analysis.py new file mode 100644 index 0000000..b9b9151 --- /dev/null +++ b/repair_challenge/carmignac_analysis.py @@ -0,0 +1,1351 @@ +""" +Carmignac Data Challenge — Pipeline Results Analysis +===================================================== +Analyses the CSV outputs produced by carmignac_repair.py: + - carmignac_scores.csv (post-surgery score history) + - carmignac_mapping.csv (reg_id mapping history) + - carmignac_surgery_log.csv (surgery operations) + +Produces a self-contained HTML report with interactive charts. + +Usage: + python carmignac_analysis.py + python carmignac_analysis.py --scores path/to/scores.csv \ + --mapping path/to/mapping.csv \ + --surgery path/to/surgery_log.csv \ + --out report.html +""" + +import argparse +import json +import os +import sys +import numpy as np +import pandas as pd + +# ───────────────────────────────────────────────────────────── +# 1. LOAD & VALIDATE +# ───────────────────────────────────────────────────────────── + +def load_outputs(scores_path, mapping_path, surgery_path): + scores = pd.read_csv(scores_path, parse_dates=["date"]) + mapping = pd.read_csv(mapping_path, parse_dates=["date"]) + surgery = pd.read_csv(surgery_path, parse_dates=["date"]) + + # Normalise dtypes + scores["reg_id"] = scores["reg_id"].astype(str) + mapping["reg_orig"] = mapping["reg_orig"].astype(str) + mapping["reg_used"] = mapping["reg_used"].astype(str) + mapping["changed"] = mapping["changed"].astype(bool) + surgery["reg_orig"] = surgery["reg_orig"].astype(str) + surgery["reg_from"] = surgery["reg_from"].astype(str) + surgery["reg_to"] = surgery["reg_to"].astype(str) + + return scores, mapping, surgery + + +# ───────────────────────────────────────────────────────────── +# 2. COMPUTE ANALYTICS +# ───────────────────────────────────────────────────────────── + +def compute_analytics(scores, mapping, surgery): + dates = sorted(scores["date"].unique()) + + # ── 2.1 Sum of scores per date (post-surgery) ────────────── + sum_post = (scores.groupby("date")["score"] + .sum() + .reindex(dates) + .rename("sum_post")) + + # ── 2.2 Reconstruct pre-surgery (counterfactual) ─────────── + # Without surgery, every reg_id that had a hard break would score 0 + # from that date backwards. We propagate the surgery "gain" as a + # cumulative deficit going back in time. + gain_by_date = surgery.groupby("date")["gain_vs_no_surgery"].sum() + # cumulative deficit = sum of gains for all surgeries at or after date t + cumulative_deficit = pd.Series(0.0, index=dates) + for d in dates: + cumulative_deficit[d] = gain_by_date[gain_by_date.index >= d].sum() + sum_pre = (sum_post - cumulative_deficit).clip(lower=0).rename("sum_pre") + + timeline = pd.DataFrame({"sum_post": sum_post, "sum_pre": sum_pre}) + timeline.index = pd.to_datetime(timeline.index) + timeline["recovery_pct"] = np.where( + sum_pre < sum_post, + (sum_post - sum_pre) / sum_post.clip(lower=1e-9) * 100, + 0.0, + ) + + # ── 2.3 Per-date surgery stats ───────────────────────────── + surgery_stats = ( + surgery.groupby("date") + .agg( + n_surgeries = ("reg_orig", "count"), + total_gain = ("gain_vs_no_surgery", "sum"), + avg_gain = ("gain_vs_no_surgery", "mean"), + avg_jaccard = ("jaccard_composite", "mean"), + avg_score_before = ("score_before", "mean"), + avg_score_after = ("score_after", "mean"), + ) + .reindex(dates, fill_value=0) + ) + + # ── 2.4 Score distribution over time ─────────────────────── + # Wide format: rows=dates, cols=reg_ids + pivot = scores.pivot_table(index="date", columns="reg_id", + values="score", aggfunc="last") + pivot = pivot.reindex(dates) + pivot.index = pd.to_datetime(pivot.index) + + # ── 2.5 Mapping churn ────────────────────────────────────── + # For each date, how many reg_ids are remapped (not using their original code)? + churn = (mapping.groupby("date")["changed"] + .sum() + .reindex(dates, fill_value=0) + .rename("n_remapped")) + + # ── 2.6 Score entropy (distribution spread) ──────────────── + def entropy(row): + p = row.dropna() + p = p[p > 0] + if len(p) == 0: + return np.nan + p = p / p.sum() + return -(p * np.log(p)).sum() + + timeline["entropy"] = pivot.apply(entropy, axis=1).values + + # ── 2.7 Individual score trajectories ────────────────────── + # Identify which reg_ids were ever remapped + ever_remapped = set(mapping.loc[mapping["changed"], "reg_orig"].unique()) + + # ── 2.8 Surgery detail table ─────────────────────────────── + surgery_detail = surgery.copy() + surgery_detail["gain_pct_of_score"] = ( + surgery_detail["gain_vs_no_surgery"] + / surgery_detail["score_before"].clip(lower=1e-9) * 100 + ).round(2) + + return { + "timeline": timeline, + "surgery_stats": surgery_stats, + "pivot": pivot, + "churn": churn, + "ever_remapped": ever_remapped, + "surgery_detail": surgery_detail, + "dates": [d.strftime("%Y-%m-%d") for d in dates], + } + + +# ───────────────────────────────────────────────────────────── +# 3. PRINT CONSOLE SUMMARY +# ───────────────────────────────────────────────────────────── + +def print_summary(analytics, surgery): + tl = analytics["timeline"] + ss = analytics["surgery_stats"] + + print("\n" + "=" * 65) + print(" CARMIGNAC PIPELINE — RESULTS SUMMARY") + print("=" * 65) + + print(f"\n Date range : {tl.index.min().date()} → {tl.index.max().date()}") + print(f" Total months : {len(tl)}") + print(f" Reg IDs : {analytics['pivot'].shape[1]}") + + print(f"\n ── Score (Σ) ──────────────────────────────────────────") + print(f" At t_ref (latest) : {tl['sum_post'].iloc[-1]:.6f}") + print(f" At t_min (earliest): {tl['sum_post'].iloc[0]:.6f}") + print(f" Min (post-surgery) : {tl['sum_post'].min():.6f} " + f"({tl['sum_post'].idxmin().date()})") + print(f" Min (pre-surgery) : {tl['sum_pre'].min():.6f} " + f"({tl['sum_pre'].idxmin().date()})") + print(f" Max recovery (pct) : {tl['recovery_pct'].max():.2f}%") + + print(f"\n ── Surgeries ─────────────────────────────────────────") + if len(surgery) == 0: + print(" No surgeries performed.") + else: + print(f" Total operations : {len(surgery)}") + print(f" Total score gained : {surgery['gain_vs_no_surgery'].sum():.6f}") + print(f" Avg Jaccard : {surgery['jaccard_composite'].mean():.4f}") + print(f" Avg gain / surgery : {surgery['gain_vs_no_surgery'].mean():.6f}") + print() + print(f" {'Date':12s} {'Reg orig':12s} {'From':15s} {'To':15s} " + f"{'Jaccard':>8s} {'Gain':>10s}") + print(" " + "-" * 78) + for _, row in surgery.sort_values("date").iterrows(): + print(f" {str(row['date'].date()):12s} {row['reg_orig']:12s} " + f"{row['reg_from']:15s} {row['reg_to']:15s} " + f"{row['jaccard_composite']:8.4f} {row['gain_vs_no_surgery']:10.6f}") + + print(f"\n ── Mapping churn ─────────────────────────────────────") + ch = analytics["churn"] + print(f" Max remapped at one date : {int(ch.max())} ({ch.idxmax().date() if ch.max()>0 else 'N/A'})") + print(f" Reg IDs ever remapped : {len(analytics['ever_remapped'])}") + + print(f"\n ── Score entropy (distribution spread) ───────────────") + ent = analytics["timeline"]["entropy"] + print(f" Mean entropy : {ent.mean():.4f}") + print(f" Std entropy : {ent.std():.4f}") + print() + + +# ───────────────────────────────────────────────────────────── +# 4. BUILD HTML REPORT +# ───────────────────────────────────────────────────────────── + +def build_html(analytics, surgery, scores, mapping): + tl = analytics["timeline"] + ss = analytics["surgery_stats"] + piv = analytics["pivot"] + ch = analytics["churn"] + dates_str = analytics["dates"] + + # ── helpers to serialise for JS ───────────────────────────── + def jf(arr, decimals=6): + return json.dumps([round(float(v), decimals) if not np.isnan(v) else None + for v in arr]) + + def js(arr): + return json.dumps(list(arr)) + + # ── colour palette ─────────────────────────────────────────── + REG_COLORS = [ + "#2563eb","#16a34a","#dc2626","#d97706","#7c3aed", + "#0891b2","#db2777","#65a30d","#ea580c","#6366f1", + "#059669","#b45309","#9333ea","#0284c7","#e11d48", + ] + + # ── 4.1 Surgery sparkline data ────────────────────────────── + surg_dates = [d.strftime("%Y-%m-%d") for d in ss.index] + n_surg = jf(ss["n_surgeries"].values, 0) + total_gain = jf(ss["total_gain"].values) + avg_gain = jf(ss["avg_gain"].values) + avg_jaccard = jf(ss["avg_jaccard"].values) + + # ── 4.2 Individual trajectories ──────────────────────────── + reg_ids = list(piv.columns) + traj_datasets = [] + # Surgery lookup: reg_orig -> list of {date, from, to, composite} + surg_by_reg = {} + for _, row in surgery.iterrows(): + surg_by_reg.setdefault(row["reg_orig"], []).append({ + "date": row["date"].strftime("%Y-%m-%d"), + "reg_from": str(row["reg_from"]), + "reg_to": str(row["reg_to"]), + "composite": round(float(row["jaccard_composite"]), 4), + "gain": round(float(row["gain_vs_no_surgery"]), 6), + }) + + for idx, rid in enumerate(reg_ids): + remapped = rid in analytics["ever_remapped"] + traj_datasets.append({ + "label": rid, + "data": [round(float(v), 6) if not np.isnan(v) else None + for v in piv[rid].values], + "borderColor": REG_COLORS[idx % len(REG_COLORS)], + "backgroundColor": REG_COLORS[idx % len(REG_COLORS)] + "22", + "borderWidth": 2, + "borderDash": [6, 3] if remapped else [], + "pointRadius": 0, + "tension": 0.3, + "fill": False, + "remapped": remapped, + "surgeries": surg_by_reg.get(rid, []), + }) + + traj_json = json.dumps(traj_datasets) + + # ── 4.3 Surgery detail table rows ────────────────────────── + sd = analytics["surgery_detail"].sort_values("date") + surg_rows_html = "" + if len(sd) == 0: + surg_rows_html = "No surgeries performed" + else: + for _, r in sd.iterrows(): + gain_class = "gain-high" if r["gain_vs_no_surgery"] > 0.05 else "gain-low" + surg_rows_html += f""" + + {r['date'].date()} + {r['reg_orig']} + {r['reg_from']} + → + {r['reg_to']} + {r['jaccard_composite']:.4f} + +{r['gain_vs_no_surgery']:.6f} + {r['gain_pct_of_score']:.1f}% + """ + + # ── 4.4 Top accounts table ────────────────────────────────── + last_date = piv.index.max() + top_accounts = piv.loc[last_date].dropna().sort_values(ascending=False) + top_rows_html = "" + for rank, (rid, sc) in enumerate(top_accounts.items(), 1): + remapped = "✓" if rid in analytics["ever_remapped"] else "" + bar_w = int(sc / top_accounts.max() * 100) + color = REG_COLORS[(rank - 1) % len(REG_COLORS)] + top_rows_html += f""" + + #{rank} + {rid} + {sc:.6f} + +
+ + {remapped} + """ + + # ───────────────────────────────────────────────────────────── + # HTML TEMPLATE + # ───────────────────────────────────────────────────────────── + html = f""" + + + + +Carmignac Pipeline — Analysis Report + + + + + + +
+
Carmignac × ENSAE · Data Challenge 2025
+

Pipeline Results — Analysis Report

+
Registrar ID repair · Score propagation · Surgery audit
+
+ + +
+
+ Σ score at t_ref + {tl['sum_post'].iloc[-1]:.4f} + post-surgery +
+
+ Σ score at t_min + {tl['sum_post'].iloc[0]:.4f} + post-surgery +
+
+ Max recovery + {tl['recovery_pct'].max():.1f}% + score rescued by surgery +
+
+ Total surgeries + {len(surgery)} + operations performed +
+
+ Reg IDs universe + {piv.shape[1]} + at reference date +
+
+ Ever remapped + {len(analytics['ever_remapped'])} + reg IDs w/ code change +
+
+ + +
+ +
01 · Score Integrity Over Time
+ + +
+
+ Sum of scores — pre vs post surgery + + Post-surgery (solid) shows the corrected score after code repairs. + Pre-surgery (dashed) is the counterfactual without any remapping. + Gap = score rescued. + +
+
+
+ +
+
+
+ + +
+
+
+ Score recovered by surgery + Difference post − pre at each date +
+
+
+ +
+
+
+ +
+
+ Portfolio concentration (entropy) + Shannon entropy of score distribution — higher = more spread +
+
+
+ +
+
+
+
+ +
02 · Individual Score Trajectories
+ +
+
+ Score explorer — per Registrar Account + + Click an account to inspect its full history. + ◆ remapped = surgery was applied. + +
+
+ + +
+ + +
+ +
+ +
+
+ + + +
+ +
+
+ +
03 · Surgery Operations
+ +
+
+
+ Surgeries per time step + Number of code remappings performed at each month +
+
+
+ +
+
+
+ +
+
+ Score gain per surgery + Average gain in Σ score from surgery at each month +
+
+
+ +
+
+
+
+ +
+
+ Jaccard similarity of surgery matches + + Composite Jaccard score of the matched code pair — closer to 1.0 = stronger portfolio overlap. + Low values may indicate uncertain matches. + +
+
+
+ +
+
+
+ +
04 · Surgery Detail Log
+ +
+
+ All surgery operations +
+
+ {'
No surgeries were performed on this dataset.
' if len(surgery) == 0 else f""" + + + + + + + + + + + + + + {surg_rows_html} +
DateReg origCode fromCode toJaccardScore gain% of score
"""} +
+
+ +
05 · Score Ranking at t_ref
+ +
+
+ Accounts ranked by weight at reference date + ✓ in last column = account was remapped at some point in history +
+
+ + + + + + + + + + + {top_rows_html} +
RankRegistrar IDScore (weight)Relative sizeRemapped
+
+
+ +
+ + + + + + +""" + + return html + + +# ───────────────────────────────────────────────────────────── +# 5. MAIN +# ───────────────────────────────────────────────────────────── + +def main(): + parser = argparse.ArgumentParser(description="Carmignac pipeline results analyser") + parser.add_argument("--scores", default="repair_results/carmignac_scores.csv") + parser.add_argument("--mapping", default="repair_results/carmignac_mapping.csv") + parser.add_argument("--surgery", default="repair_results/carmignac_surgery_log.csv") + parser.add_argument("--out", default="repair_results/carmignac_report.html") + args = parser.parse_args() + + # Resolve paths relative to this script's directory if files not found + base = os.path.dirname(os.path.abspath(__file__)) + def resolve(p): + if os.path.exists(p): + return p + alt = os.path.join(base, p) + if os.path.exists(alt): + return alt + sys.exit(f"[ERROR] File not found: {p}") + + scores_path = resolve(args.scores) + mapping_path = resolve(args.mapping) + surgery_path = resolve(args.surgery) + + print(f"[Load] scores : {scores_path}") + print(f"[Load] mapping : {mapping_path}") + print(f"[Load] surgery : {surgery_path}") + + scores, mapping, surgery = load_outputs(scores_path, mapping_path, surgery_path) + analytics = compute_analytics(scores, mapping, surgery) + + print_summary(analytics, surgery) + + html = build_html(analytics, surgery, scores, mapping) + + out_path = args.out + with open(out_path, "w", encoding="utf-8") as f: + f.write(html) + print(f"\n[Report] Written to → {out_path}") + + +if __name__ == "__main__": + main() diff --git a/repair_challenge/carmignac_repair.py b/repair_challenge/carmignac_repair.py deleted file mode 100644 index 873ef47..0000000 --- a/repair_challenge/carmignac_repair.py +++ /dev/null @@ -1,682 +0,0 @@ -""" -Carmignac Data Challenge — Registrar ID Repair Pipeline -========================================================= -Étape 1 : Filtrage & univers de référence à t=31/10/2025 -Étape 2 : Score de cohérence temporelle (propagation vers le passé) -Étape 3 : Chirurgie de code (matching 1-to-1) -""" - -import pandas as pd -import numpy as np -from collections import defaultdict -import os -import s3fs - -# ───────────────────────────────────────────── -# PARAMÈTRES -# ───────────────────────────────────────────── -ALPHA = 0.01 # tolérance réconciliation : 1% du stock à t -MIN_AUM_EUR = 5e6 # seuil filtrage étape 1 -MIN_JACCARD = 0.3 # seuil minimal similarité portefeuille pour chirurgie -SCORE_DROP_THRESHOLD = 0.5 # si score chute de >50% → candidat chirurgie - -EXCLUDE_REGISTRAR = ["Off Distribution", "Private Clients"] - -# ───────────────────────────────────────────── -# 1. CHARGEMENT -# ───────────────────────────────────────────── -def connect_s3fs(): - fs = s3fs.S3FileSystem( - client_kwargs={'endpoint_url': 'https://'+'minio-simple.lab.groupe-genes.fr'}, - key = os.environ["AWS_ACCESS_KEY_ID"], - secret = os.environ["AWS_SECRET_ACCESS_KEY"], - token = os.environ["AWS_SESSION_TOKEN"]) - return fs - -def load_data(aum_path, flows_path): - fs = connect_s3fs() - - with fs.open(aum_path, 'rb') as aum_raw: - aum = pd.read_csv(aum_raw, sep=";") - - with fs.open(flows_path, 'rb') as flows_raw: - flows = pd.read_csv(flows_raw, sep=";") - - aum['Centralisation Date'] = pd.to_datetime(aum['Centralisation Date']) - flows['Centralisation Date'] = pd.to_datetime(flows['Centralisation Date']) - - aum = aum.rename(columns={ - 'Registrar Account - ID': 'reg_id', - 'Product - Isin': 'isin', - 'Centralisation Date': 'date', - 'Quantity - AUM': 'qty_aum', - 'Value - AUM €': 'val_eur', - 'Registrar Account - Region': 'region', - }) - flows = flows.rename(columns={ - 'Registrar Account - ID': 'reg_id', - 'Product - Isin': 'isin', - 'Centralisation Date': 'date', - 'Quantity - NetFlows': 'qty_net', - 'Value € - NetFlows': 'val_net_eur', - }) - - aum['reg_id'] = aum['reg_id'].astype(str) - flows['reg_id'] = flows['reg_id'].astype(str) - - return aum, flows - -# ───────────────────────────────────────────── -# 2. ÉTAPE 1 — Univers de référence à T_REF -# ───────────────────────────────────────────── -def build_reference_universe(aum, t_ref=None): - """ - Construit l'univers de référence à t_ref (dernière date par défaut). - Retourne : - - aum_ref : AUM à t_ref pour chaque (reg_id, isin) - - weights : poids normalisé par reg_id - - universe : ensemble des reg_id retenus (>= MIN_AUM_EUR) - """ - if t_ref is None: - t_ref = aum['date'].max() - - print(f"\n[Étape 1] Date de référence : {t_ref.date()}") - - # Exclure Off Distribution / Private Clients (sur région ou nom) - mask_excl = aum['reg_id'].isin(EXCLUDE_REGISTRAR) - if 'region' in aum.columns: - mask_excl |= aum['region'].isin(EXCLUDE_REGISTRAR) - aum_clean = aum[~mask_excl].copy() - - # AUM à t_ref - aum_ref = aum_clean[aum_clean['date'] == t_ref][['reg_id', 'isin', 'qty_aum', 'val_eur']].copy() - - # AUM total par reg_id à t_ref - aum_by_reg = aum_ref.groupby('reg_id')['val_eur'].sum().rename('total_eur') - - # Filtrage >= MIN_AUM_EUR - universe = set(aum_by_reg[aum_by_reg >= MIN_AUM_EUR].index) - - total_eur_universe = aum_by_reg[aum_by_reg.index.isin(universe)].sum() - total_eur_all = aum_by_reg.sum() - coverage = total_eur_universe / total_eur_all if total_eur_all > 0 else 0 - - print(f" Registrar IDs à t_ref : {len(aum_by_reg)}") - print(f" Dont >= {MIN_AUM_EUR/1e6:.0f}M€ : {len(universe)}") - print(f" Couverture encours : {coverage:.1%}") - - # Poids initiaux (scores à t_ref) - weights = (aum_by_reg[aum_by_reg.index.isin(universe)] / total_eur_universe).to_dict() - - return aum_ref, weights, universe, t_ref - -# ───────────────────────────────────────────── -# 3. PANEL AUM MENSUEL (forward-fill) -# ───────────────────────────────────────────── -def build_monthly_panel(aum, universe, t_ref): - """ - Construit un panel mensuel complet (forward-fill des quantités AUM) - pour TOUS les reg_ids présents dans l'historique AUM — y compris les codes - historiques hors univers de référence, nécessaires pour la chirurgie. - """ - # Toutes les fin de mois entre la première date et t_ref - date_min = aum['date'].min() - all_months = pd.date_range(start=date_min, end=t_ref, freq='ME') - - # Pivot : (reg_id, isin) → série temporelle de qty_aum - aum_sorted = aum.sort_values(['reg_id', 'isin', 'date']) - - # On ne garde que les lignes jusqu'à t_ref - aum_sorted = aum_sorted[aum_sorted['date'] <= t_ref] - - # Multi-index pivot - panel = aum_sorted.pivot_table( - index='date', columns=['reg_id', 'isin'], values='qty_aum', aggfunc='last' - ) - - # Réindexer sur toutes les fins de mois - panel = panel.reindex(all_months) - - # Forward-fill : si pas de mouvement, la quantité reste la même - panel = panel.ffill() - - # Backward-fill initial pour les comptes qui démarrent après la première date - # (on ne remonte pas avant leur première apparition → on garde NaN) - - print(f"\n[Panel mensuel] {len(all_months)} mois, {panel.shape[1]} (reg_id, isin) paires") - - return panel, all_months - -# ───────────────────────────────────────────── -# 4. FLOWS AGRÉGÉS PAR MOIS -# ───────────────────────────────────────────── -def aggregate_flows_monthly(flows, all_months): - """ - Agrège les flows infra-mensuels sur chaque fenêtre ]fin_mois(t-1), fin_mois(t)]. - Retourne un DataFrame indexé par (fin_mois, reg_id, isin). - """ - flows_f = flows[flows['date'] <= all_months[-1]].copy() - - # Associer chaque transaction à la fin de mois correspondante - # = la première fin de mois >= date de transaction - flows_f['month_end'] = flows_f['date'].apply( - lambda d: all_months[all_months >= d][0] if any(all_months >= d) else pd.NaT - ) - flows_f = flows_f.dropna(subset=['month_end']) - - # Agrégation - monthly_flows = flows_f.groupby(['month_end', 'reg_id', 'isin'])['qty_net'].sum() - monthly_flows = monthly_flows.reset_index() - monthly_flows.columns = ['date', 'reg_id', 'isin', 'qty_net_month'] - - print(f"\n[Flows mensuels] {len(monthly_flows)} enregistrements (reg_id, isin, mois)") - - return monthly_flows - -# ───────────────────────────────────────────── -# 5. ÉTAPE 2 — Score de cohérence temporelle -# ───────────────────────────────────────────── -def compute_reconciliation_error(qty_t_minus1, qty_t, net_flow, alpha=ALPHA): - """ - Calcule l'erreur de réconciliation normalisée pour un (reg_id, isin, mois). - - Attendu : qty_t_minus1 + net_flow ≈ qty_t - Erreur : |qty_t_minus1 + net_flow - qty_t| / max(|qty_t|, |qty_t_minus1|) - - Retourne : - - err_ratio : erreur relative (0 = parfait) - - is_break : True si err_ratio > alpha - """ - denom = max(abs(qty_t), abs(qty_t_minus1), 1e-9) - err = abs(qty_t_minus1 + net_flow - qty_t) - err_ratio = err / denom - return err_ratio, err_ratio > alpha - -def score_propagation(panel, monthly_flows, weights, universe, all_months): - """ - Propage les scores de t_ref vers t=0 (passé). - - À chaque mois t (en remontant), pour chaque reg_id dans l'univers courant : - - Calculer l'erreur de réconciliation pondérée par ISIN - - Dégrader le score proportionnellement - - Retourne : - - scores_history : dict {date → {reg_id → score}} - - errors_history : dict {date → {reg_id → err_pondérée}} - - mapping : dict {reg_id_original → reg_id_courant} (après chirurgie) - """ - # Initialisation - scores = dict(weights) # scores à t_ref - scores_history = {all_months[-1]: dict(scores)} - errors_history = {} - - # Mapping actuel (identité au départ) - mapping = {r: r for r in universe} - - # Flows indexés pour accès rapide - flows_idx = monthly_flows.set_index(['date', 'reg_id', 'isin'])['qty_net_month'] - - # Remonter dans le temps - for i in range(len(all_months) - 2, -1, -1): - t_prev = all_months[i] - t_curr = all_months[i + 1] - - errors_at_t = {} - new_scores = {} - - for reg_orig, reg_curr in mapping.items(): - score_curr = scores.get(reg_orig, 0) - if score_curr == 0: - new_scores[reg_orig] = 0 - continue - - # ISIN détenus par ce reg à t_curr (après mapping) - if reg_curr in panel.columns.get_level_values(0): - isin_list = panel[reg_curr].columns.tolist() - else: - # reg_curr n'existe pas du tout dans le panel → rupture totale - new_scores[reg_orig] = 0 - errors_at_t[reg_orig] = 1.0 - continue - - total_aum_t = 0 - weighted_err = 0 - valid_isin_count = 0 - all_nan_at_prev = True # détecte si le compte n'existait pas à t_prev - - for isin in isin_list: - qty_t = panel[reg_curr][isin].get(t_curr, np.nan) - qty_t_prev = panel[reg_curr][isin].get(t_prev, np.nan) - - if pd.isna(qty_t): - continue - - if not pd.isna(qty_t_prev): - all_nan_at_prev = False - - if pd.isna(qty_t_prev): - # ISIN existait à t_curr mais pas à t_prev → rupture sur cet ISIN - # On le traite comme une erreur maximale pondérée par son AUM - weight_isin = abs(qty_t) - weighted_err += 1.0 * weight_isin - total_aum_t += weight_isin - valid_isin_count += 1 - continue - - if qty_t == 0 and qty_t_prev == 0: - continue - # Flow agrégé sur ]t_prev, t_curr] - try: - net_flow = flows_idx.loc[(t_curr, reg_curr, isin)] - except KeyError: - net_flow = 0.0 - - err_ratio, is_break = compute_reconciliation_error( - qty_t_prev, qty_t, net_flow, alpha=ALPHA - ) - - # Pondération par AUM à t_curr - weight_isin = abs(qty_t) - weighted_err += err_ratio * weight_isin - total_aum_t += weight_isin - valid_isin_count += 1 - - if total_aum_t > 0 and valid_isin_count > 0: - avg_err = weighted_err / total_aum_t - else: - avg_err = 0.0 - - errors_at_t[reg_orig] = avg_err - - # Dégradation du score : score(t-1) = score(t) * (1 - err_pondérée) - # Clippée entre 0 et score_curr - degradation = min(avg_err, 1.0) - new_scores[reg_orig] = score_curr * (1.0 - degradation) - - scores = new_scores - scores_history[t_prev] = dict(scores) - errors_history[t_prev] = dict(errors_at_t) - - total_score = sum(scores.values()) - print(f" {t_prev.date()} | Σ scores = {total_score:.4f} | " - f"Comptes actifs = {sum(1 for v in scores.values() if v > 0)}") - - return scores_history, errors_history, mapping - -# ───────────────────────────────────────────── -# 6. ÉTAPE 3 — Chirurgie de code -# ───────────────────────────────────────────── -def jaccard_isin(set_a, set_b): - """Coefficient de Jaccard entre deux ensembles d'ISIN.""" - if not set_a or not set_b: - return 0.0 - inter = len(set_a & set_b) - union = len(set_a | set_b) - return inter / union if union > 0 else 0.0 - -def find_best_candidate(reg_orig, reg_curr, t_prev, t_curr, - panel, flows_idx, all_regs_at_t_prev, mapping_inv): - """ - Pour un reg_id dont le score a fortement chuté, cherche le meilleur - candidat j à t_prev tel que : - - j n'est pas déjà mappé à un autre compte original - - Le portefeuille ISIN de j à t_prev est similaire à celui de reg_curr à t_curr - - La réconciliation est bonne - - Retourne (best_candidate, best_score_composite) ou (None, 0) - """ - # ISIN du compte cible à t_curr - if reg_curr not in panel.columns.get_level_values(0): - return None, 0.0 - - isin_curr = set(panel[reg_curr].columns[ - panel[reg_curr].loc[t_curr].notna() & (panel[reg_curr].loc[t_curr] != 0) - ].tolist()) - - if not isin_curr: - return None, 0.0 - - best_candidate = None - best_composite = 0.0 - - for j in all_regs_at_t_prev: - # Ne pas réutiliser un code déjà mappé - if j in mapping_inv: - continue - # Ne pas mapper sur soi-même si déjà présent - if j == reg_curr: - continue - - if j not in panel.columns.get_level_values(0): - continue - - # ISIN de j à t_prev - col_j = panel[j] - isin_j = set(col_j.columns[ - col_j.loc[t_prev].notna() & (col_j.loc[t_prev] != 0) - ].tolist()) if t_prev in col_j.index else set() - - if not isin_j: - continue - - jac = jaccard_isin(isin_curr, isin_j) - if jac < MIN_JACCARD: - continue - - # Erreur de réconciliation pour les ISIN communs - common_isin = isin_curr & isin_j - total_aum = 0 - weighted_err = 0 - - for isin in common_isin: - qty_t = panel[reg_curr][isin].get(t_curr, np.nan) if isin in panel[reg_curr].columns else np.nan - qty_t_prev = panel[j][isin].get(t_prev, np.nan) if isin in panel[j].columns else np.nan - - if pd.isna(qty_t) or pd.isna(qty_t_prev): - continue - - try: - net_flow = flows_idx.loc[(t_curr, j, isin)] - except KeyError: - net_flow = 0.0 - - err_ratio, _ = compute_reconciliation_error(qty_t_prev, qty_t, net_flow) - weight_isin = abs(qty_t) - weighted_err += err_ratio * weight_isin - total_aum += weight_isin - - avg_err = weighted_err / total_aum if total_aum > 0 else 1.0 - - composite = jac * (1.0 - min(avg_err, 1.0)) - - if composite > best_composite: - best_composite = composite - best_candidate = j - - return best_candidate, best_composite - - -def _recompute_score_with_candidate(reg_orig, candidate, t_prev, t_curr, - panel, flows_idx, score_curr): - """ - Recalcule proprement l'erreur de réconciliation pour un candidat donné, - et retourne le score après chirurgie. - """ - if candidate not in panel.columns.get_level_values(0): - return score_curr * 0 # candidat inexistant - - isin_list_cand = panel[candidate].columns.tolist() - isin_list_curr = panel[reg_orig].columns.tolist() if reg_orig in panel.columns.get_level_values(0) else [] - - total_aum = 0 - weighted_err = 0 - - for isin in isin_list_curr: - qty_t = panel[reg_orig][isin].get(t_curr, np.nan) if isin in panel[reg_orig].columns else np.nan - if pd.isna(qty_t) or qty_t == 0: - continue - - qty_t_prev = panel[candidate][isin].get(t_prev, np.nan) if isin in panel[candidate].columns else np.nan - - try: - net_flow = flows_idx.loc[(t_curr, candidate, isin)] - except KeyError: - net_flow = 0.0 - - if pd.isna(qty_t_prev): - err_ratio = 1.0 - else: - err_ratio, _ = compute_reconciliation_error(qty_t_prev, qty_t, net_flow) - - weight_isin = abs(qty_t) - weighted_err += err_ratio * weight_isin - total_aum += weight_isin - - avg_err = weighted_err / total_aum if total_aum > 0 else 1.0 - return score_curr * (1.0 - min(avg_err, 1.0)) - - -def run_surgery_pass(scores_history, errors_history, panel, monthly_flows, - weights, universe, all_months): - """ - Deuxième passe : pour chaque mois avec des ruptures fortes, - tente une chirurgie de code et recalcule les scores. - - Corrections par rapport à la passe naïve : - - Après chirurgie, le score est recalculé proprement (pas juste composite) - - Le mapping propagé en arrière utilise le bon code à chaque étape - - Pré-filtre ISIN pour performance sur grand dataset - - Retourne : - - mapping_history : {date → {reg_orig → reg_used}} - - surgery_log : liste des opérations effectuées - - scores_final : scores au dernier mois - """ - flows_idx = monthly_flows.set_index(['date', 'reg_id', 'isin'])['qty_net_month'] - - # Tous les reg_ids présents dans le panel (univers + codes historiques) - all_regs_in_panel = set(panel.columns.get_level_values(0)) - - # Pré-calcul : ensemble d'ISIN par reg_id à chaque date (pour pré-filtre rapide) - # {reg_id → {date → set(isin)}} - reg_isin_at_date = {} - for reg in all_regs_in_panel: - reg_isin_at_date[reg] = {} - col = panel[reg] - for date in col.index: - active = set(col.columns[(col.loc[date].notna()) & (col.loc[date] != 0)].tolist()) - if active: - reg_isin_at_date[reg][date] = active - - # Mapping courant : reg_orig → reg_used - mapping = {r: r for r in universe} - mapping_inv = {r: r for r in universe} - - surgery_log = [] - mapping_history = {all_months[-1]: dict(mapping)} - scores_history_corrected = {all_months[-1]: dict(weights)} - - # Scores courants (initialisés à t_ref) - scores = dict(weights) - - for i in range(len(all_months) - 2, -1, -1): - t_prev = all_months[i] - t_curr = all_months[i + 1] - - new_scores = {} - new_mapping = {} - - for reg_orig in list(mapping.keys()): - reg_curr = mapping[reg_orig] - score_curr = scores.get(reg_orig, 0) - - if score_curr == 0: - new_scores[reg_orig] = 0 - new_mapping[reg_orig] = reg_curr - continue - - # Erreur sans chirurgie (depuis étape 2) - err = errors_history.get(t_prev, {}).get(reg_orig, 0.0) - score_prev_no_surgery = score_curr * (1.0 - min(err, 1.0)) - drop_ratio = 1.0 - (score_prev_no_surgery / score_curr) if score_curr > 0 else 0 - - if drop_ratio > SCORE_DROP_THRESHOLD: - # ── ISIN du compte courant à t_curr (pour pré-filtre) ── - isin_curr = reg_isin_at_date.get(reg_curr, {}).get(t_curr, set()) - - # ── Candidats disponibles (non déjà mappés) ── - available = all_regs_in_panel - set(mapping_inv.keys()) - {reg_curr} - - best_candidate = None - best_score_after = score_prev_no_surgery # baseline = pas de chirurgie - best_composite = 0.0 - - for j in available: - # Pré-filtre rapide : overlap ISIN minimal - isin_j = reg_isin_at_date.get(j, {}).get(t_prev, set()) - if not isin_curr or not isin_j: - continue - inter = len(isin_curr & isin_j) - if inter == 0: - continue - jac = inter / len(isin_curr | isin_j) - if jac < MIN_JACCARD: - continue - - # Score après chirurgie avec ce candidat - score_after = _recompute_score_with_candidate( - reg_curr, j, t_prev, t_curr, panel, flows_idx, score_curr - ) - composite = jac * (score_after / score_curr) if score_curr > 0 else 0 - - if score_after > best_score_after: - best_score_after = score_after - best_candidate = j - best_composite = composite - - if best_candidate: - surgery_log.append({ - 'date': t_prev, - 'reg_orig': reg_orig, - 'reg_from': reg_curr, - 'reg_to': best_candidate, - 'jaccard_composite': round(best_composite, 4), - 'score_before': round(score_curr, 6), - 'score_after': round(best_score_after, 6), - 'drop_without_surgery': round(drop_ratio, 4), - 'gain_vs_no_surgery': round(best_score_after - score_prev_no_surgery, 6), - }) - print(f" 🔧 CHIRURGIE {t_prev.date()} | {reg_orig} : " - f"{reg_curr} → {best_candidate} " - f"(composite={best_composite:.3f}, " - f"score {score_curr:.4f}→{best_score_after:.4f})") - - # Mise à jour mapping - if reg_curr in mapping_inv: - del mapping_inv[reg_curr] - mapping_inv[best_candidate] = reg_orig - new_mapping[reg_orig] = best_candidate - new_scores[reg_orig] = best_score_after - else: - new_mapping[reg_orig] = reg_curr - new_scores[reg_orig] = score_prev_no_surgery - else: - new_mapping[reg_orig] = reg_curr - new_scores[reg_orig] = score_prev_no_surgery - - mapping = new_mapping - mapping_inv = {v: k for k, v in mapping.items()} - scores = new_scores - mapping_history[t_prev] = dict(mapping) - scores_history_corrected[t_prev] = dict(scores) - - total_score = sum(s for s in scores.values() if not np.isnan(s)) - n_surgeries = sum(1 for op in surgery_log if op['date'] == t_prev) - print(f" {t_prev.date()} | Σ scores = {total_score:.4f} | " - f"Chirurgies = {n_surgeries}") - - return mapping_history, surgery_log, scores, scores_history_corrected - -# ───────────────────────────────────────────── -# 7. EXPORT RÉSULTATS -# ───────────────────────────────────────────── -def export_results(scores_history, mapping_history, surgery_log, all_months, out_prefix="carmignac"): - """Exporte les résultats clés en CSV.""" - - # Score history - rows = [] - for date, sc in scores_history.items(): - for reg, score in sc.items(): - rows.append({'date': date, 'reg_id': reg, 'score': score}) - df_scores = pd.DataFrame(rows) if rows else pd.DataFrame(columns=['date', 'reg_id', 'score']) - if not df_scores.empty: - df_scores = df_scores.sort_values(['date', 'score'], ascending=[True, False]) - df_scores.to_csv(f"repair_results/{out_prefix}_scores.csv", index=False) - - # Mapping history - rows_m = [] - for date, mp in mapping_history.items(): - for reg_orig, reg_used in mp.items(): - rows_m.append({'date': date, 'reg_orig': reg_orig, 'reg_used': reg_used, - 'changed': reg_orig != reg_used}) - df_mapping = pd.DataFrame(rows_m) if rows_m else pd.DataFrame(columns=['date', 'reg_orig', 'reg_used', 'changed']) - if not df_mapping.empty: - df_mapping = df_mapping.sort_values(['date', 'reg_orig']) - df_mapping.to_csv(f"repair_results/{out_prefix}_mapping.csv", index=False) - - # Surgery log - if surgery_log: - df_surgery = pd.DataFrame(surgery_log).sort_values('date') - df_surgery.to_csv(f"repair_results/{out_prefix}_surgery_log.csv", index=False) - print(f"\n[Export] {len(surgery_log)} opérations de chirurgie sauvegardées.") - else: - print("\n[Export] Aucune chirurgie effectuée sur ce subset.") - - print(f"[Export] Scores → {out_prefix}_scores.csv") - print(f"[Export] Mapping → {out_prefix}_mapping.csv") - - return df_scores, df_mapping - -# ───────────────────────────────────────────── -# 8. PIPELINE PRINCIPAL -# ───────────────────────────────────────────── -def run_pipeline(aum_path, flows_path): - print("=" * 60) - print("CARMIGNAC — Pipeline de réparation des Registrar IDs") - print("=" * 60) - - # Chargement - aum, flows = load_data(aum_path, flows_path) - - # Étape 1 — Univers de référence - aum_ref, weights, universe, t_ref = build_reference_universe(aum) - - print(f"\n Top 5 comptes par poids :") - for reg, w in sorted(weights.items(), key=lambda x: -x[1])[:5]: - print(f" {reg} : {w:.4f} ({w*100:.2f}%)") - - # Panel mensuel - panel, all_months = build_monthly_panel(aum, universe, t_ref) - - # Flows mensuels agrégés - monthly_flows = aggregate_flows_monthly(flows, all_months) - - # Étape 2 — Score de cohérence (sans chirurgie) - print("\n[Étape 2] Propagation des scores (sans chirurgie)...") - scores_history, errors_history, _ = score_propagation( - panel, monthly_flows, weights, universe, all_months - ) - - # Étape 3 — Chirurgie - print("\n[Étape 3] Passe de chirurgie...") - mapping_history, surgery_log, final_scores, scores_history_corrected = run_surgery_pass( - scores_history, errors_history, panel, monthly_flows, - weights, universe, all_months - ) - - # Export — on utilise les scores corrigés (post-chirurgie) comme référence - print("\n[Export des résultats...]") - df_scores, df_mapping = export_results( - scores_history_corrected, mapping_history, surgery_log, all_months - ) - - # Résumé final - print("\n" + "=" * 60) - print("RÉSUMÉ FINAL") - print("=" * 60) - print(f" Dates couvertes : {all_months[0].date()} → {all_months[-1].date()}") - print(f" Comptes dans l'univers : {len(universe)}") - print(f" Chirurgies effectuées : {len(surgery_log)}") - score_by_date = {d: sum(s for s in sc.values() if s == s) - for d, sc in scores_history_corrected.items()} - print(f" Σ scores à t_ref : {score_by_date[t_ref]:.4f}") - print(f" Σ scores à t_min : {score_by_date[all_months[0]]:.4f}") - - return df_scores, df_mapping, surgery_log, scores_history_corrected, mapping_history - - -if __name__ == "__main__": - df_scores, df_mapping, surgery_log, scores_history, mapping_history = run_pipeline( - "s3://projet-bdc-carmignac-g3/stock_repaired.csv", - "projet-bdc-data//carmignac/Flows ENSAE V2 -20251105.csv" - ) diff --git a/repair_challenge/repair_results/carmignac_report.html b/repair_challenge/repair_results/carmignac_report.html new file mode 100644 index 0000000..bbaa54b --- /dev/null +++ b/repair_challenge/repair_results/carmignac_report.html @@ -0,0 +1,10009 @@ + + + + + +Carmignac Pipeline — Analysis Report + + + + + + +
+
Carmignac × ENSAE · Data Challenge 2025
+

Pipeline Results — Analysis Report

+
Registrar ID repair · Score propagation · Surgery audit
+
+ + +
+
+ Σ score at t_ref + 1.0000 + post-surgery +
+
+ Σ score at t_min + 0.2476 + post-surgery +
+
+ Max recovery + 70.5% + score rescued by surgery +
+
+ Total surgeries + 512 + operations performed +
+
+ Reg IDs universe + 432 + at reference date +
+
+ Ever remapped + 340 + reg IDs w/ code change +
+
+ + +
+ +
01 · Score Integrity Over Time
+ + +
+
+ Sum of scores — pre vs post surgery + + Post-surgery (solid) shows the corrected score after code repairs. + Pre-surgery (dashed) is the counterfactual without any remapping. + Gap = score rescued. + +
+
+
+ +
+
+
+ + +
+
+
+ Score recovered by surgery + Difference post − pre at each date +
+
+
+ +
+
+
+ +
+
+ Portfolio concentration (entropy) + Shannon entropy of score distribution — higher = more spread +
+
+
+ +
+
+
+
+ +
02 · Individual Score Trajectories
+ +
+
+ Score explorer — per Registrar Account + + Click an account to inspect its full history. + ◆ remapped = surgery was applied. + +
+
+ + +
+ + +
+ +
+ +
+
+ + + +
+ +
+
+ +
03 · Surgery Operations
+ +
+
+
+ Surgeries per time step + Number of code remappings performed at each month +
+
+
+ +
+
+
+ +
+
+ Score gain per surgery + Average gain in Σ score from surgery at each month +
+
+
+ +
+
+
+
+ +
+
+ Jaccard similarity of surgery matches + + Composite Jaccard score of the matched code pair — closer to 1.0 = stronger portfolio overlap. + Low values may indicate uncertain matches. + +
+
+
+ +
+
+
+ +
04 · Surgery Detail Log
+ +
+
+ All surgery operations +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DateReg origCode fromCode toJaccardScore gain% of score
2015-01-314202074191644061690.1294+0.0000000.0%
2015-01-312000023892000023893650880.9745+0.00125897.4%
2015-02-283653763658522065970.0887+0.0000000.0%
2015-02-282000024292000024294175440.2401+0.00001852.9%
2015-02-284202074061694191640.2260+0.0000000.0%
2015-03-312000063492000063492000006850.0745+0.00000715.6%
2015-04-304202074191644061690.1259+0.0000000.0%
2015-06-302000132312000132313663770.9000+0.000261100.0%
2015-06-302000128322000128322000018360.1833+0.00001747.2%
2015-06-302000134472000003102000020120.2842+0.0000000.0%
2015-07-312000134472000020122000003100.3283+0.0000000.0%
2015-08-312000175612000175613661100.3769+0.00006679.5%
2015-09-304202074061694191640.2092+0.0000000.0%
2015-09-302000199402000199403659000.2647+0.00103079.5%
2015-10-314202074191644061690.1313+0.0000000.0%
2016-01-312000134473655432000020120.2509+0.0000000.0%
2016-02-292000343724194203650440.4154+0.00011571.0%
2016-04-304202074061694191640.1983+0.0000000.0%
2016-06-302000065344228352000339260.9983+0.00100199.8%
2016-06-302000436903650942920710.2038+0.00004955.7%
2016-07-314202074191644061690.1130+0.0000000.0%
2016-07-312000457202000457204194840.8537+0.00023797.9%
2016-08-312159052159053657630.1294+0.0000000.0%
2016-09-302000021474202392000106100.1281+0.00006538.5%
2016-09-302000436902000436903650940.3485+0.00009778.9%
2016-10-312000540462000013772000016110.1890+0.00002248.9%
2016-10-312000525624191714196010.1987+0.00000550.0%
2016-10-313657033657032000297090.1005+0.00000422.2%
2016-10-312000522082000522082000416990.4778+0.00009495.9%
2016-11-302000559904200533663510.2721+0.00014081.4%
2016-11-30365376134633658520.2170+0.0000000.0%
2016-11-302000546372000546374192060.8890+0.00004393.5%
2016-12-31365376365376134630.1462+0.0000000.0%
2016-12-312000590253661103659280.3579+0.00005368.0%
2016-12-312000595882000595883659510.2555+0.00059651.1%
2016-12-312000525622000525624191710.2002+0.0000029.5%
2017-02-282000641422000641423647250.3162+0.00001466.7%
2017-03-312000667632000667634194030.5818+0.00003297.0%
2017-03-312000670962000016114183250.2145+0.00002964.4%
2017-03-312000559902000559904200530.3190+0.00009351.7%
2017-03-312000664552000664552000529490.2357+0.00011770.9%
2017-03-314225543654294193320.2356+0.00006955.6%
2017-03-312000666672000666672000325440.9725+0.00088797.3%
2017-04-303650943650943648670.1729+0.0000043.6%
2017-04-302000696404199094184970.1683+0.00000444.4%
2017-05-31365376134633653760.2455+0.0000000.0%
2017-05-314202074202074191640.2492+0.0000000.0%
2017-06-302000733552000733553663980.9935+0.00430499.3%
2017-06-302000001472000001473665950.4308+0.00051331.9%
2017-06-302000733542000733543664390.9958+0.00998999.6%
2017-06-302000729072000729072000001470.4889+0.00090877.5%
2017-06-302000023752000023752000021860.0035+0.0000081.1%
2017-07-312000759322000759323658650.9883+0.00775198.8%
2017-08-312000791692000791692000015680.3363+0.00000541.7%
2017-09-304184564184564202230.1918+0.00004943.8%
2017-09-302000065342000065344228350.2549+0.00072954.5%
2017-09-302000832482000832483663850.9925+0.00064099.2%
2017-10-312000856152000856153659030.9980+0.00093299.8%
2017-10-312000021472000021474202390.2682+0.00013564.3%
2017-10-312000670962000670962000016110.7052+0.00005267.5%
2017-10-312000134472000134473655430.4770+0.00000150.0%
2017-11-303650903650903648290.1564+0.00003338.4%
2017-11-302000343722000343724194200.3767+0.00004411.7%
2017-11-302000219582000219582000648770.1742+0.0000138.9%
2017-11-302000860372000022443657120.3185+0.00018995.5%
2017-11-302000866542000866544198010.5215+0.00007466.7%
2017-12-312000894061000000704199970.2458+0.00002347.9%
2017-12-312000865482000865483659590.2662+0.00171479.9%
2017-12-312000016212000016212000320280.1975+0.00000222.2%
2018-01-312000023212000023212000015200.1992+0.00001535.7%
2018-01-312000540462000540462000013770.5275+0.00005645.5%
2018-01-312000933314039042000012900.5747+0.00010070.9%
2018-01-312000920812000920812000021150.4658+0.00021993.2%
2018-02-282000933312000912894039040.2778+0.00013667.0%
2018-03-312000933312000933312000912890.3499+0.00012252.6%
2018-03-312000960022000960022000003670.9654+0.00018296.8%
2018-04-302000912882000912882000368480.6177+0.00002963.0%
2018-05-312000894062000894061000000700.2064+0.00002935.8%
2018-07-312000590252000590253661100.2181+0.00002419.1%
2018-08-312001021252001021252000380560.2034+0.00002952.7%
2018-08-312000020873659592000903390.4786+0.00039279.8%
2018-09-302000020872000020873659590.2970+0.00025738.8%
2018-09-302001021842000933042000142220.3539+0.00007256.7%
2018-10-312001022382000023823659600.3359+0.00019167.2%
2018-10-312001022202001022204190980.9583+0.00008998.9%
2018-11-302001022382001022382000023820.2762+0.00019951.5%
2018-11-302000860372000805742000022440.4747+0.00012961.7%
2018-11-302001043382001043383664250.5582+0.00001593.8%
2018-11-302001021842001021842000933040.2161+0.00006431.2%
2018-11-302000746693661072000560690.2870+0.00005650.0%
2019-02-282000746692000560693661070.2183+0.0000010.3%
2019-03-312000399982000399982000419570.2434+0.00034775.4%
2019-04-302001263804194202001018940.0991+0.00019924.8%
2019-05-312001263802001018944194200.2501+0.00047438.4%
2019-06-302001265152000015273654950.1403+0.00013342.1%
2019-07-313661073661073658500.7967+0.00005964.1%
2019-07-312001267022001267024222500.1832+0.00021848.9%
2019-08-312001267172001267172001262520.1221+0.00001312.4%
2019-09-302000949542000019302000860400.3871+0.00000571.4%
2019-10-312000746692000746692000560690.9676+0.00093298.4%
2019-10-312000949544188402000019300.1228+0.0000015.6%
2019-10-312001273812001273812000914820.2293+0.00004023.0%
2019-11-302001272012001019613648360.2248+0.00000375.0%
2019-11-302001274282001274282000903310.9556+0.00013495.7%
2019-11-302001272252001272254186110.4630+0.00001482.3%
2019-11-302001274172001274172000931490.9807+0.00035898.1%
2019-11-302001273082001273074222250.6016+0.00001970.4%
2019-11-302001274302001274302000903340.6283+0.00275394.2%
2019-11-302001273162001273162000900010.5390+0.00492783.3%
2019-11-302001276132001276134203750.9976+0.000008100.0%
2019-11-302001274103665822001266030.0352+0.0000000.0%
2019-11-302001275522001275522000916530.3369+0.00230449.4%
2019-11-302001275872001275872000903210.4706+0.00005870.7%
2019-11-302001273062001273062000903470.9844+0.00043498.6%
2019-11-302001275982001275982001022390.9724+0.000012100.0%
2019-11-302001271862001271862001263500.9880+0.00164498.8%
2019-11-302001271742001271742000902880.4421+0.00007373.7%
2019-11-302001274842001274844060480.9583+0.00012196.0%
2019-11-302001274352001274352000979370.9700+0.00020494.9%
2019-11-302001274152001274154057600.4258+0.00250559.6%
2019-11-302001273432000019424176540.0644+0.0000000.0%
2019-11-302001271912001271912000900350.3309+0.00007488.1%
2019-11-302001274542001274544163480.9826+0.01399898.3%
2019-11-302001277033645444200490.4777+0.00001386.7%
2019-11-302001274332001274332001265390.9386+0.00060593.8%
2019-11-302001274032001274032001021380.9893+0.00034298.8%
2019-11-302001273912001273912000900750.9917+0.00045799.3%
2019-11-302001273712001273714228240.1137+0.00000222.2%
2019-11-302001271572001271572000903100.4482+0.00002180.8%
2019-11-302001273622001273622000900570.9169+0.00189897.8%
2019-11-302001273762001273762000899700.9936+0.00043399.3%
2019-11-302001274562001274562001017100.2815+0.00001083.3%
2019-11-302001274042001274042000900530.5384+0.00080889.8%
2019-11-302001274342001274342001268160.9525+0.00001493.3%
2019-11-302001274012001274014166300.5000+0.000052100.0%
2019-11-302001272723653483628200.1824+0.00007452.9%
2019-11-302001271752000156993664120.0590+0.0000000.0%
2019-11-302001275532001275534066810.4392+0.00035170.9%
2019-11-302001276052001276052000931520.9783+0.00257497.8%
2019-11-302001274192001274192000903120.9776+0.00011298.2%
2019-11-302001273832001273832000932790.9781+0.00033097.9%
2019-11-302001274552001018684160780.2805+0.00000150.0%
2019-11-302001274982001274982001021530.9852+0.00001191.7%
2019-11-302001274322001274322000019680.8713+0.00217398.0%
2019-11-302001271902001272612000903460.6125+0.000001100.0%
2019-11-302001271352001271354052970.9854+0.00032698.5%
2019-11-302001271852001271852000983030.5408+0.00000375.0%
2019-11-302001275782001275782000931530.9749+0.00041497.4%
2019-11-302001275832001275832000436610.9858+0.00008598.8%
2019-11-302001274782000015222000397650.0966+0.00000320.0%
2019-11-302001276142001276144064630.9889+0.00001493.3%
2019-11-302001274632001274634063080.7038+0.00012196.8%
2019-11-302001274482001274482001019800.9956+0.00037599.7%
2019-11-302001275552001274102000899450.1977+0.0000000.0%
2019-11-302001275562001275562001022190.9620+0.00027196.1%
2019-11-302001279012001279012001279020.8532+0.00012397.6%
2019-11-302001276032001276032001265450.9973+0.000212100.0%
2019-11-302001274472001274472000240030.9753+0.00012597.7%
2019-11-302001271872001271872000900740.0583+0.00010919.4%
2019-11-302001274462001274462000900440.4933+0.00093749.3%
2019-11-302001273642001273642000900560.4829+0.00047980.5%
2019-11-302001277982001277982000022560.9627+0.00024296.0%
2019-12-312001021973628203659900.0360+0.0000000.0%
2019-12-312001277332000865002001265950.1460+0.00000125.0%
2019-12-312001278033661102001021900.3978+0.00006873.9%
2019-12-312001277642001016142000022260.9136+0.00037691.3%
2020-01-312001278162001278162001271300.4404+0.00021771.8%
2020-01-312001278092001278092001275550.0202+0.0004205.0%
2020-01-312001278172001278172000014560.1104+0.00000635.3%
2020-01-312001278152001278152001274260.1651+0.00063249.6%
2020-01-312001278122001278122001274210.2936+0.00000457.1%
2020-01-312001278032001278033661100.4959+0.00005742.9%
2020-01-312000956462000021642000018600.4739+0.00001794.4%
2020-01-312001274552001274552001018680.0909+0.0000019.1%
2020-01-31200127811200127811621260.2398+0.00044351.9%
2020-01-312001278102001278103659220.1419+0.00069128.4%
2020-01-312001277332001277332000865000.0733+0.00000315.8%
2020-01-312001274782000397652000015220.2409+0.00001553.6%
2020-02-292001279962001272622001278190.1547+0.00004746.1%
2020-02-29200102197621263628200.3124+0.00000233.3%
2020-02-292001279034201383654180.3273+0.00013997.9%
2020-03-312001265152001265152000015270.6679+0.00022742.7%
2020-03-312001284812001284814222640.2139+0.00009842.8%
2020-03-312001283632001283632001277690.9068+0.00030390.7%
2020-03-312001274162001274162001274200.9902+0.00291698.8%
2020-03-312001276392001276392001274910.9506+0.000002100.0%
2020-03-312001279032001279034201380.3627+0.00011559.0%
2020-03-312001274402001274402001273850.1926+0.00023640.9%
2020-04-302001288733628202685620.1146+0.00061328.3%
2020-04-302000956462000956462000021640.0913+0.00001622.9%
2020-04-302001021902001021904190910.1164+0.00030329.5%
2020-05-312001305002001305003660070.6486+0.000059100.0%
2020-05-312001277282000676822000007020.3018+0.00000535.7%
2020-05-312001297162001297162001276080.8960+0.00012889.5%
2020-05-312001298112001298113654560.0341+0.0000127.1%
2020-05-312000012852000012852000857430.2972+0.00005286.7%
2020-05-312001288732001288733628200.5083+0.0000741.7%
2020-06-302001279962001279962001272620.6904+0.00011976.3%
2020-06-302001277282001277282000676820.1891+0.00001033.3%
2020-07-312001304752001277692001304310.4531+0.00001890.0%
2020-08-312001277572000963862000003070.2641+0.00000685.7%
2020-08-312001305272001305272000002120.9885+0.00032998.8%
2020-09-302001305492001305492001263720.3483+0.00013965.6%
2020-09-30365376348454134630.1010+0.0000000.0%
2020-09-303660243659224227370.0058+0.0000000.0%
2020-09-304160784160784160480.8862+0.00000777.8%
2020-09-302001305282001305282496900.1937+0.00002131.8%
2020-09-302001273602001273602000143680.5380+0.00035653.8%
2020-10-312001306972001306973665060.7994+0.00199099.9%
2020-10-312001274782001274782000397650.2244+0.0000069.2%
2020-10-312001288542001305992001304940.0105+0.0000000.0%
2020-11-304225544225543654290.1349+0.00017632.7%
2020-11-302001307192001307192000014130.4541+0.00022191.0%
2020-11-30200102197200127555621260.0841+0.00000520.0%
2020-11-302001306882001306884171460.8013+0.00040980.0%
2020-11-302001277432001273852925940.0365+0.00011112.0%
2020-11-302001306844049214057720.9440+0.000004100.0%
2020-12-312001306902001306902001276380.7655+0.00014991.4%
2020-12-312001305072001305422001305030.0828+0.00001627.1%
2020-12-312001308422001308422001304261.0000+0.000129100.0%
2020-12-312001306652001306652001305980.6485+0.00108696.0%
2020-12-312001273433659902000019420.3135+0.0000000.0%
2020-12-312001305852001305852001278130.2576+0.00038846.7%
2020-12-312001305832001305832001274790.4699+0.00004266.7%
2021-01-312001272722001278133653480.3428+0.00006423.4%
2021-01-312001309032001309033649770.3448+0.000177100.0%
2021-01-31366024621263659220.0928+0.0000000.0%
2021-01-312001308552001308554226000.4995+0.00104899.9%
2021-01-312001308923659222001275310.1319+0.00043942.2%
2021-01-31Private ClientPrivate Client3664190.0240+0.0000000.0%
2021-02-282001277642001277642001016140.1127+0.0000302.1%
2021-02-282001308922001308923659220.5500+0.00064744.9%
2021-02-283073882001275312000710750.0570+0.0000000.0%
2021-03-312001273502001273502001273350.1628+0.00000337.5%
2021-03-312001273672001273672000004070.2393+0.00007772.0%
2021-03-312001309772001309772000628820.1316+0.00015239.6%
2021-03-312001306844057724049210.8336+0.00000250.0%
2021-04-302001272682001272682001272720.3098+0.00010141.4%
2021-04-302001310522001310522001307880.8768+0.00030787.5%
2021-04-303653683653683664220.2554+0.00005879.5%
2021-04-302001310562001310564116740.2280+0.00001443.8%
2021-05-312001272722001272722001278130.1389+0.00011116.7%
2021-05-312001310822001310822001309270.1194+0.00004712.0%
2021-05-314197894197892001308830.6666+0.000009100.0%
2021-05-313651273651272001308050.0442+0.00013211.7%
2021-05-312001310812001310812001304190.0072+0.0000061.6%
2021-06-302001273432001273433659900.1499+0.0000000.0%
2021-06-302001306843660054057720.0317+0.0000012.1%
2021-06-30365376134633484540.1185+0.0000000.0%
2021-06-302001313862001313862001273450.1409+0.00005922.1%
2021-07-31365376365376134630.2041+0.0000000.0%
2021-07-313666023666022000021620.3394+0.00024082.5%
2021-08-31366559366559620130.0263+0.0000676.9%
2021-08-312001305072001305072001305420.7661+0.00005789.1%
2021-08-312001298152001298152001273080.1151+0.00000637.5%
2021-08-312001265262001265262001266470.9772+0.00027997.5%
2021-08-31366470366470640430.3384+0.00337755.0%
2021-08-312001313032001274963664460.0455+0.00006714.9%
2021-08-312001021512001021513649180.9273+0.0000000.0%
2021-08-313653283653283655120.0907+0.00001825.7%
2021-08-312000552742001273252001274930.2314+0.00001037.0%
2021-08-313658073658072000104480.0868+0.00008126.9%
2021-08-313649293649293646970.0104+0.0004433.4%
2021-08-314189614189612001019180.0081+0.0002692.6%
2021-08-312001288542001288542001305990.0119+0.0000063.2%
2021-08-313653773653773665400.0903+0.00073424.1%
2021-08-31420350420350670030.0228+0.0011814.7%
2021-08-313652663659532000856160.2355+0.0000000.0%
2021-08-312000963313484543659530.0027+0.0000000.0%
2021-08-312001313012001313013657701.0000+0.000186100.0%
2021-08-312001278142145832000017470.0110+0.0000082.4%
2021-08-312001308412001308412001308190.2392+0.00000250.0%
2021-08-31365236200127644670040.0167+0.0000054.0%
2021-08-313661313661312001274620.3037+0.00002976.3%
2021-08-314225764225764188970.0545+0.00012017.2%
2021-08-313649993649992001278990.0216+0.0002177.1%
2021-08-312000818052000818052000805730.9740+0.00024497.2%
2021-08-312000593782000593783647400.0235+0.0000000.0%
2021-08-313663863663863663920.9276+0.00001292.3%
2021-08-313652413652413651370.9965+0.000226100.0%
2021-08-312000960012000960014184830.0522+0.00006912.6%
2021-08-313652423652422001309200.1120+0.00068435.9%
2021-08-312000019422000019422000358300.0702+0.00011521.9%
2021-08-312001312942000085614170720.3080+0.00027692.6%
2021-09-302001313092001313092000782890.1634+0.00048343.6%
2021-09-302001278142001275312145830.2437+0.00032641.6%
2021-09-30307388640432001275310.2385+0.0000000.0%
2021-09-302001277572001277572000963860.0217+0.0000075.6%
2021-09-302001313432001313432001309900.8852+0.0000000.0%
2021-09-302001313402001313403431210.5126+0.00030452.1%
2021-09-30365304620133652360.8954+0.0000000.0%
2021-09-302001313032001313032001274960.9844+0.00044597.2%
2021-09-302001313252001313253529580.8901+0.00087798.9%
2021-10-31365304365236620130.8381+0.0000000.0%
2021-10-312001313872001313872001263450.1625+0.00001248.0%
2021-11-302001312944202392000085610.3226+0.00015048.7%
2021-11-30365304620133652360.8031+0.0000000.0%
2021-11-302001314492001314494190900.6422+0.00017092.9%
2021-11-302001306842001306843660050.2861+0.00004032.0%
2021-12-312001373532001315212000860360.1795+0.00004335.8%
2021-12-3130738867004640430.0979+0.0000000.0%
2021-12-312001315342001315342001273230.7073+0.00012277.2%
2021-12-31365304365236620130.7733+0.0000000.0%
2022-01-312001315802001315802000710760.9941+0.00320599.4%
2022-01-312001315792001315792001271780.4803+0.00016696.5%
2022-01-312001315432001315432001019710.0011+0.0000010.1%
2022-01-31365304620133652360.7438+0.0000000.0%
2022-02-282001275283647402000548160.1777+0.0000000.0%
2022-02-28365304365236620130.7871+0.0000000.0%
2022-03-3136602464043621260.2343+0.0000000.0%
2022-03-312001316512001246612001262610.9184+0.00002496.0%
2022-03-312001316482001316482001309500.5920+0.00012559.2%
2022-04-302001317222000106103653940.0141+0.0000482.5%
2022-04-302001317502001317503652160.6573+0.00215165.7%
2022-04-30365304620133652360.7640+0.0000000.0%
2022-04-302001312942001312944202390.1730+0.0001149.6%
2022-04-302001263802001263802001018940.6341+0.00129850.7%
2022-05-3136602462126640430.2692+0.0000000.0%
2022-05-312001317772001317774223450.9973+0.00043299.8%
2022-05-312001317712001317712001271270.0755+0.00005015.2%
2022-05-312001317632001317632001277060.9153+0.00084891.5%
2022-05-3136530467003620130.3815+0.0000000.0%
2022-06-302001271752001271752000156990.1282+0.00000423.5%
2022-06-302001317222001317222000106100.3899+0.00192749.8%
2022-06-302001275162001275162001366370.0398+0.0000010.2%
2022-06-302001272012001276282001019610.1361+0.00000327.3%
2022-06-302001318042001318043654900.0919+0.00010427.5%
2022-07-313652363652362001276440.1597+0.0000235.9%
2022-07-312001316512001262612001246610.6659+0.00003368.8%
2022-07-3136602464043621260.2343+0.0000000.0%
2022-07-312001274952001274952001290850.3333+0.000001100.0%
2022-08-312001365972001365972001307910.4507+0.00040490.0%
2022-08-312001275282000548163647400.0245+0.0000000.0%
2022-08-312001274792001278143660240.0571+0.00000412.9%
2022-08-312001316512001316512001262610.4859+0.00002233.3%
2022-09-302001373563660242001273750.1991+0.00008439.8%
2022-09-302001373542001373542000096890.0220+0.0000204.5%
2022-09-302001374772000856163658360.4469+0.00003362.3%
2022-09-302001274792001273752001278140.0937+0.00002923.0%
2022-09-30200137360200137360640030.1202+0.00084528.2%
2022-09-302001021972001021972001275550.1988+0.0000044.6%
2022-09-302001317362001317362001308790.4435+0.00002172.4%
2022-09-3036530462013670030.3493+0.0000000.0%
2022-09-302001373612001373612011130.6549+0.00024967.1%
2022-10-312001374772001374772000856160.0993+0.00003219.3%
2022-10-31200137628640034189920.3844+0.00086950.5%
2022-10-312000025832000025832000331040.1897+0.00000520.0%
2022-10-31200137356670033660240.1374+0.00008014.3%
2022-10-312001376332001376332001298970.1259+0.00000125.0%
2022-10-312001374672001272622001272560.3748+0.00007764.7%
2022-10-312001278722001278722001317800.0603+0.0000018.3%
2022-11-302001374592001278133656470.0719+0.00003923.2%
2022-11-302001275282001279052000548160.1211+0.0000000.0%
2022-11-302001271272001271562001272550.1045+0.00000523.8%
2022-11-302001276272001276272001313450.3162+0.00012526.5%
2022-12-312001374592001374592001278130.1151+0.00014325.5%
2022-12-312001373532001373532001315210.7696+0.00012672.0%
2022-12-312000020762000020762000019410.0157+0.0000724.7%
2022-12-312001376192001376192001272640.1563+0.00014632.7%
2022-12-312001374672001374672001272620.2359+0.00002910.7%
2022-12-312001274792001274792001273750.5167+0.00009655.8%
2022-12-312001314772001314772001273900.2801+0.00028084.1%
2022-12-31200137356200137356670030.1234+0.00041521.7%
2023-01-3136602462126640430.2692+0.0000015.6%
2023-01-312001377594188974193360.1734+0.00006541.4%
2023-01-312000581082000581083124780.8098+0.00769482.4%
2023-01-312001275283645592001279050.3088+0.0000000.0%
2023-02-2836530467003620130.3815+0.0000000.0%
2023-02-282001278132001275552001281740.2096+0.00008752.4%
2023-03-312001275282000655643645590.3023+0.0000000.0%
2023-03-3120012764464043620870.3063+0.0000000.0%
2023-03-31307388418992670040.2582+0.0000000.0%
2023-03-312001378282001378282000002520.8399+0.00133099.8%
2023-03-312001278132001281742001275550.1733+0.00004511.8%
2023-04-302001378512001378513655000.7135+0.00012596.9%
2023-04-3036530462013670030.3493+0.0000000.0%
2023-04-302001271272001271272001271560.3137+0.0000046.7%
2023-04-302001273313664223664080.3079+0.00002052.6%
2023-04-302001278132001278132001281740.3797+0.00026851.0%
2023-04-303652662001019613659530.1320+0.0000000.0%
2023-05-3136602462087621260.3851+0.00000715.6%
2023-05-31307388670044189920.2556+0.0000000.0%
2023-05-312001379282001379282000901601.0000+0.000317100.0%
2023-05-312000320282000320282001376400.0352+0.0000021.3%
2023-05-312001275792001377962001272700.0381+0.00002010.5%
2023-06-302001272012001272012001276280.1313+0.0000146.6%
2023-06-30307388418992670040.2582+0.0000000.0%
2023-06-302001377592001377594188970.5032+0.00026152.9%
2023-07-312001379922001379922000022110.9927+0.00014699.3%
2023-08-313652662001318172001019610.0443+0.0000000.0%
2023-09-302001386153665822001022550.0367+0.00003511.1%
2023-09-303646973646972001276550.2542+0.00003725.5%
2023-09-302001278142001278142001275310.2449+0.00119959.9%
2023-09-30200127410621263665820.1667+0.0000000.0%
2023-09-302001273082001273082001273070.2422+0.0000229.6%
2023-09-30307388670044189920.2556+0.0000000.0%
2023-10-3136530467003620130.3815+0.0000000.0%
2023-10-312001375002001375004051680.0327+0.0000027.1%
2023-10-312001386253656474228400.0939+0.00004525.1%
2023-10-312001378242000829522001277120.1777+0.00000150.0%
2023-10-314195414195412001305250.3261+0.00003131.3%
2023-10-31307388418992670040.2582+0.0000000.0%
2023-10-31366024366024620870.1728+0.0000099.1%
2023-10-312001386642001386642001271230.2089+0.00010254.0%
2023-10-312001386692001386692001379350.7290+0.00006573.0%
2023-10-312001386552001386552000022170.9787+0.00018697.9%
2023-11-302001284732001267342001313640.1824+0.00000266.7%
2023-11-302001316492000328492001309960.2982+0.00104359.6%
2023-11-303073883073884189920.0975+0.0000000.0%
2023-11-302001378242001378242000829520.1339+0.00000218.2%
2023-11-302001275552001275552001274100.2804+0.0000014.8%
2023-11-302000949542000949544188400.1587+0.00002511.6%
2023-11-30200127644200127531640430.0569+0.0000000.0%
2023-11-302001386872001386872001317700.3433+0.00016668.6%
2023-11-30200138615620133665820.1647+0.00031442.8%
2023-12-312001276342001276342001275390.8475+0.00069299.7%
2023-12-312001277312001277312001275440.9176+0.00087891.8%
2023-12-312001316492001316492000328490.1727+0.00107821.3%
2023-12-312001389534226902001295460.0681+0.00002322.3%
2023-12-312001386252001386253656470.8344+0.00020896.3%
2023-12-313664013664012000013740.2804+0.00005731.0%
2023-12-312001388922001388923458370.4929+0.00020583.3%
2023-12-312001306532001306532001385890.1951+0.00009147.4%
2023-12-312001271832001271832001317950.1122+0.0000211.7%
2023-12-31365304365304670030.0289+0.0000000.0%
2023-12-312001273312001276583664220.1907+0.00005156.7%
2024-01-31200127410200127410621260.1754+0.0000000.0%
2024-01-31200137628200137628640030.1350+0.00152932.6%
2024-01-312001389532001389534226900.4771+0.00009386.1%
2024-01-312000963312000963313484540.0862+0.00007923.1%
2024-01-312001389502001273302001276400.1228+0.00000425.0%
2024-01-31200138615200131795620130.1388+0.0000281.3%
2024-02-292001389502001276402001273300.1460+0.00001121.1%
2024-02-292001274262001274262001274380.9569+0.00518195.7%
2024-02-292000022262000022264227770.2736+0.00022754.8%
2024-02-292001386152001386152001317950.8590+0.00190377.5%
2024-02-292001307432001307434156610.1939+0.00004425.9%
2024-03-312001298952001298952001306640.3357+0.00025940.4%
2024-03-312001389502001389502001276400.1773+0.00004745.2%
2024-03-312000552742000552742001273250.1495+0.0000065.3%
2024-03-312001273752001273752000963870.1458+0.00012531.3%
2024-03-312001274382001390112001305260.0977+0.00003219.9%
2024-03-312001275792001275792001377960.0521+0.00011810.5%
2024-04-302001391162001391162001022420.2708+0.00009380.9%
2024-04-302000696402000696404199090.1876+0.00001725.0%
2024-05-312001274382001274382001390110.0908+0.0000070.6%
2024-05-312001275542000511162001304090.1508+0.00001436.8%
2024-06-302001277432001277432001273850.9951+0.00143696.0%
2024-06-30200139226200139226290000.4129+0.00007779.4%
2024-07-313652662000020802001318170.2086+0.00000240.0%
2024-08-312001393112001393112001308610.5095+0.00014150.9%
2024-08-312001393192001393192001275250.9977+0.00358099.8%
2024-08-31200127644640432001275310.1984+0.0000000.0%
2024-09-302001304752001304752001277690.0463+0.00002114.2%
2024-09-302001284732001284732001267340.0043+0.0000031.1%
2024-09-302001393462001393462000510150.9444+0.00152294.5%
2024-09-30200137997200127531620740.0544+0.00000314.3%
2024-09-302001393332001393332001267780.3331+0.000077100.0%
2024-09-302001275542001275542000511160.0262+0.0000506.2%
2024-10-312001393982001393982001307840.4269+0.00066342.7%
2024-10-3120012764462074640430.3011+0.0000000.0%
2024-10-312001393752001393752001278520.3290+0.00005132.7%
2024-10-312000018352000018352001388840.1431+0.00008132.0%
2024-11-3020012764464043620740.4033+0.00000133.3%
2024-11-302001271902001271902001272610.0256+0.0000082.2%
2024-11-302000860372000860372000805740.3450+0.00021968.9%
2024-11-302001378302001378302001312571.0000+0.001091100.0%
2024-12-313652662000018492000020800.3748+0.00000466.7%
2024-12-312001273312001273312001276580.1647+0.00008747.8%
2024-12-312001395262001391632001276700.1145+0.00002224.2%
2024-12-312001379972001379972001275310.8900+0.000022100.0%
2024-12-312001311562001311562000022950.1208+0.00002125.0%
2024-12-312001389892001389892001275280.0705+0.00007315.9%
2024-12-312001395382001395382000836920.8030+0.00014080.5%
2024-12-312001395682001395682001273590.9986+0.000343100.0%
2025-01-313652662000020802000018490.2596+0.00000444.4%
2025-01-312001421682001421682001393060.0876+0.0000418.9%
2025-01-312001395262001395262001391630.2156+0.00003623.5%
2025-02-282001308182001308182001296010.2156+0.00013221.6%
2025-02-282001273452001277554134960.1770+0.00002252.4%
2025-03-312001422772001422772001378850.0617+0.00011213.7%
2025-03-312001275312001275312001307240.0081+0.0001431.8%
2025-03-31200127533200127533671000.1435+0.00014048.0%
2025-03-312001311752001311752000982790.2053+0.00020141.0%
2025-03-312001422412001422412001313200.1769+0.00007335.3%
2025-03-3120012764462074640430.3011+0.00000215.4%
2025-03-312001275282001275282000655640.0057+0.0000521.6%
2025-04-302001277032001277033645440.0895+0.00006723.1%
2025-04-302000982792000982792001379980.2634+0.00009068.7%
2025-04-303652662000013972000020800.0412+0.0000098.4%
2025-04-3020012764464043620740.4033+0.00000421.1%
2025-05-312001273452001273452001277550.1099+0.00004328.7%
2025-05-313655962001388842001394120.3454+0.00002032.8%
2025-05-313652663652662000013970.0844+0.00005212.3%
2025-05-3120012764462074640430.3011+0.00001131.4%
2025-06-302001423912001423912000486110.9634+0.00265494.5%
2025-06-302001272702001272702001315120.0941+0.00006219.7%
2025-06-302001273562001273562000692270.0909+0.00004322.9%
2025-06-3020012764464043620740.4033+0.00002142.9%
2025-06-303655962001394122001388840.4615+0.00005075.8%
2025-07-312001424612001424612000013080.5611+0.00526488.2%
2025-08-312001425142001425142001395240.3757+0.00007337.8%
2025-08-313655962001388842001394120.3454+0.00004950.5%
2025-08-3120012764462074640430.3011+0.0000054.5%
2025-09-302001307912001307912001315840.2252+0.00014525.9%
2025-09-30200142564200142564396160.2551+0.00033176.6%
2025-09-303655963655962001388840.0788+0.00008420.4%
2025-09-30200127644200127644620740.0571+0.0000182.8%
2025-09-302001425542001425543658940.0755+0.00013425.1%
2025-09-302001425522001425523663900.1056+0.00012335.3%
2025-09-302001425692001425692001425010.1227+0.00005424.6%
2025-09-302001425532001425533654090.1670+0.00017250.3%
+
+
+ +
05 · Score Ranking at t_ref
+ +
+
+ Accounts ranked by weight at reference date + ✓ in last column = account was remapped at some point in history +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RankRegistrar IDScore (weight)Relative sizeRemapped
#14203500.047517 +
+
#23647650.040489 +
+
#32001274540.025713 +
+
#43129330.024529 +
+
#52001278090.024421 +
+
#64202590.024286 +
+
#73649070.023860 +
+
#83664410.022560 +
+
#93649290.021895 +
+
#103655380.021077 +
+
#113664030.020760 +
+
#124189610.019114 +
+
#13Private Client0.018122 +
+
#142000581080.017888 +
+
#152001317220.016855 +
+
#162000733540.013524 +
+
#173658480.013359 +
+
#182000759320.012808 +
+
#192001274100.011476 +
+
#202001273160.010852 +
+
#212000013490.010685 +
+
#224202070.010312 +
+
#233649490.010078 +
+
#243666040.009730 +
+
#252000019280.008857 +
+
#263664700.008616 +
+
#272001275310.008403 +
+
#282001278110.008174 +
+
#292001275520.008026 +
+
#303650950.007653 +
+
#312001278100.007226 +
+
#323656810.006896 +
+
#333653760.006886 +
+
#342001274160.006875 +
+
#352000132310.006840 +
+
#362001314280.006485 +
+
#372001274460.006382 +
+
#383659400.006368 +
+
#392001274150.006116 +
+
#402001424610.005970 +
+
#412000733550.005847 +
+
#422001376280.005813 +
+
#432001274260.005742 +
+
#442000865480.005702 +
+
#452000001470.005614 +
+
#462001288730.005127 +
+
#472001316490.005066 +
+
#482001309060.004993 +
+
#492001273670.004733 +
+
#502000343720.004656 +
+
#512001274320.004611 +
+
#522001379970.004590 +
+
#534223100.004590 +
+
#542000000820.004293 +
+
#552000595880.004246 +
+
#563073880.004195 +
+
#573656800.004080 +
+
#582001315800.004056 +
+
#592001373600.004027 +
+
#602001317500.004013 +
+
#612001275280.003991 +
+
#623653770.003954 +
+
#632000729070.003851 +
+
#643649990.003846 +
+
#652001317770.003824 +
+
#662001274300.003822 +
+
#672001393190.003792 +
+
#682001373560.003783 +
+
#692000746690.003672 +
+
#703654450.003533 +
+
#712001374590.003490 +
+
#722001278150.003428 +
+
#732001313400.003413 +
+
#742000941720.003142 +
+
#752001276050.003064 +
+
#763651720.003022 +
+
#772000023260.003017 +
+
#782001271860.002952 +
+
#792001273620.002946 +
+
#802000457200.002879 +
+
#812001278140.002871 +
+
#822001274330.002854 +
+
#832001423910.002832 +
+
#843659580.002820 +
+
#852001274400.002743 +
+
#862000856150.002654 +
+
#872001263800.002603 +
+
#882001284810.002600 +
+
#892001386150.002550 +
+
#902001309580.002542 +
+
#914225760.002515 +
+
#922001274350.002492 +
+
#933647240.002487 +
+
#943652420.002391 +
+
#952000005660.002260 +
+
#962001306970.002243 +
+
#972000001460.002220 +
+
#982001275530.002202 +
+
#992000199400.002182 +
+
#1002001274380.002168 +
+
#1012000021690.002154 +
+
#1022001373610.002067 +
+
#1032001374670.002022 +
+
#1044223290.002010 +
+
#1052000023890.001955 +
+
#1062001424100.001907 +
+
#1072001277640.001863 +
+
#1082000065340.001854 +
+
#1092001313090.001817 +
+
#1102001275160.001790 +
+
#1112001389540.001777 +
+
#1122001308920.001766 +
+
#1132000020760.001732 +
+
#1142001021900.001638 +
+
#1152001272250.001626 +
+
#1162001393460.001611 +
+
#1172001275790.001604 +
+
#1182001305850.001584 +
+
#1193654610.001581 +
+
#1202001271830.001557 +
+
#1212001393980.001554 +
+
#1222001313860.001547 +
+
#1232001277430.001542 +
+
#1243650900.001474 +
+
#1252001378280.001470 +
+
#1262001272720.001458 +
+
#1272000666670.001454 +
+
#1282001276340.001450 +
+
#1292001274430.001450 +
+
#1302001313250.001415 +
+
#1312001386250.001410 +
+
#1323651430.001390 +
+
#1332001273640.001363 +
+
#1343665590.001322 +
+
#1352001316510.001310 +
+
#1362001306650.001300 +
+
#1372000982790.001299 +
+
#1383652360.001293 +
+
#1392000832480.001290 +
+
#1404197170.001282 +
+
#1412001306880.001253 +
+
#1423651270.001240 +
+
#1432000003580.001232 +
+
#1442001312940.001224 +
+
#1452000963310.001217 +
+
#1462001308550.001204 +
+
#1472000020870.001192 +
+
#1482001272680.001189 +
+
#1492001273560.001185 +
+
#1502001273750.001181 +
+
#1513658400.001159 +
+
#1522001425240.001148 +
+
#1532001422340.001118 +
+
#1543663970.001107 +
+
#1552001273910.001096 +
+
#1562001378300.001095 +
+
#1574190970.001059 +
+
#1582001274040.001051 +
+
#1592001274010.001049 +
+
#1603650960.001036 +
+
#1612001277310.000978 +
+
#1622000804850.000976 +
+
#1632001274840.000962 +
+
#1642001267020.000959 +
+
#165188720.000946 +
+
#1662001271870.000928 +
+
#1672001317630.000927 +
+
#1682001275540.000924 +
+
#1692001274630.000899 +
+
#1702000003400.000895 +
+
#1712000023310.000882 +
+
#1722000019390.000882 +
+
#1732000670960.000880 +
+
#1742001422770.000879 +
+
#1752001315430.000867 +
+
#1762001306900.000864 +
+
#1772001278130.000861 +
+
#1782000023750.000861 +
+
#1792000006940.000848 +
+
#1802001318040.000839 +
+
#1812001022380.000832 +
+
#1822001265150.000828 +
+
#1833654520.000822 +
+
#1842001392260.000815 +
+
#1852001271910.000808 +
+
#1862001271350.000786 +
+
#1872000006930.000766 +
+
#1882001315420.000758 +
+
#1892001306840.000751 +
+
#1902001393110.000748 +
+
#1912001273600.000743 +
+
#1922001275780.000742 +
+
#1932000399980.000734 +
+
#1942001273060.000734 +
+
#1952001389890.000726 +
+
#1962001313030.000708 +
+
#1972001273760.000706 +
+
#1982001373530.000703 +
+
#1992000960010.000700 +
+
#2003665510.000687 +
+
#2013658070.000683 +
+
#2022001276130.000675 +
+
#2032000540460.000675 +
+
#2044196790.000674 +
+
#2054225540.000665 +
+
#2062000018730.000664 +
+
#2072001392930.000657 +
+
#2082001376330.000656 +
+
#2092001277570.000642 +
+
#2102001298950.000641 +
+
#2112000128320.000640 +
+
#2122001276440.000638 +
+
#2132000522080.000625 +
+
#2142001274190.000624 +
+
#2152000023210.000621 +
+
#2162001308180.000612 +
+
#2172001278160.000594 +
+
#2182000025830.000590 +
+
#2193665370.000586 +
+
#2202001393560.000586 +
+
#2212001422430.000582 +
+
#2222001377590.000580 +
+
#2232000175610.000578 +
+
#2242001376190.000575 +
+
#2252001311750.000563 +
+
#2262001273710.000562 +
+
#2272001307910.000560 +
+
#2284203520.000552 +
+
#2292000791690.000539 +
+
#2302001275550.000534 +
+
#2312001425540.000534 +
+
#2322001279960.000532 +
+
#2332000019420.000524 +
+
#2342001317360.000523 +
+
#2352001389770.000515 +
+
#2362001315410.000514 +
+
#2372001274030.000498 +
+
#2382000866540.000496 +
+
#2392001395680.000493 +
+
#2402001274280.000493 +
+
#2412000920810.000489 +
+
#2422001276030.000489 +
+
#2432001279010.000483 +
+
#2442001365970.000479 +
+
#2452001274790.000477 +
+
#2462000023120.000475 +
+
#2472000022260.000471 +
+
#2482001276270.000471 +
+
#2492001421680.000463 +
+
#2502001305190.000454 +
+
#2512001277980.000451 +
+
#2524222190.000450 +
+
#2532001309770.000449 +
+
#2542001278030.000448 +
+
#2553666020.000448 +
+
#2562001021840.000447 +
+
#2572001275560.000447 +
+
#2582001283630.000446 +
+
#2592001373540.000443 +
+
#2602001273080.000436 +
+
#2612001315390.000435 +
+
#2623652660.000434 +
+
#2632001308410.000433 +
+
#2642001425640.000432 +
+
#2653660240.000431 +
+
#2662000016210.000425 +
+
#2672001315340.000423 +
+
#2682001274480.000422 +
+
#2692000436900.000421 +
+
#2702001273830.000421 +
+
#2712000933310.000419 +
+
#2723655960.000411 +
+
#2734224450.000405 +
+
#2742001265260.000405 +
+
#2752001275980.000405 +
+
#2762001305270.000404 +
+
#2772001274170.000398 +
+
#2782000956460.000398 +
+
#2792001310820.000393 +
+
#2804160780.000389 +
+
#2812001310810.000387 +
+
#2822001272010.000385 +
+
#2832000019260.000384 +
+
#2843653040.000374 +
+
#2853661070.000371 +
+
#2862001272700.000367 +
+
#2872000018990.000365 +
+
#2882000000760.000364 +
+
#2894226910.000363 +
+
#2902001271900.000359 +
+
#2912001021250.000354 +
+
#2922001310520.000353 +
+
#2932001316480.000348 +
+
#2942001425520.000348 +
+
#2952001378910.000346 +
+
#2962000134470.000345 +
+
#2972001425530.000342 +
+
#2982001274980.000340 +
+
#2992001395260.000335 +
+
#3002001305000.000334 +
+
#3012001314770.000333 +
+
#3022001273810.000332 +
+
#3032001378510.000330 +
+
#3042001021970.000329 +
+
#3052001317710.000329 +
+
#3062001279030.000326 +
+
#3072001273430.000321 +
+
#3082001277030.000318 +
+
#3092000860370.000318 +
+
#3102001288540.000318 +
+
#3112001379280.000317 +
+
#3124203630.000316 +
+
#3132001309030.000315 +
+
#3142001275330.000308 +
+
#3152001379510.000303 +
+
#3162001315400.000301 +
+
#3172001307190.000293 +
+
#3182001388920.000290 +
+
#3193663860.000284 +
+
#3202000007360.000284 +
+
#3212000894060.000281 +
+
#3223664010.000278 +
+
#3234228740.000275 +
+
#3242001305490.000274 +
+
#3252001274560.000272 +
+
#3262001284730.000270 +
+
#3272000022220.000269 +
+
#3283652410.000268 +
+
#3292000818050.000267 +
+
#3302001386880.000256 +
+
#3312000552740.000254 +
+
#3322000590250.000253 +
+
#3332000593780.000253 +
+
#3342000018350.000253 +
+
#3353653280.000244 +
+
#3362001273310.000243 +
+
#3372001271740.000242 +
+
#3382001386870.000242 +
+
#3392000641420.000241 +
+
#3402000021470.000239 +
+
#3412001275870.000238 +
+
#3422001276390.000238 +
+
#3433650940.000235 +
+
#3442001314490.000235 +
+
#3452001313010.000233 +
+
#3462000525620.000232 +
+
#3472001276140.000227 +
+
#3482000949540.000224 +
+
#3492000960020.000222 +
+
#3502001274340.000221 +
+
#3512001425690.000220 +
+
#3522001273450.000215 +
+
#3532001310560.000214 +
+
#3542001278170.000212 +
+
#3552001378240.000211 +
+
#3562001375000.000211 +
+
#3572000002010.000211 +
+
#3584224440.000210 +
+
#3592001374770.000210 +
+
#3602001271850.000209 +
+
#3612001386640.000207 +
+
#3622000912880.000207 +
+
#3632001422410.000207 +
+
#3642000664550.000206 +
+
#3652001305070.000205 +
+
#3662001389500.000201 +
+
#3672001021510.000200 +
+
#3682001278120.000199 +
+
#3693657030.000197 +
+
#3702001313430.000196 +
+
#3712001297160.000195 +
+
#3722000021090.000194 +
+
#3734202440.000194 +
+
#3742001306530.000194 +
+
#3752001425140.000193 +
+
#3762001313870.000192 +
+
#3772000696400.000191 +
+
#3782001386550.000190 +
+
#3792001298150.000190 +
+
#3802001305830.000189 +
+
#3812001298110.000188 +
+
#3822000559900.000187 +
+
#3832159050.000187 +
+
#3842001274950.000186 +
+
#3852000219580.000186 +
+
#3864195410.000186 +
+
#3873653680.000186 +
+
#3882001274470.000179 +
+
#3892001395380.000177 +
+
#3902001043380.000177 +
+
#3913646970.000177 +
+
#3922000063490.000176 +
+
#3932001274780.000176 +
+
#3942001315790.000172 +
+
#3952001277330.000172 +
+
#3963661310.000172 +
+
#3972001305280.000172 +
+
#3982001307430.000170 +
+
#3992001389530.000168 +
+
#4002001274550.000168 +
+
#4013657460.000166 +
+
#4022000546370.000166 +
+
#4032001308420.000164 +
+
#4042001277280.000164 +
+
#4052000012850.000162 +
+
#4062001278720.000160 +
+
#4072001319850.000158 +
+
#4084197890.000157 +
+
#4092000013020.000157 +
+
#4102001273500.000156 +
+
#4112001393750.000156 +
+
#4124184560.000156 +
+
#4134193120.000155 +
+
#4143652740.000155 +
+
#4152000024290.000154 +
+
#4162001271270.000153 +
+
#4172001311560.000153 +
+
#4182000853960.000153 +
+
#4192000667630.000152 +
+
#4203648520.000151 +
+
#4212001379920.000151 +
+
#4222001022200.000150 +
+
#4232001391160.000150 +
+
#4242000320280.000150 +
+
#4253658880.000149 +
+
#4262001267170.000149 +
+
#4272001393330.000148 +
+
#4282001304750.000148 +
+
#4292001275830.000148 +
+
#4302001271750.000147 +
+
#4312001386690.000147 +
+
#4322001271570.000147 +
+
+
+
+ +
+ + + + + + + \ No newline at end of file