diff --git a/carmignac_diagnostics.py b/carmignac_diagnostics.py deleted file mode 100644 index 45afafe..0000000 --- a/carmignac_diagnostics.py +++ /dev/null @@ -1,962 +0,0 @@ -""" -Carmignac Data Challenge — Broken Months Diagnostics -===================================================== -Detects months where the aggregate stock-flow equation is violated -at the ISIN level (across all accounts): - - Σ_r Q_{r,s}(t) - Σ_r Q_{r,s}(t-1) ≠ Σ_r F_{r,s}(t-1→t) - -The residual is the "missing flow": - missing_{s}(t) = [Q_agg(t) - Q_agg(t-1)] - F_agg(t) - -This is a market-level check, independent of individual account identity. -It captures: - - Genuinely missing flow records - - End-of-month accounting lags (transactions dated at boundary) - - Corporate actions (dividends, splits) not reflected in flows - -Outputs -------- - carmignac_broken_months.csv — machine-readable, loaded by carmignac_repair.py - carmignac_diagnostics.html — interactive HTML report - -Usage ------ - python carmignac_diagnostics.py - python carmignac_diagnostics.py \\ - --aum raw_AUM.csv \\ - --flows raw_flows.csv \\ - --out carmignac_broken_months.csv \\ - --html carmignac_diagnostics.html \\ - --alpha 0.02 -""" - -import argparse -import json -import os -import sys - -import numpy as np -import pandas as pd - - -# ───────────────────────────────────────────────────────────── -# 1. LOAD -# ───────────────────────────────────────────────────────────── - -def load_data(aum_path, flows_path): - aum = pd.read_csv(aum_path, parse_dates=["Centralisation Date"]) - flows = pd.read_csv(flows_path, parse_dates=["Centralisation Date"]) - aum["Product - Isin"] = aum["Product - Isin"].astype(str) - flows["Product - Isin"] = flows["Product - Isin"].astype(str) - return aum, flows - - -# ───────────────────────────────────────────────────────────── -# 2. AGGREGATE AND DETECT BROKEN MONTHS -# ───────────────────────────────────────────────────────────── - -def detect_broken_months(aum, flows, alpha=0.02, lag_days=3): - """ - For each (isin, month-end t), compute: - - Q_agg(t) : total shares held across all accounts - - Q_agg(t-1) : idem previous month (forward-filled) - - F_agg(t) : total net flows recorded in ]EOM(t-1), EOM(t)] - - missing(t) : [Q_agg(t) - Q_agg(t-1)] - F_agg(t) - - missing_pct : |missing| / max(Q_agg(t), Q_agg(t-1)) - - A month is flagged as "broken" when missing_pct > alpha. - - Additionally, a month is flagged as a potential "lag" when: - - It is broken with the standard window - - But would NOT be broken if flows dated within lag_days of EOM - are shifted to the adjacent month - - Parameters - ---------- - alpha : tolerance threshold (same as ALPHA in carmignac_repair.py) - lag_days : number of boundary days to test for accounting lag - - Returns - ------- - df_broken : DataFrame with all (isin, date) pairs where missing_pct > alpha - df_all : Full DataFrame including non-broken months (for plotting) - """ - # Monthly calendar - t_min = aum["Centralisation Date"].min() - t_max = aum["Centralisation Date"].max() - all_months = pd.date_range(t_min, t_max, freq="ME") - - # ── Aggregate AUM per (isin, month-end) ────────────────────── - aum_agg = ( - aum.groupby(["Product - Isin", "Centralisation Date"])["Quantity - AUM"] - .sum() - .reset_index() - .rename(columns={"Product - Isin": "isin", - "Centralisation Date": "date", - "Quantity - AUM": "qty_agg"}) - ) - # Forward-fill sparse panel - aum_pivot = aum_agg.pivot(index="date", columns="isin", values="qty_agg") - aum_pivot = aum_pivot.reindex(all_months).ffill() - - # ── Aggregate flows per (isin, month-end) — standard window ── - def bucket_flows(flows_df, months, lower_offset=0, upper_offset=0): - """Aggregate flows with optional boundary extension (in days).""" - fc = flows_df.copy() - def assign_month(d): - # Extended window: ]EOM(t-1) - lower_offset, EOM(t) + upper_offset] - for m in months: - eom_prev = m - pd.offsets.MonthEnd(1) - lo = eom_prev - pd.Timedelta(days=lower_offset) - hi = m + pd.Timedelta(days=upper_offset) - if lo < d <= hi: - return m - return pd.NaT - - fc["month_end"] = fc["Centralisation Date"].apply(assign_month) - fc = fc.dropna(subset=["month_end"]) - agg = (fc.groupby(["Product - Isin", "month_end"])["Quantity - NetFlows"] - .sum() - .reset_index() - .rename(columns={"Product - Isin": "isin", - "month_end": "date", - "Quantity - NetFlows": "flow_agg"})) - return agg - - flows_std = bucket_flows(flows, all_months) - flows_lag = bucket_flows(flows, all_months, - lower_offset=lag_days, - upper_offset=lag_days) - - def flows_to_pivot(df, months): - piv = df.pivot(index="date", columns="isin", values="flow_agg") - return piv.reindex(months).fillna(0.0) - - fpiv_std = flows_to_pivot(flows_std, all_months) - fpiv_lag = flows_to_pivot(flows_lag, all_months) - - # ── Compute residuals ───────────────────────────────────────── - rows = [] - isins = aum_pivot.columns.tolist() - - for i in range(1, len(all_months)): - t_curr = all_months[i] - t_prev = all_months[i - 1] - - for isin in isins: - q_curr = aum_pivot[isin].get(t_curr, np.nan) if isin in aum_pivot.columns else np.nan - q_prev = aum_pivot[isin].get(t_prev, np.nan) if isin in aum_pivot.columns else np.nan - - if pd.isna(q_curr) or pd.isna(q_prev): - continue - - delta = q_curr - q_prev - - # Standard window - f_std = fpiv_std[isin].get(t_curr, 0.0) if isin in fpiv_std.columns else 0.0 - missing_std = delta - f_std - - # Extended lag window - f_lag = fpiv_lag[isin].get(t_curr, 0.0) if isin in fpiv_lag.columns else 0.0 - missing_lag = delta - f_lag - - # ── Denominator choice ──────────────────────────────── - # Normalise by the size of the *movement* (max of delta_AUM - # and recorded flow), not by the stock level. This avoids - # astronomically large percentages when a position is tiny - # but the missing flow is a normal-sized number. - # - # Interpretation: "what fraction of the expected movement - # is unaccounted for?" 100% = the entire movement is missing. - # - # A minimum absolute threshold (min_abs_shares) suppresses - # noise from residual micro-positions (rounding artefacts). - min_abs_shares = 1.0 # ignore positions smaller than 1 share - movement = max(abs(delta), abs(f_std), min_abs_shares) - denom_std = movement - - movement_lag = max(abs(delta), abs(f_lag), min_abs_shares) - denom_lag = movement_lag - - pct_std = abs(missing_std) / denom_std - pct_lag = abs(missing_lag) / denom_lag - - broken_std = pct_std > alpha - broken_lag = pct_lag > alpha - - # A "lag" month: broken with standard, NOT broken with extended window - is_lag = broken_std and (not broken_lag) - - rows.append({ - "date": t_curr, - "isin": isin, - "q_agg_prev": round(q_prev, 3), - "q_agg_curr": round(q_curr, 3), - "delta_aum": round(delta, 3), - "flow_agg": round(f_std, 3), - "missing_flow": round(missing_std, 3), - "missing_pct": round(pct_std, 6), - "broken": broken_std, - "is_lag": is_lag, - }) - - df_all = pd.DataFrame(rows) - df_broken = df_all[df_all["broken"]].sort_values("missing_pct", ascending=False) - return df_broken, df_all - - - -# ───────────────────────────────────────────────────────────── -# 2b. AGGREGATE (CROSS-ISIN) BROKEN MONTHS -# ───────────────────────────────────────────────────────────── - -def detect_aggregate_broken_months(aum, flows, alpha=0.02, lag_days=3): - """ - Same stock-flow check as detect_broken_months, but aggregated - across ALL ISINs for each month: - - Q_total(t) - Q_total(t-1) != F_total(t) - - where Q_total(t) = sum over all (reg_id, isin) of Q_{r,s}(t). - - This catches months where the global portfolio is incoherent even - if every individual ISIN is fine (e.g. cross-ISIN netting errors), - and provides a cleaner high-level view. - - Returns - ------- - df_agg : DataFrame indexed by month with columns: - q_total_prev, q_total_curr, delta_aum, flow_total, - missing_flow, missing_pct, broken, is_lag - """ - t_min = aum["Centralisation Date"].min() - t_max = aum["Centralisation Date"].max() - all_months = pd.date_range(t_min, t_max, freq="ME") - - # ── Total AUM per month (all ISIN, all accounts) ───────────── - aum_monthly = ( - aum.groupby("Centralisation Date")["Quantity - AUM"] - .sum() - .reindex(all_months) - .ffill() - .rename("q_total") - ) - - # ── Bucket flows helper (reuse same window logic) ───────────── - def bucket_total_flows(flows_df, months, lower_offset=0, upper_offset=0): - fc = flows_df.copy() - def assign_month(d): - for m in months: - eom_prev = m - pd.offsets.MonthEnd(1) - lo = eom_prev - pd.Timedelta(days=lower_offset) - hi = m + pd.Timedelta(days=upper_offset) - if lo < d <= hi: - return m - return pd.NaT - fc["month_end"] = fc["Centralisation Date"].apply(assign_month) - fc = fc.dropna(subset=["month_end"]) - return (fc.groupby("month_end")["Quantity - NetFlows"] - .sum() - .reindex(months) - .fillna(0.0)) - - flow_std = bucket_total_flows(flows, all_months) - flow_lag = bucket_total_flows(flows, all_months, - lower_offset=lag_days, upper_offset=lag_days) - - # ── Compute residuals ───────────────────────────────────────── - rows = [] - min_abs_shares = 1.0 - - for i in range(1, len(all_months)): - t_curr = all_months[i] - t_prev = all_months[i - 1] - - q_curr = aum_monthly.get(t_curr, np.nan) - q_prev = aum_monthly.get(t_prev, np.nan) - if pd.isna(q_curr) or pd.isna(q_prev): - continue - - delta = q_curr - q_prev - - f_std = flow_std.get(t_curr, 0.0) - f_lag = flow_lag.get(t_curr, 0.0) - miss_std = delta - f_std - miss_lag = delta - f_lag - - movement_std = max(abs(delta), abs(f_std), min_abs_shares) - movement_lag = max(abs(delta), abs(f_lag), min_abs_shares) - pct_std = abs(miss_std) / movement_std - pct_lag = abs(miss_lag) / movement_lag - - broken_std = pct_std > alpha - broken_lag = pct_lag > alpha - is_lag = broken_std and (not broken_lag) - - rows.append({ - "date": t_curr, - "q_total_prev": round(q_prev, 3), - "q_total_curr": round(q_curr, 3), - "delta_aum": round(delta, 3), - "flow_total": round(f_std, 3), - "missing_flow": round(miss_std, 3), - "missing_pct": round(pct_std, 6), - "broken": broken_std, - "is_lag": is_lag, - }) - - df_agg = pd.DataFrame(rows) - return df_agg - -# ───────────────────────────────────────────────────────────── -# 3. PRINT SUMMARY -# ───────────────────────────────────────────────────────────── - -def print_summary(df_broken, df_all, alpha): - total = len(df_all) - n_broken = len(df_broken) - n_lag = df_broken["is_lag"].sum() - - print("\n" + "=" * 60) - print(" CARMIGNAC — Broken Months Diagnostics") - print("=" * 60) - print(f" (isin, month) pairs examined : {total}") - print(f" Broken (missing_pct > {alpha:.0%}) : {n_broken} " - f"({n_broken/total*100:.1f}%)") - print(f" Of which likely lag : {n_lag}") - print(f" Of which genuine gap : {n_broken - n_lag}") - - if n_broken: - print("\n Top 10 by missing_pct:") - cols = ["date", "isin", "missing_flow", "missing_pct", "is_lag"] - print(df_broken[cols].head(10).to_string(index=False)) - - # Monthly breakdown - by_month = (df_broken.groupby("date") - .agg(n_broken=("isin", "count"), - total_missing=("missing_flow", lambda x: x.abs().sum())) - .sort_values("n_broken", ascending=False) - .head(5)) - if len(by_month): - print("\n Most affected months:") - print(by_month.to_string()) - print() - - -# ───────────────────────────────────────────────────────────── -# 4. BUILD HTML REPORT -# ───────────────────────────────────────────────────────────── - -def build_html(df_broken, df_all, df_agg, alpha): - # ── JS-ready data ──────────────────────────────────────────── - # Timeline: n_broken and total_missing per month - tl = (df_all[df_all["broken"]] - .groupby("date") - .agg(n_broken=("isin", "count"), - total_missing=("missing_flow", lambda x: x.abs().sum()), - n_lag=("is_lag", "sum")) - .reindex(df_all["date"].sort_values().unique()) - .fillna(0)) - tl.index = pd.to_datetime(tl.index) - dates_str = json.dumps([d.strftime("%Y-%m-%d") for d in tl.index]) - - def jf(arr, dec=4): - return json.dumps([round(float(v), dec) if not np.isnan(v) else None for v in arr]) - - n_broken_js = jf(tl["n_broken"].values, 0) - total_miss_js = jf(tl["total_missing"].values) - n_lag_js = jf(tl["n_lag"].values, 0) - - # Aggregate (cross-ISIN) JS data - agg_dates_str = json.dumps([d.strftime("%Y-%m-%d") for d in pd.to_datetime(df_agg["date"])]) - agg_delta_js = jf(df_agg["delta_aum"].values) - agg_flow_js = jf(df_agg["flow_total"].values) - agg_missing_js = jf(df_agg["missing_flow"].values) - agg_pct_js = jf((df_agg["missing_pct"] * 100).values) - - # Aggregate KPIs - n_agg_broken = int(df_agg["broken"].sum()) - n_agg_lag = int(df_agg["is_lag"].sum()) - n_agg_genuine = n_agg_broken - n_agg_lag - max_agg_pct = float(df_agg["missing_pct"].max() * 100) if len(df_agg) else 0 - - # Aggregate detail table rows - agg_rows = [] - for _, r in df_agg[df_agg["broken"]].iterrows(): - lb = 'lag' if r["is_lag"] else "" - pc = "pct-high" if r["missing_pct"] > 0.1 else "pct-med" - ds = r["date"].strftime("%Y-%m-%d") if hasattr(r["date"], "strftime") else str(r["date"])[:10] - mc = "miss-neg" if r["missing_flow"] < 0 else "miss-pos" - agg_rows.append( - f'{ds}' - f'{r["q_total_prev"]:,.1f}' - f'{r["q_total_curr"]:,.1f}' - f'{r["flow_total"]:,.1f}' - f'{r["missing_flow"]:+,.1f}' - f'{r["missing_pct"]*100:.2f}%' - f'{lb}' - ) - agg_detail_rows = "".join(agg_rows) if agg_rows else ( - '✓ No broken months at aggregate level' - ) - - # Per-ISIN summary - isin_sum = (df_broken.groupby("isin") - .agg(n_months=("date", "count"), - avg_pct=("missing_pct", "mean"), - total_abs=("missing_flow", lambda x: x.abs().sum())) - .sort_values("total_abs", ascending=False)) - - ISIN_COLORS = [ - "#2563eb","#16a34a","#dc2626","#d97706","#7c3aed", - "#0891b2","#db2777","#65a30d","#ea580c","#6366f1", - ] - - # Per-ISIN missing_pct timeseries for the top 5 ISINs - top_isins = isin_sum.head(5).index.tolist() - all_dates = sorted(df_all["date"].unique()) - isin_ts_datasets = [] - for idx, isin in enumerate(top_isins): - sub = df_all[df_all["isin"] == isin].set_index("date")["missing_pct"].reindex(all_dates).fillna(0) - isin_ts_datasets.append({ - "label": isin, - "data": [round(float(v) * 100, 3) for v in sub.values], - "borderColor": ISIN_COLORS[idx % len(ISIN_COLORS)], - "backgroundColor": ISIN_COLORS[idx % len(ISIN_COLORS)] + "22", - "borderWidth": 2, - "pointRadius": 0, - "tension": 0.3, - "fill": False, - }) - isin_ts_json = json.dumps(isin_ts_datasets) - all_dates_str = json.dumps([d.strftime("%Y-%m-%d") if hasattr(d, 'strftime') - else str(d)[:10] for d in all_dates]) - - # Detail table rows - detail_rows = "" - for _, r in df_broken.head(200).iterrows(): - lag_badge = 'lag' if r["is_lag"] else "" - pct_class = "pct-high" if r["missing_pct"] > 0.1 else "pct-med" - detail_rows += f""" - - {r['date'].strftime('%Y-%m-%d') if hasattr(r['date'], 'strftime') else str(r['date'])[:10]} - {r['isin']} - {r['q_agg_prev']:,.1f} - {r['q_agg_curr']:,.1f} - {r['flow_agg']:,.1f} - {r['missing_flow']:+,.1f} - {r['missing_pct']*100:.2f}% - {lag_badge} - """ - - # ISIN summary table - isin_rows = "" - for isin, row in isin_sum.iterrows(): - isin_rows += f""" - - {isin} - {int(row['n_months'])} - {row['avg_pct']*100:.2f}% - {row['total_abs']:,.1f} - """ - - # KPIs - total = len(df_all) - n_broken_kpi = len(df_broken) - n_lag_kpi = int(df_broken["is_lag"].sum()) - n_genuine = n_broken_kpi - n_lag_kpi - max_pct = df_broken["missing_pct"].max() * 100 if len(df_broken) else 0 - n_isins = df_broken["isin"].nunique() - - no_broken_msg = "" - if n_broken_kpi == 0: - no_broken_msg = '
✓ No broken months detected at this threshold.
' - - html = f""" - - - - -Carmignac — Broken Months Diagnostics - - - - - -
-
Carmignac × ENSAE · Data Challenge 2025
-

Broken Months Diagnostics

-
- Aggregate stock-flow equation check · ISIN level · threshold α = {alpha:.1%}
- Missing % = |missing flow| / max(|ΔAUM|, |recorded flow|, 1 share) — capped at movement size, not stock level -
-
- -
-
- (ISIN, month) pairs - {total:,} - examined -
-
- Broken months - {n_broken_kpi:,} - {n_broken_kpi/total*100:.1f}% of pairs -
-
- Likely lags - {n_lag_kpi} - resolved by ±{3}d window -
-
- Genuine gaps - {n_genuine} - unresolved by lag fix -
-
- ISINs affected - {n_isins} - distinct ISINs -
-
- Max missing % - {max_pct:.1f}% - worst single (isin, month) -
-
- -
- -
00 · Aggregate view — all ISINs combined
- -
-
- Stock-flow equation — total portfolio - - Σ Q(t) − Σ Q(t−1) vs Σ F(t) across all ISINs and accounts. - Detects months where the global portfolio is incoherent, independent of ISIN-level breakdown. - -
-
-
-
-
- -
-
-
- Aggregate missing flow over time - Σ Q(t) − Σ Q(t−1) − Σ F(t) — should be near zero every month -
-
-
-
-
-
-
- Aggregate missing % of movement - |missing| / max(|ΔAUM|, |flow|) — months above α flagged in red -
-
-
-
-
-
- -
-
- Aggregate broken months — detail -
-
- - - - - - - - {agg_detail_rows} -
DateΣ Q(t−1)Σ Q(t)Σ FlowMissingMissing %
-
-
- -
01 · Timeline — per ISIN
- -
-
- Broken (isin, month) pairs per month - Stacked: genuine gaps (red) vs likely accounting lags (amber) -
-
-
-
-
- -
-
-
- Total absolute missing flow per month - Sum of |missing flow| across all broken ISINs -
-
-
-
-
- -
-
- Missing % — top 5 ISINs over time - |missing flow| / max(|ΔAUM|, |recorded flow|) per ISIN — capped at movement size -
-
-
-
-
-
- -
02 · By ISIN
- -
-
- ISIN summary — most affected -
-
- {'
No broken months detected.
' if n_broken_kpi == 0 else f""" - - - - - - {isin_rows} -
ISINBroken monthsAvg missing %Total |missing| (shares)
"""} -
-
- -
03 · Detail log
- -
-
- All broken (isin, month) pairs - - lag = likely resolved by extending flow window ±3 days - -
-
Threshold α = {alpha:.1%} · showing up to 200 rows
-
- {'
✓ No broken months detected at this threshold.
' if n_broken_kpi == 0 else f""" - - - - - - - - {detail_rows} -
DateISINQ(t-1)Q(t)Net flowMissingMissing % of movement
"""} -
-
- -
- - - - -""" - return html - - -# ───────────────────────────────────────────────────────────── -# 5. MAIN -# ───────────────────────────────────────────────────────────── - -def main(): - parser = argparse.ArgumentParser( - description="Detect broken months in Carmignac AUM/Flows data" - ) - parser.add_argument("--aum", default="AUM_head.csv") - parser.add_argument("--flows", default="flows_head.csv") - parser.add_argument("--out", default="carmignac_broken_months.csv", - help="Machine-readable output (loaded by carmignac_repair.py)") - parser.add_argument("--html", default="carmignac_diagnostics.html") - parser.add_argument("--alpha", type=float, default=0.02, - help="Tolerance threshold (default 0.02 = 2%%)") - parser.add_argument("--lag", type=int, default=3, - help="Boundary days to test for accounting lag (default 3)") - args = parser.parse_args() - - def resolve(p): - if os.path.exists(p): return p - alt = os.path.join(os.path.dirname(os.path.abspath(__file__)), p) - if os.path.exists(alt): return alt - sys.exit(f"[ERROR] File not found: {p}") - - print(f"[Load] AUM : {args.aum}") - print(f"[Load] Flows : {args.flows}") - aum, flows = load_data(resolve(args.aum), resolve(args.flows)) - - print(f"\n[Detect] Running broken-month detection (α={args.alpha:.1%}, lag=±{args.lag}d)...") - df_broken, df_all = detect_broken_months(aum, flows, alpha=args.alpha, lag_days=args.lag) - df_agg = detect_aggregate_broken_months(aum, flows, alpha=args.alpha, lag_days=args.lag) - - print_summary(df_broken, df_all, args.alpha) - - n_agg_broken = int(df_agg["broken"].sum()) - print(f" Aggregate broken months : {n_agg_broken} " - f"(of which lags: {int(df_agg['is_lag'].sum())})") - - # CSV output — this is what carmignac_repair.py will load - if len(df_broken): - df_broken.to_csv(args.out, index=False) - print(f"[Export] Broken months CSV → {args.out}") - else: - pd.DataFrame(columns=["date","isin","missing_pct","is_lag"]).to_csv(args.out, index=False) - print(f"[Export] No broken months — empty CSV → {args.out}") - - html = build_html(df_broken, df_all, df_agg, args.alpha) - with open(args.html, "w", encoding="utf-8") as f: - f.write(html) - print(f"[Export] HTML report → {args.html}") - - -if __name__ == "__main__": - main() diff --git a/data/explore.ipynb b/data/explore.ipynb deleted file mode 100644 index fc2b7b1..0000000 --- a/data/explore.ipynb +++ /dev/null @@ -1,1346 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "bd938e6e", - "metadata": {}, - "source": [ - "**Short notebook to test connectivity with S3 services and explore the data**" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "127753ac", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "ae3c64fe", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import s3fs\n", - "fs = s3fs.S3FileSystem(\n", - " client_kwargs={'endpoint_url': 'https://'+'minio-simple.lab.groupe-genes.fr'},\n", - " key = os.environ[\"AWS_ACCESS_KEY_ID\"], \n", - " secret = os.environ[\"AWS_SECRET_ACCESS_KEY\"], \n", - " token = os.environ[\"AWS_SESSION_TOKEN\"])" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "84b9ac42", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "\n", - "def sample_by_blocks(df, block_size=10, num_blocks=10, random_state=None):\n", - " \"\"\"Sample num_blocks blocks of block_size consecutive rows (no overlapping blocks).\"\"\"\n", - " n = len(df)\n", - " max_start = n - block_size\n", - " if max_start < 0:\n", - " raise ValueError(f\"DataFrame has {n} rows, need at least {block_size}\")\n", - " if max_start + 1 < num_blocks:\n", - " raise ValueError(f\"Not enough room for {num_blocks} non-overlapping blocks (need at least {num_blocks * block_size} rows)\")\n", - " rng = np.random.default_rng(random_state)\n", - " chosen_starts = rng.choice(max_start + 1, size=num_blocks, replace=False)\n", - " chosen_starts.sort() # blocks in order of position in original df\n", - " indices = np.concatenate([np.arange(s, s + block_size) for s in chosen_starts])\n", - " return df.iloc[indices].reset_index(drop=True)\n", - "\n", - "# sample_df = sample_by_blocks(df, block_size=10, num_blocks=10, random_state=42)" - ] - }, - { - "cell_type": "markdown", - "id": "7f7d45bb", - "metadata": {}, - "source": [ - "### OG AUM" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "83472648", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_175445/2279824029.py:2: DtypeWarning: Columns (0,1,2,3) have mixed types. Specify dtype option on import or set low_memory=False.\n", - " aum = pd.read_csv(f, sep =\";\")\n" - ] - } - ], - "source": [ - "with fs.open('s3://projet-bdc-data/carmignac/AUM ENSAE V2 -20251105.csv', 'rb') as f:\n", - " aum = pd.read_csv(f, sep =\";\")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "0b84ede5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Agreement - CodeCompany - IdCompany - Ultimate Parent IdRegistrar Account - IDRegistrar Account - RegionRegistrarAccount - CountryProduct - Asset TypeProduct - StrategyProduct - Legal StatusProduct - Is Dedie ?Product - FundProduct - Shareclass TypeProduct - Shareclass CurrencyProduct - IsinCentralisation DateQuantity - AUMValue - AUM CCYValue - AUM €
2716081L13415292.015292.011215LuxembourgLuxembourgNaNInfotechSICAVNOCarmignac Portfolio InfotechAEURLU01099291572015-06-300.00.00.0
2716082L13415292.015292.011215LuxembourgLuxembourgNaNInfotechSICAVNOCarmignac Portfolio InfotechAEURLU01099291572016-03-310.00.00.0
2716092L13415292.015292.011215LuxembourgLuxembourgNaNInfotechSICAVNOCarmignac Portfolio InfotechAEURLU01099291572017-05-310.00.00.0
2716093L13415292.015292.011215LuxembourgLuxembourgNaNInfotechSICAVNOCarmignac Portfolio InfotechAEURLU01099291572019-02-280.00.00.0
2716094L13415292.015292.011215LuxembourgLuxembourgNaNInfotechSICAVNOCarmignac Portfolio InfotechAEURLU01099291572019-03-310.00.00.0
.........................................................
3652177L13415292.015292.011215LuxembourgLuxembourgEquityInvestissement LatitudeFCPNOCarmignac Investissement LatitudeAEURFR00101476032018-05-310.00.00.0
3652178L13415292.015292.011215LuxembourgLuxembourgEquityInvestissement LatitudeFCPNOCarmignac Investissement LatitudeAEURFR00101476032018-06-300.00.00.0
3652179L13415292.015292.011215LuxembourgLuxembourgEquityInvestissement LatitudeFCPNOCarmignac Investissement LatitudeAEURFR00101476032018-12-310.00.00.0
3652180L13415292.015292.011215LuxembourgLuxembourgEquityInvestissement LatitudeFCPNOCarmignac Investissement LatitudeAEURFR00101476032019-08-310.00.00.0
3652181L13415292.015292.011215LuxembourgLuxembourgEquityInvestissement LatitudeFCPNOCarmignac Investissement LatitudeAEURFR00101476032020-01-310.00.00.0
\n", - "

640 rows × 18 columns

\n", - "
" - ], - "text/plain": [ - " Agreement - Code Company - Id Company - Ultimate Parent Id \\\n", - "2716081 L134 15292.0 15292.0 \n", - "2716082 L134 15292.0 15292.0 \n", - "2716092 L134 15292.0 15292.0 \n", - "2716093 L134 15292.0 15292.0 \n", - "2716094 L134 15292.0 15292.0 \n", - "... ... ... ... \n", - "3652177 L134 15292.0 15292.0 \n", - "3652178 L134 15292.0 15292.0 \n", - "3652179 L134 15292.0 15292.0 \n", - "3652180 L134 15292.0 15292.0 \n", - "3652181 L134 15292.0 15292.0 \n", - "\n", - " Registrar Account - ID Registrar Account - Region \\\n", - "2716081 11215 Luxembourg \n", - "2716082 11215 Luxembourg \n", - "2716092 11215 Luxembourg \n", - "2716093 11215 Luxembourg \n", - "2716094 11215 Luxembourg \n", - "... ... ... \n", - "3652177 11215 Luxembourg \n", - "3652178 11215 Luxembourg \n", - "3652179 11215 Luxembourg \n", - "3652180 11215 Luxembourg \n", - "3652181 11215 Luxembourg \n", - "\n", - " RegistrarAccount - Country Product - Asset Type \\\n", - "2716081 Luxembourg NaN \n", - "2716082 Luxembourg NaN \n", - "2716092 Luxembourg NaN \n", - "2716093 Luxembourg NaN \n", - "2716094 Luxembourg NaN \n", - "... ... ... \n", - "3652177 Luxembourg Equity \n", - "3652178 Luxembourg Equity \n", - "3652179 Luxembourg Equity \n", - "3652180 Luxembourg Equity \n", - "3652181 Luxembourg Equity \n", - "\n", - " Product - Strategy Product - Legal Status Product - Is Dedie ? \\\n", - "2716081 Infotech SICAV NO \n", - "2716082 Infotech SICAV NO \n", - "2716092 Infotech SICAV NO \n", - "2716093 Infotech SICAV NO \n", - "2716094 Infotech SICAV NO \n", - "... ... ... ... \n", - "3652177 Investissement Latitude FCP NO \n", - "3652178 Investissement Latitude FCP NO \n", - "3652179 Investissement Latitude FCP NO \n", - "3652180 Investissement Latitude FCP NO \n", - "3652181 Investissement Latitude FCP NO \n", - "\n", - " Product - Fund Product - Shareclass Type \\\n", - "2716081 Carmignac Portfolio Infotech A \n", - "2716082 Carmignac Portfolio Infotech A \n", - "2716092 Carmignac Portfolio Infotech A \n", - "2716093 Carmignac Portfolio Infotech A \n", - "2716094 Carmignac Portfolio Infotech A \n", - "... ... ... \n", - "3652177 Carmignac Investissement Latitude A \n", - "3652178 Carmignac Investissement Latitude A \n", - "3652179 Carmignac Investissement Latitude A \n", - "3652180 Carmignac Investissement Latitude A \n", - "3652181 Carmignac Investissement Latitude A \n", - "\n", - " Product - Shareclass Currency Product - Isin Centralisation Date \\\n", - "2716081 EUR LU0109929157 2015-06-30 \n", - "2716082 EUR LU0109929157 2016-03-31 \n", - "2716092 EUR LU0109929157 2017-05-31 \n", - "2716093 EUR LU0109929157 2019-02-28 \n", - "2716094 EUR LU0109929157 2019-03-31 \n", - "... ... ... ... \n", - "3652177 EUR FR0010147603 2018-05-31 \n", - "3652178 EUR FR0010147603 2018-06-30 \n", - "3652179 EUR FR0010147603 2018-12-31 \n", - "3652180 EUR FR0010147603 2019-08-31 \n", - "3652181 EUR FR0010147603 2020-01-31 \n", - "\n", - " Quantity - AUM Value - AUM CCY Value - AUM € \n", - "2716081 0.0 0.0 0.0 \n", - "2716082 0.0 0.0 0.0 \n", - "2716092 0.0 0.0 0.0 \n", - "2716093 0.0 0.0 0.0 \n", - "2716094 0.0 0.0 0.0 \n", - "... ... ... ... \n", - "3652177 0.0 0.0 0.0 \n", - "3652178 0.0 0.0 0.0 \n", - "3652179 0.0 0.0 0.0 \n", - "3652180 0.0 0.0 0.0 \n", - "3652181 0.0 0.0 0.0 \n", - "\n", - "[640 rows x 18 columns]" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "aum[aum[\"Registrar Account - ID\"] == 11215]" - ] - }, - { - "cell_type": "markdown", - "id": "6f40c922", - "metadata": {}, - "source": [ - "### Repaired AUM" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "b6edd4fd", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_175445/204804706.py:2: DtypeWarning: Columns (2,3,4,5) have mixed types. Specify dtype option on import or set low_memory=False.\n", - " df_repaired = pd.read_csv(f, sep =\",\")\n" - ] - } - ], - "source": [ - "with fs.open('s3://projet-bdc-carmignac-g3/AUM_repaired.csv', 'rb') as f:\n", - " df_repaired = pd.read_csv(f, sep =\",\")" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "2521a2a6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Unnamed: 0.1Unnamed: 0Agreement - CodeCompany - IdCompany - Ultimate Parent IdRegistrar Account - IDRegistrar Account - RegionRegistrarAccount - CountryProduct - Asset TypeProduct - Strategy...Product - Is Dedie ?Product - FundProduct - Shareclass TypeProduct - Shareclass CurrencyProduct - IsinCentralisation DateQuantity - AUMValue - AUM CCYValue - AUM €repair_flag
299541229954122716081L13415292.015292.011215LUXEMBOURGLUXEMBOURGNANINFOTECH...NOCARMIGNAC PORTFOLIO INFOTECHAEURLU01099291572015-06-300.0NaNNaNFalse
299541329954132716082L13415292.015292.011215LUXEMBOURGLUXEMBOURGNANINFOTECH...NOCARMIGNAC PORTFOLIO INFOTECHAEURLU01099291572016-03-310.0NaNNaNFalse
299542329954232716092L13415292.015292.011215LUXEMBOURGLUXEMBOURGNANINFOTECH...NOCARMIGNAC PORTFOLIO INFOTECHAEURLU01099291572017-05-310.0NaNNaNFalse
299542429954242716093L13415292.015292.011215LUXEMBOURGLUXEMBOURGNANINFOTECH...NOCARMIGNAC PORTFOLIO INFOTECHAEURLU01099291572019-02-280.0NaNNaNFalse
299542529954252716094L13415292.015292.011215LUXEMBOURGLUXEMBOURGNANINFOTECH...NOCARMIGNAC PORTFOLIO INFOTECHAEURLU01099291572019-03-310.0NaNNaNFalse
..................................................................
407331440733143652177L13415292.015292.011215LUXEMBOURGLUXEMBOURGEQUITYINVESTISSEMENT LATITUDE...NOCARMIGNAC INVESTISSEMENT LATITUDEAEURFR00101476032018-05-310.0NaNNaNFalse
407331540733153652178L13415292.015292.011215LUXEMBOURGLUXEMBOURGEQUITYINVESTISSEMENT LATITUDE...NOCARMIGNAC INVESTISSEMENT LATITUDEAEURFR00101476032018-06-300.0NaNNaNFalse
407331640733163652179L13415292.015292.011215LUXEMBOURGLUXEMBOURGEQUITYINVESTISSEMENT LATITUDE...NOCARMIGNAC INVESTISSEMENT LATITUDEAEURFR00101476032018-12-310.0NaNNaNFalse
407331740733173652180L13415292.015292.011215LUXEMBOURGLUXEMBOURGEQUITYINVESTISSEMENT LATITUDE...NOCARMIGNAC INVESTISSEMENT LATITUDEAEURFR00101476032019-08-310.0NaNNaNFalse
407331840733183652181L13415292.015292.011215LUXEMBOURGLUXEMBOURGEQUITYINVESTISSEMENT LATITUDE...NOCARMIGNAC INVESTISSEMENT LATITUDEAEURFR00101476032020-01-310.0NaNNaNFalse
\n", - "

640 rows × 21 columns

\n", - "
" - ], - "text/plain": [ - " Unnamed: 0.1 Unnamed: 0 Agreement - Code Company - Id \\\n", - "2995412 2995412 2716081 L134 15292.0 \n", - "2995413 2995413 2716082 L134 15292.0 \n", - "2995423 2995423 2716092 L134 15292.0 \n", - "2995424 2995424 2716093 L134 15292.0 \n", - "2995425 2995425 2716094 L134 15292.0 \n", - "... ... ... ... ... \n", - "4073314 4073314 3652177 L134 15292.0 \n", - "4073315 4073315 3652178 L134 15292.0 \n", - "4073316 4073316 3652179 L134 15292.0 \n", - "4073317 4073317 3652180 L134 15292.0 \n", - "4073318 4073318 3652181 L134 15292.0 \n", - "\n", - " Company - Ultimate Parent Id Registrar Account - ID \\\n", - "2995412 15292.0 11215 \n", - "2995413 15292.0 11215 \n", - "2995423 15292.0 11215 \n", - "2995424 15292.0 11215 \n", - "2995425 15292.0 11215 \n", - "... ... ... \n", - "4073314 15292.0 11215 \n", - "4073315 15292.0 11215 \n", - "4073316 15292.0 11215 \n", - "4073317 15292.0 11215 \n", - "4073318 15292.0 11215 \n", - "\n", - " Registrar Account - Region RegistrarAccount - Country \\\n", - "2995412 LUXEMBOURG LUXEMBOURG \n", - "2995413 LUXEMBOURG LUXEMBOURG \n", - "2995423 LUXEMBOURG LUXEMBOURG \n", - "2995424 LUXEMBOURG LUXEMBOURG \n", - "2995425 LUXEMBOURG LUXEMBOURG \n", - "... ... ... \n", - "4073314 LUXEMBOURG LUXEMBOURG \n", - "4073315 LUXEMBOURG LUXEMBOURG \n", - "4073316 LUXEMBOURG LUXEMBOURG \n", - "4073317 LUXEMBOURG LUXEMBOURG \n", - "4073318 LUXEMBOURG LUXEMBOURG \n", - "\n", - " Product - Asset Type Product - Strategy ... \\\n", - "2995412 NAN INFOTECH ... \n", - "2995413 NAN INFOTECH ... \n", - "2995423 NAN INFOTECH ... \n", - "2995424 NAN INFOTECH ... \n", - "2995425 NAN INFOTECH ... \n", - "... ... ... ... \n", - "4073314 EQUITY INVESTISSEMENT LATITUDE ... \n", - "4073315 EQUITY INVESTISSEMENT LATITUDE ... \n", - "4073316 EQUITY INVESTISSEMENT LATITUDE ... \n", - "4073317 EQUITY INVESTISSEMENT LATITUDE ... \n", - "4073318 EQUITY INVESTISSEMENT LATITUDE ... \n", - "\n", - " Product - Is Dedie ? Product - Fund \\\n", - "2995412 NO CARMIGNAC PORTFOLIO INFOTECH \n", - "2995413 NO CARMIGNAC PORTFOLIO INFOTECH \n", - "2995423 NO CARMIGNAC PORTFOLIO INFOTECH \n", - "2995424 NO CARMIGNAC PORTFOLIO INFOTECH \n", - "2995425 NO CARMIGNAC PORTFOLIO INFOTECH \n", - "... ... ... \n", - "4073314 NO CARMIGNAC INVESTISSEMENT LATITUDE \n", - "4073315 NO CARMIGNAC INVESTISSEMENT LATITUDE \n", - "4073316 NO CARMIGNAC INVESTISSEMENT LATITUDE \n", - "4073317 NO CARMIGNAC INVESTISSEMENT LATITUDE \n", - "4073318 NO CARMIGNAC INVESTISSEMENT LATITUDE \n", - "\n", - " Product - Shareclass Type Product - Shareclass Currency \\\n", - "2995412 A EUR \n", - "2995413 A EUR \n", - "2995423 A EUR \n", - "2995424 A EUR \n", - "2995425 A EUR \n", - "... ... ... \n", - "4073314 A EUR \n", - "4073315 A EUR \n", - "4073316 A EUR \n", - "4073317 A EUR \n", - "4073318 A EUR \n", - "\n", - " Product - Isin Centralisation Date Quantity - AUM Value - AUM CCY \\\n", - "2995412 LU0109929157 2015-06-30 0.0 NaN \n", - "2995413 LU0109929157 2016-03-31 0.0 NaN \n", - "2995423 LU0109929157 2017-05-31 0.0 NaN \n", - "2995424 LU0109929157 2019-02-28 0.0 NaN \n", - "2995425 LU0109929157 2019-03-31 0.0 NaN \n", - "... ... ... ... ... \n", - "4073314 FR0010147603 2018-05-31 0.0 NaN \n", - "4073315 FR0010147603 2018-06-30 0.0 NaN \n", - "4073316 FR0010147603 2018-12-31 0.0 NaN \n", - "4073317 FR0010147603 2019-08-31 0.0 NaN \n", - "4073318 FR0010147603 2020-01-31 0.0 NaN \n", - "\n", - " Value - AUM € repair_flag \n", - "2995412 NaN False \n", - "2995413 NaN False \n", - "2995423 NaN False \n", - "2995424 NaN False \n", - "2995425 NaN False \n", - "... ... ... \n", - "4073314 NaN False \n", - "4073315 NaN False \n", - "4073316 NaN False \n", - "4073317 NaN False \n", - "4073318 NaN False \n", - "\n", - "[640 rows x 21 columns]" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df_repaired[df_repaired[\"Registrar Account - ID\"] == 11215]" - ] - }, - { - "cell_type": "markdown", - "id": "74ab7fb4", - "metadata": {}, - "source": [ - "### Flows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3347dc39", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_159596/3878087020.py:2: DtypeWarning: Columns (1,2,3,4) have mixed types. Specify dtype option on import or set low_memory=False.\n", - " flows = pd.read_csv(f, sep =\",\")\n" - ] - } - ], - "source": [ - "with fs.open('s3://projet-bdc-carmignac-g3/flows.csv', 'rb') as f:\n", - " flows = pd.read_csv(f, sep =\",\")\n", - "\n", - "sample_flows = sample_by_blocks(flows, block_size=10, num_blocks=10, random_state=42)" - ] - }, - { - "cell_type": "markdown", - "id": "4bb4f9c7", - "metadata": {}, - "source": [ - "## Clustering" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "5957c389", - "metadata": {}, - "outputs": [], - "source": [ - "def load_and_clean_data(flows_path, aum_path, rates_path, gov_path):\n", - " \"\"\"\n", - " Loads raw CSVs and parses dates for consistent time-series analysis.\n", - " \"\"\"\n", - "\n", - " flows = pd.read_csv(flows_path)\n", - " flows['Centralisation Date'] = pd.to_datetime(flows['Centralisation Date'])\n", - " \n", - " aum = pd.read_csv(aum_path)\n", - " aum['Centralisation Date'] = pd.to_datetime(aum['Centralisation Date'])\n", - " \n", - " rates = pd.read_csv(rates_path)\n", - " try:\n", - " rates['Date'] = pd.to_datetime(rates['Date'], dayfirst=True)\n", - " except:\n", - " rates['Date'] = pd.to_datetime(rates['Date'])\n", - " \n", - " gov = pd.read_csv(gov_path)\n", - " gov['Date'] = pd.to_datetime(gov['Date'])\n", - " \n", - " return flows, aum, rates, gov" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "479a50b8", - "metadata": {}, - "outputs": [], - "source": [ - "flows_path = \"flows_sample.csv\"\n", - "aum_path = \"aum_sample.csv\"\n", - "rates_path = \"str_rates.csv\"\n", - "gov_path = \"eur_gov_indices.csv\"\n", - "\n", - "flows, aum, rates, gov = load_and_clean_data(flows_path, aum_path, rates_path, gov_path)" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "a6228231", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
Bond/IndexDescriptionDateTotal Return % 1-wk-LOCYield to Maturity (s.a.)Yield to Maturity (conv.)
0G0D0ICE BofA German Government Index2005-01-070.4843.063.08
1G0D0ICE BofA German Government Index2005-01-140.4143.003.03
2G0D0ICE BofA German Government Index2005-01-210.0063.023.04
3G0D0ICE BofA German Government Index2005-01-280.2083.003.03
4G0D0ICE BofA German Government Index2005-02-040.4353.013.03
5G0D0ICE BofA German Government Index2005-02-110.2212.983.00
6G0D0ICE BofA German Government Index2005-02-18-1.0203.133.16
7G0D0ICE BofA German Government Index2005-02-25-0.1983.163.19
8G0D0ICE BofA German Government Index2005-03-040.2283.153.18
9G0D0ICE BofA German Government Index2005-03-11-0.3803.203.23
10G0D0ICE BofA German Government Index2005-03-180.3433.193.21
11G0D0ICE BofA German Government Index2005-03-250.5283.153.17
12G0D0ICE BofA German Government Index2005-04-010.7133.113.13
13G0D0ICE BofA German Government Index2005-04-080.0143.093.12
14G0D0ICE BofA German Government Index2005-04-150.5153.023.04
15G0D0ICE BofA German Government Index2005-04-220.3222.983.00
16G0D0ICE BofA German Government Index2005-04-290.4442.912.94
17G0D0ICE BofA German Government Index2005-05-06-0.1822.952.97
18G0D0ICE BofA German Government Index2005-05-130.8082.852.88
19G0D0ICE BofA German Government Index2005-05-20-0.0902.892.91
\n", - "
" - ], - "text/plain": [ - " Bond/Index Description Date \\\n", - "0 G0D0 ICE BofA German Government Index 2005-01-07 \n", - "1 G0D0 ICE BofA German Government Index 2005-01-14 \n", - "2 G0D0 ICE BofA German Government Index 2005-01-21 \n", - "3 G0D0 ICE BofA German Government Index 2005-01-28 \n", - "4 G0D0 ICE BofA German Government Index 2005-02-04 \n", - "5 G0D0 ICE BofA German Government Index 2005-02-11 \n", - "6 G0D0 ICE BofA German Government Index 2005-02-18 \n", - "7 G0D0 ICE BofA German Government Index 2005-02-25 \n", - "8 G0D0 ICE BofA German Government Index 2005-03-04 \n", - "9 G0D0 ICE BofA German Government Index 2005-03-11 \n", - "10 G0D0 ICE BofA German Government Index 2005-03-18 \n", - "11 G0D0 ICE BofA German Government Index 2005-03-25 \n", - "12 G0D0 ICE BofA German Government Index 2005-04-01 \n", - "13 G0D0 ICE BofA German Government Index 2005-04-08 \n", - "14 G0D0 ICE BofA German Government Index 2005-04-15 \n", - "15 G0D0 ICE BofA German Government Index 2005-04-22 \n", - "16 G0D0 ICE BofA German Government Index 2005-04-29 \n", - "17 G0D0 ICE BofA German Government Index 2005-05-06 \n", - "18 G0D0 ICE BofA German Government Index 2005-05-13 \n", - "19 G0D0 ICE BofA German Government Index 2005-05-20 \n", - "\n", - " Total Return % 1-wk-LOC Yield to Maturity (s.a.) \\\n", - "0 0.484 3.06 \n", - "1 0.414 3.00 \n", - "2 0.006 3.02 \n", - "3 0.208 3.00 \n", - "4 0.435 3.01 \n", - "5 0.221 2.98 \n", - "6 -1.020 3.13 \n", - "7 -0.198 3.16 \n", - "8 0.228 3.15 \n", - "9 -0.380 3.20 \n", - "10 0.343 3.19 \n", - "11 0.528 3.15 \n", - "12 0.713 3.11 \n", - "13 0.014 3.09 \n", - "14 0.515 3.02 \n", - "15 0.322 2.98 \n", - "16 0.444 2.91 \n", - "17 -0.182 2.95 \n", - "18 0.808 2.85 \n", - "19 -0.090 2.89 \n", - "\n", - " Yield to Maturity (conv.) \n", - "0 3.08 \n", - "1 3.03 \n", - "2 3.04 \n", - "3 3.03 \n", - "4 3.03 \n", - "5 3.00 \n", - "6 3.16 \n", - "7 3.19 \n", - "8 3.18 \n", - "9 3.23 \n", - "10 3.21 \n", - "11 3.17 \n", - "12 3.13 \n", - "13 3.12 \n", - "14 3.04 \n", - "15 3.00 \n", - "16 2.94 \n", - "17 2.97 \n", - "18 2.88 \n", - "19 2.91 " - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gov.head(20)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/repair_challenge/carmignac_analysis.py b/repair_challenge/carmignac_analysis.py index b9b9151..4a2be1c 100644 --- a/repair_challenge/carmignac_analysis.py +++ b/repair_challenge/carmignac_analysis.py @@ -27,7 +27,8 @@ import pandas as pd # 1. LOAD & VALIDATE # ───────────────────────────────────────────────────────────── -def load_outputs(scores_path, mapping_path, surgery_path): +def load_outputs(scores_path, mapping_path, surgery_path, + err_isin_path=None, err_agg_path=None): 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"]) @@ -40,9 +41,44 @@ def load_outputs(scores_path, mapping_path, surgery_path): surgery["reg_orig"] = surgery["reg_orig"].astype(str) surgery["reg_from"] = surgery["reg_from"].astype(str) surgery["reg_to"] = surgery["reg_to"].astype(str) + if "lookback_months" not in surgery.columns: + surgery["lookback_months"] = 1 # backwards compat - return scores, mapping, surgery + # Error account (optional) + err_isin = None + err_agg = None + if err_isin_path and os.path.exists(err_isin_path): + err_isin = pd.read_csv(err_isin_path, parse_dates=["date"]) + err_isin["isin"] = err_isin["isin"].astype(str) + if err_agg_path and os.path.exists(err_agg_path): + err_agg = pd.read_csv(err_agg_path, parse_dates=["date"]) + return scores, mapping, surgery, err_isin, err_agg + + + +# ───────────────────────────────────────────────────────────── +# 1b. LOAD ERROR ACCOUNT (optional) +# ───────────────────────────────────────────────────────────── + +def load_error_account(isin_path, agg_path): + """ + Loads the error account CSVs produced by carmignac_diagnostics.py. + Returns (df_err_isin, df_err_agg) or (None, None) if files not found. + """ + if not isin_path or not agg_path: + return None, None + try: + ei = pd.read_csv(isin_path, parse_dates=["date"]) + ea = pd.read_csv(agg_path, parse_dates=["date"]) + ei["isin"] = ei["isin"].astype(str) + print(f"[Load] error account (ISIN) : {len(ei)} rows, " + f"{ei['isin'].nunique()} ISINs") + print(f"[Load] error account (agg) : {len(ea)} rows") + return ei, ea + except Exception as e: + print(f"[WARN] Could not load error account: {e}") + return None, None # ───────────────────────────────────────────────────────────── # 2. COMPUTE ANALYTICS @@ -195,7 +231,7 @@ def print_summary(analytics, surgery): # 4. BUILD HTML REPORT # ───────────────────────────────────────────────────────────── -def build_html(analytics, surgery, scores, mapping): +def build_html(analytics, surgery, scores, mapping, df_err_isin=None, df_err_agg=None): tl = analytics["timeline"] ss = analytics["surgery_stats"] piv = analytics["pivot"] @@ -257,14 +293,212 @@ def build_html(analytics, surgery, scores, mapping): traj_json = json.dumps(traj_datasets) + # ── 4.2b Error account data (optional) ──────────────────── + has_error = df_err_isin is not None and df_err_agg is not None + + if has_error: + err_dates = [d.strftime("%Y-%m-%d") for d in pd.to_datetime(df_err_agg["date"])] + err_agg_stock = [round(float(v), 3) if not pd.isna(v) else None + for v in df_err_agg["stock_error_agg"].values] + err_agg_res = [round(float(v), 3) if not pd.isna(v) else None + for v in df_err_agg["residual_agg"].values] + err_agg_pct = [round(float(v), 4) if not pd.isna(v) else None + for v in df_err_agg["stock_error_agg_pct"].values] + + # Top 5 ISINs by max |stock error| + top_err = (df_err_isin.groupby("isin")["stock_error"] + .apply(lambda x: x.abs().max()) + .nlargest(5).index.tolist()) + all_err_dates = sorted(df_err_isin["date"].unique()) + ERR_COLORS = ["#ef4444","#f59e0b","#8b5cf6","#06b6d4","#10b981"] + err_isin_ds = [] + for idx, isin in enumerate(top_err): + sub = (df_err_isin[df_err_isin["isin"] == isin] + .set_index("date")["stock_error"] + .reindex(all_err_dates)) + err_isin_ds.append({ + "label": isin, + "data": [round(float(v), 3) if not pd.isna(v) else None for v in sub.values], + "borderColor": ERR_COLORS[idx % len(ERR_COLORS)], + "backgroundColor": ERR_COLORS[idx % len(ERR_COLORS)] + "22", + "borderWidth": 1.5, "pointRadius": 0, "tension": 0.3, "fill": False, + }) + + max_err_stock = float(df_err_agg["stock_error_agg"].abs().max()) + max_err_pct = float(df_err_agg["stock_error_agg_pct"].max()) + agg_std = float(df_err_agg["stock_error_agg"].std()) + agg_mean = float(df_err_agg["stock_error_agg"].abs().mean()) + stationarity = round(agg_std / max(agg_mean, 1e-9), 3) + + err_dates_js = json.dumps(err_dates) + err_agg_stock_js = json.dumps(err_agg_stock) + err_agg_res_js = json.dumps(err_agg_res) + err_agg_pct_js = json.dumps(err_agg_pct) + err_isin_ds_js = json.dumps(err_isin_ds) + err_isin_dates_js = json.dumps([d.strftime("%Y-%m-%d") if hasattr(d, "strftime") + else str(d)[:10] for d in all_err_dates]) + + # ISIN detail table (top 100 worst) + err_rows = [] + for _, r in (df_err_isin.assign(abs_s=df_err_isin["stock_error"].abs()) + .sort_values("abs_s", ascending=False) + .head(100).iterrows()): + ds = r["date"].strftime("%Y-%m-%d") if hasattr(r["date"], "strftime") else str(r["date"])[:10] + sc = "color:var(--danger)" if r["stock_error"] < 0 else "color:var(--warn)" + rc = "color:var(--danger)" if r["residual"] < 0 else "color:var(--warn)" + pch = "color:var(--danger);font-weight:600" if r["stock_error_pct"] > 5 else ("color:var(--warn)" if r["stock_error_pct"] > 1 else "") + err_rows.append( + f'{ds}' + f'{r["isin"]}' + f'{r["residual"]:+,.2f}' + f'{r["stock_error"]:+,.2f}' + f'{r["stock_error_pct"]:.3f}%' + f'' + ) + err_isin_detail = "".join(err_rows) if err_rows else ( + '✓ Error account is flat' + ) + + # HTML block for error account section + err_section_html = f""" +
06 · Error Account
+ +
+
+ Aggregate error account stock + + Stock_error(t_ref) = 0. The stock absorbs unreconciled residuals going backwards. + A flat signal near zero = clean data. A drift = structural gap. + +
+
+
+
+
Max |error stock|
+
{max_err_stock:,.1f} shares
+
+
+
Max % of total AUM
+
{max_err_pct:.3f}%
+
+
+
Stationarity σ/μ
+
{stationarity:.3f}
+
lower = more stationary
+
+
+
+
+
+ +
+
+
+ Monthly aggregate residual + ΔQ_total − F_total per month +
+
+
+
+
+ Error stock — top 5 ISINs + Cumulative error stock per ISIN +
+
+
+
+ +
+
+ Error account detail — worst (ISIN, month) pairs +
+
+ + + + + + + + {err_isin_detail} +
DateISINMonthly residualCumul. stock% of max AUM
+
+
""" + + # JS block for error account charts + err_js_block = f""" +// ── 8. Error account charts ────────────────────────────────── +const ERR_DATES = {err_dates_js}; +const ERR_AGG_STOCK = {err_agg_stock_js}; +const ERR_AGG_RES = {err_agg_res_js}; +const ERR_ISIN_TS = {err_isin_ds_js}; +const ERR_ISIN_DATES = {err_isin_dates_js}; + +new Chart(document.getElementById('chartErrStock'), {{ + type: 'line', + data: {{ labels: ERR_DATES, datasets: [{{ + label: 'Aggregate error stock', data: ERR_AGG_STOCK, + borderColor: '#ef4444', backgroundColor: '#ef444415', + borderWidth: 2, pointRadius: 0, tension: 0.3, fill: true + }}] }}, + options: {{ + responsive: true, maintainAspectRatio: false, + interaction: {{mode:'index', intersect:false}}, + plugins: {{ legend: {{display:false}}, tooltip: tooltip() }}, + scales: {{ x: timeAxis(), y: {{ + ...yAxis('Shares'), + grid: {{ color: ctx => ctx.tick.value === 0 ? '#ffffff55' : '#1a2030', + lineWidth: ctx => ctx.tick.value === 0 ? 1.5 : 1 }} + }} }} + }} +}}); + +new Chart(document.getElementById('chartErrRes'), {{ + type: 'bar', + data: {{ labels: ERR_DATES, datasets: [{{ + label: 'Monthly residual', data: ERR_AGG_RES, + backgroundColor: ERR_AGG_RES.map(v => v != null && v < 0 ? '#ef444488' : '#f59e0b88'), + borderColor: ERR_AGG_RES.map(v => v != null && v < 0 ? '#ef4444' : '#f59e0b'), + borderWidth: 1, borderRadius: 2 + }}] }}, + options: {{ + responsive: true, maintainAspectRatio: false, + plugins: {{ legend: {{display:false}}, tooltip: tooltip() }}, + scales: {{ x: timeAxis(), y: yAxis('Shares') }} + }} +}}); + +new Chart(document.getElementById('chartErrIsin'), {{ + type: 'line', + data: {{ labels: ERR_ISIN_DATES, datasets: ERR_ISIN_TS }}, + options: {{ + responsive: true, maintainAspectRatio: false, + interaction: {{mode:'index', intersect:false}}, + plugins: {{ + legend: {{position:'right', labels:{{boxWidth:10, padding:8, font:{{size:10}}}}}}, + tooltip: tooltip() + }}, + scales: {{ x: timeAxis(), y: yAxis('Error stock (shares)') }} + }} +}});""" + + else: + err_section_html = "" + err_js_block = "" + # ── 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" + 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" + lb = int(r.get("lookback_months", 1)) + lb_cell = (f'{lb}m' if lb > 1 else "—") surg_rows_html += f""" {r['date'].date()} @@ -275,6 +509,7 @@ def build_html(analytics, surgery, scores, mapping): {r['jaccard_composite']:.4f} +{r['gain_vs_no_surgery']:.6f} {r['gain_pct_of_score']:.1f}% + {lb_cell} """ # ── 4.4 Top accounts table ────────────────────────────────── @@ -857,6 +1092,7 @@ def build_html(analytics, surgery, scores, mapping): Jaccard Score gain % of score + Lookback {surg_rows_html} @@ -887,6 +1123,9 @@ def build_html(analytics, surgery, scores, mapping): + {err_section_html} + + @@ -1297,6 +1536,7 @@ new Chart(document.getElementById('chartJaccard'), {{ }}, }}, }}); +{err_js_block} """ @@ -1314,32 +1554,49 @@ def main(): 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") + parser.add_argument("--error-account-isin", default=None, + dest="error_isin", + help="Path to carmignac_error_account.csv (optional)") + parser.add_argument("--error-account-agg", default=None, + dest="error_agg", + help="Path to carmignac_error_account_agg.csv (optional)") 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): + def resolve(p, required=True): + if p is None: + return None 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}") + if required: + sys.exit(f"[ERROR] File not found: {p}") + print(f"[WARN] Optional file not found: {p}") + return None scores_path = resolve(args.scores) mapping_path = resolve(args.mapping) surgery_path = resolve(args.surgery) + error_isin_path = resolve(args.error_isin, required=False) + error_agg_path = resolve(args.error_agg, required=False) 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) + scores, mapping, surgery, df_err_isin, df_err_agg = load_outputs( + scores_path, mapping_path, surgery_path, + err_isin_path=error_isin_path, err_agg_path=error_agg_path + ) analytics = compute_analytics(scores, mapping, surgery) print_summary(analytics, surgery) - html = build_html(analytics, surgery, scores, mapping) + html = build_html(analytics, surgery, scores, mapping, + df_err_isin=df_err_isin, df_err_agg=df_err_agg) out_path = args.out with open(out_path, "w", encoding="utf-8") as f: diff --git a/repair_challenge/carmignac_diagnostics.py b/repair_challenge/carmignac_diagnostics.py index ee94cf8..484c197 100644 --- a/repair_challenge/carmignac_diagnostics.py +++ b/repair_challenge/carmignac_diagnostics.py @@ -36,13 +36,14 @@ import json import os import sys -from collections import defaultdict -import s3fs - import numpy as np import pandas as pd +from collections import defaultdict +import s3fs + + # ───────────────────────────────────────────────────────────── # 1. LOAD # ───────────────────────────────────────────────────────────── @@ -217,6 +218,8 @@ def detect_broken_months(aum, flows, alpha=0.02, lag_days=3): df_broken = df_all[df_all["broken"]].sort_values("missing_pct", ascending=False) return df_broken, df_all + + # ───────────────────────────────────────────────────────────── # 2b. AGGREGATE (CROSS-ISIN) BROKEN MONTHS # ───────────────────────────────────────────────────────────── @@ -319,6 +322,165 @@ def detect_aggregate_broken_months(aum, flows, alpha=0.02, lag_days=3): df_agg = pd.DataFrame(rows) return df_agg +# ───────────────────────────────────────────────────────────── +# 2c. ERROR ACCOUNT +# ───────────────────────────────────────────────────────────── + +def build_error_account(aum, flows, lag_days=3): + """ + Builds a synthetic "error account" that absorbs the stock-flow + residuals that cannot be explained by recorded flows. + + Construction (backwards from t_ref): + Stock_error(t_ref) = 0 (by definition) + Stock_error(t-1) = Stock_error(t) - Residual(t) + + where Residual(t) = [Σ_r Q_{r,s}(t) - Σ_r Q_{r,s}(t-1)] - Σ_r F_{r,s}(t) + for each ISIN s independently. + + By construction, adding this error account to the AUM restores the + stock-flow equality at every (isin, month). + + Also computes an aggregated error account (summed over all ISINs). + + Returns + ------- + df_err_isin : DataFrame with columns + (date, isin, residual, stock_error, stock_error_pct) + where stock_error_pct = stock_error / max(total_isin_aum, 1) + + df_err_agg : DataFrame with columns + (date, residual_agg, stock_error_agg, stock_error_agg_pct) + """ + t_min = aum["Centralisation Date"].min() + t_max = aum["Centralisation Date"].max() + all_months = pd.date_range(t_min, t_max, freq="ME") + + # ── ISIN-level AUM panel (forward-filled) ──────────────────── + aum_agg = ( + aum.groupby(["Product - Isin", "Centralisation Date"])["Quantity - AUM"] + .sum() + .reset_index() + .rename(columns={"Product - Isin": "isin", + "Centralisation Date": "date", + "Quantity - AUM": "qty"}) + ) + aum_pivot = aum_agg.pivot(index="date", columns="isin", values="qty") + aum_pivot = aum_pivot.reindex(all_months).ffill() + + # ── ISIN-level flow aggregation (standard window) ───────────── + def bucket_isin_flows(flows_df, months): + fc = flows_df.copy() + def assign_month(d): + for m in months: + eom_prev = m - pd.offsets.MonthEnd(1) + if eom_prev < d <= m: + return m + return pd.NaT + fc["month_end"] = fc["Centralisation Date"].apply(assign_month) + fc = fc.dropna(subset=["month_end"]) + return (fc.groupby(["Product - Isin", "month_end"])["Quantity - NetFlows"] + .sum() + .unstack("Product - Isin") + .reindex(months) + .fillna(0.0)) + + flow_pivot = bucket_isin_flows(flows, all_months) + + # ── Compute residuals per (isin, month) ─────────────────────── + isins = aum_pivot.columns.tolist() + # residual[t] = delta_AUM[t] - flow[t] + residuals = {} # {isin: Series indexed by month} + + for isin in isins: + res_series = {} + for i in range(1, len(all_months)): + t_curr = all_months[i] + t_prev = all_months[i - 1] + q_curr = aum_pivot[isin].get(t_curr, np.nan) + q_prev = aum_pivot[isin].get(t_prev, np.nan) + if pd.isna(q_curr) or pd.isna(q_prev): + continue + delta = q_curr - q_prev + f = (flow_pivot[isin].get(t_curr, 0.0) + if isin in flow_pivot.columns else 0.0) + res_series[t_curr] = delta - f + residuals[isin] = pd.Series(res_series) + + # ── Build error stock backwards from t_ref ──────────────────── + t_ref = all_months[-1] + rows_isin = [] + + for isin in isins: + res = residuals[isin] + # Maximum AUM for this ISIN (for normalisation) + max_aum = aum_pivot[isin].max() + if pd.isna(max_aum) or max_aum < 1: + max_aum = 1.0 + + # Propagate backwards: stock(t_ref) = 0 + stock = 0.0 + # Build dict keyed by date + stock_by_date = {t_ref: 0.0} + for i in range(len(all_months) - 2, -1, -1): + t_curr = all_months[i + 1] + t_prev = all_months[i] + r = res.get(t_curr, 0.0) + stock = stock - r + stock_by_date[t_prev] = stock + + for t in all_months: + s = stock_by_date.get(t, np.nan) + r = res.get(t, 0.0) + rows_isin.append({ + "date": t, + "isin": isin, + "residual": round(r, 4), + "stock_error": round(s, 4) if not pd.isna(s) else np.nan, + "stock_error_pct": round(abs(s) / max_aum * 100, 4) + if not pd.isna(s) else np.nan, + }) + + df_err_isin = pd.DataFrame(rows_isin).sort_values(["date", "isin"]) + + # ── Aggregated error account ────────────────────────────────── + # Total AUM across all ISINs at each month + total_aum_by_month = aum_pivot.sum(axis=1) + max_total_aum = total_aum_by_month.max() + if pd.isna(max_total_aum) or max_total_aum < 1: + max_total_aum = 1.0 + + # Aggregate residual = sum of ISIN residuals + agg_res = {} + for i in range(1, len(all_months)): + t_curr = all_months[i] + total_r = sum(residuals[isin].get(t_curr, 0.0) for isin in isins) + agg_res[t_curr] = total_r + + agg_stock = 0.0 + agg_stock_by_date = {t_ref: 0.0} + for i in range(len(all_months) - 2, -1, -1): + t_curr = all_months[i + 1] + t_prev = all_months[i] + agg_stock = agg_stock - agg_res.get(t_curr, 0.0) + agg_stock_by_date[t_prev] = agg_stock + + rows_agg = [] + for t in all_months: + s = agg_stock_by_date.get(t, np.nan) + r = agg_res.get(t, 0.0) + rows_agg.append({ + "date": t, + "residual_agg": round(r, 4), + "stock_error_agg": round(s, 4) if not pd.isna(s) else np.nan, + "stock_error_agg_pct": round(abs(s) / max_total_aum * 100, 4) + if not pd.isna(s) else np.nan, + }) + + df_err_agg = pd.DataFrame(rows_agg).sort_values("date") + return df_err_isin, df_err_agg + + # ───────────────────────────────────────────────────────────── # 3. PRINT SUMMARY # ───────────────────────────────────────────────────────────── @@ -358,7 +520,7 @@ def print_summary(df_broken, df_all, alpha): # 4. BUILD HTML REPORT # ───────────────────────────────────────────────────────────── -def build_html(df_broken, df_all, df_agg, alpha): +def build_html(df_broken, df_all, df_agg, df_err_isin, df_err_agg, alpha): # ── JS-ready data ──────────────────────────────────────────── # Timeline: n_broken and total_missing per month tl = (df_all[df_all["broken"]] @@ -374,6 +536,11 @@ def build_html(df_broken, df_all, df_agg, alpha): def jf(arr, dec=4): return json.dumps([round(float(v), dec) if not np.isnan(v) else None for v in arr]) + ISIN_COLORS = [ + "#2563eb","#16a34a","#dc2626","#d97706","#7c3aed", + "#0891b2","#db2777","#65a30d","#ea580c","#6366f1", + ] + n_broken_js = jf(tl["n_broken"].values, 0) total_miss_js = jf(tl["total_missing"].values) n_lag_js = jf(tl["n_lag"].values, 0) @@ -412,6 +579,65 @@ def build_html(df_broken, df_all, df_agg, alpha): 'color:var(--success);font-family:var(--mono)">✓ No broken months at aggregate level' ) + # ── Error account JS data ──────────────────────────────────── + err_dates_str = json.dumps([d.strftime("%Y-%m-%d") for d in pd.to_datetime(df_err_agg["date"])]) + err_agg_stock_js = jf(df_err_agg["stock_error_agg"].values) + err_agg_res_js = jf(df_err_agg["residual_agg"].values) + err_agg_pct_js = jf(df_err_agg["stock_error_agg_pct"].values) + + # Top 5 ISINs by max absolute stock error + top_err_isins = ( + df_err_isin.groupby("isin")["stock_error"] + .apply(lambda x: x.abs().max()) + .nlargest(5).index.tolist() + ) + all_err_dates = sorted(df_err_isin["date"].unique()) + err_isin_datasets = [] + for idx, isin in enumerate(top_err_isins): + sub = (df_err_isin[df_err_isin["isin"] == isin] + .set_index("date")["stock_error"] + .reindex(all_err_dates)) + err_isin_datasets.append({ + "label": isin, + "data": [round(float(v), 3) if not pd.isna(v) else None for v in sub.values], + "borderColor": ISIN_COLORS[idx % len(ISIN_COLORS)], + "backgroundColor": ISIN_COLORS[idx % len(ISIN_COLORS)] + "22", + "borderWidth": 1.5, "pointRadius": 0, "tension": 0.3, "fill": False, + }) + err_isin_ts_json = json.dumps(err_isin_datasets) + err_isin_dates_str = json.dumps([d.strftime("%Y-%m-%d") if hasattr(d, "strftime") + else str(d)[:10] for d in all_err_dates]) + + # Error account KPIs + max_agg_stock_err = float(df_err_agg["stock_error_agg"].abs().max()) + max_agg_stock_pct = float(df_err_agg["stock_error_agg_pct"].max()) + # Stationarity proxy: std / mean_abs (lower = more stationary) + agg_std = float(df_err_agg["stock_error_agg"].std()) + agg_mean = float(df_err_agg["stock_error_agg"].abs().mean()) + stationarity = round(agg_std / max(agg_mean, 1e-9), 3) + + # Error account ISIN detail table (worst months per ISIN) + err_worst = (df_err_isin.assign(abs_stock=df_err_isin["stock_error"].abs()) + .sort_values("abs_stock", ascending=False) + .head(200)) + err_isin_rows = [] + for _, r in err_worst.iterrows(): + ds = r["date"].strftime("%Y-%m-%d") if hasattr(r["date"], "strftime") else str(r["date"])[:10] + sc = "miss-neg" if r["stock_error"] < 0 else "miss-pos" + rc = "miss-neg" if r["residual"] < 0 else "miss-pos" + pch = "pct-high" if r["stock_error_pct"] > 5 else ("pct-med" if r["stock_error_pct"] > 1 else "") + err_isin_rows.append( + f'{ds}' + f'{r["isin"]}' + f'{r["residual"]:+,.2f}' + f'{r["stock_error"]:+,.2f}' + f'{r["stock_error_pct"]:.3f}%' + ) + err_isin_detail = "".join(err_isin_rows) if err_isin_rows else ( + '✓ Error account is flat (no residuals)' + ) + # Per-ISIN summary isin_sum = (df_broken.groupby("isin") .agg(n_months=("date", "count"), @@ -419,11 +645,6 @@ def build_html(df_broken, df_all, df_agg, alpha): total_abs=("missing_flow", lambda x: x.abs().sum())) .sort_values("total_abs", ascending=False)) - ISIN_COLORS = [ - "#2563eb","#16a34a","#dc2626","#d97706","#7c3aed", - "#0891b2","#db2777","#65a30d","#ea580c","#6366f1", - ] - # Per-ISIN missing_pct timeseries for the top 5 ISINs top_isins = isin_sum.head(5).index.tolist() all_dates = sorted(df_all["date"].unique()) @@ -618,7 +839,77 @@ def build_html(df_broken, df_all, df_agg, alpha):
-
00 · Aggregate view — all ISINs combined
+
00 · Error account — cumulative residuals
+ +
+
+ Aggregate error account stock over time + + Stock_error(t_ref) = 0 by definition. At each prior month, the stock absorbs the residual + [ΔQ_total − F_total]. A stationary signal near zero = clean data. + A drifting signal = structural data quality problem. + +
+
+
+
+
Max |stock error|
+
{max_agg_stock_err:,.1f} shares
+
+
+
Max % of total AUM
+
{max_agg_stock_pct:.3f}%
+
+
+
Stationarity (σ/μ)
+
{stationarity:.3f}
+
lower = more stationary
+
+
+
+
+
+ +
+
+
+ Monthly aggregate residual + ΔQ_total − F_total per month (should be near zero) +
+
+
+
+
+
+
+ Error stock — top 5 ISINs + Cumulative error stock per ISIN (most affected) +
+
+
+
+
+
+ +
+
+ Error account detail — worst (ISIN, month) pairs + Sorted by absolute cumulative error stock. stock_error_pct = |stock| / max(ISIN AUM) +
+
+ + + + + + + + {err_isin_detail} +
DateISINMonthly residualCumulative stock% of max AUM
+
+
+ +
01 · Aggregate view — all ISINs combined
@@ -832,6 +1123,75 @@ new Chart(document.getElementById('chartIsinTs'), {{ }} }}); +// ── Error account charts ───────────────────────────────────── +const ERR_DATES = {err_dates_str}; +const ERR_AGG_STOCK = {err_agg_stock_js}; +const ERR_AGG_RES = {err_agg_res_js}; +const ERR_AGG_PCT = {err_agg_pct_js}; +const ERR_ISIN_TS = {err_isin_ts_json}; +const ERR_ISIN_DATES= {err_isin_dates_str}; + +// Aggregate error stock — line with zero reference +new Chart(document.getElementById('chartErrAggStock'), {{ + type: 'line', + data: {{ + labels: ERR_DATES, + datasets: [ + {{ label: 'Aggregate error stock (shares)', + data: ERR_AGG_STOCK, + borderColor: '#ef4444', backgroundColor: '#ef444418', + borderWidth: 2, pointRadius: 0, tension: 0.3, fill: true }}, + ] + }}, + options: {{ + responsive: true, maintainAspectRatio: false, + interaction: {{mode:'index', intersect:false}}, + plugins: {{ legend:{{display:false}}, tooltip: tip() }}, + scales: {{ + x: xAxis(), + y: {{ + ...yAxis('Shares'), + grid: {{ + color: ctx => ctx.tick.value === 0 ? '#ffffff55' : '#1a2030', + lineWidth: ctx => ctx.tick.value === 0 ? 1.5 : 1, + }} + }} + }} + }} +}}); + +// Monthly residual bar +new Chart(document.getElementById('chartErrAggRes'), {{ + type: 'bar', + data: {{ + labels: ERR_DATES, + datasets: [{{ label: 'Monthly residual (shares)', data: ERR_AGG_RES, + backgroundColor: ERR_AGG_RES.map(v => v < 0 ? '#ef444488' : '#f59e0b88'), + borderColor: ERR_AGG_RES.map(v => v < 0 ? '#ef4444' : '#f59e0b'), + borderWidth: 1, borderRadius: 2 }}] + }}, + options: {{ + responsive: true, maintainAspectRatio: false, + plugins: {{legend:{{display:false}}, tooltip: tip()}}, + scales: {{ x: xAxis(), y: yAxis('Shares') }} + }} +}}); + +// Per-ISIN error stock timeseries +new Chart(document.getElementById('chartErrIsinTs'), {{ + type: 'line', + data: {{ labels: ERR_ISIN_DATES, datasets: ERR_ISIN_TS }}, + options: {{ + responsive: true, maintainAspectRatio: false, + interaction: {{mode:'index', intersect:false}}, + plugins: {{ + legend:{{position:'right',labels:{{boxWidth:10,padding:8,font:{{size:10}}}}}}, + tooltip: tip() + }}, + scales: {{ x: xAxis(), y: yAxis('Error stock (shares)') }} + }} +}}); + // ── Aggregate charts ───────────────────────────────────────── const AGG_DATES = {agg_dates_str}; const AGG_DELTA = {agg_delta_js}; @@ -926,8 +1286,8 @@ def main(): parser.add_argument("--out", default="carmignac_broken_months.csv", help="Machine-readable output (loaded by carmignac_repair.py)") parser.add_argument("--html", default="carmignac_diagnostics.html") - parser.add_argument("--alpha", type=float, default=0.15, - help="Tolerance threshold (default 0.15 = 15%%)") + parser.add_argument("--alpha", type=float, default=0.02, + help="Tolerance threshold (default 0.02 = 2%%)") parser.add_argument("--lag", type=int, default=3, help="Boundary days to test for accounting lag (default 3)") args = parser.parse_args() @@ -948,21 +1308,35 @@ def main(): df_broken, df_all = detect_broken_months(aum, flows, alpha=args.alpha, lag_days=args.lag) df_agg = detect_aggregate_broken_months(aum, flows, alpha=args.alpha, lag_days=args.lag) + print(f"\n[Error account] Building error account...") + df_err_isin, df_err_agg = build_error_account(aum, flows, lag_days=args.lag) + print_summary(df_broken, df_all, args.alpha) n_agg_broken = int(df_agg["broken"].sum()) print(f" Aggregate broken months : {n_agg_broken} " f"(of which lags: {int(df_agg['is_lag'].sum())})") + max_err = float(df_err_agg["stock_error_agg"].abs().max()) + print(f" Max aggregate error stock : {max_err:,.1f} shares " + f"({float(df_err_agg['stock_error_agg_pct'].max()):.3f}% of total AUM)") # CSV output — this is what carmignac_repair.py will load if len(df_broken): df_broken.to_csv(args.out, index=False) - print(f"[Export] Broken months CSV → {args.out}") + print(f"[Export] Broken months CSV → {args.out}") else: - pd.DataFrame(columns=["date", "isin", "missing_pct", "is_lag"]).to_csv(args.out, index=False) + pd.DataFrame(columns=["date","isin","missing_pct","is_lag"]).to_csv(args.out, index=False) print(f"[Export] No broken months — empty CSV → {args.out}") - html = build_html(df_broken, df_all, df_agg, args.alpha) + # Error account CSV + err_out = args.out.replace("broken_months", "error_account") + df_err_isin.to_csv(err_out, index=False) + err_agg_out = err_out.replace("error_account", "error_account_agg") + df_err_agg.to_csv(err_agg_out, index=False) + print(f"[Export] Error account (ISIN) → {err_out}") + print(f"[Export] Error account (agg) → {err_agg_out}") + + html = build_html(df_broken, df_all, df_agg, df_err_isin, df_err_agg, args.alpha) with open(args.html, "w", encoding="utf-8") as f: f.write(html) print(f"[Export] HTML report → {args.html}") diff --git a/repair_challenge/carmignac_repair.py b/repair_challenge/carmignac_repair.py index 3279c4f..11980f9 100644 --- a/repair_challenge/carmignac_repair.py +++ b/repair_challenge/carmignac_repair.py @@ -6,6 +6,7 @@ Carmignac Data Challenge — Registrar ID Repair Pipeline Étape 3 : Chirurgie de code (matching 1-to-1) """ +import os import pandas as pd import numpy as np from collections import defaultdict @@ -15,10 +16,12 @@ import s3fs # ───────────────────────────────────────────── # PARAMÈTRES # ───────────────────────────────────────────── -ALPHA = 0.05 # tolérance réconciliation : 5% du stock à t -MIN_AUM_EUR = 5e6 # seuil filtrage étape 1 — 0 pour les heads de test, 5e6 en prod +ALPHA = 0.05 # tolérance réconciliation : 5% du stock à t +MIN_AUM_EUR = 5e6 # seuil filtrage étape 1 — 0 pour les heads de test, 5e6 en prod MIN_JACCARD = 0.3 # seuil minimal similarité portefeuille pour chirurgie SCORE_DROP_THRESHOLD = 0.15 # si score chute de >15% → candidat chirurgie +MAX_SURGERY_LOOKBACK = 6 # remonter jusqu'à 6 mois en arrière pour trouver un candidat +SYMMETRY_ATTENUATION = 0.05 # facteur d'atténuation si rupture symétrique détectée (cas 1/3) # ── Broken months ────────────────────────────────────────────── # Attenuation factor applied to reconciliation errors on months flagged @@ -35,6 +38,15 @@ BROKEN_MONTH_ATTENUATION = 0.2 # reduce error to 20% on broken months # attenuated (same factor as broken months). LAG_ATTENUATION = 0.2 # reduce error to 20% on likely lag months +# ── Fenêtre de chirurgie étendue ─────────────────────────────── +# Quand aucun bon candidat n'est trouvé à t-1, la chirurgie remonte +# jusqu'à MAX_SURGERY_LOOKBACK mois en arrière. Pour chaque mois k +# supplémentaire, le score composite est multiplié par un facteur de +# confiance décroissant : confidence(k) = 1 - (k-1)/MAX_SURGERY_LOOKBACK. +# Le client suggère 6 mois (délai maximal de résolution des transferts +# asymétriques, lié au cycle de paiement des rétrocessions trimestrielles). +MAX_SURGERY_LOOKBACK = 6 + EXCLUDE_REGISTRAR = ["Off Distribution", "Private Clients"] # ───────────────────────────────────────────── @@ -53,7 +65,7 @@ def load_broken_months(broken_months_path): try: df = pd.read_csv(broken_months_path, parse_dates=["date"]) broken = set(zip(pd.to_datetime(df["date"]), df["isin"].astype(str))) - lags = set(zip(pd.to_datetime(df.loc[df["is_lag"], "date"]), + lags = set(zip(pd.to_datetime(df.loc[df["is_lag"], "date"]), df.loc[df["is_lag"], "isin"].astype(str))) print(f"[Broken months] Loaded {len(broken)} flagged (isin, month) pairs " f"({len(lags)} likely lags)") @@ -276,11 +288,69 @@ def score_propagation(panel, monthly_flows, monthly_flows_lag, weights, universe flows_idx = monthly_flows.set_index(['date', 'reg_id', 'isin'])['qty_net_month'] flows_idx_lag = monthly_flows_lag.set_index(['date', 'reg_id', 'isin'])['qty_net_month'] + # ── Pré-calcul des AUM agrégés par (isin, mois) pour détection de symétrie ── + # Pour chaque (isin, t), on calcule la somme des variations de stock par compte. + # Une rupture symétrique = un compte perd X parts sur un ISIN, un autre en gagne X. + # On détecte cela via le résidu net agrégé : si faible → symétrie probable. + # Structure : {(t_curr, isin) → {reg_id → delta_qty}} + # Calculé à la volée dans la boucle, pas en pré-calcul (trop mémoire pour 400 comptes). + # 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] + # ── Détection de ruptures symétriques à ce pas de temps ────── + # Pour chaque ISIN, calculer la variation de stock par compte. + # Si la somme des variations positives ≈ somme des variations négatives + # → il y a probablement compensation (cas 1 ou 3, pas de perte nette). + # On stocke pour chaque (reg_id, isin) si sa rupture est symétrique. + symmetric_breaks = set() # ensemble de (reg_id, isin) à atténuer + + for reg in panel.columns.get_level_values(0): + for isin in panel[reg].columns: + q_t = panel[reg][isin].get(t_curr, np.nan) + q_prev = panel[reg][isin].get(t_prev, np.nan) + if pd.isna(q_t) or pd.isna(q_prev): + continue + try: + f = flows_idx.loc[(t_curr, reg, isin)] + except KeyError: + f = 0.0 + residual = (q_t - q_prev) - f + if abs(residual) < ALPHA * max(abs(q_t), abs(q_prev), 1e-9): + continue # pas de rupture sur ce compte/ISIN + + # Agrégation par ISIN : si le résidu net agrégé est petit, + # les ruptures individuelles se compensent → symétrie. + isin_residuals = {} + isin_total_abs = {} + for reg in panel.columns.get_level_values(0): + for isin in panel[reg].columns: + q_t = panel[reg][isin].get(t_curr, np.nan) + q_prev = panel[reg][isin].get(t_prev, np.nan) + if pd.isna(q_t) or pd.isna(q_prev): + continue + try: + f = flows_idx.loc[(t_curr, reg, isin)] + except KeyError: + f = 0.0 + residual = (q_t - q_prev) - f + denom = max(abs(q_t), abs(q_prev), 1e-9) + err = abs(residual) / denom + if err < ALPHA: + continue + isin_residuals[isin] = isin_residuals.get(isin, 0.0) + residual + isin_total_abs[isin] = isin_total_abs.get(isin, 0.0) + abs(residual) + + # Un ISIN est "symétrique" si le résidu net < 20% du résidu brut total + # (les erreurs individuelles s'annulent en grande partie) + symmetric_isins = set() + for isin, net in isin_residuals.items(): + total = isin_total_abs.get(isin, 0.0) + if total > 0 and abs(net) / total < 0.20: + symmetric_isins.add(isin) + errors_at_t = {} new_scores = {} @@ -335,12 +405,15 @@ def score_propagation(panel, monthly_flows, monthly_flows_lag, weights, universe qty_t_prev, qty_t, net_flow, alpha=ALPHA ) - # ── Attenuation on broken / lag months ────────────── - # If this (isin, month) is flagged as broken at market - # level, the error is not the account's fault — attenuate. + # ── Attenuation on broken / lag / symmetric months ─── + # Priority: symmetric > broken > lag if err_ratio > 0: key = (t_curr, isin) - if key in broken_months or key in lag_months: + if isin in symmetric_isins: + # Rupture compensée à l'agrégé → cas 1 ou 3, + # pas de perte nette de données → atténuation forte + err_ratio = err_ratio * SYMMETRY_ATTENUATION + elif key in broken_months or key in lag_months: # Try lag-window flow to distinguish lag vs genuine gap try: net_flow_lag = flows_idx_lag.loc[(t_curr, reg_curr, isin)] @@ -590,66 +663,84 @@ def run_surgery_pass(scores_history, errors_history, panel, monthly_flows, # ── Candidats disponibles ── # On exclut les codes déjà mappés à un autre compte, - # mais reg_curr lui-même est un candidat valide (self-mapping : - # le compte existait déjà sous ce code à t-1, dormant ou partiel). + # mais reg_curr lui-même est un candidat valide (self-mapping). available = (all_regs_in_panel - set(mapping_inv.keys())) | {reg_curr} - best_candidate = None + best_candidate = None best_score_after = score_prev_no_surgery # baseline = pas de chirurgie - best_composite = 0.0 + best_composite = 0.0 + best_lookback = 0 # nombre de mois remontés pour trouver ce candidat - 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 + # ── Fenêtre de recherche étendue : jusqu'à MAX_SURGERY_LOOKBACK mois ── + # On cherche d'abord à t-1 (k=1), puis t-2 … t-MAX si rien trouvé. + # La confiance décroît avec la distance : confidence(k) = 1 - (k-1)/MAX + for k in range(1, MAX_SURGERY_LOOKBACK + 1): + if i - (k - 1) < 0: + break # on a atteint le début de l'historique + t_lookup = all_months[i - (k - 1)] # date candidate = t_prev - (k-1) + confidence = 1.0 - (k - 1) / MAX_SURGERY_LOOKBACK - # 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 + for j in available: + # Pré-filtre rapide : overlap ISIN minimal + isin_j = reg_isin_at_date.get(j, {}).get(t_lookup, 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 - if score_after > best_score_after: - best_score_after = score_after - best_candidate = j - best_composite = composite + # Score après chirurgie avec ce candidat à t_lookup + # (on utilise t_curr comme référence de stock, t_lookup comme prior) + score_after_raw = _recompute_score_with_candidate( + reg_curr, j, t_lookup, t_curr, panel, flows_idx, score_curr + ) + # Appliquer le facteur de confiance lié à la distance temporelle + score_after = score_curr * confidence * (score_after_raw / score_curr) if score_curr > 0 else score_after_raw + composite = jac * confidence * (score_after_raw / 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 + best_lookback = k + + # Si on a trouvé un bon candidat à cette distance, on s'arrête + if best_candidate is not None: + break if best_candidate: + lookback_note = f", lookback={best_lookback}m" if best_lookback > 1 else "" 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), + '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), + 'gain_vs_no_surgery': round(best_score_after - score_prev_no_surgery, 6), + 'lookback_months': best_lookback, }) 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})") + f"score {score_curr:.4f}→{best_score_after:.4f}" + f"{lookback_note})") # Mise à jour mapping - # Si self-mapping (best_candidate == reg_curr), on ne touche pas - # mapping_inv car le code ne change pas — on met juste à jour le score. if best_candidate != reg_curr: 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 + new_scores[reg_orig] = best_score_after else: new_mapping[reg_orig] = reg_curr - new_scores[reg_orig] = score_prev_no_surgery + new_scores[reg_orig] = score_prev_no_surgery else: new_mapping[reg_orig] = reg_curr new_scores[reg_orig] = score_prev_no_surgery @@ -681,7 +772,7 @@ def export_results(scores_history, mapping_history, surgery_log, all_months, out 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) + df_scores.to_csv(f"/mnt/user-data/outputs/{out_prefix}_scores.csv", index=False) # Mapping history rows_m = [] @@ -710,13 +801,13 @@ def export_results(scores_history, mapping_history, surgery_log, all_months, out # ───────────────────────────────────────────── # 8. PIPELINE PRINCIPAL # ───────────────────────────────────────────── -def run_pipeline(broken_months_path=None): +def run_pipeline(aum_path, flows_path, broken_months_path=None): print("=" * 60) print("CARMIGNAC — Pipeline de réparation des Registrar IDs") print("=" * 60) # Chargement - aum, flows = load_data() + aum, flows = load_data(aum_path, flows_path) # Broken months (optional — produced by carmignac_diagnostics.py) broken_months, lag_months = load_broken_months(broken_months_path) diff --git a/repair_challenge/push_s3.ipynb b/repair_challenge/push_s3.ipynb new file mode 100644 index 0000000..2e0a118 --- /dev/null +++ b/repair_challenge/push_s3.ipynb @@ -0,0 +1,44 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "5c8fc6c5", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import s3fs\n", + "\n", + "def push_file(local_path, s3_path):\n", + " fs = s3fs.S3FileSystem(\n", + " client_kwargs={'endpoint_url': 'https://' + 'minio-simple.lab.groupe-genes.fr'},\n", + " key=os.environ[\"AWS_ACCESS_KEY_ID\"],\n", + " secret=os.environ[\"AWS_SECRET_ACCESS_KEY\"],\n", + " token=os.environ[\"AWS_SESSION_TOKEN\"]\n", + " )\n", + "\n", + " with open(local_path, 'rb') as local_f, fs.open(s3_path, 'wb') as s3_f:\n", + " s3_f.write(local_f.read())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d43b725e", + "metadata": {}, + "outputs": [], + "source": [ + "push_file('AUM_repaired.csv', 'projet-bdc-carmignac-g3//paco/AUM_repaired.csv')\n", + "push_file('AUM_paths.csv', 'projet-bdc-carmignac-g3//paco/AUM_paths.csv')" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/repair_challenge/repair_results/carmignac_report_0.1.html b/repair_challenge/repair_results/carmignac_report_0.1.html deleted file mode 100644 index bbaa54b..0000000 --- a/repair_challenge/repair_results/carmignac_report_0.1.html +++ /dev/null @@ -1,10009 +0,0 @@ - - - - - -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 -
-
- - -
- - - - -
-
- 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 -
-
-
- -
-
-
-
- - - -
-
- Score explorer — per Registrar Account - - Click an account to inspect its full history. - ◆ remapped = surgery was applied. - -
-
- - -
- - -
- -
- -
-
- - - -
- -
-
- - - -
-
-
- 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. - -
-
-
- -
-
-
- - - -
-
- 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%
-
-
- - - -
-
- 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