final commit on this branch, cleaned the code
This commit is contained in:
parent
c2efab321b
commit
6b8356c3fd
|
|
@ -1,101 +0,0 @@
|
|||
greement - Code,Company - Id,Company - Ultimate Parent Id,Registrar Account - ID,Registrar Account - Region,RegistrarAccount - Country,Product - Asset Type,Product - Strategy,Product - Legal Status,Product - Is Dedie ?,Product - Fund,Product - Shareclass Type,Product - Shareclass Currency,Product - Isin,Centralisation Date,Quantity - AUM,Value - AUM CCY,Value - AUM €
|
||||
088,A82.0,82.0,406321,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Profil Réactif 100,A,EUR,FR0010149211,2020-02-29,0.0,0.0,0.0
|
||||
088,82.0,82.0,406321,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Profil Réactif 100,A,EUR,FR0010149211,2021-01-31,0.0,0.0,0.0
|
||||
088,82.0,82.0,406321,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Profil Réactif 100,A,EUR,FR0010149211,2021-05-31,0.0,0.0,0.0
|
||||
088,82.0,82.0,406321,France,France,Equity,Large Cap Emerging Markets Strategy,FCP,NO,Carmignac Emergents,A,EUR,FR0010149302,2015-03-31,0.0,0.0,0.0
|
||||
088,82.0,82.0,403909,France,France,Equity,Investissement,FCP,NO,Carmignac Investissement,A,EUR,FR0010148981,2020-09-30,0.0,0.0,0.0
|
||||
088,82.0,82.0,403909,France,France,Equity,Investissement,FCP,NO,Carmignac Investissement,A,EUR,FR0010148981,2020-12-31,0.0,0.0,0.0
|
||||
088,82.0,82.0,403909,France,France,Equity,Investissement,FCP,NO,Carmignac Investissement,A,EUR,FR0010148981,2021-04-30,0.0,0.0,0.0
|
||||
088,82.0,82.0,403909,France,France,Equity,Investissement,FCP,NO,Carmignac Investissement,A,EUR,FR0010148981,2021-07-31,0.0,0.0,0.0
|
||||
088,82.0,82.0,404045,France,France,,Innovation,FCP,NO,Carmignac Innovation,A,EUR,FR0010149096,2015-02-28,0.0,0.0,0.0
|
||||
088,82.0,82.0,404045,France,France,,Innovation,FCP,NO,Carmignac Innovation,A,EUR,FR0010149096,2016-04-30,0.0,0.0,0.0
|
||||
089,151.0,877.0,403798,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Profil Réactif 75,A,EUR,FR0010148999,2017-06-30,11.93,2697.7309,2697.7309
|
||||
089,151.0,877.0,403798,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Profil Réactif 75,A,EUR,FR0010148999,2018-01-31,11.93,2829.3188,2829.3188
|
||||
092,78.0,78.0,411743,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2016-10-31,0.0,0.0,0.0
|
||||
092,78.0,78.0,411743,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2017-12-31,0.0,0.0,0.0
|
||||
092,78.0,78.0,411743,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2018-04-30,0.0,0.0,0.0
|
||||
092,78.0,78.0,411743,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2018-12-31,0.0,0.0,0.0
|
||||
092,78.0,78.0,411743,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2019-01-31,0.0,0.0,0.0
|
||||
092,78.0,78.0,411743,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2019-03-31,0.0,0.0,0.0
|
||||
092,78.0,78.0,411743,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2019-04-30,0.0,0.0,0.0
|
||||
092,78.0,78.0,411743,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2019-10-31,0.0,0.0,0.0
|
||||
1015,16161.0,16161.0,416596,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2018-04-30,0.0,0.0,0.0
|
||||
1015,16161.0,16161.0,416596,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2018-12-31,0.0,0.0,0.0
|
||||
1015,16161.0,16161.0,416596,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2019-01-31,0.0,0.0,0.0
|
||||
1015,16161.0,16161.0,416596,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2019-03-31,0.0,0.0,0.0
|
||||
1015,16161.0,16161.0,416596,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2019-04-30,0.0,0.0,0.0
|
||||
1015,16161.0,16161.0,416596,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2019-10-31,0.0,0.0,0.0
|
||||
1015,16161.0,16161.0,416596,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2020-01-31,0.0,0.0,0.0
|
||||
1015,16161.0,16161.0,416596,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2020-06-30,0.0,0.0,0.0
|
||||
1015,16161.0,16161.0,416596,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2021-06-30,0.0,0.0,0.0
|
||||
1015,16161.0,16161.0,416596,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Profil Réactif 100,A,EUR,FR0010149211,2016-10-31,0.0,0.0,0.0
|
||||
250,96.0,96.0,416079,France,France,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2016-07-31,0.0,0.0,0.0
|
||||
250,96.0,96.0,416079,France,France,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2017-06-30,0.0,0.0,0.0
|
||||
250,96.0,96.0,416070,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Multi Expertise,A,EUR,FR0010149203,2018-11-30,0.0,0.0,0.0
|
||||
250,96.0,96.0,416070,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Multi Expertise,A,EUR,FR0010149203,2019-05-31,0.0,0.0,0.0
|
||||
250,96.0,96.0,416070,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Multi Expertise,A,EUR,FR0010149203,2020-02-29,0.0,0.0,0.0
|
||||
250,96.0,96.0,416070,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Multi Expertise,A,EUR,FR0010149203,2020-06-30,0.0,0.0,0.0
|
||||
250,96.0,96.0,416070,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Multi Expertise,A,EUR,FR0010149203,2020-10-31,0.0,0.0,0.0
|
||||
250,96.0,96.0,416070,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Multi Expertise,A,EUR,FR0010149203,2021-07-31,0.0,0.0,0.0
|
||||
250,96.0,96.0,416070,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Profil Réactif 75,A,EUR,FR0010148999,2015-03-31,0.0,0.0,0.0
|
||||
250,96.0,96.0,416070,France,France,Diversified,Multi Expertise,FCP,NO,Carmignac Profil Réactif 75,A,EUR,FR0010148999,2015-04-30,0.0,0.0,0.0
|
||||
L003,32787.0,32787.0,365124,Germany,Germany,Equity,Grande Europe,SICAV,NO,Carmignac Portfolio Grande Europe,A,EUR,LU0099161993,2021-04-30,0.0,0.0,0.0
|
||||
L003,32787.0,32787.0,365136,Germany,Germany,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,A,EUR,LU0592698954,2015-06-30,0.0,0.0,0.0
|
||||
L003,32787.0,32787.0,365136,Germany,Germany,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,A,EUR,LU0592698954,2015-09-30,0.0,0.0,0.0
|
||||
L003,32787.0,32787.0,365136,Germany,Germany,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,A,EUR,LU0592698954,2017-03-31,0.0,0.0,0.0
|
||||
L003,32787.0,32787.0,365136,Germany,Germany,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,A,EUR,LU0592698954,2017-10-31,0.0,0.0,0.0
|
||||
L003,32787.0,32787.0,365136,Germany,Germany,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,A,EUR,LU0592698954,2019-09-30,0.0,0.0,0.0
|
||||
L003,32787.0,32787.0,365136,Germany,Germany,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,A,EUR,LU0592698954,2019-11-30,0.0,0.0,0.0
|
||||
L003,32787.0,32787.0,365136,Germany,Germany,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,E,EUR,LU0592699093,2015-03-31,0.0,0.0,0.0
|
||||
L003,32787.0,32787.0,365136,Germany,Germany,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,E,EUR,LU0592699093,2015-10-31,0.0,0.0,0.0
|
||||
L003,32787.0,32787.0,365136,Germany,Germany,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,E,EUR,LU0592699093,2016-04-30,0.0,0.0,0.0
|
||||
L006,32780.0,32780.0,364871,United Kingdom,United Kingdom,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2016-12-31,0.0,0.0,0.0
|
||||
L006,32780.0,32780.0,364871,United Kingdom,United Kingdom,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2017-11-30,0.0,0.0,0.0
|
||||
L006,32780.0,32780.0,364871,United Kingdom,United Kingdom,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2018-07-31,0.0,0.0,0.0
|
||||
L006,32780.0,32780.0,364871,United Kingdom,United Kingdom,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2018-11-30,0.0,0.0,0.0
|
||||
L006,32780.0,32780.0,364871,United Kingdom,United Kingdom,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2019-05-31,0.0,0.0,0.0
|
||||
L006,32780.0,32780.0,364871,United Kingdom,United Kingdom,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2019-12-31,0.0,0.0,0.0
|
||||
L006,32780.0,32780.0,365116,France,France,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,A,EUR,LU0592698954,2015-03-31,47.0,5371.63,5371.63
|
||||
L001,32783.0,32783.0,419628,Switzerland,Switzerland,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2017-11-30,0.0,0.0,0.0
|
||||
L001,32783.0,32783.0,419628,Switzerland,Switzerland,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2018-06-30,0.0,0.0,0.0
|
||||
L001,32783.0,32783.0,419628,Switzerland,Switzerland,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2018-08-31,0.0,0.0,0.0
|
||||
L198_IT127,26494.0,26494.0,200101618,Italy,Italy,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,A,EUR,LU0336083497,2024-06-30,0.0,0.0,0.0
|
||||
L198_IT127,26494.0,26494.0,200101618,Italy,Italy,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,A,EUR,LU0336083497,2024-07-31,0.0,0.0,0.0
|
||||
L198_IT127,26494.0,26494.0,200101618,Italy,Italy,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,A,EUR,LU0336083497,2025-03-31,0.0,0.0,0.0
|
||||
L198_IT127,26494.0,26494.0,200101618,Italy,Italy,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,A,EUR,LU0336083497,2025-05-31,0.0,0.0,0.0
|
||||
L198_IT127,26494.0,26494.0,200101618,Italy,Italy,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,A,EUR,LU0336083497,2025-07-31,0.0,0.0,0.0
|
||||
L198_IT127,26494.0,26494.0,200101618,Italy,Italy,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,A,EUR,LU0336083497,2025-08-31,0.0,0.0,0.0
|
||||
L198_IT127,26494.0,26494.0,200101618,Italy,Italy,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2021-12-31,28.553,51375.1274,51375.1274
|
||||
L198_UK016,16278.0,18150.0,200102165,United Kingdom,United Kingdom,Equity,Asia Discovery,SICAV,NO,Carmignac Portfolio Asia Discovery,FW & FW-R,GBP,LU0992630086,2020-03-31,30.0,3588.6,4055.2974
|
||||
L198_UK016,16278.0,18150.0,200102165,United Kingdom,United Kingdom,Equity,Asia Discovery,SICAV,NO,Carmignac Portfolio Asia Discovery,FW & FW-R,GBP,LU0992630086,2020-08-31,0.0,0.0,0.0
|
||||
L198_UK016,16278.0,18150.0,200102165,United Kingdom,United Kingdom,Equity,Asia Discovery,SICAV,NO,Carmignac Portfolio Asia Discovery,FW & FW-R,GBP,LU0992630086,2020-10-31,0.0,0.0,0.0
|
||||
L239_11,223.0,18712.0,200055274,Luxembourg,Luxembourg,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2022-05-31,808.0,514114.24,514114.24
|
||||
L239_11,223.0,18712.0,200055274,Luxembourg,Luxembourg,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2023-06-30,478.0,307922.82,307922.82
|
||||
L239_11,223.0,18712.0,200055274,Luxembourg,Luxembourg,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2024-02-29,390.0,263456.7,263456.7
|
||||
L239_17,6093.0,18712.0,422737,Switzerland,Switzerland,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,F,GBP,LU0553415323,2019-04-30,0.0,0.0,0.0
|
||||
L239_17,6093.0,18712.0,422737,Switzerland,Switzerland,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,F,GBP,LU0553415323,2020-06-30,0.0,0.0,0.0
|
||||
L239_17,6093.0,18712.0,422737,Switzerland,Switzerland,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,F,GBP,LU0553415323,2020-12-31,0.0,0.0,0.0
|
||||
L239_17,6093.0,18712.0,422737,Switzerland,Switzerland,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,F,GBP,LU0553415323,2021-03-31,0.0,0.0,0.0
|
||||
L239_17,6093.0,18712.0,422737,Switzerland,Switzerland,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,F,GBP,LU0553415323,2021-06-30,0.0,0.0,0.0
|
||||
L239_17,6093.0,18712.0,422737,Switzerland,Switzerland,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,USD,LU0807690754,2015-11-30,0.0,0.0,0.0
|
||||
L239_17,6093.0,18712.0,422737,Switzerland,Switzerland,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,USD,LU0807690754,2015-12-31,0.0,0.0,0.0
|
||||
L298,4437.0,27425.0,419097,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2022-03-31,8167.095,5365699.7441,5365699.7441
|
||||
L298,4437.0,27425.0,419097,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2022-05-31,8048.015,5120790.9842,5120790.9842
|
||||
L298,4437.0,27425.0,419097,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2022-06-30,7980.46,4973183.2582,4973183.2582
|
||||
L298,4437.0,27425.0,419097,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2022-09-30,9882.673,6095039.746,6095039.746
|
||||
L298,4437.0,27425.0,419097,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2022-10-31,9824.661,6039022.6235,6039022.6235
|
||||
L298,4437.0,27425.0,419097,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2024-12-31,7620.222,5363950.468,5363950.468
|
||||
L298,4437.0,27425.0,419097,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2025-10-31,5649.767,4494559.1415,4494559.1415
|
||||
L298,4437.0,27425.0,419097,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,E,EUR,FR0010306142,2015-08-31,25638.27,4017773.2917,4017773.2917
|
||||
L298,4437.0,27425.0,419097,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,E,EUR,FR0010306142,2015-11-30,26389.703,4204671.379,4204671.379
|
||||
L377,3138.0,16622.0,365538,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2023-03-31,139932.286,89979258.5437,89979258.5437
|
||||
L652,101.0,33676.0,364535,Luxembourg,Luxembourg,Equity,Grande Europe,SICAV,NO,Carmignac Portfolio Grande Europe,A,EUR,LU0099161993,2019-04-30,0.0,0.0,0.0
|
||||
L652,101.0,33676.0,364535,Luxembourg,Luxembourg,Equity,Grande Europe,SICAV,NO,Carmignac Portfolio Grande Europe,A,EUR,LU0099161993,2020-01-31,0.0,0.0,0.0
|
||||
L652,101.0,33676.0,364535,Luxembourg,Luxembourg,Equity,Grande Europe,SICAV,NO,Carmignac Portfolio Grande Europe,A,EUR,LU0099161993,2020-11-30,0.0,0.0,0.0
|
||||
L652,101.0,33676.0,364535,Luxembourg,Luxembourg,Equity,Grande Europe,SICAV,NO,Carmignac Portfolio Grande Europe,A,EUR,LU0099161993,2021-06-30,0.0,0.0,0.0
|
||||
L652,101.0,33676.0,364535,Luxembourg,Luxembourg,Equity,Investissement,FCP,NO,Carmignac Investissement,A,EUR,FR0010148981,2015-08-31,0.0,0.0,0.0
|
||||
L652,101.0,33676.0,364535,Luxembourg,Luxembourg,Equity,Investissement,FCP,NO,Carmignac Investissement,A,EUR,FR0010148981,2017-03-31,0.0,0.0,0.0
|
||||
L652,101.0,33676.0,364535,Luxembourg,Luxembourg,Equity,Investissement,FCP,NO,Carmignac Investissement,A,EUR,FR0010148981,2017-11-30,0.0,0.0,0.0
|
||||
L652,101.0,33676.0,364535,Luxembourg,Luxembourg,Equity,Investissement,FCP,NO,Carmignac Investissement,A,EUR,FR0010148981,2018-02-28,0.0,0.0,0.0
|
||||
L503_16,9390.0,19105.0,200037779,Belgium,Belgium,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,A,USD,LU0592699259,2017-04-30,65.0,6820.45,6263.327
|
||||
L503_16,9390.0,19105.0,200037779,Belgium,Belgium,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,A,USD,LU0592699259,2017-09-30,65.0,6857.5,5800.6259
|
||||
|
File diff suppressed because it is too large
Load Diff
|
|
@ -1,101 +0,0 @@
|
|||
Agreement - Code,Company - Id,Company - Ultimate Parent Id,Registrar Account - ID,Registrar Account - Region,RegistrarAccount - Country,Product - Asset Type,Product - Strategy,Product - Legal Status,Product - Is Dedie ?,Product - Fund,Product - Shareclass Type,Product - Shareclass Currency,Product - Isin,Centralisation Date,Quantity - Subscription,Quantity - Redemption,Quantity - NetFlows,Value Ccy - Subscription,Value Ccy - Redemption,Value Ccy - NetFlows,Value € - Subscription,Value € - Redemption,Value € - NetFlows
|
||||
404,11,16646,416348,France,France,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,A,EUR,LU0336083497,2016-11-16,2451.924,0.0,2451.924,3409768.11,0.0,3409768.11,3409768.11,0.0,3409768.11
|
||||
404,11,16646,416348,France,France,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,A,EUR,LU0336083497,2016-11-28,78.922,0.0,78.922,109802.16,0.0,109802.16,109802.16,0.0,109802.16
|
||||
404,11,16646,416348,France,France,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,A,EUR,LU0336083497,2016-11-29,7.799,0.0,7.799,10849.58,0.0,10849.58,10849.58,0.0,10849.58
|
||||
404,11,16646,416348,France,France,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,A,EUR,LU0336083497,2016-12-12,28.552,0.0,28.552,39850.03,0.0,39850.03,39850.03,0.0,39850.03
|
||||
404,11,16646,416348,France,France,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,A,EUR,LU0336083497,2016-12-27,51.164,0.0,51.164,71781.56,0.0,71781.56,71781.56,0.0,71781.56
|
||||
404,11,16646,416348,France,France,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,A,EUR,LU0336083497,2016-12-29,19.595,0.0,19.595,27491.98,0.0,27491.98,27491.98,0.0,27491.98
|
||||
404,11,16646,416348,France,France,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,A,EUR,LU0336083497,2017-01-11,8.953,0.0,8.953,12595.8,0.0,12595.8,12595.8,0.0,12595.8
|
||||
415,966,966,200043690,France,France,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,A,EUR,LU0592698954,2020-11-03,235.05,0.0,235.05,31200.54,0.0,31200.54,31200.54,0.0,31200.54
|
||||
415,966,966,200043690,France,France,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,A,EUR,LU0592698954,2020-11-23,255.42,0.0,255.42,35968.24,0.0,35968.24,35968.24,0.0,35968.24
|
||||
415,966,966,200043690,France,France,Diversified,Emerging Patrimoine,SICAV,NO,Carmignac Portfolio Emerging Patrimoine,A,EUR,LU0592698954,2020-12-03,397.976,0.0,397.976,56325.54,0.0,56325.54,56325.54,0.0,56325.54
|
||||
424,14063,29557,200127433,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2023-09-01,0.0,-26.18,-26.18,0.0,-10454.72,-10454.72,0.0,-10454.72,-10454.72
|
||||
424,14063,29557,200127433,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2023-09-07,0.0,-10.406,-10.406,0.0,-4140.86,-4140.86,0.0,-4140.86,-4140.86
|
||||
424,14063,29557,200127433,France,France,Alternative,Absolute Return Europe,FCP,NO,Carmignac Absolute Return Europe,A,EUR,FR0010149179,2024-04-24,0.0,-8.527,-8.527,0.0,-3623.89,-3623.89,0.0,-3623.89,-3623.89
|
||||
424,14063,29557,200127433,France,France,Equity,Large Cap Emerging Markets Strategy,FCP,NO,Carmignac Emergents,A,EUR,FR0010149302,2025-06-10,0.0,-15.22,-15.22,0.0,-19461.51,-19461.51,0.0,-19461.51,-19461.51
|
||||
424,14063,29557,200127433,France,France,Equity,Large Cap Emerging Markets Strategy,FCP,NO,Carmignac Emergents,A,EUR,FR0010149302,2025-06-11,0.0,-5.251,-5.251,0.0,-6687.52,-6687.52,0.0,-6687.52,-6687.52
|
||||
424,14063,29557,200127433,France,France,Equity,Large Cap Emerging Markets Strategy,FCP,NO,Carmignac Emergents,A,EUR,FR0010149302,2025-06-16,0.0,-10.718,-10.718,0.0,-13493.32,-13493.32,0.0,-13493.32,-13493.32
|
||||
424,14063,29557,200127433,France,France,Equity,Large Cap Emerging Markets Strategy,FCP,NO,Carmignac Emergents,A,EUR,FR0010149302,2025-06-17,7.399,0.0,7.399,9336.8,0.0,9336.8,9336.8,0.0,9336.8
|
||||
424,14063,29557,200127433,France,France,Equity,Large Cap Emerging Markets Strategy,FCP,NO,Carmignac Emergents,A,EUR,FR0010149302,2025-09-12,13.247,0.0,13.247,18457.31,0.0,18457.31,18457.31,0.0,18457.31
|
||||
424,14063,29557,200127433,France,France,Equity,Large Cap Emerging Markets Strategy,FCP,NO,Carmignac Emergents,A,EUR,FR0010149302,2025-09-30,4.248,0.0,4.248,5985.39,0.0,5985.39,5985.39,0.0,5985.39
|
||||
424,14063,29557,200127433,France,France,Equity,Large Cap Emerging Markets Strategy,FCP,NO,Carmignac Emergents,A,EUR,FR0010149302,2025-10-01,3.725,0.0,3.725,5302.31,0.0,5302.31,5302.31,0.0,5302.31
|
||||
424_A,7878,29557,200127434,France,France,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2021-06-28,41.471,0.0,41.471,74827.37,0.0,74827.37,74827.37,0.0,74827.37
|
||||
424_A,7878,29557,200127434,France,France,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2021-07-05,0.0,-12.257,-12.257,0.0,-22133.08,-22133.08,0.0,-22133.08,-22133.08
|
||||
424_A,7878,29557,200127434,France,France,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2021-07-12,6.072,0.0,6.072,10962.87,0.0,10962.87,10962.87,0.0,10962.87
|
||||
424_A,7878,29557,200127434,France,France,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2021-07-15,0.0,-17.184,-17.184,0.0,-31056.82,-31056.82,0.0,-31056.82,-31056.82
|
||||
424_A,7878,29557,200054226,France,France,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2018-04-18,4.0,0.0,4.0,1262.6,0.0,1262.6,1262.6,0.0,1262.6
|
||||
424_A,7878,29557,200054226,France,France,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2018-09-03,45.229,0.0,45.229,14465.14,0.0,14465.14,14465.14,0.0,14465.14
|
||||
424_A,7878,29557,200054226,France,France,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2018-11-20,7.597,0.0,7.597,2127.01,0.0,2127.01,2127.01,0.0,2127.01
|
||||
424_A,7878,29557,200054226,France,France,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2019-02-21,0.0,-35.963,-35.963,0.0,-10320.66,-10320.66,0.0,-10320.66,-10320.66
|
||||
424_A,7878,29557,200054226,France,France,Equity,Euro-Entrepreneurs,FCP,NO,Carmignac Euro-Entrepreneurs,A,EUR,FR0010149112,2017-04-04,0.0,-7.0,-7.0,0.0,-2279.34,-2279.34,0.0,-2279.34,-2279.34
|
||||
424_A,7878,29557,200054226,France,France,Equity,Euro-Entrepreneurs,FCP,NO,Carmignac Euro-Entrepreneurs,A,EUR,FR0010149112,2017-04-05,36.0,0.0,36.0,11727.36,0.0,11727.36,11727.36,0.0,11727.36
|
||||
900_12,118,16994,406161,France,France,Equity,Asia Discovery,SICAV,NO,Carmignac Portfolio Asia Discovery,A,EUR,LU0336083810,2019-06-20,0.0,-5.805,-5.805,0.0,-9091.27,-9091.27,0.0,-9091.27,-9091.27
|
||||
900_12,118,16994,406161,France,France,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2017-08-18,2.85,0.0,2.85,760.92,0.0,760.92,760.92,0.0,760.92
|
||||
900_12,118,16994,406161,France,France,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2017-08-23,0.0,-60.236,-60.236,0.0,-16248.66,-16248.66,0.0,-16248.66,-16248.66
|
||||
900_12,118,16994,406161,France,France,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2017-10-06,0.0,-19.396,-19.396,0.0,-5672.17,-5672.17,0.0,-5672.17,-5672.17
|
||||
900_12,118,16994,406161,France,France,Equity,Climate Transition,SICAV,NO,Carmignac Portfolio Climate Transition,A,EUR,LU0164455502,2017-10-18,14.575,0.0,14.575,4281.11,0.0,4281.11,4281.11,0.0,4281.11
|
||||
900_12,118,16994,406161,France,France,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0011269588,2018-01-03,231.299,-717.459,-486.16,25847.66,-80176.04,-54328.38,25847.66,-80176.04,-54328.38
|
||||
900_12,118,16994,406161,France,France,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0011269588,2018-01-16,79.5,0.0,79.5,8969.19,0.0,8969.19,8969.19,0.0,8969.19
|
||||
900_12,118,16994,406161,France,France,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0011269588,2018-01-18,0.0,-481.151,-481.151,0.0,-54422.99,-54422.99,0.0,-54422.99,-54422.99
|
||||
900_12,118,16994,406161,France,France,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0011269588,2018-01-23,0.0,-309.151,-309.151,0.0,-35311.23,-35311.23,0.0,-35311.23,-35311.23
|
||||
900_12,118,16994,406161,France,France,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0011269588,2018-01-26,168.784,0.0,168.784,19344.33,0.0,19344.33,19344.33,0.0,19344.33
|
||||
L206,86.0,16644.0,365192,Belgium,Belgium,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2016-03-18,0.0,-50.926,-50.926,0.0,-31194.21,-31194.21,0.0,-31194.21,-31194.21
|
||||
L206,86.0,16644.0,365192,Belgium,Belgium,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2016-04-08,137.938,0.0,137.938,85120.16,0.0,85120.16,85120.16,0.0,85120.16
|
||||
L206,86.0,16644.0,365192,Belgium,Belgium,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2016-04-28,0.0,-623.044,-623.044,0.0,-384654.9,-384654.9,0.0,-384654.9,-384654.9
|
||||
L206,86.0,16644.0,365192,Belgium,Belgium,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2016-05-24,0.0,-68.407,-68.407,0.0,-42365.82,-42365.82,0.0,-42365.82,-42365.82
|
||||
L206,86.0,16644.0,365192,Belgium,Belgium,Equity,Investissement Latitude,FCP,NO,Carmignac Investissement Latitude,A,EUR,FR0010147603,2015-02-24,0.0,-14.44,-14.44,0.0,-4103.28,-4103.28,0.0,-4103.28,-4103.28
|
||||
L206,86.0,16644.0,365192,Belgium,Belgium,Equity,Investissement Latitude,FCP,NO,Carmignac Investissement Latitude,A,EUR,FR0010147603,2015-02-27,21.42,0.0,21.42,6166.31,0.0,6166.31,6166.31,0.0,6166.31
|
||||
L206,86.0,16644.0,365192,Belgium,Belgium,Equity,Investissement Latitude,FCP,NO,Carmignac Investissement Latitude,A,EUR,FR0010147603,2015-03-11,0.0,-84.82,-84.82,0.0,-24948.07,-24948.07,0.0,-24948.07,-24948.07
|
||||
L206,86.0,16644.0,365192,Belgium,Belgium,Equity,Investissement Latitude,FCP,NO,Carmignac Investissement Latitude,A,EUR,FR0010147603,2015-04-01,5.572,0.0,5.572,1633.04,0.0,1633.04,1633.04,0.0,1633.04
|
||||
L206,86.0,16644.0,365192,Belgium,Belgium,Equity,Investissement Latitude,FCP,NO,Carmignac Investissement Latitude,A,EUR,FR0010147603,2015-04-08,0.0,-357.842,-357.842,0.0,-107323.97,-107323.97,0.0,-107323.97,-107323.97
|
||||
L206,86.0,16644.0,365192,Belgium,Belgium,Equity,Investissement Latitude,FCP,NO,Carmignac Investissement Latitude,A,EUR,FR0010147603,2015-04-22,0.0,-810.982,-810.982,0.0,-244665.16,-244665.16,0.0,-244665.16,-244665.16
|
||||
L209,2989.0,2989.0,200034372,Spain,Spain,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2018-06-25,79.651,-541.548,-461.897,138274.93,-940132.74,-801857.81,138274.93,-940132.74,-801857.81
|
||||
L217,8290.0,2203.0,364721,Germany,Germany,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,E,EUR,FR0010306142,2015-03-04,155.0,0.0,155.0,26804.15,0.0,26804.15,26804.15,0.0,26804.15
|
||||
L217,8290.0,2203.0,364721,Germany,Germany,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,E,EUR,FR0010306142,2015-03-05,14.0,-6.0,8.0,2433.06,-1042.74,1390.32,2433.06,-1042.74,1390.32
|
||||
L217,8290.0,2203.0,364721,Germany,Germany,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,USD,FR0011269067,2015-01-15,52.0,0.0,52.0,6009.64,0.0,6009.64,5226.9101,0.0,5226.9101
|
||||
L217,8290.0,2203.0,364721,Germany,Germany,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0011269588,2015-02-02,690.11,0.0,690.11,80128.56,0.0,80128.56,80128.56,0.0,80128.56
|
||||
L217,8290.0,2203.0,364721,Germany,Germany,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0011269588,2015-02-13,182.0,-100.0,82.0,21159.32,-11626.0,9533.32,21159.32,-11626.0,9533.32
|
||||
L217,8290.0,2203.0,364721,Germany,Germany,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0011269588,2015-02-18,414.0,-92.0,322.0,48235.14,-10718.92,37516.22,48235.14,-10718.92,37516.22
|
||||
L217,8290.0,2203.0,364721,Germany,Germany,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0011269588,2015-03-06,840.0,0.0,840.0,103017.6,0.0,103017.6,103017.6,0.0,103017.6
|
||||
L217,8290.0,2203.0,364721,Germany,Germany,Equity,Asia Discovery,SICAV,NO,Carmignac Portfolio Asia Discovery,A,EUR,LU0336083810,2015-02-06,0.0,-4.0,-4.0,0.0,-5248.12,-5248.12,0.0,-5248.12,-5248.12
|
||||
L209,2989.0,2989.0,200102190,Spain,Spain,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2019-09-18,6.05,0.0,6.05,10642.29,0.0,10642.29,10642.29,0.0,10642.29
|
||||
L475,3395,28240,200130892,Belgium,Belgium,Diversified,Patrimoine,SICAV,NO,Carmignac Portfolio Patrimoine,A,EUR,LU1299305356,2021-08-19,0.0,-250.0,-250.0,0.0,-28002.5,-28002.5,0.0,-28002.5,-28002.5
|
||||
L475,3395,28240,200130892,Belgium,Belgium,Diversified,Patrimoine,SICAV,NO,Carmignac Portfolio Patrimoine,A,EUR,LU1299305356,2021-10-07,0.0,-22.3,-22.3,0.0,-2506.3,-2506.3,0.0,-2506.3,-2506.3
|
||||
L475,3395,28240,200130892,Belgium,Belgium,Diversified,Patrimoine,SICAV,NO,Carmignac Portfolio Patrimoine,A,EUR,LU1299305356,2021-10-12,0.0,-525.0,-525.0,0.0,-58731.75,-58731.75,0.0,-58731.75,-58731.75
|
||||
L475,3395,28240,200130892,Belgium,Belgium,Diversified,Patrimoine,SICAV,NO,Carmignac Portfolio Patrimoine,A,EUR,LU1299305356,2021-10-14,0.0,-22.0,-22.0,0.0,-2478.52,-2478.52,0.0,-2478.52,-2478.52
|
||||
L475,3395,28240,200130892,Belgium,Belgium,Diversified,Patrimoine,SICAV,NO,Carmignac Portfolio Patrimoine,A,EUR,LU1299305356,2021-10-26,0.0,-122.31,-122.31,0.0,-13880.96,-13880.96,0.0,-13880.96,-13880.96
|
||||
L479,5608,5608,418840,Netherlands,Netherlands,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,F,EUR,LU0992630599,2024-04-09,2.813,0.0,2.813,398.46,0.0,398.46,398.46,0.0,398.46
|
||||
L479,5608,5608,418840,Netherlands,Netherlands,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,F,EUR,LU0992630599,2024-04-18,0.0,-4.301,-4.301,0.0,-606.35,-606.35,0.0,-606.35,-606.35
|
||||
L479,5608,5608,418840,Netherlands,Netherlands,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,F,EUR,LU0992630599,2024-05-21,0.0,-4.268,-4.268,0.0,-602.47,-602.47,0.0,-602.47,-602.47
|
||||
L479,5608,5608,418840,Netherlands,Netherlands,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,F,EUR,LU0992630599,2024-05-28,0.0,-9.602,-9.602,0.0,-1345.53,-1345.53,0.0,-1345.53,-1345.53
|
||||
L479,5608,5608,418840,Netherlands,Netherlands,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,F,EUR,LU0992630599,2024-06-25,0.407,0.0,0.407,57.66,0.0,57.66,57.66,0.0,57.66
|
||||
Private Client,Private Client,Private Client,Private Client,France,France,Equity,Investissement Latitude,FCP,NO,Carmignac Investissement Latitude,A,EUR,FR0010147603,2019-03-01,0.0,-242.401,-242.401,0.0,-55664.97,-55664.97,0.0,-55664.97,-55664.97
|
||||
Private Client,Private Client,Private Client,Private Client,France,France,Equity,Investissement Latitude,FCP,NO,Carmignac Investissement Latitude,A,EUR,FR0010147603,2019-04-17,144.94327,-122.82715,22.11612,34354.45,-29419.56,4934.89,34354.45,-29419.56,4934.89
|
||||
Private Client,Private Client,Private Client,Private Client,France,France,Equity,Investissement Latitude,FCP,NO,Carmignac Investissement Latitude,A,EUR,FR0010147603,2019-05-02,0.0,-44.31849,-44.31849,0.0,-10565.97,-10565.97,0.0,-10565.97,-10565.97
|
||||
Private Client,Private Client,Private Client,Private Client,France,France,Equity,Investissement Latitude,FCP,NO,Carmignac Investissement Latitude,A,EUR,FR0010147603,2019-06-11,0.0,-239.05122,-239.05122,0.0,-54886.16,-54886.16,0.0,-54886.16,-54886.16
|
||||
Private Client,Private Client,Private Client,Private Client,France,France,Equity,Investissement Latitude,FCP,NO,Carmignac Investissement Latitude,A,EUR,FR0010147603,2019-07-01,0.0,-372.878,-372.878,0.0,-87492.09,-87492.09,0.0,-87492.09,-87492.09
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,E,EUR,FR0010306142,2019-12-10,0.0,-23.906,-23.906,0.0,-3685.11,-3685.11,0.0,-3685.11,-3685.11
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,E,EUR,FR0010306142,2020-01-03,135.405,0.0,135.405,21368.26,0.0,21368.26,21368.26,0.0,21368.26
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,E,EUR,FR0010306142,2020-02-04,2.321,0.0,2.321,372.03,0.0,372.03,372.03,0.0,372.03
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,E,EUR,FR0010306142,2020-02-07,0.0,-9.009,-9.009,0.0,-1451.26,-1451.26,0.0,-1451.26,-1451.26
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,E,EUR,FR0010306142,2020-02-14,1.356,-318.563,-317.207,222.33,-52231.59,-52009.26,222.33,-52231.59,-52009.26
|
||||
L846_T0,16266.0,16266.0,200130050,United Kingdom,United Kingdom,Equity,European Leaders,OEIC,NO,FP Carmignac European Leaders,OEIC,GBP,GB00BJHPXB21,2022-07-26,3.6807,0.0,3.6807,5.21,0.0,5.21,6.19,0.0,6.19
|
||||
L925,2348.0,16634.0,366131,Spain,Spain,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2018-12-24,0.0,-46.056,-46.056,0.0,-78298.43,-78298.43,0.0,-78298.43,-78298.43
|
||||
L925,2348.0,16634.0,366131,Spain,Spain,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2019-01-07,0.0,-7.993,-7.993,0.0,-13547.26,-13547.26,0.0,-13547.26,-13547.26
|
||||
L925,2348.0,16634.0,366131,Spain,Spain,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2019-01-08,0.0,-1.947,-1.947,0.0,-3301.29,-3301.29,0.0,-3301.29,-3301.29
|
||||
L925,2348.0,16634.0,366131,Spain,Spain,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2019-01-16,0.0,-25.051,-25.051,0.0,-42525.57,-42525.57,0.0,-42525.57,-42525.57
|
||||
L925,2348.0,16634.0,366131,Spain,Spain,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2019-02-26,0.0,-22.021,-22.021,0.0,-37524.66,-37524.66,0.0,-37524.66,-37524.66
|
||||
L925,2348.0,16634.0,366131,Spain,Spain,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2019-03-04,0.0,-1.877,-1.877,0.0,-3199.22,-3199.22,0.0,-3199.22,-3199.22
|
||||
L925,2348.0,16634.0,366131,Spain,Spain,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2019-03-18,0.0,-22.784,-22.784,0.0,-38882.04,-38882.04,0.0,-38882.04,-38882.04
|
||||
L925,2348.0,16634.0,366131,Spain,Spain,Fixed Income,Sécurité,FCP,NO,Carmignac Sécurité,AW & AW-R,EUR,FR0010149120,2019-03-27,0.0,-19.581,-19.581,0.0,-33477.24,-33477.24,0.0,-33477.24,-33477.24
|
||||
L861_T0,16929.0,16929.0,200001522,United Kingdom,United Kingdom,Fixed Income,Global Bond,SICAV,NO,Carmignac Portfolio Global Bond,FW & FW-R,GBP,LU0992630839,2023-09-13,194.085,0.0,194.085,27670.65,0.0,27670.65,32187.88,0.0,32187.88
|
||||
L198_IT235,13030.0,25509.0,200131580,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2023-08-04,0.0,-15.535,-15.535,0.0,-9982.79,-9982.79,0.0,-9982.79,-9982.79
|
||||
L198_IT235,13030.0,25509.0,200131580,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2023-08-09,0.0,-23.329,-23.329,0.0,-15055.6,-15055.6,0.0,-15055.6,-15055.6
|
||||
L198_IT235,13030.0,25509.0,200131580,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2023-08-16,0.0,-6.226,-6.226,0.0,-3971.69,-3971.69,0.0,-3971.69,-3971.69
|
||||
L198_IT235,13030.0,25509.0,200131580,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2023-09-14,0.0,-154.998,-154.998,0.0,-100985.85,-100985.85,0.0,-100985.85,-100985.85
|
||||
L198_IT235,13030.0,25509.0,200131580,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2023-10-19,0.0,-59.22,-59.22,0.0,-37171.21,-37171.21,0.0,-37171.21,-37171.21
|
||||
L198_IT235,13030.0,25509.0,200131580,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2023-11-20,0.0,-4.758,-4.758,0.0,-3012.53,-3012.53,0.0,-3012.53,-3012.53
|
||||
L198_IT235,13030.0,25509.0,200131580,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2023-12-12,0.0,-3.268,-3.268,0.0,-2094.27,-2094.27,0.0,-2094.27,-2094.27
|
||||
L198_IT235,13030.0,25509.0,200131580,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2024-01-09,0.0,-22.583,-22.583,0.0,-14730.21,-14730.21,0.0,-14730.21,-14730.21
|
||||
L198_IT235,13030.0,25509.0,200131580,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2024-01-10,0.0,-117.441,-117.441,0.0,-76479.93,-76479.93,0.0,-76479.93,-76479.93
|
||||
L198_IT235,13030.0,25509.0,200131580,Italy,Italy,Diversified,Patrimoine,FCP,NO,Carmignac Patrimoine,A,EUR,FR0010135103,2024-01-12,0.0,-0.167,-0.167,0.0,-109.45,-109.45,0.0,-109.45,-109.45
|
||||
|
2827
data/str_rates.csv
2827
data/str_rates.csv
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,89 +0,0 @@
|
|||
Agreement - Code,Company - Id,Company - Ultimate Parent Id,Registrar Account - ID,Registrar Account - Region,RegistrarAccount - Country,Product - Asset Type,Product - Strategy,Product - Legal Status,Product - Is Dedie ?,Product - Fund,Product - Shareclass Type,Product - Shareclass Currency,Product - Isin,Centralisation Date,Quantity - AUM,Value - AUM CCY,Value - AUM €
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Belgium,Belgium,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-08-31,1952.057468572,246564.3789,246564.3789
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Belgium,Belgium,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-10-31,1952.057468572,245920.1999,245920.1999
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Belgium,Belgium,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-04-30,1952.057468572,246076.3645,246076.3645
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2024-12-31,1655.0,206560.55,206560.55
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-01-31,1655.0,206726.05,206726.05
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-05-31,1655.0,207768.7,207768.7
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-01-31,158337.249595417,19858657.8442,19858657.8442
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2024-11-30,1655.0,202108.6,202108.6
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2024-12-31,156782.473642618,19638572.6485,19638572.6485
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-10-31,156782.473595417,19751456.0235,19751456.0235
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,International,Singapore,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-03-31,798.339453936,102195.4335,102195.4335
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,International,Singapore,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-08-31,798.339453936,100838.2564,100838.2564
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2024-09-30,833.0,99910.02,99910.02
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2024-10-31,1655.0,201181.8,201181.8
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-10-31,2855.384,356808.7846,356808.7846
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2024-05-31,109957.0,11857762.88,11857762.88
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2024-11-30,153189.946099906,18764236.4978,18764236.4978
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-02-28,156782.473595417,20079131.3934,20079131.3934
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-06-30,156782.473595417,19651115.2405,19651115.2405
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,International,Singapore,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-04-30,798.339453936,100638.6716,100638.6716
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2024-07-31,1000.0,116310.0,116310.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-01-31,1000.0,124910.0,124910.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2025-08-31,898577.51775,113822814.1734,113822814.1734
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2025-10-31,898577.51775,113544255.1429,113544255.1429
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2024-10-31,1000.0,121560.0,121560.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-03-31,1000.0,127370.0,127370.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-04-30,1000.0,125360.0,125360.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-08-31,1000.0,125360.0,125360.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2024-07-31,481103.43069,56183258.636,56183258.636
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2024-12-31,898577.51775,112690606.501,112690606.501
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2025-01-31,898577.51775,112852350.4542,112852350.4542
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2024-10-31,898577.51775,109608485.6151,109608485.6151
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2024-11-30,898577.51775,110183575.2265,110183575.2265
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Belgium,Belgium,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-03-31,1952.057468572,249882.8766,249882.8766
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2024-06-30,109957.0,12469123.8,12469123.8
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2024-07-31,109957.0,12822085.77,12822085.77
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-03-31,156782.473595417,20069724.4449,20069724.4449
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-05-31,156782.473595417,19803194.2398,19803194.2398
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-07-31,156782.473595417,19751456.0235,19751456.0235
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-08-31,156782.473595417,19803194.2398,19803194.2398
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,International,Singapore,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2024-12-31,798.339453936,100000.0,100000.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,International,Singapore,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-09-30,798.339453936,100574.8044,100574.8044
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,International,Singapore,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-10-31,798.339453936,100574.8044,100574.8044
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Belgium,Belgium,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-05-31,1952.057468572,246564.3789,246564.3789
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Belgium,Belgium,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-06-30,1952.057468572,244670.8831,244670.8831
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Belgium,Belgium,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-09-30,1952.057468572,245920.1999,245920.1999
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2025-07-31,898577.51775,113499326.267,113499326.267
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-09-30,2855.384,356808.7846,356808.7846
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2024-09-30,151557.15975,18215655.0304,18215655.0304
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-09-30,156782.473595417,19751456.0235,19751456.0235
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,International,Singapore,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-05-31,798.339453936,100838.2564,100838.2564
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,International,Singapore,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-06-30,798.339453936,100063.8672,100063.8672
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2024-05-31,1000.0,107810.0,107810.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2024-08-31,1000.0,119010.0,119010.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2024-05-31,481103.43069,51887004.9999,51887004.9999
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2025-04-30,898577.51775,113508312.0422,113508312.0422
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-03-31,1655.0,210797.35,210797.35
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-04-30,1655.0,207470.8,207470.8
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2024-06-30,1000.0,113260.0,113260.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2024-09-30,1000.0,119940.0,119940.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2024-11-30,1000.0,122120.0,122120.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-02-28,1000.0,127490.0,127490.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-05-31,1000.0,125540.0,125540.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-06-30,1000.0,124520.0,124520.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-07-31,1000.0,125090.0,125090.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-09-30,1000.0,124960.0,124960.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2024-09-30,898577.51775,108071918.0598,108071918.0598
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2025-06-30,898577.51775,112906265.1053,112906265.1053
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2025-09-30,898577.51775,113544255.1429,113544255.1429
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Belgium,Belgium,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-07-31,1952.057468572,245920.1999,245920.1999
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-07-31,1655.0,207023.95,207023.95
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-08-31,1655.0,207470.8,207470.8
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,International,Singapore,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-01-31,798.339453936,100127.7343,100127.7343
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,International,Singapore,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-07-31,798.339453936,100574.8044,100574.8044
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2024-12-31,1000.0,124810.0,124810.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-10-31,1000.0,124960.0,124960.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2024-06-30,481103.43069,54595617.3147,54595617.3147
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2025-03-31,898577.51775,115233580.8763,115233580.8763
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2025-05-31,898577.51775,113750927.972,113750927.972
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Belgium,Belgium,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-02-28,1952.057468572,250000.0,250000.0
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-02-28,1655.0,210995.95,210995.95
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,A,EUR,LU2799473124,2025-06-30,1655.0,206080.6,206080.6
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2024-08-31,109957.0,13106874.4,13106874.4
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2024-10-31,151557.15975,18470271.0587,18470271.0587
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,France,France,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-04-30,156782.473595417,19763998.6214,19763998.6214
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,International,Singapore,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,F,EUR,LU2799473397,2025-02-28,798.339453936,102243.3339,102243.3339
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2024-08-31,690711.8821,82381206.1781,82381206.1781
|
||||
Off Distribution,Off Distribution,Off Distribution,Off Distribution,Luxembourg,Luxembourg,Private Assets,Evergreen,SICAV,NO,Carmignac S.A. SICAV - PART II UCI Private Evergreen,I,EUR,LU2799473470,2025-02-28,898577.51775,115260538.2018,115260538.2018
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,475 +0,0 @@
|
|||
Product - Isin,n_accounts,n_days,total_netflows,vol_flows
|
||||
FR0010135103,2690,2723,-25713267.79064,2622.6092438691894
|
||||
FR0010147603,733,2719,-2562187.27941,1206.2482050174879
|
||||
FR0010148981,1841,2722,-3609439.79658,1051.0691834175664
|
||||
FR0010148999,454,2306,-713029.69203,1265.364137812261
|
||||
FR0010149112,934,2000,-943890.10113,1834.9617214458644
|
||||
FR0010149120,1549,2722,-1091549.84051,886.806204108447
|
||||
FR0010149161,519,2719,148152.59466000003,1240.396952286695
|
||||
FR0010149179,880,2719,-996423.97435,2210.6922766973207
|
||||
FR0010149203,651,2719,-1401048.54834,1327.1285879020998
|
||||
FR0010149211,443,2309,-541085.99754,394.2137843506413
|
||||
FR0010149302,1563,2720,-823684.28275,573.9202407418198
|
||||
FR0010306142,694,2717,-14014345.585,1086.8748343092184
|
||||
FR0010312660,488,2716,-2782918.775,908.5342479109894
|
||||
FR0010956607,25,281,-12346.618,340.0190990309257
|
||||
FR0010956615,12,20,-3243.54,263.9106467751888
|
||||
FR0010956649,67,203,-70012.833,2372.1350488717826
|
||||
FR0011147446,172,2271,7127.196999999993,3130.5826503707212
|
||||
FR0011269067,381,2029,-1210874.069,2038.611462468244
|
||||
FR0011269075,9,10,-3560.649,1200.229410576382
|
||||
FR0011269083,311,2713,1359817.442,2087.3359820443466
|
||||
FR0011269091,67,255,-292208.606,6166.98117729719
|
||||
FR0011269109,238,1885,-902808.489,3478.414986026307
|
||||
FR0011269125,13,21,-39563.248,6179.552354792236
|
||||
FR0011269158,27,71,-13666.34,985.0615849467151
|
||||
FR0011269182,106,2704,-283746.288,2536.456282655056
|
||||
FR0011269190,84,1383,-52504.146,618.5858929241117
|
||||
FR0011269323,17,45,-23.453999999999997,44.48383077893611
|
||||
FR0011269331,25,94,-240.256,22.65353946003738
|
||||
FR0011269349,96,1594,5030.347999999961,17083.564807562478
|
||||
FR0011269364,88,199,-57774.403,956.3928559077355
|
||||
FR0011269380,37,128,-33340.035,1112.451095781243
|
||||
FR0011269406,81,1670,10410.462,592.0694476079855
|
||||
FR0011269547,15,40,-3436.399,287.0665542628759
|
||||
FR0011269554,99,292,-100198.01,1096.8036239074336
|
||||
FR0011269570,6,5,615.0,411.6267873290191
|
||||
FR0011269588,302,2714,-1880972.307,2307.1680409536643
|
||||
FR0011269596,228,2094,-392872.488,1646.4370455501714
|
||||
FR0011365873,2,266,-396409.3186,14966.98682979238
|
||||
FR0011443852,4,1525,-2173376.0,5973.376458666843
|
||||
FR0011443860,4,1506,-810560.0,3019.978902190364
|
||||
FR0013467024,20,107,753832.2,51983.54193211123
|
||||
FR0013515970,124,1030,2822576.1890000002,3505.3611798353345
|
||||
FR0013515996,34,741,176067.994,1184.8775673367647
|
||||
FR0013516002,12,48,8170.984,1796.5834277085266
|
||||
FR0013516010,3,7,991.2120000000001,633.3831435549665
|
||||
FR0013516028,76,721,94455.073,3394.7086139001026
|
||||
FR0013516036,17,428,144504.324,2597.8368683136164
|
||||
FR0013516044,1,6,16692.32699999999,86806.01493981322
|
||||
FR0013527827,16,41,308288.01999999996,44503.74361606571
|
||||
FR0014000AL1,9,101,58419.507,14892.187243197617
|
||||
FR0014002E46,20,296,130696.451,12683.788585217599
|
||||
FR00140051L1,2,920,1978966.7759999998,62373.8739800961
|
||||
FR00140081Y1,225,832,11350644.267,2183.8064846207276
|
||||
FR00140081Z8,51,770,722205.741,953.8176973764289
|
||||
FR0014008207,16,70,14959.934,538.9870001311091
|
||||
FR0014008215,4,9,1262.42,305.91093845613153
|
||||
FR0014008223,119,687,966323.723,4578.516493804853
|
||||
FR0014008231,29,659,490056.277,1701.3277802520063
|
||||
FR001400DXJ6,1,2,0.0,14.142135623730951
|
||||
FR001400JG56,10,211,79857.164,21504.70902071331
|
||||
FR001400JG64,1,2,0.0,707.1067811865476
|
||||
FR001400KAV4,188,502,10133066.832,1832.7512954116935
|
||||
FR001400KAW2,39,471,788396.129,1544.1103707759805
|
||||
FR001400KAX0,112,488,1354269.878,6165.657996045809
|
||||
FR001400KAY8,40,439,562440.724,1100.6308866571721
|
||||
FR001400KIF0,2,73,729310.375,210948.20155138557
|
||||
FR001400M1N0,12,56,28677.377,1346.3592269586381
|
||||
FR001400M1O8,6,127,19487.223,1187.276893126291
|
||||
FR001400M1P5,17,89,101771.87,16016.083508984239
|
||||
FR001400M1Q3,6,109,25573.074,1694.7406465848674
|
||||
FR001400R3Z5,8,10,-12.009000000000006,88.19701229749533
|
||||
FR001400TU23,1,51,2309206.085,213381.0688577376
|
||||
FR001400TVB3,1,6,222171.0,96952.84985135816
|
||||
FR001400TVC1,1,5,85330.321,62893.43657635368
|
||||
FR001400TVD9,1,6,84110.54600000002,63301.488598667434
|
||||
FR001400TVF4,1,27,1782150.324,204230.40102024158
|
||||
FR001400TVH0,1,25,151151.0,59297.60457393424
|
||||
FR001400TXI4,2,44,601315.1129999999,86903.61049075224
|
||||
FR001400U4S3,145,188,3311214.595,2381.582889476824
|
||||
FR001400U4T1,21,182,533707.574,2430.950432169126
|
||||
FR001400U4U9,76,176,634629.8759999999,3643.7822889338245
|
||||
FR001400U4V7,24,166,128614.16,3843.623188563404
|
||||
FR001400U4W5,9,55,27185.76,888.0121535411246
|
||||
FR001400U4X3,3,8,1296.0,73.59347797189639
|
||||
FR001400U4Y1,18,46,169755.97,8385.034521845737
|
||||
FR001400U4Z8,6,26,15140.259,1922.0993730971368
|
||||
FR001400U777,3,25,26096.0,85.76858399204221
|
||||
FR001400UTO8,1,5,42254.02100000001,17388.974866357254
|
||||
FR001400XC60,4,39,89786.11,8792.62535313296
|
||||
FR001400XCS9,1,21,203304.0,96423.60472896961
|
||||
FR001400XCT7,2,3,215.0,111.16804097101529
|
||||
FR00140139E9,1,1,20.0,
|
||||
FR00140139F6,1,12,93788.885,37804.981236297936
|
||||
GB00BJHPHX25,19,392,-6058.02339999974,919803.7694127462
|
||||
GB00BJHPHY32,12,263,-4602.0345999994315,1154101.970342966
|
||||
GB00BJHPHZ49,38,1111,14337533.497900002,615354.1940240156
|
||||
GB00BJHPJ035,24,649,16170185.890899999,420825.93064356234
|
||||
GB00BJHPX895,8,90,-0.0004000000189989805,145144.2541145788
|
||||
GB00BJHPXB21,119,1519,40112931.0637,143719.8752946441
|
||||
GB00BJHPZ502,24,844,1674461.8353000002,100910.98488930766
|
||||
GB00BJHQ2H40,1,1,1000000.0,
|
||||
GB00BJHQ2J63,4,17,1012312.1543,242711.8238904066
|
||||
GB00BJHQ2K78,8,80,1012337.8367,111156.36239931718
|
||||
GB00BK1W2N12,13,73,9.999884423450567e-05,2634173.847184156
|
||||
GB00BK1W2P36,39,1352,10768300.2403,150423.72477126715
|
||||
GB00BMF9P332,6,60,1755820.2392,81787.34289042004
|
||||
GB00BMGLBK75,13,211,35586751.8099,1370799.1637462943
|
||||
GB00BMGLBL82,32,924,11215381.9301,65558.19068254779
|
||||
GB00BNDQ7N71,5,312,22908.538600000007,6484.773943838482
|
||||
GB00BNDQ7P95,25,485,4265620.911400002,550422.1080119008
|
||||
GB00BNDQ7Q03,12,490,478082.25729999994,57835.415128547815
|
||||
GB00BPDZX858,1,1,20000.0,
|
||||
GB00BPDZZH84,1,1,20000.0,
|
||||
GB00BQXJRP97,10,183,50076037.408800006,1507584.2417501535
|
||||
GB00BRBXQT75,2,4,20010.0,9998.393620977322
|
||||
IE00049IEN86,13,4,53352.4231,4765.742335228673
|
||||
IE000EFWOJR9,1,1,606428.1382,
|
||||
IE000JQKKF49,1,1,760.6877,
|
||||
IE000L20NS05,20,33,180181.93979999996,79163.57410211617
|
||||
IE000RU6SF09,2,3,-10000.0,11563.49513383578
|
||||
IE000VS8IJD2,1,1,-10000.0,
|
||||
IE000YC0EJX1,1,2,-859109.51,555448.512517119
|
||||
LU0099161993,877,2716,-1281375.51817,2682.4790076241356
|
||||
LU0164455502,974,2613,-1047629.38319,1666.1163303968765
|
||||
LU0294249692,156,2121,-22698.532000000003,446.8975796118456
|
||||
LU0336083497,700,2719,-92766.85223,565.5106025031906
|
||||
LU0336083810,568,2705,-127077.09413,446.23462681837833
|
||||
LU0336084032,833,2718,-713633.0749,783.1908315262529
|
||||
LU0553405878,16,194,-1611.193,690.5655463830622
|
||||
LU0553407650,35,764,-16937.989,810.9153434633681
|
||||
LU0553411090,24,199,-1863.44,53.07021690643891
|
||||
LU0553413385,47,2226,25383.082000000006,743.8088663622179
|
||||
LU0553415323,32,413,-11968.643,2810.804153270726
|
||||
LU0592698954,829,2721,-4559173.92153,3464.1999766148274
|
||||
LU0592699093,347,2715,-1389894.772,467.9955676020268
|
||||
LU0592699176,18,121,-10637.929,473.18754227053444
|
||||
LU0592699259,160,1070,-255058.085,1347.1247932560334
|
||||
LU0705572823,124,2414,47809.411,298.3201330101671
|
||||
LU0807688931,38,288,11660.125,231.02834903081225
|
||||
LU0807689079,120,959,36115.581723945,332.5039986207934
|
||||
LU0807689152,53,1163,37156.034,437.32820459194346
|
||||
LU0807689400,35,135,-6391.397,406.17867510558983
|
||||
LU0807689582,102,519,-6540.112000000001,5303.469614141496
|
||||
LU0807689665,92,774,-35741.026,403.75791086024395
|
||||
LU0807689749,201,1597,-50323.083999999995,407.98068091739555
|
||||
LU0807689822,94,1281,-434400.965,6550.211700599443
|
||||
LU0807690085,356,2411,24880.137000000017,1268.9583161138255
|
||||
LU0807690168,161,2544,603032.938,837.3695654365948
|
||||
LU0807690242,13,229,-9.99600000000055,799.1268596896961
|
||||
LU0807690671,35,161,-41932.03,1946.0893091841442
|
||||
LU0807690754,104,1156,-32678.292000000005,2463.90074166183
|
||||
LU0807690838,84,734,-47195.517,1142.1994521622437
|
||||
LU0807690911,144,2338,-103136.405,651.1466198851854
|
||||
LU0807691059,4,5,-359.406,108.32084998143247
|
||||
LU0992624949,650,2725,10375660.653,25633.73503588275
|
||||
LU0992625086,36,1403,-141439.07299999995,18905.83459216869
|
||||
LU0992625169,41,530,-30075.47799999999,6568.367871930741
|
||||
LU0992625243,209,1168,24836.532999999967,5994.510949467968
|
||||
LU0992625326,75,890,-250907.37399999998,8173.652527227877
|
||||
LU0992625599,7,20,-31054.405,7001.603849235641
|
||||
LU0992625672,41,786,-242.26700000000295,1467.796050558907
|
||||
LU0992625755,65,135,-0.9999999999998437,319.1052380310533
|
||||
LU0992625839,205,2710,-1439550.98,20017.45094123279
|
||||
LU0992625912,7,34,-181646.2620000001,291665.63139923895
|
||||
LU0992626050,11,58,-10000.5,2276.5204468171914
|
||||
LU0992626134,12,58,-10039.379,949.3409440731306
|
||||
LU0992626217,3,8,-10000.0,66861.94424240019
|
||||
LU0992626308,1,1,-1.0,
|
||||
LU0992626480,309,2671,861862.6709999999,6791.079448574136
|
||||
LU0992626563,26,1091,-160999.70700000002,7327.190926942596
|
||||
LU0992626647,18,409,-60750.479999999996,2397.3727869084123
|
||||
LU0992626720,64,2105,18621.943000000003,534.0615251387524
|
||||
LU0992626993,33,173,-25405.248000000003,3063.5367358904095
|
||||
LU0992627025,22,15,-0.9999999999999147,357.25439164460994
|
||||
LU0992627298,381,2299,94520.76400000021,12676.96390215798
|
||||
LU0992627371,35,421,732.5700000000016,1671.1932076604828
|
||||
LU0992627454,54,1676,-26186.408,2833.21428845401
|
||||
LU0992627538,51,289,-62064.67900000002,18300.121179383237
|
||||
LU0992627611,426,2715,1824213.83,27756.18936552804
|
||||
LU0992627702,29,376,190081.80800000002,20837.13592797025
|
||||
LU0992627884,33,922,-49742.495,1791.4220647187776
|
||||
LU0992627967,61,579,-12765.617999999999,2330.639901312931
|
||||
LU0992628007,6,35,-58.60600000000002,166.7051607893012
|
||||
LU0992628189,4,14,-26.34700000000001,39.650782317156114
|
||||
LU0992628346,159,653,-19926.46400000002,9298.366807884626
|
||||
LU0992628429,103,1008,6870.583999999981,4542.916855930407
|
||||
LU0992628692,30,1039,14059.105999999998,1602.773687608923
|
||||
LU0992628775,12,17,-1.0000000000000284,221.70858170115665
|
||||
LU0992628858,215,2235,173889.34600000002,12280.159568357869
|
||||
LU0992628932,20,224,-268.5679999999998,352.60024586752456
|
||||
LU0992629070,22,37,-0.99899999999991,2425.6967331793453
|
||||
LU0992629153,4,8,-1.0,185.06785542365566
|
||||
LU0992629237,184,2538,-690557.3080000001,10912.127900367612
|
||||
LU0992629310,3,13,-1.0,1512.2232977301344
|
||||
LU0992629401,50,1978,3315.754000000004,761.6712280953111
|
||||
LU0992629583,14,34,-1.0050000000006776,1073.6582526816635
|
||||
LU0992629666,3,5,-1.0,984.6038289586326
|
||||
LU0992629740,170,2271,-561153.117,9968.454848640735
|
||||
LU0992629823,8,126,-0.99999999999971,667.4804300870267
|
||||
LU0992630086,91,2688,-9377.703000000003,598.9534011559246
|
||||
LU0992630169,34,471,-6849.999999999998,1401.556962347248
|
||||
LU0992630243,173,229,-0.9999999999989768,649.9402455286594
|
||||
LU0992630326,44,98,16690.201,1375.9096540797482
|
||||
LU0992630599,308,2677,-3719451.974,18044.063694846387
|
||||
LU0992630755,32,1462,-547924.509,7299.753623110597
|
||||
LU0992630839,38,2268,8166.582000000004,560.6550458207864
|
||||
LU0992630912,328,463,25802.459,2361.034162082158
|
||||
LU0992631050,127,2298,94309.317,216.41118513020555
|
||||
LU0992631134,1,1,-0.1,
|
||||
LU0992631217,380,2708,46350.616,2428.2974577625264
|
||||
LU0992631308,20,267,-181449.108,4746.014928290653
|
||||
LU0992631480,10,82,-13821.439999999999,2048.646808362147
|
||||
LU0992631563,7,23,-1.0,307.6626410250332
|
||||
LU0992631647,205,2644,-805551.718,15268.743017033825
|
||||
LU0992631720,10,120,-334.1289999999776,38719.09171571633
|
||||
LU0992631993,36,1505,3485.739,105.4251593514022
|
||||
LU0992632025,18,79,-4906.607,1255.037584058349
|
||||
LU1046327000,65,919,-910342.94282,11759.194129185986
|
||||
LU1046327182,19,118,-0.9999999999997939,401.17648888613775
|
||||
LU1046327265,1,1,-10.0,
|
||||
LU1046327349,65,577,-227911.888,20619.3959251305
|
||||
LU1046327422,2,3,-10.0,1050.0158728958972
|
||||
LU1048598442,16,73,-1.0000000000002274,562.2925004917739
|
||||
LU1048598525,4,15,-499999.99999999994,170745.27408809622
|
||||
LU1122072793,9,273,-65.0,4592.2663818023675
|
||||
LU1122076430,2,9,-250000.003,81477.76063933321
|
||||
LU1122113498,25,69,-0.9999999999997158,357.7544652312273
|
||||
LU1122116673,1,1,-1.0,
|
||||
LU1122119347,1,1,-1.0,
|
||||
LU1126269643,1,2,0.0,282842.71247461904
|
||||
LU1126269999,1,2,0.0,1.4142135623730951
|
||||
LU1163533349,73,1546,110107.454,728.8992453424623
|
||||
LU1163533422,211,2641,888260.696,1466.8359148555403
|
||||
LU1163533695,15,222,-5200.5999999999985,1712.4792269100294
|
||||
LU1163533778,46,843,18683.833000000002,3418.6638271648726
|
||||
LU1163533851,3,43,23.999999999999996,14.531142482172879
|
||||
LU1163533935,5,8,-0.9999999999999787,256.39230673195584
|
||||
LU1299300803,1,2,0.0,141421.35623730952
|
||||
LU1299301017,2,32,4.263256414560601e-13,1288.6319069411209
|
||||
LU1299301280,6,60,-1.1368683772161603e-13,190.74604656485337
|
||||
LU1299301876,10,134,-4524.000000000002,2322.2471146242165
|
||||
LU1299302098,60,931,54076.963,1011.167023582949
|
||||
LU1299302254,88,1989,163505.897,468.0922918257668
|
||||
LU1299302411,1,2,0.0,1.4142135623730951
|
||||
LU1299302684,38,657,19836.487,96.06218114832576
|
||||
LU1299302841,1,2,0.0,1.4142135623730951
|
||||
LU1299303062,18,109,-3.979039320256561e-13,915.8907888364345
|
||||
LU1299303229,107,1663,285793.949,758.4106373952532
|
||||
LU1299303575,15,145,13650.784,993.0811505955538
|
||||
LU1299303732,1,2,0.0,1.4142135623730951
|
||||
LU1299303906,17,173,999.9999999999992,385.7355433902248
|
||||
LU1299304201,13,23,1.7053025658242404e-13,541.0563902505912
|
||||
LU1299304540,44,254,-5.4569682106375694e-12,3259.4312273713936
|
||||
LU1299304896,20,353,1.3358203432289883e-12,1171.0758407649635
|
||||
LU1299305190,124,2205,1297250.2640000002,17743.403618338372
|
||||
LU1299305356,19,851,22855.543,901.1988354291697
|
||||
LU1299305513,23,394,11159.260999999997,2754.3122497951063
|
||||
LU1299305786,237,736,15286.91699999997,10334.371190412163
|
||||
LU1299305943,60,1719,165212.94,669.3715474474863
|
||||
LU1299306321,225,2409,7845625.829,6236.500693701201
|
||||
LU1299306677,67,2109,315577.719,8685.361507399839
|
||||
LU1299306834,233,1218,102148.337,1784.9359522378568
|
||||
LU1299307055,32,1085,-1630.5230000000065,2351.386296846786
|
||||
LU1299307212,1,2,0.0,7071.067811865475
|
||||
LU1299307485,33,329,1135.9069999999565,27182.31304905699
|
||||
LU1299307725,1,2,0.0,1.4142135623730951
|
||||
LU1299308020,2,4,0.0,1.1443554226433907
|
||||
LU1299308376,1,2,0.0,1.4142135623730951
|
||||
LU1299308533,1,2,0.0,7071.067811865475
|
||||
LU1299308707,1,2,0.0,1.4142135623730951
|
||||
LU1299309341,1,2,0.0,1.4142135623730951
|
||||
LU1299309853,1,2,0.0,1.4142135623730951
|
||||
LU1299310190,1,2,0.0,1.4142135623730951
|
||||
LU1299310356,1,2,0.0,1.4142135623730951
|
||||
LU1299310604,1,2,0.0,1.4142135623730951
|
||||
LU1299310943,1,2,0.0,1.4142135623730951
|
||||
LU1299311164,89,1479,394903.6780000001,17156.857525693547
|
||||
LU1299311321,4,79,-2000.3559999999964,7264.082670887005
|
||||
LU1299311677,14,41,5708.495000000001,4074.2333818718835
|
||||
LU1299311834,26,870,26528.473,230.32777626627808
|
||||
LU1317704051,214,2005,1777891.463,3844.0022200235903
|
||||
LU1317704135,74,1920,141939.556,688.9858747802464
|
||||
LU1317704218,4,7,0.0,290.9583686364005
|
||||
LU1317704309,9,167,2.7000623958883807e-13,1133.5144136286397
|
||||
LU1435245151,1,1,-1.0,
|
||||
LU1623761951,58,1399,50317.45700000001,24311.54150124378
|
||||
LU1623762090,5,33,104508.07299999999,30093.28158525151
|
||||
LU1623762256,15,63,4367.965999999995,29680.28693621311
|
||||
LU1623762330,3,11,0.0,4802.666752328866
|
||||
LU1623762413,35,1308,136334.737,14707.523646862322
|
||||
LU1623762504,1,3,0.0,45210.61822182926
|
||||
LU1623762686,10,32,0.0,15915.701401405788
|
||||
LU1623762769,13,547,56047.000000000015,33298.30456340105
|
||||
LU1623762843,382,1631,8322482.90357,1479.2533787673342
|
||||
LU1623762926,43,1071,624334.127,815.8746661547297
|
||||
LU1623763064,397,712,225258.562,1669.0248397379994
|
||||
LU1623763148,75,1494,2519042.781,12777.087715402457
|
||||
LU1623763221,125,1057,601930.79548,2005.1205622952923
|
||||
LU1623763494,1,2,0.0,7071.067811865475
|
||||
LU1623763577,1,2,0.0,14142.13562373095
|
||||
LU1623763734,24,166,-41658.966999999946,54696.325849533336
|
||||
LU1720511549,8,693,66652.80500000001,1990.0727976208327
|
||||
LU1720511895,6,247,18697.501,1276.2617981065105
|
||||
LU1720513321,14,626,141976.805,4482.2150328895605
|
||||
LU1720513677,4,346,33172.779,1070.7972116459737
|
||||
LU1720515615,1,11,1.4551915228366852e-11,63266.49780732987
|
||||
LU1720515706,4,237,21217.208000000002,5075.200463939039
|
||||
LU1744628287,234,1529,879508.26931,800.3607544408013
|
||||
LU1744628873,1,2,0.0,14142.13562373095
|
||||
LU1744630424,101,1355,2379700.359,15961.705522845159
|
||||
LU1748451231,9,138,0.0,3918.792099092554
|
||||
LU1792391242,6,634,28536.24,128.6830449285469
|
||||
LU1792391325,1,2,0.0,141.4213562373095
|
||||
LU1792391598,1,2,0.0,141.4213562373095
|
||||
LU1792391671,13,182,-4947.863000000002,3426.1350296441474
|
||||
LU1792391754,2,7,-8.881784197001252e-16,57.7845687272995
|
||||
LU1792391838,5,69,0.0009999999999763531,715.8809304553549
|
||||
LU1792391911,37,1544,111775.572,7366.4619996901865
|
||||
LU1792392059,2,63,0.0,887.6970459879755
|
||||
LU1792392133,1,2,0.0,141.4213562373095
|
||||
LU1792392216,25,225,23449.954,3888.2364951426325
|
||||
LU1792392307,2,4,0.0,3232.724753722985
|
||||
LU1792392489,1,2,0.0,141.4213562373095
|
||||
LU1792392562,1,2,0.0,141.4213562373095
|
||||
LU1792392646,7,13,-1.4210854715202004e-14,422.23245823816785
|
||||
LU1792392729,2,3,0.0,411.6113038624344
|
||||
LU1873147984,3,338,14.623912319997658,10787.791451533792
|
||||
LU1873148016,6,858,-4.008647456021436,29973.9322451726
|
||||
LU1910837258,43,229,6051.021899999992,10808.85089492048
|
||||
LU1910837332,1,2,0.0,14142.13562373095
|
||||
LU1910837415,43,464,4091.0090000000087,14570.091769448965
|
||||
LU1910837506,1,2,0.0,14142.13562373095
|
||||
LU1932476879,12,231,280599.82,12839.67647264954
|
||||
LU1932489690,278,1661,2849346.74706,7101.925889577037
|
||||
LU1966630706,66,627,18370.951910000018,5959.5175171135925
|
||||
LU1966630961,5,81,54.00000000003638,37946.48273265417
|
||||
LU1966631001,108,1102,761425.48457,1737.014482294523
|
||||
LU1966631266,10,135,12022.143999999998,4663.349775305098
|
||||
LU2004385154,3,39,-2556.2310000000252,37470.20916599314
|
||||
LU2004385667,81,1237,585470.252,5731.546741306934
|
||||
LU2020612490,18,59,64023.107,3707.0248188677924
|
||||
LU2020612730,25,261,151073.486,2582.683732550569
|
||||
LU2020612813,10,160,51687.287,1713.906898607105
|
||||
LU2020612904,53,531,285908.499,3996.1675206271643
|
||||
LU2125044326,1,4,0.0,295222.7360337811
|
||||
LU2125044839,1,2,0.0,70710.67811865476
|
||||
LU2139905785,5,170,23893.91,1167.479937170095
|
||||
LU2154448133,2,18,0.0,3425.232995877988
|
||||
LU2181689576,2,17,-6.217248937900877e-14,175.40694597991268
|
||||
LU2206982626,37,1217,27789.358999999997,815.4169492173645
|
||||
LU2212178615,20,128,12433.413,431.03624627832534
|
||||
LU2250732281,1,1007,9960041.452999998,942238.9571438879
|
||||
LU2250732448,1,277,858025.069,124711.11709571003
|
||||
LU2251305236,1,1115,2123036.8680000002,9768.562750022931
|
||||
LU2251305319,1,1131,1584501.245,3737.5294792876534
|
||||
LU2251305400,1,884,292169.832,1624.94618332704
|
||||
LU2277146382,64,949,281962.50800000003,5836.9066182986335
|
||||
LU2278971804,2,6,1.8189894035458565e-12,8123.516117421691
|
||||
LU2278973172,6,20,0.0,7744.895415240639
|
||||
LU2295992163,50,185,40309.692,873.2044558385983
|
||||
LU2295992247,7,101,686703.865,40416.82672564786
|
||||
LU2295992320,66,862,124352.236,1540.5089328072002
|
||||
LU2295992676,68,553,-72217.03699999988,32558.257783641886
|
||||
LU2346238343,6,25,31067.414,4034.018736465175
|
||||
LU2369619742,4,384,0.004999999992193693,8306.13125491701
|
||||
LU2420650777,1,4,-72334.0,34881.59631763814
|
||||
LU2420651072,13,379,992045.0380000001,24543.91232286013
|
||||
LU2420651155,2,5,159221.767,70596.29814141431
|
||||
LU2420651239,5,33,-157388.726,25847.57169833958
|
||||
LU2420651825,3,434,507515.18100000004,23000.899034253613
|
||||
LU2420652047,11,111,-470843.73399999994,63926.3859739832
|
||||
LU2420652393,3,66,546510.0,43430.72746356048
|
||||
LU2420652476,10,553,847943.152,45216.06366648746
|
||||
LU2420652633,4,91,212295.269,17501.353778679513
|
||||
LU2420652807,11,78,1017513.901,105528.14924225434
|
||||
LU2420652989,4,65,-199.9999999999925,12270.700855048595
|
||||
LU2420653367,9,481,1226144.1579999998,72562.98670994752
|
||||
LU2426951195,11,121,118223.121,2454.512866610321
|
||||
LU2427320499,11,490,2534.8609999999994,2082.1302019818017
|
||||
LU2427320572,5,92,-216.82600000000062,858.549692116248
|
||||
LU2427320739,2,4,-200.0,3489.5080837657715
|
||||
LU2427320812,4,8,47310.0,12510.625127124087
|
||||
LU2427320903,7,20,165.0,14306.065013684582
|
||||
LU2427321034,1,1,-200.0,
|
||||
LU2427321117,2,30,-200.00000000001285,24515.35152531157
|
||||
LU2427321208,6,191,27336.188000000002,1245.8964754433468
|
||||
LU2427321380,1,1,-500.0,
|
||||
LU2427321463,1,1,-500.0,
|
||||
LU2427321547,4,22,34080.569,3864.1049914925584
|
||||
LU2462965026,1,194,101877.325,11214.52289198647
|
||||
LU2475941915,2,55,789715.846,151457.06278728426
|
||||
LU2483484882,1,2,-10000.0,2056.815234552681
|
||||
LU2483485004,1,3,-989400.719,283871.41358832724
|
||||
LU2490324253,2,28,315.7929999999997,1824.131437814838
|
||||
LU2490324337,5,107,33487.037,542.1014795348636
|
||||
LU2490324410,11,341,235526.316,8213.359550716663
|
||||
LU2490324501,13,55,63472.630000000005,3112.4991380121055
|
||||
LU2490324683,4,22,2145.863,306.61940378880627
|
||||
LU2490324766,1,2,7638.58,14950.765968133137
|
||||
LU2490324840,1,1,-200.0,
|
||||
LU2534983825,2,72,-199.99999999999994,1064.7668998997608
|
||||
LU2544562502,1,1,227196.0,
|
||||
LU2567381129,1,1,-500.0,
|
||||
LU2585800795,4,12,0.0,16088.954159007952
|
||||
LU2585800878,1,2,0.0,707.1067811865476
|
||||
LU2585801090,1,12,-2.9103830456733704e-11,659295.1997742131
|
||||
LU2585801173,7,19,16874.006999999998,1293.2616090579613
|
||||
LU2585801256,8,16,1398.117,137.2174332251403
|
||||
LU2585801330,4,46,1401054.869,99447.69192459979
|
||||
LU2601233948,1,2,0.0,707.1067811865476
|
||||
LU2601234086,1,1,500.0,
|
||||
LU2601234169,1,2,0.0,707.1067811865476
|
||||
LU2601234326,1,1,500.0,
|
||||
LU2601234839,4,29,1782.217,159.26404167121171
|
||||
LU2638444914,5,277,1810770.487,49108.105101598114
|
||||
LU2638445218,1,1,500.0,
|
||||
LU2668162196,1,2,0.0,2828.42712474619
|
||||
LU2668162279,1,2,0.0,2828.42712474619
|
||||
LU2715954330,27,20,102394.801,8642.222765232942
|
||||
LU2715954413,1,2,299944.011,183808.1729069305
|
||||
LU2715954504,19,50,36208.933,1177.3747313893032
|
||||
LU2721494966,1,31,2342.372,79.90103969606295
|
||||
LU2721495005,1,5,601.39,123.42237082676706
|
||||
LU2721495260,1,287,139082.821,2597.4157213328253
|
||||
LU2721495427,1,387,422250.272,5518.897098362031
|
||||
LU2772084070,1,1,500.0,
|
||||
LU2772084237,1,1,500.0,
|
||||
LU2772084310,3,44,872765.906,60528.40921756121
|
||||
LU2782951763,2,9,200.0,3042.111839568763
|
||||
LU2799473124,4,10,13885.158963521,884.5910496665695
|
||||
LU2799473397,15,15,196170.04955877402,16821.908343252253
|
||||
LU2799473470,1,4,898577.51775,19721.029510758515
|
||||
LU2809794220,164,280,36996.438,616.5912595948682
|
||||
LU2809794493,14,115,5581.595,280.2810789951842
|
||||
LU2809794576,32,101,64845.309,5874.4581162461545
|
||||
LU2809794659,1,1,200.0,
|
||||
LU2809794733,4,16,435089.904,70447.53695175752
|
||||
LU2809794816,29,86,-3632.65,112.80118760927351
|
||||
LU2812616816,11,21,-388.516,84.9603807354572
|
||||
LU2870281644,1,1,200.0,
|
||||
LU2914157503,3,228,843279.973,22502.155645346014
|
||||
LU2923680206,1,1,200.0,
|
||||
LU2923680388,3,6,261.241,148.38336279302564
|
||||
LU2923680461,1,1,500.0,
|
||||
LU2923680545,1,3,492974.565,722850.2100997841
|
||||
LU2931970912,1,9,411909.0,152993.65934982404
|
||||
LU2931971050,1,35,547182.673,76809.89962023415
|
||||
LU2931971134,1,10,387679.0,146057.09839073973
|
||||
LU2931971217,1,6,204784.0,98784.36720588266
|
||||
LU2947293564,2,99,65672.26699999999,5236.136186843469
|
||||
LU2970252875,2,2,438.0,26.870057685088806
|
||||
LU2970252958,5,32,3125.068,148.8006536253313
|
||||
LU2970271743,1,5,84256.662,61645.37702005857
|
||||
LU3003216234,1,1,200.0,
|
||||
LU3003216408,1,1,200.0,
|
||||
LU3003216580,1,1,200.0,
|
||||
LU3003216747,1,1,200.0,
|
||||
LU3016365556,5,153,523334.01,11779.08632392621
|
||||
LU3060210443,2,81,437165.16599999997,21262.847464428924
|
||||
LU3060210526,6,15,6906.73,1430.1602644542033
|
||||
LU3088560464,12,26,57831.259,2811.5003118426844
|
||||
LU3112062065,1,1,200.0,
|
||||
LU3119442666,1,3,98400.0,56464.8563267454
|
||||
LU3124376941,1,25,9116.185,754.0060039090648
|
||||
LU3133501935,1,1,200.0,
|
||||
LU3133502073,1,1,200.0,
|
||||
LU3133502156,1,1,199600.0,
|
||||
LU3135111204,4,5,3157.0,279.63869546255575
|
||||
LU3149200233,1,1,200.0,
|
||||
LU3149200746,3,4,1068.158,257.532081336093
|
||||
LU3186888858,1,1,200.0,
|
||||
LU3198990908,1,1,2.0,
|
||||
LU3201918581,1,1,200.0,
|
||||
MAPFRECG0001,1,253,1982483.9469925,79683.70114772388
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
isin
|
||||
|
|
|
@ -1,408 +0,0 @@
|
|||
isin
|
||||
LU0992629310
|
||||
LU1744630424
|
||||
FR001400U4S3
|
||||
LU2809794816
|
||||
LU0553405878
|
||||
LU1163533349
|
||||
LU2020612730
|
||||
LU0992627371
|
||||
FR0011269364
|
||||
LU0992625839
|
||||
GB00BJHPHZ49
|
||||
LU2809794576
|
||||
LU2420652807
|
||||
LU2490324337
|
||||
FR0011269554
|
||||
LU2420652476
|
||||
FR001400KIF0
|
||||
FR001400TU23
|
||||
LU0992627884
|
||||
FR0013515970
|
||||
LU0992628932
|
||||
FR001400UTO8
|
||||
LU2420650777
|
||||
LU0992631134
|
||||
LU2715954330
|
||||
LU3112062065
|
||||
FR0010956607
|
||||
LU2427320903
|
||||
LU2420651825
|
||||
FR001400U4V7
|
||||
GB00BJHPHY32
|
||||
GB00BNDQ7Q03
|
||||
LU0992628858
|
||||
LU2004385154
|
||||
LU1792392489
|
||||
LU2427321380
|
||||
LU2139905785
|
||||
LU3133502156
|
||||
LU2715954504
|
||||
IE00049IEN86
|
||||
FR0014000AL1
|
||||
FR0011269083
|
||||
LU0992631050
|
||||
FR0011269158
|
||||
LU0992631993
|
||||
LU0992625169
|
||||
LU2206982626
|
||||
LU0992631308
|
||||
LU2947293564
|
||||
LU2782951763
|
||||
GB00BJHPZ502
|
||||
GB00BJHQ2K78
|
||||
LU0099161993
|
||||
LU3003216408
|
||||
GB00BJHQ2H40
|
||||
LU1299305356
|
||||
LU0992628007
|
||||
LU0992626308
|
||||
LU1623763577
|
||||
FR001400U4Y1
|
||||
FR001400M1Q3
|
||||
LU2420652393
|
||||
LU2154448133
|
||||
FR0011269406
|
||||
FR001400U777
|
||||
LU1299305786
|
||||
LU0992632025
|
||||
LU3149200233
|
||||
FR00140081Z8
|
||||
FR0010149112
|
||||
LU2601234086
|
||||
LU0992629070
|
||||
LU0294249692
|
||||
FR0010147603
|
||||
GB00BMF9P332
|
||||
LU0807689400
|
||||
LU0992628346
|
||||
LU2427321034
|
||||
LU0992627454
|
||||
LU0992631217
|
||||
LU2278973172
|
||||
LU2585801090
|
||||
LU2970271743
|
||||
LU1299306677
|
||||
LU2427320739
|
||||
FR0011269596
|
||||
GB00BRBXQT75
|
||||
LU1623763148
|
||||
GB00BMGLBL82
|
||||
LU2420652047
|
||||
LU0992631647
|
||||
LU1299303575
|
||||
LU1435245151
|
||||
FR001400U4T1
|
||||
GB00BJHPJ035
|
||||
LU1792392216
|
||||
LU0992626480
|
||||
LU0992628189
|
||||
LU3201918581
|
||||
LU1299303062
|
||||
LU1792391911
|
||||
LU2799473470
|
||||
LU0807689749
|
||||
LU0992627702
|
||||
FR0011269182
|
||||
FR0010312660
|
||||
LU0992628775
|
||||
LU1873148016
|
||||
LU2923680206
|
||||
LU2490324766
|
||||
LU2931971217
|
||||
LU2483484882
|
||||
FR00140051L1
|
||||
LU2601234839
|
||||
LU3003216234
|
||||
LU0992630243
|
||||
FR001400U4X3
|
||||
LU2931970912
|
||||
LU2914157503
|
||||
GB00BJHPXB21
|
||||
LU3016365556
|
||||
LU2346238343
|
||||
LU1163533778
|
||||
LU0705572823
|
||||
LU1966631266
|
||||
FR0013516036
|
||||
LU1748451231
|
||||
LU2295992247
|
||||
LU1299311321
|
||||
LU1317704309
|
||||
LU2601234326
|
||||
FR0014002E46
|
||||
LU1299302841
|
||||
IE000VS8IJD2
|
||||
FR0014008207
|
||||
LU0992629401
|
||||
LU1299303229
|
||||
LU2295992320
|
||||
FR0011269570
|
||||
LU0992630839
|
||||
LU2970252958
|
||||
GB00BNDQ7N71
|
||||
FR0010148999
|
||||
FR001400TVF4
|
||||
LU1744628873
|
||||
LU1299311677
|
||||
LU1163533422
|
||||
LU2585800795
|
||||
FR0010149161
|
||||
LU0992626720
|
||||
LU0992630755
|
||||
LU1299311834
|
||||
GB00BNDQ7P95
|
||||
LU1299302098
|
||||
LU0592699176
|
||||
LU0992629583
|
||||
LU0992627298
|
||||
FR0011269067
|
||||
GB00BJHQ2J63
|
||||
LU0592699259
|
||||
LU2799473124
|
||||
GB00BJHPHX25
|
||||
LU2585801330
|
||||
LU1623763221
|
||||
LU0807689582
|
||||
LU1792392307
|
||||
GB00BMGLBK75
|
||||
LU2534983825
|
||||
FR0013516010
|
||||
LU0336083810
|
||||
LU1966630961
|
||||
LU0992626134
|
||||
LU0992627967
|
||||
FR001400XCT7
|
||||
LU1299311164
|
||||
FR0010149179
|
||||
LU1966630706
|
||||
FR0014008223
|
||||
FR0010135103
|
||||
FR0011269091
|
||||
FR0011269125
|
||||
LU0992630086
|
||||
LU2715954413
|
||||
LU2420651155
|
||||
LU1299305190
|
||||
FR0011443852
|
||||
LU0807690671
|
||||
LU1932476879
|
||||
LU2809794659
|
||||
LU0992630912
|
||||
FR0011269547
|
||||
LU2490324683
|
||||
LU2923680545
|
||||
FR001400U4U9
|
||||
LU0992628692
|
||||
LU1317704135
|
||||
LU0592698954
|
||||
LU2420652989
|
||||
LU1792391598
|
||||
LU1623762256
|
||||
LU0336084032
|
||||
LU2799473397
|
||||
LU2427321208
|
||||
LU3133501935
|
||||
LU2427321117
|
||||
LU2420653367
|
||||
IE000JQKKF49
|
||||
FR0014008231
|
||||
LU1966631001
|
||||
LU2420651239
|
||||
LU2020612904
|
||||
LU3060210443
|
||||
FR0013516028
|
||||
LU2772084237
|
||||
FR001400JG64
|
||||
FR001400KAY8
|
||||
FR0011269588
|
||||
FR001400KAX0
|
||||
LU3133502073
|
||||
LU0553413385
|
||||
LU1299306834
|
||||
LU2490324253
|
||||
LU1623762090
|
||||
FR0011269075
|
||||
LU0992630326
|
||||
LU3149200746
|
||||
LU0992631720
|
||||
FR0013515996
|
||||
LU2277146382
|
||||
LU3060210526
|
||||
LU2212178615
|
||||
LU1623762769
|
||||
GB00BJHPX895
|
||||
LU0553407650
|
||||
LU0807690085
|
||||
LU0992627025
|
||||
LU1792392729
|
||||
GB00BK1W2P36
|
||||
FR0011269190
|
||||
LU0807690838
|
||||
LU0992628429
|
||||
FR0010148981
|
||||
LU2638445218
|
||||
LU0807689665
|
||||
LU1623763494
|
||||
LU0992626217
|
||||
FR00140081Y1
|
||||
LU1299301280
|
||||
LU1163533935
|
||||
FR0010149211
|
||||
LU0553411090
|
||||
LU0164455502
|
||||
FR0013467024
|
||||
FR001400TVC1
|
||||
LU1299306321
|
||||
FR0011147446
|
||||
FR0011269331
|
||||
LU2812616816
|
||||
LU0992631563
|
||||
LU2490324501
|
||||
LU3135111204
|
||||
LU2668162196
|
||||
LU2809794220
|
||||
LU2427320572
|
||||
LU0807689079
|
||||
LU2931971050
|
||||
FR0013516002
|
||||
FR0011269349
|
||||
LU1623762504
|
||||
FR001400TVH0
|
||||
LU2427320812
|
||||
FR0011269109
|
||||
LU0992626993
|
||||
LU2181689576
|
||||
LU3088560464
|
||||
LU1792391325
|
||||
LU2772084070
|
||||
LU0992626563
|
||||
LU0992625243
|
||||
LU1792392133
|
||||
LU1623762330
|
||||
LU2427321547
|
||||
LU1623762843
|
||||
GB00BPDZX858
|
||||
IE000L20NS05
|
||||
LU2585801256
|
||||
LU1623762926
|
||||
LU2923680388
|
||||
GB00BPDZZH84
|
||||
LU1317704051
|
||||
LU0807690754
|
||||
FR001400U4W5
|
||||
FR0011269380
|
||||
LU2420652633
|
||||
FR00140139F6
|
||||
FR001400U4Z8
|
||||
IE000EFWOJR9
|
||||
FR0010956615
|
||||
LU2475941915
|
||||
FR0010149203
|
||||
FR0010956649
|
||||
LU3119442666
|
||||
LU0992627538
|
||||
FR001400TVB3
|
||||
LU2426951195
|
||||
LU2427320499
|
||||
LU1744628287
|
||||
LU2295992676
|
||||
FR00140139E9
|
||||
FR0010149120
|
||||
IE000YC0EJX1
|
||||
LU0553415323
|
||||
LU2483485004
|
||||
LU1792391754
|
||||
FR001400KAV4
|
||||
LU2809794733
|
||||
FR0014008215
|
||||
LU0807690911
|
||||
FR001400M1N0
|
||||
LU2004385667
|
||||
LU2427321463
|
||||
LU0992630599
|
||||
LU1932489690
|
||||
LU0992627611
|
||||
LU1163533695
|
||||
LU2369619742
|
||||
LU2923680461
|
||||
LU2567381129
|
||||
LU0992629237
|
||||
LU2772084310
|
||||
LU0992625086
|
||||
LU1792391838
|
||||
LU2870281644
|
||||
LU0992629666
|
||||
FR001400KAW2
|
||||
LU2020612813
|
||||
LU2462965026
|
||||
LU1623763734
|
||||
LU3003216580
|
||||
FR001400XC60
|
||||
FR001400M1P5
|
||||
LU0992629740
|
||||
FR0011443860
|
||||
LU0807690242
|
||||
LU0807690168
|
||||
FR0013516044
|
||||
LU2638444914
|
||||
LU2601233948
|
||||
LU0992631480
|
||||
LU0992629823
|
||||
LU0992626647
|
||||
LU1299307055
|
||||
LU2295992163
|
||||
LU1873147984
|
||||
LU0807689152
|
||||
FR0010149302
|
||||
FR0011269323
|
||||
FR001400TVD9
|
||||
LU0807689822
|
||||
LU1163533851
|
||||
GB00BK1W2N12
|
||||
LU2668162279
|
||||
LU1623761951
|
||||
LU1299305513
|
||||
LU1792391242
|
||||
LU2585801173
|
||||
LU3003216747
|
||||
LU0992625912
|
||||
FR001400M1O8
|
||||
LU2420651072
|
||||
GB00BQXJRP97
|
||||
FR0010306142
|
||||
LU0807691059
|
||||
FR001400XCS9
|
||||
LU2809794493
|
||||
LU2490324410
|
||||
LU2020612490
|
||||
LU1317704218
|
||||
LU0592699093
|
||||
LU2931971134
|
||||
LU0992629153
|
||||
FR0013527827
|
||||
LU1299303732
|
||||
LU1623762413
|
||||
LU0992630169
|
||||
LU1792391671
|
||||
LU3198990908
|
||||
LU1299302254
|
||||
LU1623763064
|
||||
LU0992626050
|
||||
LU1792392059
|
||||
LU2970252875
|
||||
LU0336083497
|
||||
LU2601234169
|
||||
LU2585800878
|
||||
FR001400TXI4
|
||||
IE000RU6SF09
|
||||
LU1299305943
|
||||
LU0807688931
|
||||
LU1299302411
|
||||
LU2490324840
|
||||
FR001400R3Z5
|
||||
LU3186888858
|
||||
LU1299301876
|
||||
LU1299302684
|
||||
LU0992624949
|
||||
FR001400JG56
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
isin
|
||||
LU1299308376
|
||||
LU0992625755
|
||||
LU1299304201
|
||||
LU1299310943
|
||||
LU1122076430
|
||||
LU2544562502
|
||||
LU0992625599
|
||||
LU1122116673
|
||||
LU2251305400
|
||||
LU1299310604
|
||||
LU1122072793
|
||||
LU1910837506
|
||||
LU1299307725
|
||||
LU1046327422
|
||||
LU1623762686
|
||||
LU1299310190
|
||||
LU1122119347
|
||||
LU3124376941
|
||||
MAPFRECG0001
|
||||
LU1046327349
|
||||
LU2251305236
|
||||
LU1910837415
|
||||
LU0553414516
|
||||
LU1126269999
|
||||
LU1299303906
|
||||
LU2125044839
|
||||
FR0000999999
|
||||
FR001400DXJ6
|
||||
LU1299307485
|
||||
LU1720511895
|
||||
LU1299304540
|
||||
LU1299309341
|
||||
LU0280656397
|
||||
LU1720515706
|
||||
LU1720511549
|
||||
LU2721495005
|
||||
LU1720513321
|
||||
LU2251305319
|
||||
LU1720513677
|
||||
LU1299301017
|
||||
LU2721495260
|
||||
LU1048598442
|
||||
LU1046327265
|
||||
LU0992625326
|
||||
LU1910837332
|
||||
LU1299309853
|
||||
LU1792392562
|
||||
LU2721495427
|
||||
LU1792392646
|
||||
LU0807690325
|
||||
LU1046327000
|
||||
FR0010149278
|
||||
LU1048598525
|
||||
LU1126269643
|
||||
LU1299310356
|
||||
LU1046327182
|
||||
FR0011365873
|
||||
LU1299307212
|
||||
LU2721494966
|
||||
LU2250732448
|
||||
LU1299300803
|
||||
LU1299308020
|
||||
LU0807690598
|
||||
LU1720515615
|
||||
LU0992625672
|
||||
LU2250732281
|
||||
LU1122113498
|
||||
LU2125044326
|
||||
FR0010149096
|
||||
LU1299304896
|
||||
LU1299308533
|
||||
LU2278971804
|
||||
LU0109929157
|
||||
LU1299308707
|
||||
LU0413372060
|
||||
LU1910837258
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
isin
|
||||
LU2427320655
|
||||
LU0807690598
|
||||
IE00084T1EH2
|
||||
FR0011269166
|
||||
LU0553414516
|
||||
FR0000999999
|
||||
LU0807689236
|
||||
LU0280656397
|
||||
FR0011269372
|
||||
LU0109929157
|
||||
FR0010149096
|
||||
LU0807690325
|
||||
IE000NNU0CA7
|
||||
FR0010149278
|
||||
FR0011269174
|
||||
LU0413372060
|
||||
FR0011269141
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,71 +0,0 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"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": 3,
|
||||
"id": "d43b725e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"push_file('repair_challenge/alpha_5%/carmignac_broken_months.csv', 'projet-bdc-carmignac-g3//paco/carmignac_broken_months.csv')\n",
|
||||
"push_file('repair_challenge/alpha_5%/carmignac_error_account_agg.csv', 'projet-bdc-carmignac-g3//paco/carmignac_error_account_agg.csv')\n",
|
||||
"push_file('repair_challenge/alpha_5%/carmignac_error_account.csv', 'projet-bdc-carmignac-g3//paco/carmignac_error_account.csv')\n",
|
||||
"push_file('AUM_repaired.csv', 'projet-bdc-carmignac-g3//paco/AUM_repaired.csv')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "d9b0290a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"push_file('AUM_repair_audit.csv', 'projet-bdc-carmignac-g3//paco/AUM_repair_audit.csv')\n",
|
||||
"push_file('AUM_paths.csv', 'projet-bdc-carmignac-g3//paco/AUM_paths.csv')"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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.12"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,36 +0,0 @@
|
|||
============================================================
|
||||
CARMIGNAC — Broken Months Diagnostics
|
||||
============================================================
|
||||
(isin, month) pairs examined : 39874
|
||||
Broken (missing_pct > 5%) : 7545 (18.9%)
|
||||
Of which likely lag : 491
|
||||
Of which genuine gap : 7054
|
||||
|
||||
Top 10 by missing_pct:
|
||||
date isin missing_flow missing_pct is_lag
|
||||
2021-10-31 LU1299301876 400.000 2.000000 False
|
||||
2019-07-31 LU0992625086 3940.000 1.995856 False
|
||||
2021-02-28 LU0992626563 -681.559 1.995325 False
|
||||
2019-01-31 FR0010148999 4595.753 1.994832 False
|
||||
2020-08-31 LU0992631217 14928.331 1.991141 False
|
||||
2020-11-30 LU1744630424 -35632.925 1.987345 False
|
||||
2020-02-29 LU0992626480 25812.484 1.985526 False
|
||||
2020-03-31 LU1299302098 930.648 1.982950 False
|
||||
2020-07-31 LU1966631001 240.957 1.980076 False
|
||||
2020-11-30 LU0807689822 -6181.993 1.972331 False
|
||||
|
||||
Most affected months:
|
||||
n_broken total_missing
|
||||
date
|
||||
2019-12-31 123 2052282.985
|
||||
2021-09-30 113 1892096.272
|
||||
2020-12-31 107 652351.530
|
||||
2020-11-30 105 833084.202
|
||||
2021-01-31 104 784362.333
|
||||
|
||||
Aggregate broken months : 85 (of which lags: 2)
|
||||
Max aggregate error stock : 3,057,936.2 shares (0.749% of total AUM)
|
||||
[Export] Broken months CSV → carmignac_broken_months.csv
|
||||
[Export] Error account (ISIN) → carmignac_error_account.csv
|
||||
[Export] Error account (agg) → carmignac_error_account_agg.csv
|
||||
[Export] HTML report → carmignac_diagnostics.html
|
||||
|
|
@ -1,480 +0,0 @@
|
|||
"""
|
||||
feature_engineering.py
|
||||
───────────────────────
|
||||
Construction du dataset de features pour la modélisation prédictive.
|
||||
|
||||
Ce module assemble trois familles de features :
|
||||
|
||||
[A] Features comportementales client (depuis stocks/AUM)
|
||||
- Encours actuel et lags (1m, 3m, 6m)
|
||||
- Croissance de l'AUM sur différentes fenêtres
|
||||
- Concentration du portefeuille client (part du fonds dans son total)
|
||||
|
||||
[B] Features de performance absolue (depuis weekly_perf)
|
||||
- Rendements 6Mo et 1Yr du fonds détenu
|
||||
- Percentile Morningstar (rang brut dans la catégorie)
|
||||
|
||||
[C] Features de performance relative (depuis relative_performance.py)
|
||||
- Spread vs médiane des vrais peers
|
||||
- Rang dans le groupe de peers restreint
|
||||
- Momentum de rang, ratio d'outperformance
|
||||
- Dummies top/bottom quartile (lien avec la relation convexe de Sirri & Tufano)
|
||||
|
||||
Variable cible :
|
||||
flux_net_proxy = ΔAum(t → t+1)
|
||||
→ À remplacer par les flux transactionnels bruts dès que disponibles.
|
||||
|
||||
Usage :
|
||||
from peers_loader import PeersLoader
|
||||
from relative_performance import RelativePerformanceCalculator
|
||||
from feature_engineering import FeatureBuilder
|
||||
|
||||
loader = PeersLoader("peers/").load()
|
||||
calc = RelativePerformanceCalculator(loader)
|
||||
builder = FeatureBuilder(loader, calc)
|
||||
|
||||
dataset = builder.build(
|
||||
stocks_df = stocks,
|
||||
perf_df = weekly_perf,
|
||||
target_lag = 1 # prédire les flux à t+1 mois
|
||||
)
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# ── Constantes ────────────────────────────────────────────────────────────────
|
||||
|
||||
# Périodes de performance à inclure comme features
|
||||
PERF_PERIODS_TO_USE = ["6MoRet", "1YrRet"]
|
||||
|
||||
# Lags AUM (en mois)
|
||||
AUM_LAGS = [1, 3, 6]
|
||||
|
||||
# Fenêtre de tolérance pour le merge_asof (en jours)
|
||||
MERGE_ASOF_TOLERANCE_DAYS = 35
|
||||
|
||||
|
||||
# ── Classe principale ─────────────────────────────────────────────────────────
|
||||
|
||||
class FeatureBuilder:
|
||||
"""
|
||||
Construit le dataset de modélisation en assemblant les trois familles
|
||||
de features.
|
||||
|
||||
Paramètres
|
||||
----------
|
||||
loader : PeersLoader (déjà chargé)
|
||||
rel_calc : RelativePerformanceCalculator
|
||||
"""
|
||||
|
||||
def __init__(self, loader, rel_calc):
|
||||
self.loader = loader
|
||||
self.rel_calc = rel_calc
|
||||
|
||||
# ── Point d'entrée principal ──────────────────────────────────────────────
|
||||
|
||||
def build(self,
|
||||
stocks_df: pd.DataFrame,
|
||||
perf_df: pd.DataFrame,
|
||||
target_lag: int = 1,
|
||||
perf_periods: list[str] | None = None,
|
||||
verbose: bool = True) -> pd.DataFrame:
|
||||
"""
|
||||
Construit le dataset final avec features et variable cible.
|
||||
|
||||
Paramètres
|
||||
----------
|
||||
stocks_df : AUM mensuels (equity_stocks_full.csv ou similaire)
|
||||
perf_df : performances hebdomadaires (weekly_perf_full.csv)
|
||||
target_lag : horizon de prédiction en mois (1 = flux du mois suivant)
|
||||
perf_periods: périodes de perf à utiliser (défaut : PERF_PERIODS_TO_USE)
|
||||
|
||||
Retourne
|
||||
--------
|
||||
DataFrame avec une ligne par (compte × fonds × mois)
|
||||
contenant toutes les features et la variable cible.
|
||||
"""
|
||||
if perf_periods is None:
|
||||
perf_periods = [p for p in PERF_PERIODS_TO_USE
|
||||
if p in perf_df["perfPeriod"].unique()]
|
||||
if not perf_periods:
|
||||
# Fallback : utiliser toutes les périodes disponibles
|
||||
perf_periods = perf_df["perfPeriod"].unique().tolist()
|
||||
|
||||
if verbose:
|
||||
print("── FeatureBuilder ──────────────────────────────────────")
|
||||
print(f"Périodes de performance utilisées : {perf_periods}")
|
||||
|
||||
# Étape 1 : résolution ISIN dans perf via peers
|
||||
perf_df = self._resolve_perf_isin(perf_df)
|
||||
|
||||
# Étape 2 : calcul des métriques relatives
|
||||
if verbose:
|
||||
print("\nCalcul des performances relatives...")
|
||||
rel_df = self.rel_calc.compute(perf_df, perf_periods=perf_periods,
|
||||
verbose=verbose)
|
||||
|
||||
# Étape 3 : features client (AUM)
|
||||
if verbose:
|
||||
print("\nConstruction des features AUM...")
|
||||
stocks_feat = self._build_aum_features(stocks_df)
|
||||
|
||||
# Étape 4 : features de performance absolue
|
||||
if verbose:
|
||||
print("\nJointure des performances absolues...")
|
||||
perf_abs = self._build_absolute_perf_features(perf_df, perf_periods)
|
||||
|
||||
# Étape 5 : jointure AUM × perf absolue
|
||||
dataset = self._merge_aum_and_perf(stocks_feat, perf_abs, verbose)
|
||||
|
||||
# Étape 6 : jointure avec les métriques relatives
|
||||
if not rel_df.empty:
|
||||
if verbose:
|
||||
print("\nJointure des métriques relatives...")
|
||||
dataset = self._merge_relative_perf(dataset, rel_df, verbose)
|
||||
|
||||
# Étape 7 : variable cible
|
||||
if verbose:
|
||||
print(f"\nConstruction de la variable cible (lag = {target_lag} mois)...")
|
||||
dataset = self._build_target(dataset, lag=target_lag)
|
||||
|
||||
# Étape 8 : nettoyage final
|
||||
dataset = self._final_cleanup(dataset, verbose)
|
||||
|
||||
if verbose:
|
||||
self._print_dataset_summary(dataset)
|
||||
|
||||
return dataset
|
||||
|
||||
# ── Étape 1 : résolution ISIN dans perf ──────────────────────────────────
|
||||
|
||||
def _resolve_perf_isin(self, perf_df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Ajoute la colonne 'isin' dans perf_df via PeersLoader."""
|
||||
perf_df = perf_df.copy()
|
||||
perf_df["Date"] = pd.to_datetime(perf_df["Date"])
|
||||
perf_df["isin"] = perf_df["shareClass_name"].apply(
|
||||
self.loader.resolve_shareclass_name
|
||||
)
|
||||
|
||||
# Ajouter la stratégie Carmignac si disponible
|
||||
isin_to_strategy = dict(
|
||||
zip(self.loader.carmignac_df["ISIN"],
|
||||
self.loader.carmignac_df["carmignac_strategy"])
|
||||
)
|
||||
perf_df["carmignac_strategy"] = perf_df["isin"].map(isin_to_strategy)
|
||||
return perf_df
|
||||
|
||||
# ── Étape 2 : features AUM ────────────────────────────────────────────────
|
||||
|
||||
def _build_aum_features(self, stocks_df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Construit les features comportementales depuis les snapshots AUM mensuels.
|
||||
|
||||
Features produites (par compte × fonds × date) :
|
||||
aum_t : encours à t
|
||||
aum_lag{n} : encours à t-n mois (n ∈ AUM_LAGS)
|
||||
aum_growth_{n}m : croissance relative sur n mois
|
||||
aum_share_wallet : part du fonds dans le portefeuille total du compte
|
||||
"""
|
||||
df = stocks_df.copy()
|
||||
df["Centralisation Date"] = pd.to_datetime(df["Centralisation Date"])
|
||||
|
||||
# Tri pour les lags
|
||||
df = df.sort_values(["Registrar Account - ID", "Product - Isin",
|
||||
"Centralisation Date"])
|
||||
|
||||
grp = df.groupby(["Registrar Account - ID", "Product - Isin"])
|
||||
|
||||
# Lags AUM
|
||||
for lag in AUM_LAGS:
|
||||
df[f"aum_lag{lag}"] = grp["Value - AUM €"].shift(lag)
|
||||
|
||||
# Croissances
|
||||
for lag in AUM_LAGS:
|
||||
df[f"aum_growth_{lag}m"] = (
|
||||
(df["Value - AUM €"] - df[f"aum_lag{lag}"])
|
||||
/ (df[f"aum_lag{lag}"].abs() + 1.0)
|
||||
)
|
||||
|
||||
# Part dans le portefeuille total du compte (concentration)
|
||||
total_aum_by_account = (
|
||||
df.groupby(["Registrar Account - ID", "Centralisation Date"])["Value - AUM €"]
|
||||
.transform("sum")
|
||||
)
|
||||
df["aum_share_wallet"] = df["Value - AUM €"] / (total_aum_by_account + 1.0)
|
||||
|
||||
# Renommage pour clarté
|
||||
df = df.rename(columns={"Value - AUM €": "aum_t"})
|
||||
|
||||
# Colonnes à conserver
|
||||
keep = (
|
||||
["Registrar Account - ID", "Product - Isin", "Centralisation Date",
|
||||
"Registrar Account - Region", "RegistrarAccount - Country",
|
||||
"Product - Asset Type", "Product - Strategy", "Product - Fund",
|
||||
"aum_t", "aum_share_wallet"]
|
||||
+ [f"aum_lag{lag}" for lag in AUM_LAGS]
|
||||
+ [f"aum_growth_{lag}m" for lag in AUM_LAGS]
|
||||
)
|
||||
keep = [c for c in keep if c in df.columns]
|
||||
return df[keep]
|
||||
|
||||
# ── Étape 3 : features de performance absolue ─────────────────────────────
|
||||
|
||||
def _build_absolute_perf_features(self, perf_df: pd.DataFrame,
|
||||
perf_periods: list[str]) -> pd.DataFrame:
|
||||
"""
|
||||
Pivote weekly_perf pour obtenir une ligne par (isin, date)
|
||||
avec une colonne par (période × métrique).
|
||||
|
||||
Colonnes produites : perf_return_6MoRet, perf_pct_1YrRet, etc.
|
||||
"""
|
||||
relevant = perf_df[perf_df["perfPeriod"].isin(perf_periods)].copy()
|
||||
if relevant.empty:
|
||||
return pd.DataFrame(columns=["isin", "Date"])
|
||||
|
||||
pivoted = relevant.pivot_table(
|
||||
index=["isin", "Date"],
|
||||
columns="perfPeriod",
|
||||
values=["return", "percentile"],
|
||||
aggfunc="mean"
|
||||
)
|
||||
# Aplatir les colonnes multi-index
|
||||
pivoted.columns = [
|
||||
f"perf_{metric}_{period}"
|
||||
for metric, period in pivoted.columns
|
||||
]
|
||||
return pivoted.reset_index()
|
||||
|
||||
# ── Étape 4 : jointure AUM × perf absolue ────────────────────────────────
|
||||
|
||||
def _merge_aum_and_perf(self, stocks_feat: pd.DataFrame,
|
||||
perf_abs: pd.DataFrame,
|
||||
verbose: bool) -> pd.DataFrame:
|
||||
"""
|
||||
merge_asof temporel : pour chaque snapshot mensuel AUM,
|
||||
trouve la performance hebdomadaire la plus récente ≤ date snapshot.
|
||||
"""
|
||||
if perf_abs.empty:
|
||||
if verbose:
|
||||
print(" ⚠ Aucune performance absolue à joindre.")
|
||||
return stocks_feat
|
||||
|
||||
merged_parts = []
|
||||
for isin_val in stocks_feat["Product - Isin"].unique():
|
||||
s = stocks_feat[stocks_feat["Product - Isin"] == isin_val].sort_values(
|
||||
"Centralisation Date")
|
||||
p = perf_abs[perf_abs["isin"] == isin_val].sort_values("Date")
|
||||
|
||||
if p.empty:
|
||||
merged_parts.append(s)
|
||||
continue
|
||||
|
||||
m = pd.merge_asof(
|
||||
s, p,
|
||||
left_on="Centralisation Date",
|
||||
right_on="Date",
|
||||
direction="backward",
|
||||
tolerance=pd.Timedelta(f"{MERGE_ASOF_TOLERANCE_DAYS}d")
|
||||
)
|
||||
merged_parts.append(m)
|
||||
|
||||
result = pd.concat(merged_parts, ignore_index=True)
|
||||
perf_cols_joined = [c for c in result.columns if c.startswith("perf_")]
|
||||
|
||||
if verbose:
|
||||
n_matched = result[perf_cols_joined[0]].notna().sum() if perf_cols_joined else 0
|
||||
print(f" {n_matched}/{len(result)} lignes avec performance jointe "
|
||||
f"({len(perf_cols_joined)} colonnes)")
|
||||
|
||||
return result
|
||||
|
||||
# ── Étape 5 : jointure métriques relatives ────────────────────────────────
|
||||
|
||||
def _merge_relative_perf(self, dataset: pd.DataFrame,
|
||||
rel_df: pd.DataFrame,
|
||||
verbose: bool) -> pd.DataFrame:
|
||||
"""
|
||||
Joint les métriques relatives sur (carmignac_strategy, date).
|
||||
|
||||
Stratégie de jointure :
|
||||
- La stratégie Carmignac d'un compte est déduite depuis
|
||||
Product - Strategy (nom court) ou ISIN via peers_loader.
|
||||
- merge_asof temporel avec tolérance ±35j.
|
||||
"""
|
||||
# Récupérer la stratégie Carmignac depuis l'ISIN
|
||||
isin_to_strategy = dict(
|
||||
zip(self.loader.carmignac_df["ISIN"],
|
||||
self.loader.carmignac_df["carmignac_strategy"])
|
||||
)
|
||||
dataset["carmignac_strategy"] = dataset["Product - Isin"].map(isin_to_strategy)
|
||||
|
||||
# Agréger rel_df sur toutes périodes (moyenne par stratégie × date)
|
||||
rel_cols = [c for c in rel_df.columns if c.startswith("rel_")]
|
||||
if not rel_cols:
|
||||
return dataset
|
||||
|
||||
rel_agg = (rel_df
|
||||
.groupby(["carmignac_strategy", "Date"])[rel_cols]
|
||||
.mean()
|
||||
.reset_index()
|
||||
.sort_values(["carmignac_strategy", "Date"]))
|
||||
|
||||
merged_parts = []
|
||||
for strat in dataset["carmignac_strategy"].dropna().unique():
|
||||
d_strat = dataset[dataset["carmignac_strategy"] == strat].sort_values(
|
||||
"Centralisation Date")
|
||||
r_strat = rel_agg[rel_agg["carmignac_strategy"] == strat].sort_values("Date")
|
||||
|
||||
if r_strat.empty:
|
||||
merged_parts.append(d_strat)
|
||||
continue
|
||||
|
||||
m = pd.merge_asof(
|
||||
d_strat, r_strat,
|
||||
left_on="Centralisation Date",
|
||||
right_on="Date",
|
||||
direction="backward",
|
||||
tolerance=pd.Timedelta(f"{MERGE_ASOF_TOLERANCE_DAYS}d")
|
||||
)
|
||||
merged_parts.append(m)
|
||||
|
||||
# Ajouter les comptes sans stratégie identifiée
|
||||
no_strat = dataset[dataset["carmignac_strategy"].isna()]
|
||||
if not no_strat.empty:
|
||||
merged_parts.append(no_strat)
|
||||
|
||||
result = pd.concat(merged_parts, ignore_index=True)
|
||||
|
||||
if verbose:
|
||||
n_rel = result[rel_cols[0]].notna().sum() if rel_cols else 0
|
||||
print(f" {n_rel}/{len(result)} lignes avec métriques relatives jointes "
|
||||
f"({len(rel_cols)} colonnes)")
|
||||
|
||||
return result
|
||||
|
||||
# ── Étape 6 : variable cible ──────────────────────────────────────────────
|
||||
|
||||
def _build_target(self, dataset: pd.DataFrame, lag: int) -> pd.DataFrame:
|
||||
"""
|
||||
Construit la variable cible : ΔAum(t → t+lag).
|
||||
|
||||
flux_net_proxy = aum(t+lag) - aum(t)
|
||||
|
||||
Note : Dans un contexte de production, remplacer par :
|
||||
flux_net = sum(souscriptions[t:t+lag]) - sum(rachats[t:t+lag])
|
||||
depuis le fichier de transactions quotidiennes.
|
||||
"""
|
||||
dataset = dataset.sort_values(
|
||||
["Registrar Account - ID", "Product - Isin", "Centralisation Date"]
|
||||
)
|
||||
grp = dataset.groupby(["Registrar Account - ID", "Product - Isin"])
|
||||
|
||||
dataset["aum_next"] = grp["aum_t"].shift(-lag)
|
||||
dataset["flux_net_proxy"] = dataset["aum_next"] - dataset["aum_t"]
|
||||
|
||||
# Feature supplémentaire : flux relatif (normalisé par l'AUM)
|
||||
dataset["flux_net_relative"] = (
|
||||
dataset["flux_net_proxy"] / (dataset["aum_t"].abs() + 1.0)
|
||||
)
|
||||
|
||||
return dataset
|
||||
|
||||
# ── Étape 7 : nettoyage final ─────────────────────────────────────────────
|
||||
|
||||
def _final_cleanup(self, dataset: pd.DataFrame,
|
||||
verbose: bool) -> pd.DataFrame:
|
||||
"""
|
||||
Supprime les doublons de colonnes, retire les lignes sans cible,
|
||||
log les taux de remplissage.
|
||||
"""
|
||||
# Supprimer les colonnes en double (artefacts du merge)
|
||||
dataset = dataset.loc[:, ~dataset.columns.duplicated()]
|
||||
|
||||
# Retirer les lignes sans variable cible
|
||||
n_before = len(dataset)
|
||||
dataset = dataset.dropna(subset=["flux_net_proxy"])
|
||||
n_after = len(dataset)
|
||||
|
||||
if verbose and n_before > n_after:
|
||||
print(f" Lignes supprimées (cible manquante) : {n_before - n_after}")
|
||||
|
||||
return dataset.reset_index(drop=True)
|
||||
|
||||
# ── Résumé ────────────────────────────────────────────────────────────────
|
||||
|
||||
def _print_dataset_summary(self, dataset: pd.DataFrame):
|
||||
feature_cols = self.get_feature_columns(dataset)
|
||||
print("\n── Dataset final ───────────────────────────────────────")
|
||||
print(f"Lignes : {len(dataset):,}")
|
||||
print(f"Colonnes totales : {len(dataset.columns)}")
|
||||
print(f"Features : {len(feature_cols)}")
|
||||
print(f"\nTaux de remplissage des features :")
|
||||
|
||||
families = {
|
||||
"AUM": [c for c in feature_cols if c.startswith("aum_")],
|
||||
"Perf abs": [c for c in feature_cols if c.startswith("perf_")],
|
||||
"Perf rel": [c for c in feature_cols if c.startswith("rel_")],
|
||||
}
|
||||
for family, cols in families.items():
|
||||
if cols:
|
||||
fill_rates = dataset[cols].notna().mean()
|
||||
avg_fill = fill_rates.mean()
|
||||
print(f" {family:<12} ({len(cols):2d} cols) : {avg_fill:.1%} en moyenne")
|
||||
|
||||
print(f"\nVariable cible 'flux_net_proxy' :")
|
||||
t = dataset["flux_net_proxy"]
|
||||
print(f" Médiane : {t.median():+,.0f} €")
|
||||
print(f" Std : {t.std():,.0f} €")
|
||||
print(f" % positif (souscription nette) : {(t > 0).mean():.1%}")
|
||||
print(f" % négatif (rachat net) : {(t < 0).mean():.1%}")
|
||||
print("─────────────────────────────────────────────────────────")
|
||||
|
||||
# ── API publique ──────────────────────────────────────────────────────────
|
||||
|
||||
@staticmethod
|
||||
def get_feature_columns(dataset: pd.DataFrame) -> list[str]:
|
||||
"""Retourne la liste des colonnes de features (exclut les métadonnées et la cible)."""
|
||||
exclude = {
|
||||
"Registrar Account - ID", "Product - Isin", "Centralisation Date",
|
||||
"Product - Fund", "Product - Strategy", "Product - Asset Type",
|
||||
"Registrar Account - Region", "RegistrarAccount - Country",
|
||||
"carmignac_strategy", "isin", "Date",
|
||||
"aum_next", "flux_net_proxy", "flux_net_relative", "shareClass_name",
|
||||
}
|
||||
return [c for c in dataset.columns if c not in exclude
|
||||
and dataset[c].dtype in [np.float64, np.int64, float, int]]
|
||||
|
||||
@staticmethod
|
||||
def get_target_column() -> str:
|
||||
return "flux_net_proxy"
|
||||
|
||||
|
||||
# ── Usage autonome ────────────────────────────────────────────────────────────
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
from peers_loader import PeersLoader
|
||||
from relative_performance import RelativePerformanceCalculator
|
||||
|
||||
peers_dir = sys.argv[1] if len(sys.argv) > 1 else "."
|
||||
stocks_path = sys.argv[2] if len(sys.argv) > 2 else "equity_stocks_head.csv"
|
||||
perf_path = sys.argv[3] if len(sys.argv) > 3 else "weekly_perf_head.csv"
|
||||
|
||||
loader = PeersLoader(peers_dir=peers_dir).load()
|
||||
calc = RelativePerformanceCalculator(loader)
|
||||
builder = FeatureBuilder(loader, calc)
|
||||
|
||||
stocks_df = pd.read_csv(stocks_path)
|
||||
perf_df = pd.read_csv(perf_path)
|
||||
|
||||
dataset = builder.build(stocks_df, perf_df)
|
||||
|
||||
print("\nAperçu du dataset :")
|
||||
feature_cols = FeatureBuilder.get_feature_columns(dataset)
|
||||
print(dataset[feature_cols].describe().round(3).to_string())
|
||||
|
||||
dataset.to_csv("dataset_features.csv", index=False)
|
||||
print("\nDataset sauvegardé dans dataset_features.csv")
|
||||
|
|
@ -1,399 +0,0 @@
|
|||
"""
|
||||
peers_loader.py
|
||||
---------------
|
||||
Chargement et consolidation de tous les fichiers *_peers.csv.
|
||||
|
||||
Rôles :
|
||||
1. Parser tous les fichiers <STRATEGY>_peers.csv depuis un dossier local
|
||||
ou depuis un bucket S3 (via s3fs)
|
||||
2. Identifier automatiquement les lignes Carmignac vs concurrents
|
||||
3. Construire une table de référence ISIN ↔ shareClass_name ↔ stratégie Carmignac
|
||||
→ résout le problème de jointure avec weekly_perf (qui n'a que les noms)
|
||||
4. Exposer des helpers utilisés par les autres modules
|
||||
|
||||
Usage (local) :
|
||||
from peers_loader import PeersLoader
|
||||
loader = PeersLoader("path/to/peers/")
|
||||
loader.load()
|
||||
|
||||
Usage (S3) :
|
||||
from peers_loader import PeersLoader
|
||||
loader = PeersLoader(
|
||||
"s3://my-bucket/peers/",
|
||||
s3_options={"key": "ACCESS_KEY", "secret": "SECRET_KEY"},
|
||||
# ou laisser s3_options=None pour utiliser les credentials AWS standard
|
||||
# (variables d'environnement, ~/.aws/credentials, IAM role...)
|
||||
)
|
||||
loader.load()
|
||||
|
||||
# Table de référence complète
|
||||
loader.peers_df # tous les fonds (Carmignac + concurrents)
|
||||
loader.isin_map # dict shareClass_name → ISIN
|
||||
loader.carmignac_df # uniquement les shareclasses Carmignac
|
||||
loader.competitors_df # uniquement les concurrents
|
||||
"""
|
||||
|
||||
import re
|
||||
import io
|
||||
import pandas as pd
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
# ── Constantes ────────────────────────────────────────────────────────────────
|
||||
|
||||
PEERS_SEPARATOR = "|"
|
||||
CARMIGNAC_IDENTIFIERS = ["carmignac"] # en minuscules — permet de détecter les lignes Carmignac
|
||||
|
||||
COLUMNS_DTYPES = {
|
||||
"Name": str,
|
||||
"ISIN": str,
|
||||
"SecId_MS": str,
|
||||
"FundId": str,
|
||||
"Global Broad Category Group": str,
|
||||
"Global Category": str,
|
||||
"Morningstar Category": str,
|
||||
"Index Fund": str,
|
||||
"Enhanced Index": str,
|
||||
"Domicile": str,
|
||||
}
|
||||
|
||||
DATE_COLUMNS = ["Inception Date", "Inception Date of Fund's Oldest Share Class"]
|
||||
|
||||
S3_PREFIX = "s3://"
|
||||
|
||||
|
||||
# ── Backends d'accès aux fichiers ─────────────────────────────────────────────
|
||||
|
||||
class _LocalBackend:
|
||||
"""Accès au système de fichiers local via pathlib."""
|
||||
|
||||
def __init__(self, root: str):
|
||||
self.root = Path(root)
|
||||
|
||||
def list_files(self, pattern: re.Pattern) -> list[str]:
|
||||
return sorted(
|
||||
str(f) for f in self.root.iterdir()
|
||||
if f.is_file() and pattern.match(f.name)
|
||||
)
|
||||
|
||||
def read_csv(self, path: str, **kwargs) -> pd.DataFrame:
|
||||
return pd.read_csv(path, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def filename(path: str) -> str:
|
||||
return Path(path).name
|
||||
|
||||
|
||||
class _S3Backend:
|
||||
"""
|
||||
Accès à un bucket S3 via s3fs.
|
||||
|
||||
s3fs expose une interface quasi-identique à pathlib/os, ce qui permet
|
||||
de garder la logique de PeersLoader totalement inchangée.
|
||||
|
||||
Authentification (par ordre de priorité) :
|
||||
1. s3_options explicites : {"key": ..., "secret": ..., "token": ...}
|
||||
2. Variables d'environnement : AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
|
||||
3. Fichier ~/.aws/credentials
|
||||
4. IAM role (si exécution sur EC2/ECS/Lambda)
|
||||
"""
|
||||
|
||||
def __init__(self, root: str, s3_options: Optional[dict] = None):
|
||||
try:
|
||||
import s3fs
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Le package s3fs est requis pour lire depuis S3.\n"
|
||||
"Installer avec : pip install s3fs"
|
||||
)
|
||||
|
||||
# Normalisation du chemin : retirer le préfixe s3://
|
||||
self.root = root.rstrip("/")
|
||||
if self.root.startswith(S3_PREFIX):
|
||||
self.root = self.root[len(S3_PREFIX):]
|
||||
|
||||
# Création du filesystem S3
|
||||
# s3_options=None → s3fs utilise les credentials AWS standard
|
||||
self.fs = s3fs.S3FileSystem(**(s3_options or {}))
|
||||
|
||||
def list_files(self, pattern: re.Pattern) -> list[str]:
|
||||
"""Liste les fichiers dans le dossier S3 correspondant au pattern."""
|
||||
all_files = self.fs.ls(self.root, detail=False)
|
||||
matched = sorted(
|
||||
f"s3://{p}" for p in all_files
|
||||
if pattern.match(p.split("/")[-1])
|
||||
)
|
||||
return matched
|
||||
|
||||
def read_csv(self, path: str, **kwargs) -> pd.DataFrame:
|
||||
"""Lit un CSV depuis S3 en passant par un buffer en mémoire."""
|
||||
# Normaliser : s3fs.open() accepte "bucket/key" ou "s3://bucket/key"
|
||||
s3_path = path.removeprefix(S3_PREFIX)
|
||||
with self.fs.open(s3_path, "rb") as f:
|
||||
return pd.read_csv(io.BytesIO(f.read()), **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def filename(path: str) -> str:
|
||||
"""Retourne le nom de fichier depuis un chemin S3 complet."""
|
||||
return path.split("/")[-1]
|
||||
|
||||
|
||||
# ── Classe principale ─────────────────────────────────────────────────────────
|
||||
|
||||
class PeersLoader:
|
||||
"""
|
||||
Charge et consolide l'ensemble des fichiers *_peers.csv.
|
||||
Supporte les sources locales et S3 de manière transparente.
|
||||
|
||||
Paramètres
|
||||
----------
|
||||
peers_dir : str
|
||||
Dossier local (ex: "data/peers/") ou chemin S3 (ex: "s3://bucket/peers/").
|
||||
|
||||
s3_options : dict, optionnel
|
||||
Options d'authentification S3 passées à s3fs.S3FileSystem.
|
||||
Exemples :
|
||||
{"key": "AKID...", "secret": "secret..."} # clés explicites
|
||||
{"profile": "my-aws-profile"} # profil AWS CLI
|
||||
{"anon": True} # bucket public
|
||||
Si None (défaut), s3fs utilise la chaîne de credentials AWS standard.
|
||||
Ignoré si peers_dir est un chemin local.
|
||||
"""
|
||||
|
||||
def __init__(self, peers_dir: str, s3_options: Optional[dict] = None):
|
||||
self._backend = (
|
||||
_S3Backend(peers_dir, s3_options)
|
||||
if peers_dir.startswith(S3_PREFIX)
|
||||
else _LocalBackend(peers_dir)
|
||||
)
|
||||
self.peers_dir = peers_dir
|
||||
self.peers_df = pd.DataFrame()
|
||||
self.carmignac_df = pd.DataFrame()
|
||||
self.competitors_df = pd.DataFrame()
|
||||
self.isin_map = {}
|
||||
self._loaded = False
|
||||
|
||||
# ── Chargement ────────────────────────────────────────────────────────────
|
||||
|
||||
def load(self, verbose: bool = True) -> "PeersLoader":
|
||||
"""Charge tous les fichiers peers et construit les tables dérivées."""
|
||||
files = self._discover_files()
|
||||
if not files:
|
||||
raise FileNotFoundError(
|
||||
f"Aucun fichier *_peers.csv trouvé dans {self.peers_dir}"
|
||||
)
|
||||
|
||||
parts = []
|
||||
for f in files:
|
||||
strategy_code = self._extract_strategy_code(f)
|
||||
df = self._load_single_file(f, strategy_code)
|
||||
parts.append(df)
|
||||
if verbose:
|
||||
n_carm = df["is_carmignac"].sum()
|
||||
n_comp = (~df["is_carmignac"]).sum()
|
||||
print(f" [{strategy_code}] {len(df)} fonds chargés "
|
||||
f"({n_carm} Carmignac, {n_comp} concurrents)")
|
||||
|
||||
self.peers_df = pd.concat(parts, ignore_index=True)
|
||||
self._build_derived_tables()
|
||||
self._loaded = True
|
||||
|
||||
if verbose:
|
||||
self._print_summary()
|
||||
|
||||
return self
|
||||
|
||||
def _discover_files(self) -> list[str]:
|
||||
"""Retourne les chemins des fichiers *_peers.csv via le backend actif."""
|
||||
pattern = re.compile(r"^[A-Z]+_peers\.csv$")
|
||||
return self._backend.list_files(pattern)
|
||||
|
||||
def _extract_strategy_code(self, filepath: str) -> str:
|
||||
"""Extrait le code stratégie depuis le nom de fichier (ex: CAD, CARE)."""
|
||||
filename = self._backend.filename(filepath)
|
||||
return Path(filename).stem.replace("_peers", "")
|
||||
|
||||
def _load_single_file(self, filepath: str, strategy_code: str) -> pd.DataFrame:
|
||||
"""Charge un fichier peers (local ou S3) et l'enrichit avec les colonnes de contexte."""
|
||||
df = self._backend.read_csv(filepath, sep=PEERS_SEPARATOR, dtype=str)
|
||||
|
||||
# Parsing des dates
|
||||
for col in DATE_COLUMNS:
|
||||
if col in df.columns:
|
||||
df[col] = pd.to_datetime(df[col], errors="coerce")
|
||||
|
||||
# Nettoyage des colonnes texte
|
||||
for col in COLUMNS_DTYPES:
|
||||
if col in df.columns:
|
||||
df[col] = df[col].str.strip()
|
||||
|
||||
# Colonnes de contexte
|
||||
df["carmignac_strategy"] = strategy_code
|
||||
df["is_carmignac"] = df["Name"].str.lower().str.contains(
|
||||
"|".join(CARMIGNAC_IDENTIFIERS), na=False
|
||||
)
|
||||
df["is_index_fund"] = df["Index Fund"].str.lower().eq("yes")
|
||||
|
||||
return df
|
||||
|
||||
# ── Construction des tables dérivées ─────────────────────────────────────
|
||||
|
||||
def _build_derived_tables(self):
|
||||
"""Construit carmignac_df, competitors_df et isin_map."""
|
||||
|
||||
# Tables Carmignac / concurrents
|
||||
self.carmignac_df = self.peers_df[self.peers_df["is_carmignac"]].copy()
|
||||
self.competitors_df = self.peers_df[~self.peers_df["is_carmignac"]].copy()
|
||||
|
||||
# Table de correspondance Name → ISIN (utile pour joindre weekly_perf)
|
||||
valid = self.peers_df[self.peers_df["ISIN"].notna() & (self.peers_df["ISIN"] != "")]
|
||||
self.isin_map = dict(zip(valid["Name"], valid["ISIN"]))
|
||||
|
||||
# Table de correspondance enrichie : shareClass_name (format Morningstar)
|
||||
# → ISIN + stratégie Carmignac. Utilisée par feature_engineering.py.
|
||||
self.name_to_strategy = dict(
|
||||
zip(
|
||||
self.carmignac_df["Name"],
|
||||
self.carmignac_df["carmignac_strategy"]
|
||||
)
|
||||
)
|
||||
|
||||
# ── API publique ──────────────────────────────────────────────────────────
|
||||
|
||||
def get_peers_for_strategy(self, strategy_code: str,
|
||||
exclude_index: bool = False,
|
||||
exclude_carmignac: bool = False) -> pd.DataFrame:
|
||||
"""
|
||||
Retourne les fonds peers d'une stratégie Carmignac donnée.
|
||||
|
||||
Paramètres
|
||||
----------
|
||||
strategy_code : ex. "CAD", "CARE"
|
||||
exclude_index : si True, exclut les fonds indiciels (ETF)
|
||||
exclude_carmignac : si True, exclut les fonds Carmignac eux-mêmes
|
||||
"""
|
||||
self._check_loaded()
|
||||
mask = self.peers_df["carmignac_strategy"] == strategy_code
|
||||
df = self.peers_df[mask].copy()
|
||||
|
||||
if exclude_index:
|
||||
df = df[~df["is_index_fund"]]
|
||||
if exclude_carmignac:
|
||||
df = df[~df["is_carmignac"]]
|
||||
|
||||
return df
|
||||
|
||||
def get_carmignac_isin_for_strategy(self, strategy_code: str) -> list[str]:
|
||||
"""Retourne la liste des ISIN Carmignac pour une stratégie donnée."""
|
||||
self._check_loaded()
|
||||
mask = (
|
||||
(self.carmignac_df["carmignac_strategy"] == strategy_code)
|
||||
& self.carmignac_df["ISIN"].notna()
|
||||
)
|
||||
return self.carmignac_df[mask]["ISIN"].tolist()
|
||||
|
||||
def get_competitor_isin_for_strategy(self, strategy_code: str,
|
||||
exclude_index: bool = True) -> list[str]:
|
||||
"""Retourne la liste des ISIN concurrents pour une stratégie donnée."""
|
||||
self._check_loaded()
|
||||
peers = self.get_peers_for_strategy(strategy_code,
|
||||
exclude_index=exclude_index,
|
||||
exclude_carmignac=True)
|
||||
return peers["ISIN"].dropna().tolist()
|
||||
|
||||
def get_strategy_universe(self) -> pd.DataFrame:
|
||||
"""
|
||||
Retourne un résumé par stratégie Carmignac :
|
||||
nombre de shareclasses Carmignac, nombre de concurrents,
|
||||
catégories Morningstar couvertes.
|
||||
"""
|
||||
self._check_loaded()
|
||||
rows = []
|
||||
for strat in self.peers_df["carmignac_strategy"].unique():
|
||||
sub = self.peers_df[self.peers_df["carmignac_strategy"] == strat]
|
||||
rows.append({
|
||||
"strategy": strat,
|
||||
"n_carmignac_sc": sub["is_carmignac"].sum(),
|
||||
"n_competitors": (~sub["is_carmignac"] & ~sub["is_index_fund"]).sum(),
|
||||
"n_index_funds": sub["is_index_fund"].sum(),
|
||||
"ms_categories": ", ".join(sub["Morningstar Category"].dropna().unique()),
|
||||
"broad_category": sub["Global Broad Category Group"].dropna().iloc[0]
|
||||
if len(sub) > 0 else "",
|
||||
})
|
||||
return pd.DataFrame(rows)
|
||||
|
||||
def resolve_shareclass_name(self, name: str) -> str | None:
|
||||
"""
|
||||
Tente de retrouver l'ISIN depuis un nom de shareclass Morningstar.
|
||||
Matching exact d'abord, puis matching partiel (substring).
|
||||
Utilisé pour joindre weekly_perf avec la table peers.
|
||||
"""
|
||||
self._check_loaded()
|
||||
# 1. Exact match
|
||||
if name in self.isin_map:
|
||||
return self.isin_map[name]
|
||||
# 2. Substring match (le nom dans weekly_perf peut légèrement différer)
|
||||
name_lower = name.lower()
|
||||
for ref_name, isin in self.isin_map.items():
|
||||
if ref_name.lower() in name_lower or name_lower in ref_name.lower():
|
||||
return isin
|
||||
return None
|
||||
|
||||
# ── Helpers internes ──────────────────────────────────────────────────────
|
||||
|
||||
def _check_loaded(self):
|
||||
if not self._loaded:
|
||||
raise RuntimeError("Appeler .load() avant d'utiliser PeersLoader.")
|
||||
|
||||
def _print_summary(self):
|
||||
print("\n── Résumé PeersLoader ───────────────────────────────────")
|
||||
universe = self.get_strategy_universe()
|
||||
print(universe.to_string(index=False))
|
||||
print(f"\nTotal fonds uniques (ISIN) : {self.peers_df['ISIN'].nunique()}")
|
||||
print(f" dont Carmignac : {self.carmignac_df['ISIN'].nunique()}")
|
||||
print(f" dont concurrents actifs : "
|
||||
f"{self.competitors_df[~self.competitors_df['is_index_fund']]['ISIN'].nunique()}")
|
||||
print("─────────────────────────────────────────────────────────\n")
|
||||
|
||||
|
||||
# ── Usage autonome ────────────────────────────────────────────────────────────
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="PeersLoader — test autonome")
|
||||
parser.add_argument("peers_dir", nargs="?", default=".",
|
||||
help="Chemin local ou s3://bucket/prefix/")
|
||||
parser.add_argument("--aws-key", default=None, help="AWS Access Key ID")
|
||||
parser.add_argument("--aws-secret", default=None, help="AWS Secret Access Key")
|
||||
parser.add_argument("--aws-token", default=None, help="AWS Session Token (optionnel)")
|
||||
parser.add_argument("--aws-profile",default=None, help="Profil AWS CLI (~/.aws/credentials)")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Construction de s3_options uniquement si des credentials sont fournis
|
||||
s3_options = None
|
||||
if args.aws_key and args.aws_secret:
|
||||
s3_options = {"key": args.aws_key, "secret": args.aws_secret}
|
||||
if args.aws_token:
|
||||
s3_options["token"] = args.aws_token
|
||||
elif args.aws_profile:
|
||||
s3_options = {"profile": args.aws_profile}
|
||||
|
||||
loader = PeersLoader(args.peers_dir, s3_options=s3_options)
|
||||
loader.load()
|
||||
|
||||
print("\nShareclasses Carmignac identifiées :")
|
||||
print(loader.carmignac_df[["carmignac_strategy", "Name", "ISIN",
|
||||
"Morningstar Category"]].to_string(index=False))
|
||||
|
||||
print("\nTest resolve_shareclass_name :")
|
||||
test_names = [
|
||||
"Carmignac Pf Asia Discovery A EUR Acc",
|
||||
"Carmignac Absolute Ret Eur A EUR Acc",
|
||||
"MS INVF Asia Opportunity A",
|
||||
]
|
||||
for n in test_names:
|
||||
isin = loader.resolve_shareclass_name(n)
|
||||
print(f" {n!r:50s} → {isin}")
|
||||
415
src/pipeline.py
415
src/pipeline.py
|
|
@ -1,415 +0,0 @@
|
|||
"""
|
||||
pipeline.py
|
||||
------------
|
||||
Orchestrateur du pipeline complet.
|
||||
|
||||
Ce script assemble les quatre modules dans l'ordre et produit :
|
||||
- dataset_features.csv : dataset de modelisation complet
|
||||
- model_results.png : graphiques walk-forward + importances
|
||||
- peers_summary.csv : resume de l'univers concurrentiel
|
||||
|
||||
Modules appeles :
|
||||
peers_loader.py -> PeersLoader
|
||||
relative_performance.py -> RelativePerformanceCalculator
|
||||
feature_engineering.py -> FeatureBuilder
|
||||
predictive_model.py -> WalkForwardModel
|
||||
|
||||
Configuration :
|
||||
Tous les chemins sont centralises dans la section CONFIG ci-dessous.
|
||||
Les chemins s3:// sont supportes nativement pour les inputs ET les outputs.
|
||||
|
||||
Usage (local) :
|
||||
python pipeline.py
|
||||
|
||||
Usage (S3 / MinIO) :
|
||||
python pipeline.py \\
|
||||
--peers-dir s3://projet-bdc-carmignac-g3/peers/ \\
|
||||
--stocks s3://projet-bdc-carmignac-g3/aum_equity_95pct.csv \\
|
||||
--perf s3://projet-bdc-data/carmignac/Data Modélisation/competitors/weekly_perf_full.csv \\
|
||||
--out-dir s3://projet-bdc-carmignac-g3/outputs/
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import pandas as pd
|
||||
import s3fs
|
||||
|
||||
# -- Imports des modules locaux -----------------------------------------------
|
||||
from peers_loader import PeersLoader
|
||||
from relative_performance import RelativePerformanceCalculator, summarize_relative_performance
|
||||
from feature_engineering import FeatureBuilder
|
||||
from predictive_model import WalkForwardModel
|
||||
|
||||
|
||||
S3_PREFIX = "s3://"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CONFIG — Pointeur vers les donnees completes
|
||||
# =============================================================================
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
# Dossier contenant tous les *_peers.csv (local ou s3://)
|
||||
"peers_dir": "s3://projet-bdc-carmignac-g3/peers/",
|
||||
|
||||
# Fichiers de donnees principaux (local ou s3://)
|
||||
"stocks_path": "s3://projet-bdc-carmignac-g3/stock_repaired.csv",
|
||||
"perf_path": "s3://projet-bdc-data/carmignac/Data Modélisation/competitors/weekly_perf_full.csv",
|
||||
|
||||
# Outputs (local ou s3://)
|
||||
"out_dataset": "dataset_features.csv",
|
||||
"out_plot": "model_results.png",
|
||||
"out_peers": "peers_summary.csv",
|
||||
"out_rel_perf": "relative_performance.csv",
|
||||
|
||||
# Parametres modele
|
||||
"target_lag": 1, # horizon de prediction (mois)
|
||||
"min_train_frac": 0.4, # fraction min de dates en train
|
||||
"perf_periods": None, # None = toutes disponibles
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
|
||||
|
||||
# -- Helpers S3 ---------------------------------------------------------------
|
||||
|
||||
def build_s3_filesystem() -> s3fs.S3FileSystem:
|
||||
"""
|
||||
Construit le filesystem S3 avec les credentials du groupe GENES / MinIO.
|
||||
|
||||
Les credentials sont lus depuis les variables d'environnement :
|
||||
AWS_ACCESS_KEY_ID
|
||||
AWS_SECRET_ACCESS_KEY
|
||||
AWS_SESSION_TOKEN (optionnel — requis si session temporaire)
|
||||
|
||||
L'endpoint MinIO est fixe : minio-simple.lab.groupe-genes.fr
|
||||
"""
|
||||
fs = s3fs.S3FileSystem(
|
||||
client_kwargs={
|
||||
"endpoint_url": "https://minio-simple.lab.groupe-genes.fr"
|
||||
},
|
||||
key=os.environ["AWS_ACCESS_KEY_ID"],
|
||||
secret=os.environ["AWS_SECRET_ACCESS_KEY"],
|
||||
token=os.environ.get("AWS_SESSION_TOKEN"), # None si absent
|
||||
)
|
||||
return fs
|
||||
|
||||
|
||||
def build_s3_options() -> dict:
|
||||
"""
|
||||
Construit le dictionnaire s3_options attendu par PeersLoader._S3Backend,
|
||||
en utilisant les memes credentials et endpoint que build_s3_filesystem().
|
||||
"""
|
||||
return {
|
||||
"client_kwargs": {
|
||||
"endpoint_url": "https://minio-simple.lab.groupe-genes.fr"
|
||||
},
|
||||
"key": os.environ["AWS_ACCESS_KEY_ID"],
|
||||
"secret": os.environ["AWS_SECRET_ACCESS_KEY"],
|
||||
"token": os.environ.get("AWS_SESSION_TOKEN"),
|
||||
}
|
||||
|
||||
|
||||
def _is_s3(path: str) -> bool:
|
||||
return str(path).startswith(S3_PREFIX)
|
||||
|
||||
|
||||
def _s3_key(path: str) -> str:
|
||||
"""Retire le prefixe s3:// pour obtenir la cle brute attendue par s3fs."""
|
||||
return path[len(S3_PREFIX):]
|
||||
|
||||
|
||||
def read_csv_any(path: str, fs: Optional[s3fs.S3FileSystem] = None,
|
||||
sep=",") -> pd.DataFrame:
|
||||
"""
|
||||
Lit un CSV depuis un chemin local ou S3, de maniere transparente.
|
||||
|
||||
Parametres
|
||||
----------
|
||||
path : chemin local ou s3://bucket/key
|
||||
fs : filesystem s3fs (obligatoire si path est S3)
|
||||
kwargs : passes a pd.read_csv
|
||||
"""
|
||||
if _is_s3(path):
|
||||
if fs is None:
|
||||
raise ValueError("Un filesystem s3fs est requis pour lire depuis S3.")
|
||||
with fs.open(_s3_key(path), "rb") as f:
|
||||
return pd.read_csv(io.BytesIO(f.read()), sep=sep)
|
||||
return pd.read_csv(path, sep=sep)
|
||||
|
||||
|
||||
def write_csv_any(df: pd.DataFrame, path: str,
|
||||
fs: Optional[s3fs.S3FileSystem] = None, **kwargs):
|
||||
"""
|
||||
Ecrit un DataFrame en CSV vers un chemin local ou S3.
|
||||
|
||||
Parametres
|
||||
----------
|
||||
df : DataFrame a ecrire
|
||||
path : chemin local ou s3://bucket/key
|
||||
fs : filesystem s3fs (obligatoire si path est S3)
|
||||
kwargs : passes a df.to_csv (index=False par defaut)
|
||||
"""
|
||||
kwargs.setdefault("index", False)
|
||||
if _is_s3(path):
|
||||
if fs is None:
|
||||
raise ValueError("Un filesystem s3fs est requis pour ecrire sur S3.")
|
||||
csv_bytes = df.to_csv(**kwargs).encode("utf-8")
|
||||
with fs.open(_s3_key(path), "wb") as f:
|
||||
f.write(csv_bytes)
|
||||
else:
|
||||
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
||||
df.to_csv(path, **kwargs)
|
||||
|
||||
|
||||
def write_bytes_any(data: bytes, path: str,
|
||||
fs: Optional[s3fs.S3FileSystem] = None):
|
||||
"""
|
||||
Ecrit des bytes bruts (ex : image PNG) vers un chemin local ou S3.
|
||||
"""
|
||||
if _is_s3(path):
|
||||
if fs is None:
|
||||
raise ValueError("Un filesystem s3fs est requis pour ecrire sur S3.")
|
||||
with fs.open(_s3_key(path), "wb") as f:
|
||||
f.write(data)
|
||||
else:
|
||||
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(path).write_bytes(data)
|
||||
|
||||
|
||||
def path_exists(path: str, fs: Optional[s3fs.S3FileSystem] = None) -> bool:
|
||||
"""Verifie l'existence d'un fichier local ou S3."""
|
||||
if _is_s3(path):
|
||||
return fs is not None and fs.exists(_s3_key(path))
|
||||
return Path(path).exists()
|
||||
|
||||
|
||||
def file_size_kb(path: str, fs: Optional[s3fs.S3FileSystem] = None) -> int:
|
||||
"""Retourne la taille en Ko d'un fichier local ou S3."""
|
||||
if _is_s3(path):
|
||||
if fs is None:
|
||||
return 0
|
||||
return fs.info(_s3_key(path)).get("size", 0) // 1024
|
||||
return Path(path).stat().st_size // 1024
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def run_pipeline(config: dict, verbose: bool = True) -> dict:
|
||||
"""
|
||||
Execute le pipeline complet et retourne un dictionnaire des outputs.
|
||||
|
||||
Retourne
|
||||
--------
|
||||
dict avec les cles :
|
||||
loader : PeersLoader
|
||||
dataset : DataFrame features + target
|
||||
model : WalkForwardModel entraine
|
||||
results_df : metriques walk-forward
|
||||
"""
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(" PIPELINE CARMIGNAC x ENSAE")
|
||||
print(" Performance -> Flux nets")
|
||||
print("=" * 60)
|
||||
|
||||
# Construire le filesystem S3 si au moins un chemin est S3
|
||||
any_s3 = any(
|
||||
_is_s3(str(config.get(k, "")))
|
||||
for k in ["peers_dir", "stocks_path", "perf_path",
|
||||
"out_dataset", "out_plot", "out_peers", "out_rel_perf"]
|
||||
)
|
||||
fs: Optional[s3fs.S3FileSystem] = build_s3_filesystem() if any_s3 else None
|
||||
if fs is not None:
|
||||
print(" Mode S3/MinIO actif (minio-simple.lab.groupe-genes.fr)")
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# ETAPE 1 — Chargement des peers
|
||||
# -------------------------------------------------------------------------
|
||||
print("\n[1/5] Chargement des peers...")
|
||||
|
||||
s3_opts = build_s3_options() if _is_s3(config["peers_dir"]) else None
|
||||
loader = PeersLoader(config["peers_dir"], s3_options=s3_opts)
|
||||
loader.load(verbose=verbose)
|
||||
|
||||
write_csv_any(loader.get_strategy_universe(), config["out_peers"], fs=fs)
|
||||
print(f" -> {config['out_peers']} sauvegarde")
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# ETAPE 2 — Chargement des donnees
|
||||
# -------------------------------------------------------------------------
|
||||
print("\n[2/5] Chargement des donnees de marche...")
|
||||
|
||||
stocks_df = read_csv_any(config["stocks_path"], fs=fs, sep=";")
|
||||
perf_df = read_csv_any(config["perf_path"], fs=fs, sep=";")
|
||||
perf_df["Date"] = pd.to_datetime(perf_df["Date"])
|
||||
|
||||
print(f" stocks : {stocks_df.shape[0]:,} lignes")
|
||||
print(f" perf : {perf_df.shape[0]:,} lignes | "
|
||||
f"periodes : {sorted(perf_df['perfPeriod'].unique())}")
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# ETAPE 3 — Feature engineering (incl. perf relative)
|
||||
# -------------------------------------------------------------------------
|
||||
print("\n[3/5] Feature engineering...")
|
||||
rel_calc = RelativePerformanceCalculator(loader)
|
||||
builder = FeatureBuilder(loader, rel_calc)
|
||||
|
||||
dataset = builder.build(
|
||||
stocks_df = stocks_df,
|
||||
perf_df = perf_df,
|
||||
target_lag = config["target_lag"],
|
||||
perf_periods = config["perf_periods"],
|
||||
verbose = verbose,
|
||||
)
|
||||
|
||||
if not dataset.empty:
|
||||
# Export performances relatives
|
||||
rel_cols = [c for c in dataset.columns if c.startswith("rel_")]
|
||||
id_cols = [c for c in ["Registrar Account - ID", "Product - Isin",
|
||||
"Centralisation Date", "carmignac_strategy"]
|
||||
if c in dataset.columns]
|
||||
if rel_cols:
|
||||
rel_export = dataset[id_cols + rel_cols].dropna(subset=rel_cols, how="all")
|
||||
write_csv_any(rel_export, config["out_rel_perf"], fs=fs)
|
||||
print(f" -> {config['out_rel_perf']} sauvegarde ({len(rel_export):,} lignes)")
|
||||
|
||||
# Export dataset complet
|
||||
write_csv_any(dataset, config["out_dataset"], fs=fs)
|
||||
print(f" -> {config['out_dataset']} sauvegarde ({len(dataset):,} lignes)")
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# ETAPE 4 — Modelisation
|
||||
# -------------------------------------------------------------------------
|
||||
print("\n[4/5] Modelisation (walk-forward)...")
|
||||
feature_cols = FeatureBuilder.get_feature_columns(dataset) if not dataset.empty else []
|
||||
model = WalkForwardModel(min_train_frac=config["min_train_frac"])
|
||||
|
||||
if feature_cols and not dataset.empty:
|
||||
results_df = model.fit_evaluate(dataset, feature_cols, verbose=verbose)
|
||||
else:
|
||||
print(" Pas de features disponibles — modelisation ignoree.")
|
||||
results_df = pd.DataFrame()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# ETAPE 5 — Visualisations
|
||||
# -------------------------------------------------------------------------
|
||||
print("\n[5/5] Generation des visualisations...")
|
||||
|
||||
# predictive_model.plot_results() ecrit un fichier local.
|
||||
# Si l'output cible est S3, on ecrit dans /tmp puis on transfere.
|
||||
tmp_plot = "/tmp/model_results_tmp.png"
|
||||
model.plot_results(output_path=tmp_plot)
|
||||
|
||||
if _is_s3(config["out_plot"]):
|
||||
write_bytes_any(Path(tmp_plot).read_bytes(), config["out_plot"], fs=fs)
|
||||
print(f" -> {config['out_plot']} transfere sur S3")
|
||||
else:
|
||||
shutil.move(tmp_plot, config["out_plot"])
|
||||
print(f" -> {config['out_plot']} sauvegarde en local")
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# RESUME FINAL
|
||||
# -------------------------------------------------------------------------
|
||||
print("\n" + "=" * 60)
|
||||
print(" RESUME DU PIPELINE")
|
||||
print("=" * 60)
|
||||
|
||||
print(f"\nStrategies Carmignac identifiees : "
|
||||
f"{loader.peers_df['carmignac_strategy'].nunique()}")
|
||||
print(f"Shareclasses Carmignac : {len(loader.carmignac_df)}")
|
||||
print(f"Fonds concurrents (actifs) : "
|
||||
f"{len(loader.competitors_df[~loader.competitors_df['is_index_fund']])}")
|
||||
|
||||
if not dataset.empty:
|
||||
rel_cols_present = [c for c in dataset.columns if c.startswith("rel_")]
|
||||
perf_cols_present = [c for c in dataset.columns if c.startswith("perf_")]
|
||||
aum_cols_present = [c for c in feature_cols if c.startswith("aum_")]
|
||||
print(f"\nFeatures AUM : {len(aum_cols_present)}")
|
||||
print(f"Features perf absolue : {len(perf_cols_present)}")
|
||||
print(f"Features perf relative (peers) : {len(rel_cols_present)}")
|
||||
print(f"Lignes dans le dataset : {len(dataset):,}")
|
||||
|
||||
if not results_df.empty:
|
||||
best = model.get_best_model()
|
||||
best_mae = results_df[results_df["model"] == best]["mae"].median()
|
||||
baseline_mae = results_df[results_df["model"] == "Baseline (zero)"]["mae"].median()
|
||||
gain = (1 - best_mae / baseline_mae) * 100 if baseline_mae > 0 else 0
|
||||
print(f"\nMeilleur modele : {best}")
|
||||
print(f"MAE mediane : {best_mae:,.0f} EUR")
|
||||
print(f"Gain vs baseline : {gain:.1f}%")
|
||||
if model.importances_:
|
||||
top = model.get_top_features(3)
|
||||
print(f"Top 3 features : {top}")
|
||||
|
||||
print("\nOutputs generes :")
|
||||
for key in ["out_dataset", "out_plot", "out_peers", "out_rel_perf"]:
|
||||
p = config.get(key, "")
|
||||
if p and path_exists(p, fs=fs):
|
||||
size = file_size_kb(p, fs=fs)
|
||||
print(f" OK {p:<50} ({size} Ko)")
|
||||
elif p:
|
||||
print(f" -- {p:<50} (non genere)")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
|
||||
return {
|
||||
"loader": loader,
|
||||
"dataset": dataset,
|
||||
"model": model,
|
||||
"results_df": results_df,
|
||||
}
|
||||
|
||||
|
||||
# -- CLI -----------------------------------------------------------------------
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Pipeline Carmignac — Performance -> Flux nets"
|
||||
)
|
||||
parser.add_argument("--peers-dir", default=DEFAULT_CONFIG["peers_dir"],
|
||||
help="Dossier contenant les *_peers.csv (local ou s3://)")
|
||||
parser.add_argument("--stocks", default=DEFAULT_CONFIG["stocks_path"],
|
||||
help="Fichier AUM mensuel (local ou s3://)")
|
||||
parser.add_argument("--perf", default=DEFAULT_CONFIG["perf_path"],
|
||||
help="Fichier performances hebdomadaires (local ou s3://)")
|
||||
parser.add_argument("--out-dir", default=".",
|
||||
help="Dossier de sortie des outputs (local ou s3://)")
|
||||
parser.add_argument("--target-lag", default=DEFAULT_CONFIG["target_lag"],
|
||||
type=int, help="Horizon de prediction (mois)")
|
||||
parser.add_argument("--quiet", action="store_true",
|
||||
help="Mode silencieux")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
out_dir = args.out_dir.rstrip("/")
|
||||
|
||||
def out(filename: str) -> str:
|
||||
"""Construit un chemin de sortie dans le dossier cible (local ou S3)."""
|
||||
if _is_s3(out_dir):
|
||||
return f"{out_dir}/{filename}"
|
||||
p = Path(out_dir)
|
||||
p.mkdir(parents=True, exist_ok=True)
|
||||
return str(p / filename)
|
||||
|
||||
config = {
|
||||
**DEFAULT_CONFIG,
|
||||
"peers_dir": args.peers_dir,
|
||||
"stocks_path": args.stocks,
|
||||
"perf_path": args.perf,
|
||||
"target_lag": args.target_lag,
|
||||
"out_dataset": out("dataset_features.csv"),
|
||||
"out_plot": out("model_results.png"),
|
||||
"out_peers": out("peers_summary.csv"),
|
||||
"out_rel_perf": out("relative_performance.csv"),
|
||||
}
|
||||
|
||||
run_pipeline(config, verbose=not args.quiet)
|
||||
|
|
@ -1,422 +0,0 @@
|
|||
"""
|
||||
predictive_model.py
|
||||
────────────────────
|
||||
Modélisation prédictive des flux nets avec walk-forward validation.
|
||||
|
||||
Ce module est intentionnellement séparé du feature engineering :
|
||||
il prend en entrée le dataset produit par FeatureBuilder et se concentre
|
||||
sur l'entraînement, la validation et l'interprétation des modèles.
|
||||
|
||||
Modèles implémentés :
|
||||
- Baseline : prédiction zéro (benchmark naïf)
|
||||
- Ridge : régression linéaire régularisée (interprétable)
|
||||
- RandomForest : non-linéaire, robuste aux outliers
|
||||
- GradientBoosting : état de l'art sur données tabulaires
|
||||
|
||||
Validation : walk-forward expanding window (pas de data leakage).
|
||||
|
||||
Usage :
|
||||
from feature_engineering import FeatureBuilder
|
||||
from predictive_model import WalkForwardModel
|
||||
|
||||
feature_cols = FeatureBuilder.get_feature_columns(dataset)
|
||||
model = WalkForwardModel()
|
||||
results = model.fit_evaluate(dataset, feature_cols)
|
||||
model.plot_results(results, output_path="results.png")
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.gridspec as gridspec
|
||||
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
|
||||
from sklearn.linear_model import Ridge
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
from sklearn.metrics import mean_absolute_error, r2_score
|
||||
from sklearn.inspection import permutation_importance
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
|
||||
# ── Constantes ────────────────────────────────────────────────────────────────
|
||||
|
||||
COLORS = ["#1f4e79", "#2e75b6", "#70ad47", "#ed7d31", "#a50026"]
|
||||
|
||||
MODEL_CONFIGS = {
|
||||
"Ridge": {
|
||||
"cls": Ridge,
|
||||
"kwargs": {"alpha": 1.0},
|
||||
"scale": True, # nécessite standardisation
|
||||
},
|
||||
"Random Forest": {
|
||||
"cls": RandomForestRegressor,
|
||||
"kwargs": {"n_estimators": 200, "max_depth": 6,
|
||||
"min_samples_leaf": 3, "random_state": 42, "n_jobs": -1},
|
||||
"scale": False,
|
||||
},
|
||||
"Gradient Boosting": {
|
||||
"cls": GradientBoostingRegressor,
|
||||
"kwargs": {"n_estimators": 200, "max_depth": 4,
|
||||
"learning_rate": 0.05, "subsample": 0.8,
|
||||
"random_state": 42},
|
||||
"scale": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# ── Classe principale ─────────────────────────────────────────────────────────
|
||||
|
||||
class WalkForwardModel:
|
||||
"""
|
||||
Entraîne et évalue plusieurs modèles via walk-forward validation.
|
||||
|
||||
Paramètres
|
||||
----------
|
||||
date_col : colonne de date dans le dataset (snapshots mensuels)
|
||||
target_col : colonne de la variable cible
|
||||
min_train_frac : fraction minimale de dates en train (défaut 0.4)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
date_col: str = "Centralisation Date",
|
||||
target_col: str = "flux_net_proxy",
|
||||
min_train_frac: float = 0.4):
|
||||
self.date_col = date_col
|
||||
self.target_col = target_col
|
||||
self.min_train_frac = min_train_frac
|
||||
|
||||
# Stockage post-fit
|
||||
self.results_df_ = pd.DataFrame()
|
||||
self.importances_ = {}
|
||||
self.final_models_ = {}
|
||||
|
||||
# ── Walk-forward ──────────────────────────────────────────────────────────
|
||||
|
||||
def fit_evaluate(self,
|
||||
dataset: pd.DataFrame,
|
||||
feature_cols: list[str],
|
||||
verbose: bool = True) -> pd.DataFrame:
|
||||
"""
|
||||
Exécute le walk-forward expanding window sur tous les modèles.
|
||||
|
||||
Retourne un DataFrame avec les métriques par (modèle, date de test).
|
||||
"""
|
||||
dataset = dataset.copy()
|
||||
dataset[self.date_col] = pd.to_datetime(dataset[self.date_col])
|
||||
|
||||
dates_sorted = sorted(dataset[self.date_col].unique())
|
||||
n_dates = len(dates_sorted)
|
||||
min_train = max(3, int(n_dates * self.min_train_frac))
|
||||
|
||||
if verbose:
|
||||
print(f"Walk-forward : {n_dates} dates | min train = {min_train}")
|
||||
|
||||
if n_dates <= min_train:
|
||||
print("⚠ Pas assez de dates pour le walk-forward. "
|
||||
"Augmenter la taille du dataset.")
|
||||
return pd.DataFrame()
|
||||
|
||||
records = []
|
||||
for test_idx in range(min_train, n_dates):
|
||||
train_dates = dates_sorted[:test_idx]
|
||||
test_date = dates_sorted[test_idx]
|
||||
|
||||
train = dataset[dataset[self.date_col].isin(train_dates)]
|
||||
test = dataset[dataset[self.date_col] == test_date]
|
||||
|
||||
X_train, y_train = self._prepare(train, feature_cols, fit=True)
|
||||
X_test, y_test = self._prepare(test, feature_cols, fit=False)
|
||||
|
||||
if len(X_test) == 0 or y_test.std() == 0:
|
||||
continue
|
||||
|
||||
# Baseline
|
||||
records.append({
|
||||
"test_date": test_date,
|
||||
"model": "Baseline (zéro)",
|
||||
"mae": mean_absolute_error(y_test, np.zeros(len(y_test))),
|
||||
"r2": r2_score(y_test, np.zeros(len(y_test))),
|
||||
"n_test": len(y_test),
|
||||
"n_train": len(X_train),
|
||||
})
|
||||
|
||||
for model_name, cfg in MODEL_CONFIGS.items():
|
||||
model = cfg["cls"](**cfg["kwargs"])
|
||||
scaler = StandardScaler() if cfg["scale"] else None
|
||||
|
||||
X_tr = scaler.fit_transform(X_train) if scaler else X_train
|
||||
X_te = scaler.transform(X_test) if scaler else X_test
|
||||
|
||||
model.fit(X_tr, y_train)
|
||||
preds = model.predict(X_te)
|
||||
|
||||
records.append({
|
||||
"test_date": test_date,
|
||||
"model": model_name,
|
||||
"mae": mean_absolute_error(y_test, preds),
|
||||
"r2": r2_score(y_test, preds),
|
||||
"n_test": len(y_test),
|
||||
"n_train": len(X_train),
|
||||
})
|
||||
|
||||
self.results_df_ = pd.DataFrame(records)
|
||||
|
||||
if verbose:
|
||||
self._print_results_summary()
|
||||
|
||||
# Entraîner les modèles finaux sur toutes les données
|
||||
self._fit_final_models(dataset, feature_cols)
|
||||
|
||||
return self.results_df_
|
||||
|
||||
# ── Modèles finaux (pour importance des variables) ────────────────────────
|
||||
|
||||
def _fit_final_models(self, dataset: pd.DataFrame, feature_cols: list[str]):
|
||||
"""Entraîne chaque modèle sur l'intégralité du dataset (pour l'interprétation)."""
|
||||
X, y = self._prepare(dataset, feature_cols, fit=True)
|
||||
if len(X) == 0:
|
||||
return
|
||||
|
||||
for model_name, cfg in MODEL_CONFIGS.items():
|
||||
model = cfg["cls"](**cfg["kwargs"])
|
||||
scaler = StandardScaler() if cfg["scale"] else None
|
||||
X_fit = scaler.fit_transform(X) if scaler else X
|
||||
model.fit(X_fit, y)
|
||||
self.final_models_[model_name] = (model, scaler, feature_cols)
|
||||
|
||||
# Importance des variables : Random Forest (Gini) + Permutation
|
||||
rf_model, _, _ = self.final_models_.get("Random Forest", (None, None, None))
|
||||
if rf_model is not None:
|
||||
self.importances_["gini"] = pd.Series(
|
||||
rf_model.feature_importances_, index=feature_cols
|
||||
).sort_values(ascending=False)
|
||||
|
||||
perm = permutation_importance(
|
||||
rf_model, X, y, n_repeats=10, random_state=42, n_jobs=-1
|
||||
)
|
||||
self.importances_["permutation"] = pd.Series(
|
||||
perm.importances_mean, index=feature_cols
|
||||
).sort_values(ascending=False)
|
||||
|
||||
# ── Prédiction ────────────────────────────────────────────────────────────
|
||||
|
||||
def predict(self, new_data: pd.DataFrame,
|
||||
model_name: str = "Random Forest") -> np.ndarray:
|
||||
"""Prédit les flux nets sur de nouvelles données."""
|
||||
if model_name not in self.final_models_:
|
||||
raise ValueError(f"Modèle '{model_name}' non disponible. "
|
||||
f"Disponibles : {list(self.final_models_.keys())}")
|
||||
|
||||
model, scaler, feature_cols = self.final_models_[model_name]
|
||||
X, _ = self._prepare(new_data, feature_cols, fit=False)
|
||||
X_pred = scaler.transform(X) if scaler else X
|
||||
return model.predict(X_pred)
|
||||
|
||||
# ── Visualisation ─────────────────────────────────────────────────────────
|
||||
|
||||
def plot_results(self, output_path: str = "model_results.png"):
|
||||
"""Génère les graphiques de résultats du walk-forward."""
|
||||
|
||||
if self.results_df_.empty:
|
||||
print("⚠ Aucun résultat à visualiser (walk-forward non exécuté).")
|
||||
self._plot_schema(output_path)
|
||||
return
|
||||
|
||||
fig = plt.figure(figsize=(16, 14))
|
||||
fig.patch.set_facecolor("white")
|
||||
gs = gridspec.GridSpec(3, 2, figure=fig, hspace=0.45, wspace=0.35)
|
||||
|
||||
# ── [A] MAE par modèle et date ────────────────────────────────────────
|
||||
ax1 = fig.add_subplot(gs[0, :])
|
||||
for i, (model_name, grp) in enumerate(self.results_df_.groupby("model")):
|
||||
style = "--" if "Baseline" in model_name else "-"
|
||||
ax1.plot(grp["test_date"], grp["mae"],
|
||||
label=model_name, linewidth=1.8,
|
||||
color=COLORS[i % len(COLORS)], linestyle=style)
|
||||
ax1.set_title("Walk-Forward Validation — MAE par modèle", fontsize=13, fontweight="bold")
|
||||
ax1.set_ylabel("MAE (€)")
|
||||
ax1.legend(fontsize=9)
|
||||
ax1.tick_params(axis="x", rotation=20)
|
||||
|
||||
# ── [B] R² par modèle ─────────────────────────────────────────────────
|
||||
ax2 = fig.add_subplot(gs[1, 0])
|
||||
for i, (model_name, grp) in enumerate(self.results_df_.groupby("model")):
|
||||
if "Baseline" not in model_name:
|
||||
ax2.plot(grp["test_date"], grp["r2"].clip(-1, 1),
|
||||
label=model_name, linewidth=1.5,
|
||||
color=COLORS[i % len(COLORS)])
|
||||
ax2.axhline(0, color="black", linestyle="--", linewidth=1, alpha=0.5)
|
||||
ax2.set_title("R² par modèle", fontsize=12, fontweight="bold")
|
||||
ax2.set_ylabel("R²")
|
||||
ax2.legend(fontsize=9)
|
||||
ax2.tick_params(axis="x", rotation=20)
|
||||
|
||||
# ── [C] MAE agrégée (boîtes) ──────────────────────────────────────────
|
||||
ax3 = fig.add_subplot(gs[1, 1])
|
||||
model_names = self.results_df_["model"].unique().tolist()
|
||||
mae_by_model = [
|
||||
self.results_df_[self.results_df_["model"] == m]["mae"].dropna().values
|
||||
for m in model_names
|
||||
]
|
||||
bp = ax3.boxplot(mae_by_model, labels=model_names, patch_artist=True,
|
||||
medianprops=dict(color="black", linewidth=2))
|
||||
for patch, color in zip(bp["boxes"], COLORS):
|
||||
patch.set_facecolor(color)
|
||||
patch.set_alpha(0.7)
|
||||
ax3.set_title("Distribution de MAE (tous folds)", fontsize=12, fontweight="bold")
|
||||
ax3.set_ylabel("MAE (€)")
|
||||
ax3.tick_params(axis="x", rotation=20)
|
||||
|
||||
# ── [D] Importance des variables (Gini) ───────────────────────────────
|
||||
ax4 = fig.add_subplot(gs[2, 0])
|
||||
if "gini" in self.importances_:
|
||||
imp = self.importances_["gini"].head(15)
|
||||
colors_imp = [
|
||||
"#70ad47" if c.startswith("rel_") else
|
||||
"#ed7d31" if c.startswith("perf_") else
|
||||
"#1f4e79"
|
||||
for c in imp.index
|
||||
]
|
||||
ax4.barh(imp.index[::-1], imp.values[::-1], color=colors_imp[::-1])
|
||||
ax4.set_title("Importance (Gini) — Top 15 features", fontsize=12, fontweight="bold")
|
||||
ax4.set_xlabel("Importance relative")
|
||||
from matplotlib.patches import Patch
|
||||
legend_elements = [
|
||||
Patch(color="#70ad47", label="Perf relative (peers)"),
|
||||
Patch(color="#ed7d31", label="Perf absolue"),
|
||||
Patch(color="#1f4e79", label="Comportement AUM"),
|
||||
]
|
||||
ax4.legend(handles=legend_elements, fontsize=8, loc="lower right")
|
||||
else:
|
||||
ax4.axis("off")
|
||||
ax4.text(0.5, 0.5, "Importance des variables\nnon disponible",
|
||||
ha="center", va="center", fontsize=12)
|
||||
|
||||
# ── [E] Importance permutation ────────────────────────────────────────
|
||||
ax5 = fig.add_subplot(gs[2, 1])
|
||||
if "permutation" in self.importances_:
|
||||
pimp = self.importances_["permutation"].head(15)
|
||||
pimp = pimp[pimp > 0] # garder seulement les features utiles
|
||||
colors_pimp = [
|
||||
"#70ad47" if c.startswith("rel_") else
|
||||
"#ed7d31" if c.startswith("perf_") else
|
||||
"#1f4e79"
|
||||
for c in pimp.index
|
||||
]
|
||||
ax5.barh(pimp.index[::-1], pimp.values[::-1], color=colors_pimp[::-1])
|
||||
ax5.set_title("Permutation Importance — Top 15", fontsize=12, fontweight="bold")
|
||||
ax5.set_xlabel("Δ MAE moyen (permutation)")
|
||||
else:
|
||||
ax5.axis("off")
|
||||
|
||||
plt.suptitle("Carmignac × ENSAE — Résultats du modèle prédictif",
|
||||
fontsize=14, fontweight="bold", y=1.01)
|
||||
|
||||
plt.savefig(output_path, dpi=150, bbox_inches="tight", facecolor="white")
|
||||
plt.close()
|
||||
print(f"✅ Graphiques sauvegardés : {output_path}")
|
||||
|
||||
def _plot_schema(self, output_path: str):
|
||||
"""Affiche un schéma du pipeline si les données sont insuffisantes."""
|
||||
fig, ax = plt.subplots(figsize=(12, 6))
|
||||
fig.patch.set_facecolor("white")
|
||||
ax.axis("off")
|
||||
ax.set_xlim(0, 10)
|
||||
ax.set_ylim(0, 5)
|
||||
|
||||
schema = (
|
||||
"WALK-FORWARD VALIDATION — SCHÉMA\n\n"
|
||||
" t₁ t₂ t₃ t₄ t₅ t₆ t₇ ...\n"
|
||||
" ─────────────────────────────────\n"
|
||||
" TRAIN ████████ │TEST│\n"
|
||||
" TRAIN ███████████│TEST│\n"
|
||||
" TRAIN ██████████████│TEST│\n"
|
||||
" ...\n\n"
|
||||
"Principe :\n"
|
||||
" → Expanding window : la fenêtre de train s'agrandit à chaque fold\n"
|
||||
" → Test = 1 date future (mois suivant)\n"
|
||||
" → Aucune information future dans le train → pas de data leakage\n\n"
|
||||
"Métriques calculées à chaque fold :\n"
|
||||
" → MAE (Mean Absolute Error) en € de flux\n"
|
||||
" → R² (coefficient de détermination)\n\n"
|
||||
"Relancer avec les données complètes pour obtenir les résultats réels."
|
||||
)
|
||||
ax.text(0.5, 4.8, schema, va="top", fontsize=11, fontfamily="monospace",
|
||||
bbox=dict(boxstyle="round", facecolor="#eaf2fb", alpha=0.8))
|
||||
ax.set_title("Modèle prédictif — En attente de données complètes",
|
||||
fontsize=13, fontweight="bold")
|
||||
|
||||
plt.savefig(output_path, dpi=150, bbox_inches="tight", facecolor="white")
|
||||
plt.close()
|
||||
print(f"✅ Schéma sauvegardé : {output_path}")
|
||||
|
||||
# ── Helpers internes ──────────────────────────────────────────────────────
|
||||
|
||||
def _prepare(self, df: pd.DataFrame,
|
||||
feature_cols: list[str],
|
||||
fit: bool) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Extrait X et y depuis le DataFrame, gère les NaN."""
|
||||
available = [c for c in feature_cols if c in df.columns]
|
||||
X = df[available].fillna(0).values
|
||||
y = df[self.target_col].values if self.target_col in df.columns else np.array([])
|
||||
return X, y
|
||||
|
||||
def _print_results_summary(self):
|
||||
print("\n── Résultats walk-forward (médiane sur tous les folds) ──")
|
||||
summary = (
|
||||
self.results_df_
|
||||
.groupby("model")
|
||||
.agg(
|
||||
MAE_median=("mae", "median"),
|
||||
MAE_mean=("mae", "mean"),
|
||||
R2_median=("r2", "median"),
|
||||
n_folds=("mae", "count"),
|
||||
)
|
||||
.round(4)
|
||||
.sort_values("MAE_median")
|
||||
)
|
||||
print(summary.to_string())
|
||||
print("─────────────────────────────────────────────────────────")
|
||||
|
||||
# ── API publique ──────────────────────────────────────────────────────────
|
||||
|
||||
def get_best_model(self) -> str:
|
||||
"""Retourne le nom du modèle avec la MAE médiane la plus faible."""
|
||||
if self.results_df_.empty:
|
||||
return "Random Forest"
|
||||
summary = (self.results_df_
|
||||
[self.results_df_["model"] != "Baseline (zéro)"]
|
||||
.groupby("model")["mae"]
|
||||
.median())
|
||||
return summary.idxmin()
|
||||
|
||||
def get_top_features(self, n: int = 10,
|
||||
method: str = "permutation") -> list[str]:
|
||||
"""Retourne les n features les plus importantes."""
|
||||
if method not in self.importances_:
|
||||
method = list(self.importances_.keys())[0] if self.importances_ else None
|
||||
if method is None:
|
||||
return []
|
||||
return self.importances_[method].head(n).index.tolist()
|
||||
|
||||
|
||||
# ── Usage autonome ────────────────────────────────────────────────────────────
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
from feature_engineering import FeatureBuilder
|
||||
|
||||
dataset_path = sys.argv[1] if len(sys.argv) > 1 else "dataset_features.csv"
|
||||
|
||||
dataset = pd.read_csv(dataset_path)
|
||||
feature_cols = FeatureBuilder.get_feature_columns(dataset)
|
||||
|
||||
print(f"Dataset : {dataset.shape} | {len(feature_cols)} features")
|
||||
|
||||
model = WalkForwardModel()
|
||||
results = model.fit_evaluate(dataset, feature_cols)
|
||||
model.plot_results("model_results.png")
|
||||
|
||||
if not results.empty:
|
||||
print(f"\nMeilleur modèle : {model.get_best_model()}")
|
||||
print(f"Top features : {model.get_top_features(5)}")
|
||||
|
|
@ -1,329 +0,0 @@
|
|||
"""
|
||||
relative_performance.py
|
||||
────────────────────────
|
||||
Calcul de métriques de performance relative de Carmignac vs ses vrais peers.
|
||||
|
||||
Contexte :
|
||||
weekly_perf contient déjà un percentile Morningstar (rang brut dans la
|
||||
catégorie). Ce module construit des métriques plus fines en s'appuyant
|
||||
sur la liste explicite de peers issue des fichiers *_peers.csv :
|
||||
|
||||
- Spread de performance Carmignac vs médiane des peers (par période)
|
||||
- Rang Carmignac dans son groupe de peers restreint (ex: top/bottom quartile)
|
||||
- Stabilité du rang sur fenêtres glissantes (volatilité du rang)
|
||||
- Ratio d'outperformance : % de semaines où Carmignac > médiane peers
|
||||
|
||||
Ces métriques sont ensuite utilisées comme features dans feature_engineering.py.
|
||||
|
||||
Usage :
|
||||
from peers_loader import PeersLoader
|
||||
from relative_performance import RelativePerformanceCalculator
|
||||
|
||||
loader = PeersLoader("peers/").load()
|
||||
calc = RelativePerformanceCalculator(loader)
|
||||
rel_df = calc.compute(weekly_perf_df)
|
||||
# → DataFrame avec colonnes rel_* par stratégie Carmignac
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from scipy import stats
|
||||
|
||||
|
||||
# ── Classe principale ─────────────────────────────────────────────────────────
|
||||
|
||||
class RelativePerformanceCalculator:
|
||||
"""
|
||||
Calcule les métriques de performance relative Carmignac vs peers.
|
||||
|
||||
Paramètres
|
||||
----------
|
||||
loader : PeersLoader (déjà chargé)
|
||||
"""
|
||||
|
||||
def __init__(self, loader):
|
||||
self.loader = loader
|
||||
|
||||
# ── Point d'entrée principal ──────────────────────────────────────────────
|
||||
|
||||
def compute(self, perf_df: pd.DataFrame,
|
||||
perf_periods: list[str] | None = None,
|
||||
verbose: bool = True) -> pd.DataFrame:
|
||||
"""
|
||||
Calcule toutes les métriques de performance relative.
|
||||
|
||||
Paramètres
|
||||
----------
|
||||
perf_df : DataFrame issu de weekly_perf_full.csv
|
||||
Colonnes attendues : Date, perfPeriod, shareClass_name, return, percentile
|
||||
perf_periods : liste des périodes à traiter (ex: ['6MoRet', '1YrRet'])
|
||||
Si None → toutes les périodes disponibles.
|
||||
|
||||
Retourne
|
||||
--------
|
||||
DataFrame avec index (carmignac_isin, carmignac_strategy, Date, perfPeriod)
|
||||
et colonnes de métriques relatives.
|
||||
"""
|
||||
perf_df = perf_df.copy()
|
||||
perf_df["Date"] = pd.to_datetime(perf_df["Date"])
|
||||
|
||||
if perf_periods is None:
|
||||
perf_periods = perf_df["perfPeriod"].dropna().unique().tolist()
|
||||
|
||||
# Résolution ISIN depuis les noms de shareclass
|
||||
perf_df = self._resolve_isin(perf_df)
|
||||
|
||||
# Séparation Carmignac / peers
|
||||
carm_perf = perf_df[perf_df["is_carmignac"]].copy()
|
||||
peers_perf = perf_df[~perf_df["is_carmignac"]].copy()
|
||||
|
||||
if carm_perf.empty:
|
||||
raise ValueError(
|
||||
"Aucune ligne Carmignac trouvée dans perf_df après résolution ISIN. "
|
||||
"Vérifier que les noms de shareclass correspondent aux fichiers peers."
|
||||
)
|
||||
|
||||
results = []
|
||||
for strategy_code in self.loader.peers_df["carmignac_strategy"].unique():
|
||||
carm_isin_list = self.loader.get_carmignac_isin_for_strategy(strategy_code)
|
||||
peers_isin_list = self.loader.get_competitor_isin_for_strategy(
|
||||
strategy_code, exclude_index=True
|
||||
)
|
||||
|
||||
if not carm_isin_list:
|
||||
continue
|
||||
|
||||
for period in perf_periods:
|
||||
df_period = self._compute_period(
|
||||
carm_perf, peers_perf,
|
||||
carm_isin_list, peers_isin_list,
|
||||
strategy_code, period
|
||||
)
|
||||
if df_period is not None:
|
||||
results.append(df_period)
|
||||
|
||||
if not results:
|
||||
if verbose:
|
||||
print("⚠ Aucune métrique calculée. Vérifier le chevauchement "
|
||||
"temporel entre perf_df et les peers.")
|
||||
return pd.DataFrame()
|
||||
|
||||
out = pd.concat(results, ignore_index=True)
|
||||
|
||||
if verbose:
|
||||
print(f"✅ Métriques relatives calculées : {out.shape[0]} lignes")
|
||||
print(f" Stratégies : {out['carmignac_strategy'].unique().tolist()}")
|
||||
print(f" Périodes : {out['perfPeriod'].unique().tolist()}")
|
||||
cols = [c for c in out.columns if c.startswith("rel_")]
|
||||
print(f" Features : {cols}")
|
||||
|
||||
return out
|
||||
|
||||
# ── Calcul par (stratégie, période) ──────────────────────────────────────
|
||||
|
||||
def _compute_period(self,
|
||||
carm_perf: pd.DataFrame,
|
||||
peers_perf: pd.DataFrame,
|
||||
carm_isin_list: list[str],
|
||||
peers_isin_list: list[str],
|
||||
strategy_code: str,
|
||||
period: str) -> pd.DataFrame | None:
|
||||
|
||||
# Filtrer sur la stratégie et la période
|
||||
c = carm_perf[
|
||||
carm_perf["isin"].isin(carm_isin_list) &
|
||||
(carm_perf["perfPeriod"] == period)
|
||||
].copy()
|
||||
|
||||
p = peers_perf[
|
||||
peers_perf["isin"].isin(peers_isin_list) &
|
||||
(peers_perf["perfPeriod"] == period)
|
||||
].copy()
|
||||
|
||||
if c.empty or p.empty:
|
||||
return None
|
||||
|
||||
# Agrégation Carmignac : moyenne des shareclasses (si plusieurs)
|
||||
c_agg = (c.groupby("Date")["return"]
|
||||
.mean()
|
||||
.reset_index()
|
||||
.rename(columns={"return": "carm_return"}))
|
||||
|
||||
# Stats des peers par date
|
||||
p_stats = (p.groupby("Date")["return"]
|
||||
.agg(
|
||||
peers_median="median",
|
||||
peers_mean="mean",
|
||||
peers_q25=lambda x: x.quantile(0.25),
|
||||
peers_q75=lambda x: x.quantile(0.75),
|
||||
peers_std="std",
|
||||
peers_n="count",
|
||||
)
|
||||
.reset_index())
|
||||
|
||||
# Jointure
|
||||
merged = c_agg.merge(p_stats, on="Date", how="inner")
|
||||
if merged.empty:
|
||||
return None
|
||||
|
||||
# ── Métriques relatives ───────────────────────────────────────────────
|
||||
|
||||
# 1. Spread vs médiane (en points de %)
|
||||
merged["rel_spread_vs_median"] = merged["carm_return"] - merged["peers_median"]
|
||||
|
||||
# 2. Spread vs moyenne
|
||||
merged["rel_spread_vs_mean"] = merged["carm_return"] - merged["peers_mean"]
|
||||
|
||||
# 3. Rang dans le groupe (0 = meilleur, 1 = pire)
|
||||
# Recalculé proprement depuis les données brutes
|
||||
merged["rel_rank_in_peers"] = merged.apply(
|
||||
lambda row: self._compute_rank(
|
||||
row["carm_return"],
|
||||
p[p["Date"] == row["Date"]]["return"].values
|
||||
),
|
||||
axis=1
|
||||
)
|
||||
|
||||
# 4. Quartile (1=top, 4=bottom)
|
||||
merged["rel_quartile"] = (merged["rel_rank_in_peers"] * 4).apply(
|
||||
lambda x: min(int(x) + 1, 4) if pd.notna(x) else np.nan
|
||||
)
|
||||
|
||||
# 5. Dummy top quartile / bottom quartile
|
||||
merged["rel_is_top_quartile"] = (merged["rel_quartile"] == 1).astype(float)
|
||||
merged["rel_is_bottom_quartile"] = (merged["rel_quartile"] == 4).astype(float)
|
||||
|
||||
# 6. Z-score dans la distribution des peers
|
||||
merged["rel_zscore"] = (
|
||||
(merged["carm_return"] - merged["peers_mean"]) /
|
||||
merged["peers_std"].replace(0, np.nan)
|
||||
)
|
||||
|
||||
# 7. Volatilité du rang sur fenêtre glissante (12 semaines)
|
||||
merged = merged.sort_values("Date")
|
||||
merged["rel_rank_volatility_12w"] = (
|
||||
merged["rel_rank_in_peers"]
|
||||
.rolling(12, min_periods=4)
|
||||
.std()
|
||||
)
|
||||
|
||||
# 8. Ratio d'outperformance glissant (26 semaines = ~6 mois)
|
||||
merged["rel_outperf_ratio_26w"] = (
|
||||
(merged["rel_spread_vs_median"] > 0)
|
||||
.astype(float)
|
||||
.rolling(26, min_periods=8)
|
||||
.mean()
|
||||
)
|
||||
|
||||
# 9. Momentum de rang (amélioration du rang sur 4 semaines)
|
||||
merged["rel_rank_momentum_4w"] = (
|
||||
merged["rel_rank_in_peers"]
|
||||
.diff(-4) # négatif = amélioration (rang plus bas = mieux)
|
||||
)
|
||||
|
||||
# Colonnes de contexte
|
||||
merged["carmignac_strategy"] = strategy_code
|
||||
merged["perfPeriod"] = period
|
||||
merged["peers_n_funds"] = merged["peers_n"]
|
||||
|
||||
cols_out = (
|
||||
["Date", "carmignac_strategy", "perfPeriod",
|
||||
"carm_return", "peers_median", "peers_q25", "peers_q75",
|
||||
"peers_n_funds"]
|
||||
+ [c for c in merged.columns if c.startswith("rel_")]
|
||||
)
|
||||
return merged[[c for c in cols_out if c in merged.columns]]
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
def _resolve_isin(self, perf_df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Ajoute une colonne 'isin' et 'is_carmignac' à perf_df
|
||||
en résolvant les noms de shareclass via PeersLoader.
|
||||
"""
|
||||
perf_df = perf_df.copy()
|
||||
|
||||
# Mapping direct depuis peers
|
||||
all_isin_map = self.loader.isin_map # Name → ISIN
|
||||
|
||||
def resolve(name):
|
||||
return self.loader.resolve_shareclass_name(name)
|
||||
|
||||
perf_df["isin"] = perf_df["shareClass_name"].map(resolve)
|
||||
|
||||
# Identifier les fonds Carmignac
|
||||
carm_isins = set(self.loader.carmignac_df["ISIN"].dropna())
|
||||
perf_df["is_carmignac"] = perf_df["isin"].isin(carm_isins)
|
||||
|
||||
n_resolved = perf_df["isin"].notna().sum()
|
||||
n_total = len(perf_df)
|
||||
n_carmignac = perf_df["is_carmignac"].sum()
|
||||
print(f"Résolution ISIN : {n_resolved}/{n_total} lignes résolues "
|
||||
f"({n_carmignac} Carmignac, {n_resolved - n_carmignac} peers)")
|
||||
|
||||
return perf_df
|
||||
|
||||
@staticmethod
|
||||
def _compute_rank(carm_value: float, peers_values: np.ndarray) -> float:
|
||||
"""
|
||||
Rang de carm_value dans peers_values (percentile, 0=meilleur, 1=pire).
|
||||
Un rang faible = Carmignac surperforme ses peers.
|
||||
"""
|
||||
if len(peers_values) == 0 or np.isnan(carm_value):
|
||||
return np.nan
|
||||
all_values = np.append(peers_values, carm_value)
|
||||
all_values = all_values[~np.isnan(all_values)]
|
||||
# Percentile de rang (inversé : 0 = meilleur)
|
||||
rank = 1.0 - stats.percentileofscore(all_values, carm_value, kind="rank") / 100.0
|
||||
return rank
|
||||
|
||||
|
||||
# ── Fonctions utilitaires standalone ─────────────────────────────────────────
|
||||
|
||||
def summarize_relative_performance(rel_df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Produit un tableau résumé des métriques relatives par stratégie et période.
|
||||
Utile pour un reporting rapide ou un tableau de bord.
|
||||
"""
|
||||
if rel_df.empty:
|
||||
return pd.DataFrame()
|
||||
|
||||
agg = (rel_df
|
||||
.groupby(["carmignac_strategy", "perfPeriod"])
|
||||
.agg(
|
||||
median_spread=("rel_spread_vs_median", "median"),
|
||||
pct_outperform=("rel_outperf_ratio_26w", "mean"),
|
||||
avg_quartile=("rel_quartile", "mean"),
|
||||
pct_top_quartile=("rel_is_top_quartile", "mean"),
|
||||
pct_bottom_quartile=("rel_is_bottom_quartile", "mean"),
|
||||
avg_zscore=("rel_zscore", "mean"),
|
||||
avg_rank_vol=("rel_rank_volatility_12w", "mean"),
|
||||
n_obs=("carm_return", "count"),
|
||||
)
|
||||
.round(3)
|
||||
.reset_index())
|
||||
return agg
|
||||
|
||||
|
||||
# ── Usage autonome ────────────────────────────────────────────────────────────
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
from peers_loader import PeersLoader
|
||||
|
||||
peers_dir = sys.argv[1] if len(sys.argv) > 1 else "."
|
||||
perf_path = sys.argv[2] if len(sys.argv) > 2 else "weekly_perf_full.csv"
|
||||
|
||||
print("Chargement des peers...")
|
||||
loader = PeersLoader(peers_dir=peers_dir).load()
|
||||
|
||||
print("\nChargement des performances...")
|
||||
perf_df = pd.read_csv(perf_path)
|
||||
perf_df["Date"] = pd.to_datetime(perf_df["Date"])
|
||||
|
||||
print("\nCalcul des métriques relatives...")
|
||||
calc = RelativePerformanceCalculator(loader)
|
||||
rel_df = calc.compute(perf_df)
|
||||
|
||||
print("\nRésumé des performances relatives :")
|
||||
print(summarize_relative_performance(rel_df).to_string(index=False))
|
||||
328
src/repair_challenge/carmignac_analysis.py
Normal file
328
src/repair_challenge/carmignac_analysis.py
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
"""
|
||||
Pipeline Results Analysis
|
||||
=====================================================
|
||||
Analyses the CSV outputs produced by carmignac_repair.py:
|
||||
- carmignac_scores.csv (post-surgery score history)
|
||||
- carmignac_mapping.csv (reg_id mapping history)
|
||||
- carmignac_surgery_log.csv (surgery operations)
|
||||
|
||||
Produces a self-contained HTML report with interactive charts.
|
||||
|
||||
Usage:
|
||||
python carmignac_analysis.py
|
||||
python carmignac_analysis.py --scores path/to/scores.csv \
|
||||
--mapping path/to/mapping.csv \
|
||||
--surgery path/to/surgery_log.csv \
|
||||
--out report.html
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from helpers import build_html_repair
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 1. LOAD & VALIDATE
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
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"])
|
||||
|
||||
# Normalise dtypes
|
||||
scores["reg_id"] = scores["reg_id"].astype(str)
|
||||
mapping["reg_orig"] = mapping["reg_orig"].astype(str)
|
||||
mapping["reg_used"] = mapping["reg_used"].astype(str)
|
||||
mapping["changed"] = mapping["changed"].astype(bool)
|
||||
surgery["reg_orig"] = surgery["reg_orig"].astype(str)
|
||||
surgery["reg_from"] = surgery["reg_from"].astype(str)
|
||||
surgery["reg_to"] = surgery["reg_to"].astype(str)
|
||||
if "lookback_months" not in surgery.columns:
|
||||
surgery["lookback_months"] = 1 # backwards compat
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 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
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def compute_analytics(scores, mapping, surgery):
|
||||
dates = sorted(scores["date"].unique())
|
||||
|
||||
# ── 2.1 Sum of scores per date (post-surgery) ──────────────
|
||||
sum_post = scores.groupby("date")["score"].sum().reindex(dates).rename("sum_post")
|
||||
|
||||
# ── 2.2 Reconstruct pre-surgery (counterfactual) ───────────
|
||||
# Without surgery, every reg_id that had a hard break would score 0
|
||||
# from that date backwards. We propagate the surgery "gain" as a
|
||||
# cumulative deficit going back in time.
|
||||
gain_by_date = surgery.groupby("date")["gain_vs_no_surgery"].sum()
|
||||
# cumulative deficit = sum of gains for all surgeries at or after date t
|
||||
cumulative_deficit = pd.Series(0.0, index=dates)
|
||||
for d in dates:
|
||||
cumulative_deficit[d] = gain_by_date[gain_by_date.index >= d].sum()
|
||||
sum_pre = (sum_post - cumulative_deficit).clip(lower=0).rename("sum_pre")
|
||||
|
||||
timeline = pd.DataFrame({"sum_post": sum_post, "sum_pre": sum_pre})
|
||||
timeline.index = pd.to_datetime(timeline.index)
|
||||
timeline["recovery_pct"] = np.where(
|
||||
sum_pre < sum_post,
|
||||
(sum_post - sum_pre) / sum_post.clip(lower=1e-9) * 100,
|
||||
0.0,
|
||||
)
|
||||
|
||||
# ── 2.3 Per-date surgery stats ─────────────────────────────
|
||||
surgery_stats = (
|
||||
surgery.groupby("date")
|
||||
.agg(
|
||||
n_surgeries=("reg_orig", "count"),
|
||||
total_gain=("gain_vs_no_surgery", "sum"),
|
||||
avg_gain=("gain_vs_no_surgery", "mean"),
|
||||
avg_jaccard=("jaccard_composite", "mean"),
|
||||
avg_score_before=("score_before", "mean"),
|
||||
avg_score_after=("score_after", "mean"),
|
||||
)
|
||||
.reindex(dates, fill_value=0)
|
||||
)
|
||||
|
||||
# ── 2.4 Score distribution over time ───────────────────────
|
||||
# Wide format: rows=dates, cols=reg_ids
|
||||
pivot = scores.pivot_table(
|
||||
index="date", columns="reg_id", values="score", aggfunc="last"
|
||||
)
|
||||
pivot = pivot.reindex(dates)
|
||||
pivot.index = pd.to_datetime(pivot.index)
|
||||
|
||||
# ── 2.5 Mapping churn ──────────────────────────────────────
|
||||
# For each date, how many reg_ids are remapped (not using their original code)?
|
||||
churn = (
|
||||
mapping.groupby("date")["changed"]
|
||||
.sum()
|
||||
.reindex(dates, fill_value=0)
|
||||
.rename("n_remapped")
|
||||
)
|
||||
|
||||
# ── 2.6 Score entropy (distribution spread) ────────────────
|
||||
def entropy(row):
|
||||
p = row.dropna()
|
||||
p = p[p > 0]
|
||||
if len(p) == 0:
|
||||
return np.nan
|
||||
p = p / p.sum()
|
||||
return -(p * np.log(p)).sum()
|
||||
|
||||
timeline["entropy"] = pivot.apply(entropy, axis=1).values
|
||||
|
||||
# ── 2.7 Individual score trajectories ──────────────────────
|
||||
# Identify which reg_ids were ever remapped
|
||||
ever_remapped = set(mapping.loc[mapping["changed"], "reg_orig"].unique())
|
||||
|
||||
# ── 2.8 Surgery detail table ───────────────────────────────
|
||||
surgery_detail = surgery.copy()
|
||||
surgery_detail["gain_pct_of_score"] = (
|
||||
surgery_detail["gain_vs_no_surgery"]
|
||||
/ surgery_detail["score_before"].clip(lower=1e-9)
|
||||
* 100
|
||||
).round(2)
|
||||
|
||||
return {
|
||||
"timeline": timeline,
|
||||
"surgery_stats": surgery_stats,
|
||||
"pivot": pivot,
|
||||
"churn": churn,
|
||||
"ever_remapped": ever_remapped,
|
||||
"surgery_detail": surgery_detail,
|
||||
"dates": [d.strftime("%Y-%m-%d") for d in dates],
|
||||
}
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 3. PRINT CONSOLE SUMMARY
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def print_summary(analytics, surgery):
|
||||
tl = analytics["timeline"]
|
||||
ss = analytics["surgery_stats"]
|
||||
|
||||
print("\n" + "=" * 65)
|
||||
print(" CARMIGNAC PIPELINE — RESULTS SUMMARY")
|
||||
print("=" * 65)
|
||||
|
||||
print(f"\n Date range : {tl.index.min().date()} → {tl.index.max().date()}")
|
||||
print(f" Total months : {len(tl)}")
|
||||
print(f" Reg IDs : {analytics['pivot'].shape[1]}")
|
||||
|
||||
print("\n ── Score (Σ) ──────────────────────────────────────────")
|
||||
print(f" At t_ref (latest) : {tl['sum_post'].iloc[-1]:.6f}")
|
||||
print(f" At t_min (earliest): {tl['sum_post'].iloc[0]:.6f}")
|
||||
print(
|
||||
f" Min (post-surgery) : {tl['sum_post'].min():.6f} "
|
||||
f"({tl['sum_post'].idxmin().date()})"
|
||||
)
|
||||
print(
|
||||
f" Min (pre-surgery) : {tl['sum_pre'].min():.6f} "
|
||||
f"({tl['sum_pre'].idxmin().date()})"
|
||||
)
|
||||
print(f" Max recovery (pct) : {tl['recovery_pct'].max():.2f}%")
|
||||
|
||||
print("\n ── Surgeries ─────────────────────────────────────────")
|
||||
if len(surgery) == 0:
|
||||
print(" No surgeries performed.")
|
||||
else:
|
||||
print(f" Total operations : {len(surgery)}")
|
||||
print(f" Total score gained : {surgery['gain_vs_no_surgery'].sum():.6f}")
|
||||
print(f" Avg Jaccard : {surgery['jaccard_composite'].mean():.4f}")
|
||||
print(f" Avg gain / surgery : {surgery['gain_vs_no_surgery'].mean():.6f}")
|
||||
print()
|
||||
print(
|
||||
f" {'Date':12s} {'Reg orig':12s} {'From':15s} {'To':15s} "
|
||||
f"{'Jaccard':>8s} {'Gain':>10s}"
|
||||
)
|
||||
print(" " + "-" * 78)
|
||||
for _, row in surgery.sort_values("date").iterrows():
|
||||
print(
|
||||
f" {str(row['date'].date()):12s} {row['reg_orig']:12s} "
|
||||
f"{row['reg_from']:15s} {row['reg_to']:15s} "
|
||||
f"{row['jaccard_composite']:8.4f} {row['gain_vs_no_surgery']:10.6f}"
|
||||
)
|
||||
|
||||
print("\n ── Mapping churn ─────────────────────────────────────")
|
||||
ch = analytics["churn"]
|
||||
print(
|
||||
f" Max remapped at one date : {int(ch.max())} ({ch.idxmax().date() if ch.max() > 0 else 'N/A'})"
|
||||
)
|
||||
print(f" Reg IDs ever remapped : {len(analytics['ever_remapped'])}")
|
||||
|
||||
print("\n ── Score entropy (distribution spread) ───────────────")
|
||||
ent = analytics["timeline"]["entropy"]
|
||||
print(f" Mean entropy : {ent.mean():.4f}")
|
||||
print(f" Std entropy : {ent.std():.4f}")
|
||||
print()
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# MAIN
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Carmignac pipeline results analyser")
|
||||
parser.add_argument("--scores", default="repair_results/carmignac_scores.csv")
|
||||
parser.add_argument("--mapping", default="repair_results/carmignac_mapping.csv")
|
||||
parser.add_argument("--surgery", default="repair_results/carmignac_surgery_log.csv")
|
||||
parser.add_argument("--out", default="repair_results/carmignac_report.html")
|
||||
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, 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
|
||||
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, 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_repair(
|
||||
analytics,
|
||||
surgery,
|
||||
scores,
|
||||
mapping,
|
||||
df_err_isin=df_err_isin,
|
||||
df_err_agg=df_err_agg,
|
||||
)
|
||||
|
||||
out_path = "../" + args.out
|
||||
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
f.write(html)
|
||||
print(f"\n[Report] Written to → {out_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Carmignac Data Challenge — AUM Branching / Repair
|
||||
AUM Branching / Repair
|
||||
==================================================
|
||||
Takes as input:
|
||||
- The original AUM file (pre-repair)
|
||||
|
|
@ -42,43 +42,16 @@ Usage
|
|||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import s3fs
|
||||
import pandas as pd
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 1. LOAD
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
def load_inputs(mapping_path, surgery_path):
|
||||
fs = s3fs.S3FileSystem(
|
||||
client_kwargs={'endpoint_url': 'https://'+'minio-simple.lab.groupe-genes.fr'},
|
||||
key = os.environ["AWS_ACCESS_KEY_ID"],
|
||||
secret = os.environ["AWS_SECRET_ACCESS_KEY"],
|
||||
token = os.environ["AWS_SESSION_TOKEN"])
|
||||
|
||||
with fs.open('s3://projet-bdc-data/carmignac/AUM ENSAE V2 -20251105.csv', 'rb') as f:
|
||||
aum = pd.read_csv(f, sep=";")
|
||||
|
||||
mapping = pd.read_csv(mapping_path, parse_dates=["date"])
|
||||
surgery = pd.read_csv(surgery_path, parse_dates=["date"]) if surgery_path else pd.DataFrame()
|
||||
|
||||
# Normalise ID columns to string
|
||||
aum["Registrar Account - ID"] = aum["Registrar Account - ID"].astype(str)
|
||||
mapping["reg_orig"] = mapping["reg_orig"].astype(str)
|
||||
mapping["reg_used"] = mapping["reg_used"].astype(str)
|
||||
if not surgery.empty:
|
||||
surgery["reg_orig"] = surgery["reg_orig"].astype(str)
|
||||
surgery["reg_from"] = surgery["reg_from"].astype(str)
|
||||
surgery["reg_to"] = surgery["reg_to"].astype(str)
|
||||
|
||||
return aum, mapping, surgery
|
||||
from helpers import load_inputs_branch
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 2. BUILD RENAME LOOKUP
|
||||
# BUILD RENAME LOOKUP
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def build_rename_lookup(mapping):
|
||||
"""
|
||||
Returns a dict {(date, reg_used) -> reg_orig}
|
||||
|
|
@ -92,11 +65,11 @@ def build_rename_lookup(mapping):
|
|||
for _, row in changed.iterrows():
|
||||
key = (row["date"], row["reg_used"])
|
||||
if key in lookup and lookup[key] != row["reg_orig"]:
|
||||
# Conflict: two different reg_origs claim the same (date, reg_used)
|
||||
# Should never happen with a well-formed mapping, but warn if it does
|
||||
print(f" [WARN] Conflicting mapping at {row['date'].date()} "
|
||||
f"reg_used={row['reg_used']}: "
|
||||
f"{lookup[key]} vs {row['reg_orig']} — keeping first")
|
||||
print(
|
||||
f" [WARN] Conflicting mapping at {row['date'].date()} "
|
||||
f"reg_used={row['reg_used']}: "
|
||||
f"{lookup[key]} vs {row['reg_orig']} — keeping first"
|
||||
)
|
||||
else:
|
||||
lookup[key] = row["reg_orig"]
|
||||
|
||||
|
|
@ -104,9 +77,10 @@ def build_rename_lookup(mapping):
|
|||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 3. APPLY BRANCHING
|
||||
# BRANCHING
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def apply_branching(aum, lookup):
|
||||
"""
|
||||
Renames Registrar Account - ID in the AUM dataframe according to
|
||||
|
|
@ -122,12 +96,12 @@ def apply_branching(aum, lookup):
|
|||
aum = aum.copy()
|
||||
aum["Centralisation Date"] = pd.to_datetime(aum["Centralisation Date"])
|
||||
aum["_date_key"] = aum["Centralisation Date"]
|
||||
aum["_reg_key"] = aum["Registrar Account - ID"].astype(str)
|
||||
aum["_reg_key"] = aum["Registrar Account - ID"].astype(str)
|
||||
|
||||
# Vectorised lookup via merge
|
||||
lookup_df = pd.DataFrame(
|
||||
[(d, reg_used, reg_orig) for (d, reg_used), reg_orig in lookup.items()],
|
||||
columns=["_date_key", "_reg_key", "_canonical_id"]
|
||||
columns=["_date_key", "_reg_key", "_canonical_id"],
|
||||
)
|
||||
|
||||
merged = aum.merge(lookup_df, on=["_date_key", "_reg_key"], how="left")
|
||||
|
|
@ -137,11 +111,21 @@ def apply_branching(aum, lookup):
|
|||
audit = merged[renamed_mask].copy()
|
||||
audit["original_reg_id"] = audit["_reg_key"]
|
||||
audit["canonical_reg_id"] = audit["_canonical_id"]
|
||||
audit = audit[["Centralisation Date", "original_reg_id", "canonical_reg_id",
|
||||
"Product - Isin", "Quantity - AUM", "Value - AUM €"]]
|
||||
audit = audit[
|
||||
[
|
||||
"Centralisation Date",
|
||||
"original_reg_id",
|
||||
"canonical_reg_id",
|
||||
"Product - Isin",
|
||||
"Quantity - AUM",
|
||||
"Value - AUM €",
|
||||
]
|
||||
]
|
||||
|
||||
# Apply rename
|
||||
merged.loc[renamed_mask, "Registrar Account - ID"] = merged.loc[renamed_mask, "_canonical_id"]
|
||||
# Rename
|
||||
merged.loc[renamed_mask, "Registrar Account - ID"] = merged.loc[
|
||||
renamed_mask, "_canonical_id"
|
||||
]
|
||||
|
||||
# Drop helper columns
|
||||
repaired = merged.drop(columns=["_date_key", "_reg_key", "_canonical_id"])
|
||||
|
|
@ -150,9 +134,10 @@ def apply_branching(aum, lookup):
|
|||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 4. CONSISTENCY CHECK
|
||||
# CONSISTENCY CHECK
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def consistency_check(original, repaired, mapping, surgery):
|
||||
"""
|
||||
Sanity checks after branching:
|
||||
|
|
@ -165,148 +150,181 @@ def consistency_check(original, repaired, mapping, surgery):
|
|||
"""
|
||||
print("\n[Consistency checks]")
|
||||
|
||||
# 1. Row count
|
||||
# Row count
|
||||
if len(original) == len(repaired):
|
||||
print(f" ✓ Row count preserved : {len(repaired)}")
|
||||
else:
|
||||
print(f" ✗ Row count changed : {len(original)} → {len(repaired)}")
|
||||
|
||||
# 2. Aliases eliminated
|
||||
# Aliases eliminated
|
||||
changed = mapping[mapping["changed"] & (mapping["reg_orig"] != mapping["reg_used"])]
|
||||
aliases = set(changed["reg_used"].unique())
|
||||
still_present = set(repaired["Registrar Account - ID"].astype(str)) & aliases
|
||||
if not still_present:
|
||||
print(f" ✓ All {len(aliases)} aliased code(s) successfully relabelled")
|
||||
else:
|
||||
print(f" ✗ {len(still_present)} aliased code(s) still present: {still_present}")
|
||||
print(
|
||||
f" ✗ {len(still_present)} aliased code(s) still present: {still_present}"
|
||||
)
|
||||
|
||||
# 3. Duplicates
|
||||
# Duplicates
|
||||
key_cols = ["Registrar Account - ID", "Product - Isin", "Centralisation Date"]
|
||||
dup_count = repaired.duplicated(subset=key_cols).sum()
|
||||
if dup_count == 0:
|
||||
print(f" ✓ No duplicate (reg_id, isin, date) keys")
|
||||
print(" ✓ No duplicate (reg_id, isin, date) keys")
|
||||
else:
|
||||
print(f" ✗ {dup_count} duplicate (reg_id, isin, date) rows found — inspect manually")
|
||||
print(repaired[repaired.duplicated(subset=key_cols, keep=False)]
|
||||
[key_cols + ["Quantity - AUM"]].head(10).to_string(index=False))
|
||||
print(
|
||||
f" ✗ {dup_count} duplicate (reg_id, isin, date) rows found — inspect manually"
|
||||
)
|
||||
print(
|
||||
repaired[repaired.duplicated(subset=key_cols, keep=False)][
|
||||
key_cols + ["Quantity - AUM"]
|
||||
]
|
||||
.head(10)
|
||||
.to_string(index=False)
|
||||
)
|
||||
|
||||
# 4. Surgery summary
|
||||
# Surgery summary
|
||||
if not surgery.empty:
|
||||
print(f"\n[Surgery operations applied]")
|
||||
print("\n[Surgery operations applied]")
|
||||
for _, op in surgery.sort_values("date").iterrows():
|
||||
self_map = " [self-map — data quality flag, no rename]" \
|
||||
if op["reg_from"] == op["reg_to"] else ""
|
||||
print(f" {op['date'].date()} | {op['reg_orig']} : "
|
||||
f"{op['reg_from']} → {op['reg_to']}"
|
||||
f" (Jaccard={op['jaccard_composite']:.4f}, "
|
||||
f"gain={op['gain_vs_no_surgery']:.6f}){self_map}")
|
||||
self_map = (
|
||||
" [self-map — data quality flag, no rename]"
|
||||
if op["reg_from"] == op["reg_to"]
|
||||
else ""
|
||||
)
|
||||
print(
|
||||
f" {op['date'].date()} | {op['reg_orig']} : "
|
||||
f"{op['reg_from']} → {op['reg_to']}"
|
||||
f" (Jaccard={op['jaccard_composite']:.4f}, "
|
||||
f"gain={op['gain_vs_no_surgery']:.6f}){self_map}"
|
||||
)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 5. EXPORT PATHS (branched accounts only)
|
||||
# EXPORT PATHS (branched accounts only)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def export_paths(aum, mapping, surgery, repaired):
|
||||
"""
|
||||
Builds a condensed AUM file for ALL accounts in the repair universe
|
||||
(i.e. every reg_orig present in the mapping).
|
||||
|
||||
|
||||
- Stable accounts (no surgery): single leg where reg_used == reg_orig
|
||||
throughout, pulled directly from the repaired AUM.
|
||||
- Branched accounts (at least one genuine surgery): multiple legs,
|
||||
reg_used shows which physical code was active at each date.
|
||||
|
||||
|
||||
The output makes every account's full path explicit:
|
||||
|
||||
|
||||
reg_orig | reg_used | date | isin | qty_aum | ...
|
||||
─────────┼───────────────┼────────────┼──────┼─────────┼───
|
||||
REG_001 | REG_001 | 2020-01-31 | ... | ... | <- stable
|
||||
REG_002 | REG_002_OLD | 2020-01-31 | ... | ... | <- leg 1
|
||||
REG_002 | REG_002 | 2022-07-31 | ... | ... | <- leg 2
|
||||
|
||||
|
||||
Self-mapped surgeries (reg_from == reg_to) are noted in the summary
|
||||
but do not add extra legs — the account kept its code.
|
||||
|
||||
|
||||
Returns the paths DataFrame (never None if mapping is non-empty).
|
||||
"""
|
||||
# All canonical accounts in the universe
|
||||
all_accounts = sorted(mapping["reg_orig"].astype(str).unique())
|
||||
|
||||
|
||||
# Branched accounts (genuine code changes only)
|
||||
branched_accounts = set()
|
||||
if not surgery.empty:
|
||||
genuine = surgery[surgery["reg_from"] != surgery["reg_to"]]
|
||||
branched_accounts = set(genuine["reg_orig"].astype(str).unique())
|
||||
|
||||
print(f"\n[Paths] {len(all_accounts)} account(s) in universe, "
|
||||
f"{len(branched_accounts)} branched: "
|
||||
f"{sorted(branched_accounts) or 'none'}")
|
||||
|
||||
|
||||
print(
|
||||
f"\n[Paths] {len(all_accounts)} account(s) in universe, "
|
||||
f"{len(branched_accounts)} branched: "
|
||||
f"{sorted(branched_accounts) or 'none'}"
|
||||
)
|
||||
|
||||
# Build (date, reg_orig) → reg_used lookup from mapping
|
||||
map_df = mapping[["date", "reg_orig", "reg_used"]].copy()
|
||||
map_df["date"] = pd.to_datetime(map_df["date"])
|
||||
map_df["date"] = pd.to_datetime(map_df["date"])
|
||||
map_df["reg_orig"] = map_df["reg_orig"].astype(str)
|
||||
map_df["reg_used"] = map_df["reg_used"].astype(str)
|
||||
map_df = map_df.rename(columns={"date": "_date_key", "reg_orig": "_reg_key"})
|
||||
|
||||
|
||||
# Pull all universe rows from the repaired AUM
|
||||
aum_universe = repaired[
|
||||
repaired["Registrar Account - ID"].astype(str).isin(all_accounts)
|
||||
].copy()
|
||||
aum_universe["Centralisation Date"] = pd.to_datetime(aum_universe["Centralisation Date"])
|
||||
aum_universe["Centralisation Date"] = pd.to_datetime(
|
||||
aum_universe["Centralisation Date"]
|
||||
)
|
||||
aum_universe["_date_key"] = aum_universe["Centralisation Date"]
|
||||
aum_universe["_reg_key"] = aum_universe["Registrar Account - ID"].astype(str)
|
||||
|
||||
aum_universe["_reg_key"] = aum_universe["Registrar Account - ID"].astype(str)
|
||||
|
||||
# Join reg_used from mapping
|
||||
paths = aum_universe.merge(
|
||||
map_df[["_date_key", "_reg_key", "reg_used"]],
|
||||
on=["_date_key", "_reg_key"],
|
||||
how="left",
|
||||
).drop(columns=["_date_key", "_reg_key"])
|
||||
|
||||
|
||||
# For stable accounts, mapping may not cover every AUM date (e.g. sparse
|
||||
# months) — fall back to reg_orig (= Registrar Account - ID) for those.
|
||||
paths["reg_used"] = paths["reg_used"].fillna(
|
||||
paths["Registrar Account - ID"].astype(str)
|
||||
)
|
||||
|
||||
|
||||
# Rename canonical column
|
||||
paths = paths.rename(columns={"Registrar Account - ID": "reg_orig"})
|
||||
|
||||
|
||||
# Column order
|
||||
other_cols = [c for c in paths.columns if c not in ("reg_orig", "reg_used")]
|
||||
paths = paths[["reg_orig", "reg_used"] + other_cols]
|
||||
paths = paths.sort_values(["reg_orig", "Centralisation Date", "Product - Isin"])
|
||||
paths = paths.reset_index(drop=True)
|
||||
|
||||
|
||||
# Summary
|
||||
for acc in all_accounts:
|
||||
sub = paths[paths["reg_orig"] == acc]
|
||||
sub = paths[paths["reg_orig"] == acc]
|
||||
legs = list(sub["reg_used"].unique())
|
||||
tag = " [branched]" if acc in branched_accounts else " [stable]"
|
||||
tag = " [branched]" if acc in branched_accounts else " [stable]"
|
||||
print(f" {acc}: {len(sub)} rows, legs = {legs}{tag}")
|
||||
|
||||
|
||||
return paths
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 6. MAIN
|
||||
# MAIN
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Apply Carmignac repair mapping to the raw AUM file"
|
||||
)
|
||||
parser.add_argument("--mapping", default="repair_results/carmignac_mapping.csv",
|
||||
help="Path to mapping CSV from carmignac_repair.py")
|
||||
parser.add_argument("--surgery", default="repair_results/carmignac_surgery_log.csv",
|
||||
help="Path to surgery log CSV (optional, for audit)")
|
||||
parser.add_argument("--out", default="AUM_repaired.csv",
|
||||
help="Output path for repaired AUM CSV")
|
||||
parser.add_argument("--audit", default="AUM_repair_audit.csv",
|
||||
help="Output path for audit CSV (renamed rows only)")
|
||||
parser.add_argument("--paths", default="AUM_paths.csv",
|
||||
help="Output path for condensed paths CSV (branched accounts only)")
|
||||
parser.add_argument(
|
||||
"--mapping",
|
||||
default="repair_results/carmignac_mapping.csv",
|
||||
help="Path to mapping CSV from carmignac_repair.py",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--surgery",
|
||||
default="repair_results/carmignac_surgery_log.csv",
|
||||
help="Path to surgery log CSV (optional, for audit)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out", default="AUM_repaired.csv", help="Output path for repaired AUM CSV"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--audit",
|
||||
default="AUM_repair_audit.csv",
|
||||
help="Output path for audit CSV (renamed rows only)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--paths",
|
||||
default="AUM_paths.csv",
|
||||
help="Output path for condensed paths CSV (branched accounts only)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
def resolve(p, required=True):
|
||||
|
|
@ -329,7 +347,7 @@ def main():
|
|||
print(f" Surgery : {surgery_path or '(not provided)'}")
|
||||
|
||||
# Load
|
||||
aum, mapping, surgery = load_inputs(mapping_path, surgery_path)
|
||||
aum, mapping, surgery = load_inputs_branch(mapping_path, surgery_path)
|
||||
print(f"\n Raw AUM rows : {len(aum)}")
|
||||
print(f" Mapping rows : {len(mapping)}")
|
||||
print(f" Mapping changed rows : {mapping['changed'].sum()}")
|
||||
|
|
@ -363,7 +381,7 @@ def main():
|
|||
audit.to_csv(args.audit, index=False)
|
||||
print(f" ✓ Audit log → {args.audit}")
|
||||
else:
|
||||
print(f"(No rows renamed — audit log not written)")
|
||||
print("(No rows renamed — audit log not written)")
|
||||
|
||||
# Paths: condensed AUM for branched accounts
|
||||
df_paths = export_paths(aum, mapping, surgery, repaired)
|
||||
|
|
@ -374,12 +392,16 @@ def main():
|
|||
# Print renamed reg_ids summary
|
||||
if len(audit) > 0:
|
||||
print("\n[Renamed identifiers]")
|
||||
summary = (audit.groupby(["original_reg_id", "canonical_reg_id"])
|
||||
.size()
|
||||
.reset_index(name="n_rows"))
|
||||
summary = (
|
||||
audit.groupby(["original_reg_id", "canonical_reg_id"])
|
||||
.size()
|
||||
.reset_index(name="n_rows")
|
||||
)
|
||||
for _, row in summary.iterrows():
|
||||
print(f" {row['original_reg_id']:20s} → {row['canonical_reg_id']:20s} "
|
||||
f"({row['n_rows']} rows)")
|
||||
print(
|
||||
f" {row['original_reg_id']:20s} → {row['canonical_reg_id']:20s} "
|
||||
f"({row['n_rows']} rows)"
|
||||
)
|
||||
|
||||
print("\nDone.")
|
||||
|
||||
632
src/repair_challenge/carmignac_diagnostics.py
Normal file
632
src/repair_challenge/carmignac_diagnostics.py
Normal file
|
|
@ -0,0 +1,632 @@
|
|||
"""
|
||||
Broken Months Diagnostics
|
||||
=====================================================
|
||||
Detects months where the aggregate stock-flow equation is violated at the ISIN level (across all accounts)
|
||||
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 os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from helpers import build_html_diagnostics, load_data_diagnostics
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 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?"
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 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
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 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
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 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()
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# MAIN
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Detect broken months in Carmignac AUM/Flows data"
|
||||
)
|
||||
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.05,
|
||||
help="Tolerance threshold (default 0.05 = 5%%)",
|
||||
)
|
||||
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("[Load] AUM")
|
||||
print("[Load] Flows")
|
||||
aum, flows = load_data_diagnostics()
|
||||
|
||||
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("\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 loads
|
||||
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}")
|
||||
|
||||
# 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_diagnostics(
|
||||
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}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,27 +1,30 @@
|
|||
"""
|
||||
Carmignac Data Challenge — Registrar ID Repair Pipeline
|
||||
Registrar ID Repair Pipeline
|
||||
=========================================================
|
||||
Étape 1 : Filtrage & univers de référence à t=31/10/2025
|
||||
Étape 2 : Score de cohérence temporelle (propagation vers le passé)
|
||||
Étape 3 : Chirurgie de code (matching 1-to-1)
|
||||
|
||||
À appliquer après le diagnostic de broken months
|
||||
"""
|
||||
|
||||
import os
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from collections import defaultdict
|
||||
import os
|
||||
import s3fs
|
||||
|
||||
from helpers import load_data_repair
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# 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
|
||||
MIN_JACCARD = 0.3 # seuil minimal similarité portefeuille pour chirurgie
|
||||
ALPHA = 0.05 # tolérance réconciliation : 5% du stock à t
|
||||
MIN_AUM_EUR = 5e6 # seuil filtrage étape 1
|
||||
MIN_JACCARD = 0.3 # seuil minimal similarité portefeuille pour chirurgie
|
||||
SCORE_DROP_THRESHOLD = 0.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)
|
||||
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
|
||||
|
|
@ -29,93 +32,61 @@ SYMMETRY_ATTENUATION = 0.05 # facteur d'atténuation si rupture symétrique dét
|
|||
# is multiplied by this factor before degrading the score, so a genuine
|
||||
# data-quality problem at market level does not unfairly penalise an
|
||||
# account. Set to 1.0 to disable attenuation.
|
||||
BROKEN_MONTH_ATTENUATION = 0.2 # reduce error to 20% on broken months
|
||||
BROKEN_MONTH_ATTENUATION = 0.2 # reduce error to 20% on broken months
|
||||
|
||||
# ── Accounting lag window ──────────────────────────────────────
|
||||
# Transactions dated within this many days of a month-end boundary are
|
||||
# considered "boundary" flows. When the standard-window reconciliation
|
||||
# fails but the lag-adjusted reconciliation passes, the error is
|
||||
# attenuated (same factor as broken months).
|
||||
LAG_ATTENUATION = 0.2 # reduce error to 20% on likely lag months
|
||||
LAG_ATTENUATION = 0.1 # reduce error to 10% 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
|
||||
# Carmignac 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"]
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# 1. CHARGEMENT
|
||||
# CHARGEMENT
|
||||
# ─────────────────────────────────────────────
|
||||
def load_broken_months(broken_months_path):
|
||||
"""
|
||||
Loads the broken-months CSV produced by carmignac_diagnostics.py.
|
||||
Returns a set of (date, isin) tuples flagged as broken, and a
|
||||
separate set flagged as likely accounting lags.
|
||||
|
||||
If the file does not exist or is empty, returns two empty sets.
|
||||
"""
|
||||
|
||||
if not broken_months_path or not os.path.exists(broken_months_path):
|
||||
print("Could not find the path")
|
||||
return set(), set()
|
||||
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"]),
|
||||
df.loc[df["is_lag"], "isin"].astype(str)))
|
||||
print(f"[Broken months] Loaded {len(broken)} flagged (isin, month) pairs "
|
||||
f"({len(lags)} likely lags)")
|
||||
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)"
|
||||
)
|
||||
return broken, lags
|
||||
except Exception as e:
|
||||
print(f"[Broken months] Could not load '{broken_months_path}': {e}")
|
||||
return set(), set()
|
||||
|
||||
|
||||
def load_data():
|
||||
fs = s3fs.S3FileSystem(
|
||||
client_kwargs={'endpoint_url': 'https://'+'minio-simple.lab.groupe-genes.fr'},
|
||||
key = os.environ["AWS_ACCESS_KEY_ID"],
|
||||
secret = os.environ["AWS_SECRET_ACCESS_KEY"],
|
||||
token = os.environ["AWS_SESSION_TOKEN"])
|
||||
|
||||
with fs.open('projet-bdc-data//carmignac/Flows ENSAE V2 -20251105.csv', 'rb') as f:
|
||||
flows = pd.read_csv(f, sep=";")
|
||||
|
||||
with fs.open('projet-bdc-data//carmignac/AUM ENSAE V2 -20251105.csv', 'rb') as f:
|
||||
aum = pd.read_csv(f, sep=";")
|
||||
|
||||
aum['Centralisation Date'] = pd.to_datetime(aum['Centralisation Date'])
|
||||
flows['Centralisation Date'] = pd.to_datetime(flows['Centralisation Date'])
|
||||
|
||||
# Noms courts
|
||||
aum = aum.rename(columns={
|
||||
'Registrar Account - ID': 'reg_id',
|
||||
'Product - Isin': 'isin',
|
||||
'Centralisation Date': 'date',
|
||||
'Quantity - AUM': 'qty_aum',
|
||||
'Value - AUM €': 'val_eur',
|
||||
'Registrar Account - Region': 'region',
|
||||
})
|
||||
flows = flows.rename(columns={
|
||||
'Registrar Account - ID': 'reg_id',
|
||||
'Product - Isin': 'isin',
|
||||
'Centralisation Date': 'date',
|
||||
'Quantity - NetFlows': 'qty_net',
|
||||
'Value € - NetFlows': 'val_net_eur',
|
||||
})
|
||||
|
||||
aum['reg_id'] = aum['reg_id'].astype(str)
|
||||
flows['reg_id'] = flows['reg_id'].astype(str)
|
||||
|
||||
return aum, flows
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# 2. ÉTAPE 1 — Univers de référence à T_REF
|
||||
# ÉTAPE 1 — Univers de référence à T_REF
|
||||
# ─────────────────────────────────────────────
|
||||
def build_reference_universe(aum, t_ref=None):
|
||||
"""
|
||||
|
|
@ -126,21 +97,23 @@ def build_reference_universe(aum, t_ref=None):
|
|||
- universe : ensemble des reg_id retenus (>= MIN_AUM_EUR)
|
||||
"""
|
||||
if t_ref is None:
|
||||
t_ref = aum['date'].max()
|
||||
t_ref = aum["date"].max()
|
||||
|
||||
print(f"\n[Étape 1] Date de référence : {t_ref.date()}")
|
||||
|
||||
# Exclure Off Distribution / Private Clients (sur région ou nom)
|
||||
mask_excl = aum['reg_id'].isin(EXCLUDE_REGISTRAR)
|
||||
if 'region' in aum.columns:
|
||||
mask_excl |= aum['region'].isin(EXCLUDE_REGISTRAR)
|
||||
mask_excl = aum["reg_id"].isin(EXCLUDE_REGISTRAR)
|
||||
if "region" in aum.columns:
|
||||
mask_excl |= aum["region"].isin(EXCLUDE_REGISTRAR)
|
||||
aum_clean = aum[~mask_excl].copy()
|
||||
|
||||
# AUM à t_ref
|
||||
aum_ref = aum_clean[aum_clean['date'] == t_ref][['reg_id', 'isin', 'qty_aum', 'val_eur']].copy()
|
||||
aum_ref = aum_clean[aum_clean["date"] == t_ref][
|
||||
["reg_id", "isin", "qty_aum", "val_eur"]
|
||||
].copy()
|
||||
|
||||
# AUM total par reg_id à t_ref
|
||||
aum_by_reg = aum_ref.groupby('reg_id')['val_eur'].sum().rename('total_eur')
|
||||
aum_by_reg = aum_ref.groupby("reg_id")["val_eur"].sum().rename("total_eur")
|
||||
|
||||
# Filtrage >= MIN_AUM_EUR
|
||||
universe = set(aum_by_reg[aum_by_reg >= MIN_AUM_EUR].index)
|
||||
|
|
@ -150,14 +123,17 @@ def build_reference_universe(aum, t_ref=None):
|
|||
coverage = total_eur_universe / total_eur_all if total_eur_all > 0 else 0
|
||||
|
||||
print(f" Registrar IDs à t_ref : {len(aum_by_reg)}")
|
||||
print(f" Dont >= {MIN_AUM_EUR/1e6:.0f}M€ : {len(universe)}")
|
||||
print(f" Dont >= {MIN_AUM_EUR / 1e6:.0f}M€ : {len(universe)}")
|
||||
print(f" Couverture encours : {coverage:.1%}")
|
||||
|
||||
# Poids initiaux (scores à t_ref)
|
||||
weights = (aum_by_reg[aum_by_reg.index.isin(universe)] / total_eur_universe).to_dict()
|
||||
weights = (
|
||||
aum_by_reg[aum_by_reg.index.isin(universe)] / total_eur_universe
|
||||
).to_dict()
|
||||
|
||||
return aum_ref, weights, universe, t_ref
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# 3. PANEL AUM MENSUEL (forward-fill)
|
||||
# ─────────────────────────────────────────────
|
||||
|
|
@ -168,18 +144,18 @@ def build_monthly_panel(aum, universe, t_ref):
|
|||
historiques hors univers de référence, nécessaires pour la chirurgie.
|
||||
"""
|
||||
# Toutes les fin de mois entre la première date et t_ref
|
||||
date_min = aum['date'].min()
|
||||
all_months = pd.date_range(start=date_min, end=t_ref, freq='ME')
|
||||
date_min = aum["date"].min()
|
||||
all_months = pd.date_range(start=date_min, end=t_ref, freq="ME")
|
||||
|
||||
# Pivot : (reg_id, isin) → série temporelle de qty_aum
|
||||
aum_sorted = aum.sort_values(['reg_id', 'isin', 'date'])
|
||||
aum_sorted = aum.sort_values(["reg_id", "isin", "date"])
|
||||
|
||||
# On ne garde que les lignes jusqu'à t_ref
|
||||
aum_sorted = aum_sorted[aum_sorted['date'] <= t_ref]
|
||||
aum_sorted = aum_sorted[aum_sorted["date"] <= t_ref]
|
||||
|
||||
# Multi-index pivot
|
||||
panel = aum_sorted.pivot_table(
|
||||
index='date', columns=['reg_id', 'isin'], values='qty_aum', aggfunc='last'
|
||||
index="date", columns=["reg_id", "isin"], values="qty_aum", aggfunc="last"
|
||||
)
|
||||
|
||||
# Réindexer sur toutes les fins de mois
|
||||
|
|
@ -191,10 +167,13 @@ def build_monthly_panel(aum, universe, t_ref):
|
|||
# Backward-fill initial pour les comptes qui démarrent après la première date
|
||||
# (on ne remonte pas avant leur première apparition → on garde NaN)
|
||||
|
||||
print(f"\n[Panel mensuel] {len(all_months)} mois, {panel.shape[1]} (reg_id, isin) paires")
|
||||
print(
|
||||
f"\n[Panel mensuel] {len(all_months)} mois, {panel.shape[1]} (reg_id, isin) paires"
|
||||
)
|
||||
|
||||
return panel, all_months
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# 4. FLOWS AGRÉGÉS PAR MOIS
|
||||
# ─────────────────────────────────────────────
|
||||
|
|
@ -207,7 +186,7 @@ def aggregate_flows_monthly(flows, all_months, lag_days=3):
|
|||
autour de chaque fin de mois. Utilisé pour détecter
|
||||
les ruptures dues à un décalage comptable de fin de mois.
|
||||
"""
|
||||
flows_f = flows[flows['date'] <= all_months[-1]].copy()
|
||||
flows_f = flows[flows["date"] <= all_months[-1]].copy()
|
||||
|
||||
def assign_month(d, lower_offset=0, upper_offset=0):
|
||||
for m in all_months:
|
||||
|
|
@ -219,26 +198,38 @@ def aggregate_flows_monthly(flows, all_months, lag_days=3):
|
|||
return pd.NaT
|
||||
|
||||
# Standard window
|
||||
flows_f['month_end'] = flows_f['date'].apply(lambda d: assign_month(d))
|
||||
flows_std = flows_f.dropna(subset=['month_end'])
|
||||
monthly_flows = flows_std.groupby(['month_end', 'reg_id', 'isin'])['qty_net'].sum().reset_index()
|
||||
monthly_flows.columns = ['date', 'reg_id', 'isin', 'qty_net_month']
|
||||
flows_f["month_end"] = flows_f["date"].apply(lambda d: assign_month(d))
|
||||
flows_std = flows_f.dropna(subset=["month_end"])
|
||||
monthly_flows = (
|
||||
flows_std.groupby(["month_end", "reg_id", "isin"])["qty_net"]
|
||||
.sum()
|
||||
.reset_index()
|
||||
)
|
||||
monthly_flows.columns = ["date", "reg_id", "isin", "qty_net_month"]
|
||||
|
||||
# Lag window (±lag_days around each EOM)
|
||||
flows_f2 = flows[flows['date'] <= all_months[-1]].copy()
|
||||
flows_f2['month_end'] = flows_f2['date'].apply(
|
||||
lambda d: assign_month(d, lower_offset=lag_days, upper_offset=lag_days))
|
||||
flows_lag = flows_f2.dropna(subset=['month_end'])
|
||||
monthly_flows_lag = flows_lag.groupby(['month_end', 'reg_id', 'isin'])['qty_net'].sum().reset_index()
|
||||
monthly_flows_lag.columns = ['date', 'reg_id', 'isin', 'qty_net_month']
|
||||
flows_f2 = flows[flows["date"] <= all_months[-1]].copy()
|
||||
flows_f2["month_end"] = flows_f2["date"].apply(
|
||||
lambda d: assign_month(d, lower_offset=lag_days, upper_offset=lag_days)
|
||||
)
|
||||
flows_lag = flows_f2.dropna(subset=["month_end"])
|
||||
monthly_flows_lag = (
|
||||
flows_lag.groupby(["month_end", "reg_id", "isin"])["qty_net"]
|
||||
.sum()
|
||||
.reset_index()
|
||||
)
|
||||
monthly_flows_lag.columns = ["date", "reg_id", "isin", "qty_net_month"]
|
||||
|
||||
print(f"\n[Flows mensuels] {len(monthly_flows)} enregistrements (standard) | "
|
||||
f"{len(monthly_flows_lag)} (lag window ±{lag_days}d)")
|
||||
print(
|
||||
f"\n[Flows mensuels] {len(monthly_flows)} enregistrements (standard) | "
|
||||
f"{len(monthly_flows_lag)} (lag window ±{lag_days}d)"
|
||||
)
|
||||
|
||||
return monthly_flows, monthly_flows_lag
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# 5. ÉTAPE 2 — Score de cohérence temporelle
|
||||
# ÉTAPE 2 — Score de cohérence temporelle
|
||||
# ─────────────────────────────────────────────
|
||||
def compute_reconciliation_error(qty_t_minus1, qty_t, net_flow, alpha=ALPHA):
|
||||
"""
|
||||
|
|
@ -256,8 +247,17 @@ def compute_reconciliation_error(qty_t_minus1, qty_t, net_flow, alpha=ALPHA):
|
|||
err_ratio = err / denom
|
||||
return err_ratio, err_ratio > alpha
|
||||
|
||||
def score_propagation(panel, monthly_flows, monthly_flows_lag, weights, universe,
|
||||
all_months, broken_months=None, lag_months=None):
|
||||
|
||||
def score_propagation(
|
||||
panel,
|
||||
monthly_flows,
|
||||
monthly_flows_lag,
|
||||
weights,
|
||||
universe,
|
||||
all_months,
|
||||
broken_months=None,
|
||||
lag_months=None,
|
||||
):
|
||||
"""
|
||||
Propage les scores de t_ref vers t=0 (passé).
|
||||
|
||||
|
|
@ -275,7 +275,7 @@ def score_propagation(panel, monthly_flows, monthly_flows_lag, weights, universe
|
|||
- mapping : dict {reg_id_original → reg_id_courant} (après chirurgie)
|
||||
"""
|
||||
broken_months = broken_months or set()
|
||||
lag_months = lag_months or set()
|
||||
lag_months = lag_months or set()
|
||||
|
||||
# Initialisation
|
||||
scores = dict(weights) # scores à t_ref
|
||||
|
|
@ -286,8 +286,10 @@ def score_propagation(panel, monthly_flows, monthly_flows_lag, weights, universe
|
|||
mapping = {r: r for r in universe}
|
||||
|
||||
# Flows indexés pour accès rapide
|
||||
flows_idx = monthly_flows.set_index(['date', 'reg_id', 'isin'])['qty_net_month']
|
||||
flows_idx_lag = monthly_flows_lag.set_index(['date', 'reg_id', 'isin'])['qty_net_month']
|
||||
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.
|
||||
|
|
@ -310,7 +312,7 @@ def score_propagation(panel, monthly_flows, monthly_flows_lag, weights, universe
|
|||
|
||||
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_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
|
||||
|
|
@ -328,7 +330,7 @@ def score_propagation(panel, monthly_flows, monthly_flows_lag, weights, universe
|
|||
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_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
|
||||
|
|
@ -341,8 +343,8 @@ def score_propagation(panel, monthly_flows, monthly_flows_lag, weights, universe
|
|||
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)
|
||||
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)
|
||||
|
|
@ -426,9 +428,11 @@ def score_propagation(panel, monthly_flows, monthly_flows_lag, weights, universe
|
|||
# Use whichever flow window gives the smaller error,
|
||||
# then attenuate the result
|
||||
best_err = min(err_ratio, err_lag)
|
||||
attenuation = (BROKEN_MONTH_ATTENUATION
|
||||
if key in broken_months
|
||||
else LAG_ATTENUATION)
|
||||
attenuation = (
|
||||
BROKEN_MONTH_ATTENUATION
|
||||
if key in broken_months
|
||||
else LAG_ATTENUATION
|
||||
)
|
||||
err_ratio = best_err * attenuation
|
||||
|
||||
# Pondération par AUM à t_curr
|
||||
|
|
@ -454,13 +458,16 @@ def score_propagation(panel, monthly_flows, monthly_flows_lag, weights, universe
|
|||
errors_history[t_prev] = dict(errors_at_t)
|
||||
|
||||
total_score = sum(scores.values())
|
||||
print(f" {t_prev.date()} | Σ scores = {total_score:.4f} | "
|
||||
f"Comptes actifs = {sum(1 for v in scores.values() if v > 0)}")
|
||||
print(
|
||||
f" {t_prev.date()} | Σ scores = {total_score:.4f} | "
|
||||
f"Comptes actifs = {sum(1 for v in scores.values() if v > 0)}"
|
||||
)
|
||||
|
||||
return scores_history, errors_history, mapping
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# 6. ÉTAPE 3 — Chirurgie de code
|
||||
# ÉTAPE 3 — Chirurgie de code
|
||||
# ─────────────────────────────────────────────
|
||||
def jaccard_isin(set_a, set_b):
|
||||
"""Coefficient de Jaccard entre deux ensembles d'ISIN."""
|
||||
|
|
@ -470,8 +477,17 @@ def jaccard_isin(set_a, set_b):
|
|||
union = len(set_a | set_b)
|
||||
return inter / union if union > 0 else 0.0
|
||||
|
||||
def find_best_candidate(reg_orig, reg_curr, t_prev, t_curr,
|
||||
panel, flows_idx, all_regs_at_t_prev, mapping_inv):
|
||||
|
||||
def find_best_candidate(
|
||||
reg_orig,
|
||||
reg_curr,
|
||||
t_prev,
|
||||
t_curr,
|
||||
panel,
|
||||
flows_idx,
|
||||
all_regs_at_t_prev,
|
||||
mapping_inv,
|
||||
):
|
||||
"""
|
||||
Pour un reg_id dont le score a fortement chuté, cherche le meilleur
|
||||
candidat j à t_prev tel que :
|
||||
|
|
@ -485,9 +501,13 @@ def find_best_candidate(reg_orig, reg_curr, t_prev, t_curr,
|
|||
if reg_curr not in panel.columns.get_level_values(0):
|
||||
return None, 0.0
|
||||
|
||||
isin_curr = set(panel[reg_curr].columns[
|
||||
panel[reg_curr].loc[t_curr].notna() & (panel[reg_curr].loc[t_curr] != 0)
|
||||
].tolist())
|
||||
isin_curr = set(
|
||||
panel[reg_curr]
|
||||
.columns[
|
||||
panel[reg_curr].loc[t_curr].notna() & (panel[reg_curr].loc[t_curr] != 0)
|
||||
]
|
||||
.tolist()
|
||||
)
|
||||
|
||||
if not isin_curr:
|
||||
return None, 0.0
|
||||
|
|
@ -508,9 +528,15 @@ def find_best_candidate(reg_orig, reg_curr, t_prev, t_curr,
|
|||
|
||||
# ISIN de j à t_prev
|
||||
col_j = panel[j]
|
||||
isin_j = set(col_j.columns[
|
||||
col_j.loc[t_prev].notna() & (col_j.loc[t_prev] != 0)
|
||||
].tolist()) if t_prev in col_j.index else set()
|
||||
isin_j = (
|
||||
set(
|
||||
col_j.columns[
|
||||
col_j.loc[t_prev].notna() & (col_j.loc[t_prev] != 0)
|
||||
].tolist()
|
||||
)
|
||||
if t_prev in col_j.index
|
||||
else set()
|
||||
)
|
||||
|
||||
if not isin_j:
|
||||
continue
|
||||
|
|
@ -525,8 +551,16 @@ def find_best_candidate(reg_orig, reg_curr, t_prev, t_curr,
|
|||
weighted_err = 0
|
||||
|
||||
for isin in common_isin:
|
||||
qty_t = panel[reg_curr][isin].get(t_curr, np.nan) if isin in panel[reg_curr].columns else np.nan
|
||||
qty_t_prev = panel[j][isin].get(t_prev, np.nan) if isin in panel[j].columns else np.nan
|
||||
qty_t = (
|
||||
panel[reg_curr][isin].get(t_curr, np.nan)
|
||||
if isin in panel[reg_curr].columns
|
||||
else np.nan
|
||||
)
|
||||
qty_t_prev = (
|
||||
panel[j][isin].get(t_prev, np.nan)
|
||||
if isin in panel[j].columns
|
||||
else np.nan
|
||||
)
|
||||
|
||||
if pd.isna(qty_t) or pd.isna(qty_t_prev):
|
||||
continue
|
||||
|
|
@ -552,8 +586,9 @@ def find_best_candidate(reg_orig, reg_curr, t_prev, t_curr,
|
|||
return best_candidate, best_composite
|
||||
|
||||
|
||||
def _recompute_score_with_candidate(reg_orig, candidate, t_prev, t_curr,
|
||||
panel, flows_idx, score_curr):
|
||||
def _recompute_score_with_candidate(
|
||||
reg_orig, candidate, t_prev, t_curr, panel, flows_idx, score_curr
|
||||
):
|
||||
"""
|
||||
Recalcule proprement l'erreur de réconciliation pour un candidat donné,
|
||||
et retourne le score après chirurgie.
|
||||
|
|
@ -562,17 +597,29 @@ def _recompute_score_with_candidate(reg_orig, candidate, t_prev, t_curr,
|
|||
return score_curr * 0 # candidat inexistant
|
||||
|
||||
isin_list_cand = panel[candidate].columns.tolist()
|
||||
isin_list_curr = panel[reg_orig].columns.tolist() if reg_orig in panel.columns.get_level_values(0) else []
|
||||
isin_list_curr = (
|
||||
panel[reg_orig].columns.tolist()
|
||||
if reg_orig in panel.columns.get_level_values(0)
|
||||
else []
|
||||
)
|
||||
|
||||
total_aum = 0
|
||||
weighted_err = 0
|
||||
|
||||
for isin in isin_list_curr:
|
||||
qty_t = panel[reg_orig][isin].get(t_curr, np.nan) if isin in panel[reg_orig].columns else np.nan
|
||||
qty_t = (
|
||||
panel[reg_orig][isin].get(t_curr, np.nan)
|
||||
if isin in panel[reg_orig].columns
|
||||
else np.nan
|
||||
)
|
||||
if pd.isna(qty_t) or qty_t == 0:
|
||||
continue
|
||||
|
||||
qty_t_prev = panel[candidate][isin].get(t_prev, np.nan) if isin in panel[candidate].columns else np.nan
|
||||
qty_t_prev = (
|
||||
panel[candidate][isin].get(t_prev, np.nan)
|
||||
if isin in panel[candidate].columns
|
||||
else np.nan
|
||||
)
|
||||
|
||||
try:
|
||||
net_flow = flows_idx.loc[(t_curr, candidate, isin)]
|
||||
|
|
@ -592,9 +639,18 @@ def _recompute_score_with_candidate(reg_orig, candidate, t_prev, t_curr,
|
|||
return score_curr * (1.0 - min(avg_err, 1.0))
|
||||
|
||||
|
||||
def run_surgery_pass(scores_history, errors_history, panel, monthly_flows,
|
||||
monthly_flows_lag, weights, universe, all_months,
|
||||
broken_months=None, lag_months=None):
|
||||
def run_surgery_pass(
|
||||
scores_history,
|
||||
errors_history,
|
||||
panel,
|
||||
monthly_flows,
|
||||
monthly_flows_lag,
|
||||
weights,
|
||||
universe,
|
||||
all_months,
|
||||
broken_months=None,
|
||||
lag_months=None,
|
||||
):
|
||||
"""
|
||||
Deuxième passe : pour chaque mois avec des ruptures fortes,
|
||||
tente une chirurgie de code et recalcule les scores.
|
||||
|
|
@ -609,8 +665,10 @@ def run_surgery_pass(scores_history, errors_history, panel, monthly_flows,
|
|||
- surgery_log : liste des opérations effectuées
|
||||
- scores_final : scores au dernier mois
|
||||
"""
|
||||
flows_idx = monthly_flows.set_index(['date', 'reg_id', 'isin'])['qty_net_month']
|
||||
flows_idx_lag = monthly_flows_lag.set_index(['date', 'reg_id', 'isin'])['qty_net_month']
|
||||
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"
|
||||
]
|
||||
|
||||
# Tous les reg_ids présents dans le panel (univers + codes historiques)
|
||||
all_regs_in_panel = set(panel.columns.get_level_values(0))
|
||||
|
|
@ -622,7 +680,9 @@ def run_surgery_pass(scores_history, errors_history, panel, monthly_flows,
|
|||
reg_isin_at_date[reg] = {}
|
||||
col = panel[reg]
|
||||
for date in col.index:
|
||||
active = set(col.columns[(col.loc[date].notna()) & (col.loc[date] != 0)].tolist())
|
||||
active = set(
|
||||
col.columns[(col.loc[date].notna()) & (col.loc[date] != 0)].tolist()
|
||||
)
|
||||
if active:
|
||||
reg_isin_at_date[reg][date] = active
|
||||
|
||||
|
|
@ -656,7 +716,9 @@ def run_surgery_pass(scores_history, errors_history, panel, monthly_flows,
|
|||
# Erreur sans chirurgie (depuis étape 2)
|
||||
err = errors_history.get(t_prev, {}).get(reg_orig, 0.0)
|
||||
score_prev_no_surgery = score_curr * (1.0 - min(err, 1.0))
|
||||
drop_ratio = 1.0 - (score_prev_no_surgery / score_curr) if score_curr > 0 else 0
|
||||
drop_ratio = (
|
||||
1.0 - (score_prev_no_surgery / score_curr) if score_curr > 0 else 0
|
||||
)
|
||||
|
||||
if drop_ratio > SCORE_DROP_THRESHOLD:
|
||||
# ── ISIN du compte courant à t_curr (pour pré-filtre) ──
|
||||
|
|
@ -667,10 +729,10 @@ def run_surgery_pass(scores_history, errors_history, panel, monthly_flows,
|
|||
# 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_lookback = 0 # nombre de mois remontés pour trouver ce candidat
|
||||
best_composite = 0.0
|
||||
best_lookback = 0 # nombre de mois remontés pour trouver ce candidat
|
||||
|
||||
# ── 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é.
|
||||
|
|
@ -678,7 +740,9 @@ def run_surgery_pass(scores_history, errors_history, panel, monthly_flows,
|
|||
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)
|
||||
t_lookup = all_months[
|
||||
i - (k - 1)
|
||||
] # date candidate = t_prev - (k-1)
|
||||
confidence = 1.0 - (k - 1) / MAX_SURGERY_LOOKBACK
|
||||
|
||||
for j in available:
|
||||
|
|
@ -699,38 +763,54 @@ def run_surgery_pass(scores_history, errors_history, panel, monthly_flows,
|
|||
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
|
||||
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
|
||||
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),
|
||||
'drop_without_surgery': round(drop_ratio, 4),
|
||||
'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"{lookback_note})")
|
||||
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),
|
||||
"drop_without_surgery": round(drop_ratio, 4),
|
||||
"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"{lookback_note})"
|
||||
)
|
||||
|
||||
# Mise à jour mapping
|
||||
if best_candidate != reg_curr:
|
||||
|
|
@ -738,10 +818,10 @@ def run_surgery_pass(scores_history, errors_history, panel, monthly_flows,
|
|||
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
|
||||
|
|
@ -753,43 +833,68 @@ def run_surgery_pass(scores_history, errors_history, panel, monthly_flows,
|
|||
scores_history_corrected[t_prev] = dict(scores)
|
||||
|
||||
total_score = sum(s for s in scores.values() if not np.isnan(s))
|
||||
n_surgeries = sum(1 for op in surgery_log if op['date'] == t_prev)
|
||||
print(f" {t_prev.date()} | Σ scores = {total_score:.4f} | "
|
||||
f"Chirurgies = {n_surgeries}")
|
||||
n_surgeries = sum(1 for op in surgery_log if op["date"] == t_prev)
|
||||
print(
|
||||
f" {t_prev.date()} | Σ scores = {total_score:.4f} | "
|
||||
f"Chirurgies = {n_surgeries}"
|
||||
)
|
||||
|
||||
return mapping_history, surgery_log, scores, scores_history_corrected
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# 7. EXPORT RÉSULTATS
|
||||
# EXPORT RÉSULTATS
|
||||
# ─────────────────────────────────────────────
|
||||
def export_results(scores_history, mapping_history, surgery_log, all_months, out_prefix="carmignac"):
|
||||
def export_results(
|
||||
scores_history, mapping_history, surgery_log, all_months, out_prefix="carmignac"
|
||||
):
|
||||
"""Exporte les résultats clés en CSV."""
|
||||
|
||||
# Score history
|
||||
rows = []
|
||||
for date, sc in scores_history.items():
|
||||
for reg, score in sc.items():
|
||||
rows.append({'date': date, 'reg_id': reg, 'score': score})
|
||||
df_scores = pd.DataFrame(rows) if rows else pd.DataFrame(columns=['date', 'reg_id', 'score'])
|
||||
rows.append({"date": date, "reg_id": reg, "score": score})
|
||||
df_scores = (
|
||||
pd.DataFrame(rows)
|
||||
if rows
|
||||
else pd.DataFrame(columns=["date", "reg_id", "score"])
|
||||
)
|
||||
if not df_scores.empty:
|
||||
df_scores = df_scores.sort_values(['date', 'score'], ascending=[True, False])
|
||||
df_scores.to_csv(f"repair_challenge/repair_results/{out_prefix}_scores.csv", index=False)
|
||||
df_scores = df_scores.sort_values(["date", "score"], ascending=[True, False])
|
||||
df_scores.to_csv(
|
||||
f"repair_challenge/repair_results/{out_prefix}_scores.csv", index=False
|
||||
)
|
||||
|
||||
# Mapping history
|
||||
rows_m = []
|
||||
for date, mp in mapping_history.items():
|
||||
for reg_orig, reg_used in mp.items():
|
||||
rows_m.append({'date': date, 'reg_orig': reg_orig, 'reg_used': reg_used,
|
||||
'changed': reg_orig != reg_used})
|
||||
df_mapping = pd.DataFrame(rows_m) if rows_m else pd.DataFrame(columns=['date', 'reg_orig', 'reg_used', 'changed'])
|
||||
rows_m.append(
|
||||
{
|
||||
"date": date,
|
||||
"reg_orig": reg_orig,
|
||||
"reg_used": reg_used,
|
||||
"changed": reg_orig != reg_used,
|
||||
}
|
||||
)
|
||||
df_mapping = (
|
||||
pd.DataFrame(rows_m)
|
||||
if rows_m
|
||||
else pd.DataFrame(columns=["date", "reg_orig", "reg_used", "changed"])
|
||||
)
|
||||
if not df_mapping.empty:
|
||||
df_mapping = df_mapping.sort_values(['date', 'reg_orig'])
|
||||
df_mapping.to_csv(f"repair_challenge/repair_results/{out_prefix}_mapping.csv", index=False)
|
||||
df_mapping = df_mapping.sort_values(["date", "reg_orig"])
|
||||
df_mapping.to_csv(
|
||||
f"repair_challenge/repair_results/{out_prefix}_mapping.csv", index=False
|
||||
)
|
||||
|
||||
# Surgery log
|
||||
if surgery_log:
|
||||
df_surgery = pd.DataFrame(surgery_log).sort_values('date')
|
||||
df_surgery.to_csv(f"repair_challenge/repair_results/{out_prefix}_surgery_log.csv", index=False)
|
||||
df_surgery = pd.DataFrame(surgery_log).sort_values("date")
|
||||
df_surgery.to_csv(
|
||||
f"repair_challenge/repair_results/{out_prefix}_surgery_log.csv", index=False
|
||||
)
|
||||
print(f"\n[Export] {len(surgery_log)} opérations de chirurgie sauvegardées.")
|
||||
else:
|
||||
print("\n[Export] Aucune chirurgie effectuée sur ce subset.")
|
||||
|
|
@ -799,16 +904,19 @@ def export_results(scores_history, mapping_history, surgery_log, all_months, out
|
|||
|
||||
return df_scores, df_mapping
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# 8. PIPELINE PRINCIPAL
|
||||
# PIPELINE PRINCIPAL
|
||||
# ─────────────────────────────────────────────
|
||||
def run_pipeline(broken_months_path="repair_challenge/alpha_5%/carmignac_broken_months.csv"):
|
||||
def run_pipeline(
|
||||
broken_months_path="repair_challenge/alpha_5%/carmignac_broken_months.csv",
|
||||
):
|
||||
print("=" * 60)
|
||||
print("CARMIGNAC — Pipeline de réparation des Registrar IDs")
|
||||
print("=" * 60)
|
||||
|
||||
# Chargement
|
||||
aum, flows = load_data()
|
||||
aum, flows = load_data_repair()
|
||||
|
||||
# Broken months (optional — produced by carmignac_diagnostics.py)
|
||||
broken_months, lag_months = load_broken_months(broken_months_path)
|
||||
|
|
@ -816,9 +924,9 @@ def run_pipeline(broken_months_path="repair_challenge/alpha_5%/carmignac_broken_
|
|||
# Étape 1 — Univers de référence
|
||||
aum_ref, weights, universe, t_ref = build_reference_universe(aum)
|
||||
|
||||
print(f"\n Top 5 comptes par poids :")
|
||||
print("\n Top 5 comptes par poids :")
|
||||
for reg, w in sorted(weights.items(), key=lambda x: -x[1])[:5]:
|
||||
print(f" {reg} : {w:.4f} ({w*100:.2f}%)")
|
||||
print(f" {reg} : {w:.4f} ({w * 100:.2f}%)")
|
||||
|
||||
# Panel mensuel
|
||||
panel, all_months = build_monthly_panel(aum, universe, t_ref)
|
||||
|
|
@ -829,16 +937,31 @@ def run_pipeline(broken_months_path="repair_challenge/alpha_5%/carmignac_broken_
|
|||
# Étape 2 — Score de cohérence (sans chirurgie)
|
||||
print("\n[Étape 2] Propagation des scores (sans chirurgie)...")
|
||||
scores_history, errors_history, _ = score_propagation(
|
||||
panel, monthly_flows, monthly_flows_lag, weights, universe, all_months,
|
||||
broken_months=broken_months, lag_months=lag_months
|
||||
panel,
|
||||
monthly_flows,
|
||||
monthly_flows_lag,
|
||||
weights,
|
||||
universe,
|
||||
all_months,
|
||||
broken_months=broken_months,
|
||||
lag_months=lag_months,
|
||||
)
|
||||
|
||||
# Étape 3 — Chirurgie
|
||||
print("\n[Étape 3] Passe de chirurgie...")
|
||||
mapping_history, surgery_log, final_scores, scores_history_corrected = run_surgery_pass(
|
||||
scores_history, errors_history, panel, monthly_flows, monthly_flows_lag,
|
||||
weights, universe, all_months,
|
||||
broken_months=broken_months, lag_months=lag_months
|
||||
mapping_history, surgery_log, final_scores, scores_history_corrected = (
|
||||
run_surgery_pass(
|
||||
scores_history,
|
||||
errors_history,
|
||||
panel,
|
||||
monthly_flows,
|
||||
monthly_flows_lag,
|
||||
weights,
|
||||
universe,
|
||||
all_months,
|
||||
broken_months=broken_months,
|
||||
lag_months=lag_months,
|
||||
)
|
||||
)
|
||||
|
||||
# Export — on utilise les scores corrigés (post-chirurgie) comme référence
|
||||
|
|
@ -847,16 +970,19 @@ def run_pipeline(broken_months_path="repair_challenge/alpha_5%/carmignac_broken_
|
|||
scores_history_corrected, mapping_history, surgery_log, all_months
|
||||
)
|
||||
|
||||
|
||||
# Résumé final
|
||||
print("\n" + "=" * 60)
|
||||
print("RÉSUMÉ FINAL")
|
||||
print("=" * 60)
|
||||
print(f" Dates couvertes : {all_months[0].date()} → {all_months[-1].date()}")
|
||||
print(
|
||||
f" Dates couvertes : {all_months[0].date()} → {all_months[-1].date()}"
|
||||
)
|
||||
print(f" Comptes dans l'univers : {len(universe)}")
|
||||
print(f" Chirurgies effectuées : {len(surgery_log)}")
|
||||
score_by_date = {d: sum(s for s in sc.values() if s == s)
|
||||
for d, sc in scores_history_corrected.items()}
|
||||
score_by_date = {
|
||||
d: sum(s for s in sc.values() if s == s)
|
||||
for d, sc in scores_history_corrected.items()
|
||||
}
|
||||
print(f" Σ scores à t_ref : {score_by_date[t_ref]:.4f}")
|
||||
print(f" Σ scores à t_min : {score_by_date[all_months[0]]:.4f}")
|
||||
|
||||
2329
src/repair_challenge/helpers.py
Normal file
2329
src/repair_challenge/helpers.py
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user