Credit Risk Simulation and Policy Optimization — Python edition of creditools
Project description
📊 PyCrediTools
Credit Risk Simulation, Policy Optimization, and Risk Clustering for Python
PyCrediTools é uma biblioteca de ponta desenvolvida especificamente para equipes de Risco de Crédito. Ela fornece motores computacionais de alta performance para Simulação de Funis de Decisão (Trade-off de Políticas) e Agrupamento Autónomo de Risco (Risk Clustering).
Esqueça o método de tentativa e erro. Com o PyCrediTools, você pode testar filtros de bureau, simular e otimizar cortes de score por loja/região e agrupar seu portfólio aprovado em Ratings contíguos de risco de forma matematicamente ótima e temporalmente estável.
🚀 Instalação
O pacote pode ser instalado diretamente do repositório GitHub:
pip install git+https://github.com/matheuspasche/pycreditools.git
💡 Core Features
- Credit Policy Simulation: Monte esteiras de decisão compostas por múltiplos estágios (Filtros duros de bureau, Regras de Corte de Score, Propensões Variáveis de Fechamento de Contrato).
- Automated Risk Clustering: Agrupe faixas de score em "Ratings de Risco" (A a E) sob restrições rigorosas de negócio (Monotonicidade de risco, Volume Mínimo por grupo e Estabilidade Temporal de Safras).
- Monotonic Sorting Kernel: (Novo!) Garante que ratings de score único sejam estritamente contíguos e ordenados, eliminando sobreposição ou cruzamento de notas (scores mais altos sempre recebendo classificações melhores ou iguais).
- Longitudinal Stability: O motor Ward calcula inibições de cruzamento de risco de forma contínua ao longo de múltiplos períodos (vintages/safras).
📖 Caso de Uso: Substituição de Política Legada (Showcase V14)
Esta seção demonstra o uso real da biblioteca para simular e aprovar a substituição de uma política legada (legacy_score com corte único p78) por um novo modelo campeão (score_5) regionalizado por loja.
O fluxo de execução e validação completo está disponível em tutorial_masterclass_v14.ipynb.
1. Modelagem do Funil de Aprovação (Bureau + Regras de Entrada)
Aplicamos os novos escudos rígidos de entrada (hard filters) e incluímos o ponto de corte do score vigente para comparação direta do funil cumulativo sobre a base de propostas:
import pandas as pd
from pycreditools import CreditPolicy, col
# Criamos a política base com escudos rígidos de entrada
policy_hf = (
CreditPolicy(
applicant_id_col="applicant_id",
score_cols=["score_2", "score_3", "score_4", "score_5"],
current_approval_col="approved",
actual_default_col="actual_default",
time_col="safra"
)
.filter("CPF Válido", col("cpf_valido") == True)
.filter("Teto Negativação", col("vl_negativacao") <= 1500)
.filter("Teto Atraso SCR", col("vl_vencido_scr") <= 3000)
.filter("Teto Protestos", col("vl_protestos") <= 500)
)
# Adicionamos o ponto de corte vigente na política de comparação legada
policy_legacy_hf = (
policy_hf
.filter("Ponto de Corte Vigente", col("legacy_score") >= LEGACY_CUT)
)
O funil de aprovação cumulativo resultante (incluindo o corte vigente):
| Etapa | Volume | % Funil | Δ Etapa |
|---|---|---|---|
| Top of Funnel | 667,348 | 100.0% | — |
| Após CPF Válido | 666,017 | 99.8% | -0.2% |
| Após Teto Negativação | 604,823 | 90.6% | -9.2% |
| Após Teto SCR | 565,019 | 84.7% | -6.6% |
| Após Teto Protestos | 528,232 | 79.2% | -6.5% |
| Após Ponto de Corte Vigente | 136,711 | 20.5% | -74.1% |
| Pós-todos os HF (combinado) | 136,711 | 20.5% | — |
2. Curva de Otimização e Fronteira Eficiente
Avaliamos os quatro scores candidatos (score_2 a score_5) confrontando sua taxa de aprovação contra a inadimplência projetada. O gráfico de Fronteira Eficiente abaixo exibe o desempenho de cada modelo, marcando com um "X" a performance da política histórica legada.
O Score 5 domina claramente a fronteira de eficiência. Ao mesmo nível de aprovação da política histórica (~20.4%), ele projeta uma inadimplência de aprovados drasticamente menor. Prosseguimos a calibração final exclusivamente com ele.
3. Calibração de Inadimplência Plana (Flat Default Rate) por Loja
Para otimizar a alocação de capital e limite de crédito por região geográfica, substituímos a estratégia de aprovação plana por uma política de Inadimplência Plana (Flat Default Rate). Calibramos os cortes por região para atingir um alvo estável de 5.80% de PD Estressada localmente, permitindo que a taxa de aprovação flutue de acordo com a qualidade do público local.
# Notas de corte regionalizadas para atingir alvo de 5.80% PD estressada
cutoffs_loja = {
"Centro-Oeste": 804,
"Nordeste": 806,
"Norte": 794,
"Sudeste": 778,
"Sul": 776,
}
def politica_loja(df_in):
passa = pd.Series(False, index=df_in.index)
for loja, cut in cutoffs_loja.items():
passa.loc[(df_in["region"]==loja) & (df_in["score_5"]>=cut)] = True
return passa
policy_final = (
policy_hf
.filter("Score Regionalizado Flat PD", politica_loja)
.rate("Propensão de Contrato", base_rate=1.0, variable="take_up_rate")
)
Estatísticas regionais resultantes da simulação da política final:
| Região / Loja | Nota de Corte | Taxa de Aprovação Local | PD Estressada |
|---|---|---|---|
| Centro-Oeste | 804 | 18.37% | 5.54% |
| Nordeste | 806 | 16.94% | 5.67% |
| Norte | 794 | 17.67% | 5.79% |
| Sudeste | 778 | 22.32% | 5.86% |
| Sul | 776 | 23.33% | 5.83% |
4. Agrupamento de Risco (Clustering) nos Sobreviventes
Classificamos os clientes aprovados sob a nova política (sobreviventes) em DEV no modelo de agrupamento Ward, gerando Ratings contíguos. O motor garante a contiguidade e a ordenação de forma 100% consistente.
# Treinado na população aprovada sobrevivente
df_train_dev = res_final[
(res_final["new_approval"] > 0.0) &
(res_final["sample"] == "DEV")
].copy()
group_res = find_risk_groups(
data=df_train_dev,
score_cols="score_5",
default_col="actual_default",
bins=30,
max_groups=5,
min_vol_ratio=0.01,
method="ward",
time_col="safra",
max_crossings=1
)
A estrutura de Ratings resultante e sua validação temporal:
| Rating | Faixa de Score 5 | Inad. DEV | Inad. OOT | Vol. DEV (Aprovados) | Vol. OOT (Aprovados) |
|---|---|---|---|---|---|
| A | 941 a 1000 |
1.66% | 1.69% | 42,205 | 19,642 |
| B | 890 a 940 |
4.05% | 4.31% | 27,381 | 12,860 |
| C | 870 a 889 |
5.77% | 5.73% | 14,181 | 6,667 |
| D | 822 a 869 |
8.01% | 8.11% | 36,879 | 17,719 |
| E | 776 a 821 |
10.22% | 10.74% | 18,421 | 9,005 |
Abaixo, plotamos a estabilidade temporal das safras de performance observada dos aprovados sob a nova política, provando que a segregação se mantém robusta e livre de sobreposições ao longo de todo o histórico:
5. Dissecção dos Swaps e Quadrantes de Decisão
A transição de modelo altera a composição da carteira. Avaliamos a performance real observada para os clientes recusados na nova mas aceitos na antiga (Swap Out) vs a performance estressada para os novos aceitos (Swap In):
| Quadrante | Vol. Contratado Esperado | Taxa de Inadimplência | Origem dos Dados |
|---|---|---|---|
| Keep In | 68,912 | 4.54% | Observado (actual_default) |
| Swap In | 23,159 | 10.11% | Simulado Estressado (Angulado) |
| Swap Out | 25,952 | 14.33% | Observado Histórico Legado |
| Keep Out | 0 | N/A | Sem dados (Rejeitados por ambas) |
[!NOTE] Para a simulação dos Swap Ins (Magnum), utilizamos uma estratégia de Agravamento Angulado de risco para precificar de forma conservadora a seleção adversa. O estresse é aplicado de forma incremental por Rating de risco (do melhor para o pior), penalizando mais os ratings mais arriscados:
- Rating A: 1.20x (+20% de estresse)
- Rating B: 1.30x (+30% de estresse)
- Rating C: 1.40x (+40% de estresse)
- Rating D: 1.40x (+40% de estresse)
- Rating E: 1.50x (+50% de estresse)
Aqui está um trecho do código onde definimos e adicionamos esse agravamento angulado por Rating de risco através da classe CustomStress:
from pycreditools import CustomStress
# Agravamento Angulado Swap In (A=1.2× a E=1.50×)
def angulado(df_swap, pd_col):
mapa = {"A": 1.20, "B": 1.30, "C": 1.40, "D": 1.40, "E": 1.50}
fator = df_swap["Rating"].map(mapa).fillna(1.4)
return (df_swap[pd_col] * fator).clip(0, 1)
policy_magnum = (
policy_final
.add_stress(CustomStress(angulado))
)
6. Equilíbrio de Volume e Risco no P&L
A nova política estruturada alcança um resultado extremamente equilibrado no P&L contratado esperado:
- Atração Saudável: Com um motor discriminatório muito superior (Score 5), aprovamos clientes de menor risco. Ao calibrar a taxa de conversão (take-up rate) refletindo o apetite real dos clientes (de 41% nos melhores scores até 95% nos scores mais baixos), mitigamos a seleção adversa.
- Substituição Eficiente (Swaps): Trocamos com sucesso o público de alto risco do legado (Swap Out com inadimplência de 14.33%) por um público qualificado (Swap In com inadimplência esperada mesmo com estresse angulado de 10.11%).
- Efeito Win-Win: O resultado final demonstra que conseguimos reduzir a inadimplência contratada global do portfólio de 7.18% para 5.94% (uma redução de -17.3% no risco total contratado sob estresse angulado rigoroso) enquanto o volume de contratos esperado ficou em 92,071 (contra 94,675 legados, uma pequena redução planejada de -2.8% para maior segurança operacional e controle de risco).
7. Resumo do Impacto de P&L Executivo (Tabela Delta)
A comparação consolidada entre as políticas prova o sucesso do novo motor de simulação:
| Métrica | Política Legada | Nova Política (Flat PD) | Delta Absoluto | Delta Relativo |
|---|---|---|---|---|
| Aprovação Global (% ToF) | 20.47% | 20.50% | +0.02% | +0.1% |
| Inadimplência Contratada (P&L) | 7.18% | 5.94% | -1.24% | -17.3% |
| Volume Contratado Esperado | 94,675 | 92,071 | -2,604 | -2.8% |
8. Crash Test: Resiliência dos Swap Ins (Estresse e Breakeven)
Como a performance dos Swap Ins é simulada, realizamos um teste de estresse severo aplicando multiplicadores de inadimplência sobre essa população. Variamos o fator de estresse de 1.0x até 10.0x no TradeoffAnalyzer para determinar a resiliência da carteira até o ponto de equilíbrio (breakeven) com a política histórica.
O ponto de breakeven é atingido em 2.25x. Isto significa que a inadimplência real do público Swap In teria de ser 2.25 vezes maior do que a estimada pelo modelo (e já estressada angularmente) para que a perda agregada da nova carteira subisse até os 7.18% da política antiga. Esse colchão de resiliência de 125% de sobrecarga prova a segurança operacional e resiliência extrema da nova política de Inadimplência Plana.
9. Exportação e Uso em Produção (Decision Engines)
O pacote permite integrar e implantar a inteligência de decisão calibrada diretamente em motores e microsserviços de produção. Para isso, o DeploymentPolicy expõe exportadores simplificados para pontuar proponentes:
-
Exportar Regras Limpas (Produção): Gera um arquivo JSON contendo apenas as expressões lógicas e faixas contínuas de score de cada Rating. Este formato é ideal para motores que rodam em Go, Java, Node.js ou SQL.
dep_policy = policy.export(rating_recipe=group_res.recipe) dep_policy.save_production_rules("politica_producao.json")
-
JSON Limpo Exportado:
{ "etapas_funil": [ { "posicao": 1, "nome": "CPF Válido", "tipo": "filtro", "expressao": "(cpf_valido == True)" }, { "posicao": 2, "nome": "Corte Regional por Loja", "tipo": "filtro", "expressao": "(region == 'Sudeste' & score_5 >= 778) | (region == 'Nordeste' & score_5 >= 806)" } ], "classificacao_ratings": { "score_coluna": "score_5", "coluna_segmentacao": "region", "segmentos": { "Sudeste": [ { "rating": "A", "nota_minima": 928, "nota_maxima": 1000 }, { "rating": "B", "nota_minima": 890, "nota_maxima": 927 }, { "rating": "C", "nota_minima": 778, "nota_maxima": 889 } ], "Nordeste": [ { "rating": "A", "nota_minima": 915, "nota_maxima": 1000 }, { "rating": "B", "nota_minima": 860, "nota_maxima": 914 }, { "rating": "C", "nota_minima": 806, "nota_maxima": 859 } ] } } }
-
Bases de Retorno Padronizadas e Imutáveis: O método
.predict(df, simple=True)retorna uma cópia isolada (.copy()) contendo exatamente as colunas padrão de controle (decisao,motivo,contratou,inadimplente,cenario,rating).- Base Simplificada (simple=True): Ótima para auditoria e logs de API de produção.
- Base Bruta/Analítica (simple=False): Retorna todas as colunas intermediárias (PDs, status por etapa do funil) para modelagem e estudos analíticos de risco.
🛠️ Contribuir e Desenvolver
Para executar a suíte de testes unitários e verificar o comportamento do motor:
git clone https://github.com/matheuspasche/pycreditools.git
cd pycreditools
pip install -e .
pytest tests/
📜 Licença
Distribuído sob a licença MIT. Desenvolvido para modelagem e engenharia de risco financeiro moderno.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pycreditools-0.2.6.tar.gz.
File metadata
- Download URL: pycreditools-0.2.6.tar.gz
- Upload date:
- Size: 969.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
421ab6bcdfbb47f9b616b03c23730e048f11ed5a22b0766d847d311dd501094f
|
|
| MD5 |
388b0b7b87102017e19969f07929e8ec
|
|
| BLAKE2b-256 |
e381fd3b27a050c0a05bd199b8d0522763f872d6b39a8d5b777d40b2d75714c2
|
File details
Details for the file pycreditools-0.2.6-py3-none-any.whl.
File metadata
- Download URL: pycreditools-0.2.6-py3-none-any.whl
- Upload date:
- Size: 508.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5464e951beb8de8bc993a60cf54c750eb19f0c7d64a16165c46f4a708b9eb93c
|
|
| MD5 |
67db17db064221f34bd31000dd1ddfe1
|
|
| BLAKE2b-256 |
22418a8168ca47d7583f28cac798db70fb1f482a5476f888912dc2209841cfd7
|