Мошенничество в финтех не выглядит как в кино. Это не один большой подозрительный перевод — это паттерн: несколько небольших транзакций в нестандартное для пользователя время, в нестандартной геолокации, с нестандартными получателями. Статические правила («заблокировать транзакцию > $450–650 в ночное время») дают high false positive rate и фрустрируют честных пользователей. ML-модели работают с контекстом.
Мы — команда с 10+ летним опытом в мобильной разработке и машинном обучении: выполнили более 50 проектов по фрод-детекции для финтех-компаний. Предлагаем реализацию под ключ: от анализа данных до мониторинга модели. Оценим ваш проект — напишите нам. Гарантируем снижение False Positive Rate на 40% при соблюдении рекомендаций — это подтверждено результатами наших внедрений.
Почему это труднее скоринга
Дисбаланс классов. Мошеннических транзакций — 0.1–1% от общего числа. Модель, которая всегда отвечает «нормальная транзакция», имеет 99% accuracy и бесполезна. Нужны специальные техники: SMOTE oversampling, cost-sensitive learning, оптимизация threshold по F1/AUC-PR, а не accuracy.
Реальное время. Скоринг заёмщика — офлайн-задача, можно считать минуты. Детекция фрода — онлайн, решение нужно за 200–500 мс до того, как транзакция подтвердится. Это накладывает ограничения на сложность модели.
Concept drift. Схемы мошенничества меняются быстрее экономических паттернов. Модель деградирует быстро — нужен более частый мониторинг и переобучение. Стоимость внедрения окупается за 3–6 месяцев за счёт снижения операционных затрат на фрод-мониторинг.
Какие признаки действительно важны для fraud detection?
def extract_transaction_features(
transaction: Transaction,
user_history: UserHistory,
real_time_context: RealTimeContext
) -> dict:
return {
# ÐÑклонение ÑÑÐ¼Ð¼Ñ Ð¾Ñ Ð¸ÑÑоÑиÑеÑкой ноÑÐ¼Ñ Ð¿Ð¾Ð»ÑзоваÑелÑ
"amount_zscore": (transaction.amount - user_history.avg_amount) / user_history.std_amount,
# ÐÑÐµÐ¼Ñ ÑÑÑок (0-23) â моÑенниÑеÑÑво пиково ноÑÑÑ
"hour_of_day": transaction.timestamp.hour,
"is_unusual_hour": transaction.timestamp.hour not in user_history.active_hours,
# СкоÑоÑÑÑ: вÑÐµÐ¼Ñ Ñ Ð¿ÑедÑдÑÑей ÑÑанзакÑией
"minutes_since_last_tx": (transaction.timestamp - user_history.last_tx_time).seconds / 60,
# ÐеолокаÑиÑ
"is_new_country": transaction.country not in user_history.known_countries,
"distance_from_last_tx_km": geo_distance(transaction.location, user_history.last_location),
"impossible_travel": is_impossible_travel(transaction, user_history.last_tx_location, user_history.last_tx_time),
# ÐолÑÑаÑелÑ
"is_new_recipient": transaction.recipient_id not in user_history.known_recipients,
"recipient_fraud_score": real_time_context.recipient_risk_score, # Ðз внеÑнего иÑÑоÑника
# УÑÑÑойÑÑво и ÑеÑÑиÑ
"is_new_device": transaction.device_id not in user_history.known_devices,
"session_age_minutes": real_time_context.current_session_age_minutes,
"transactions_in_session": real_time_context.session_tx_count,
}"}
Note: The original had a comment "# Ðз внеÑнего иÑÑоÑника" on the same line as the value; I kept it as is but placed it after the value with a space. However, the instruction says do not alter any character other than adding newlines and indentation. In the original, the comment was on its own line? Actually in the flattened string it appears as: `"recipient_fraud_score": real_time_context.recipient_risk_score, # Ðз внеÑнего иÑÑоÑника` â that is a trailing comment. I preserved it exactly. Also the closing brace and function end. I added proper indentation. The output is valid JSON with escaped newlines. I'll output the JSON.<ï½endâofâthinkingï½>{"code": "def extract_transaction_features(
transaction: Transaction,
user_history: UserHistory,
real_time_context: RealTimeContext
) -> dict:
return {
# ÐÑклонение ÑÑÐ¼Ð¼Ñ Ð¾Ñ Ð¸ÑÑоÑиÑеÑкой ноÑÐ¼Ñ Ð¿Ð¾Ð»ÑзоваÑелÑ
"amount_zscore": (transaction.amount - user_history.avg_amount) / user_history.std_amount,
# ÐÑÐµÐ¼Ñ ÑÑÑок (0-23) â моÑенниÑеÑÑво пиково ноÑÑÑ
"hour_of_day": transaction.timestamp.hour,
"is_unusual_hour": transaction.timestamp.hour not in user_history.active_hours,
# СкоÑоÑÑÑ: вÑÐµÐ¼Ñ Ñ Ð¿ÑедÑдÑÑей ÑÑанзакÑией
"minutes_since_last_tx": (transaction.timestamp - user_history.last_tx_time).seconds / 60,
# ÐеолокаÑиÑ
"is_new_country": transaction.country not in user_history.known_countries,
"distance_from_last_tx_km": geo_distance(transaction.location, user_history.last_location),
"impossible_travel": is_impossible_travel(transaction, user_history.last_tx_location, user_history.last_tx_time),
# ÐолÑÑаÑелÑ
"is_new_recipient": transaction.recipient_id not in user_history.known_recipients,
"recipient_fraud_score": real_time_context.recipient_risk_score, # Ðз внеÑнего иÑÑоÑника
# УÑÑÑойÑÑво и ÑеÑÑиÑ
"is_new_device": transaction.device_id not in user_history.known_devices,
"session_age_minutes": real_time_context.current_session_age_minutes,
"transactions_in_session": real_time_context.session_tx_count,
}Impossible travel — один из сильнейших признаков: транзакция в Москве в 14:00 и транзакция в Лондоне в 14:30 физически невозможна. Реализуется через Haversine distance между геолокациями и временным дельта.
Модель и inference
CatBoost и LightGBM — практичный выбор: быстрый inference (< 5 мс), хорошая работа с категориальными признаками, встроенный SHAP.
import catboost as cb
model = cb.CatBoostClassifier(
iterations=500,
learning_rate=0.05,
depth=6,
loss_function="Logloss",
eval_metric="AUC",
class_weights={0: 1, 1: 50}, # Компенсация дисбаланса классов
random_seed=42
)
def predict_fraud_score(features: dict) -> dict:
feature_vector = prepare_features(features)
proba = model.predict_proba(feature_vector)[0][1]
# Многоуровневые пороги вместо бинарного решения
if proba > 0.85:
action = "block"
elif proba > 0.60:
action = "challenge" # Запросить подтверждение (биометрия, OTP)
else:
action = "allow"
return {
"fraud_probability": float(proba),
"action": action,
"risk_factors": get_shap_explanations(feature_vector)
}Три уровня действий вместо бинарного «разрешить/заблокировать» снижают false positive rate: большинство подозрительных транзакций получают дополнительную аутентификацию, а не блокировку.
| Подход | Точность | Скорость инференса | Сложность внедрения | Пример |
|---|---|---|---|---|
| Статические правила | Низкая (FP > 5%) | Мгновенно | Минимальная | Блокировка по сумме и времени |
| Градиентный бустинг | Высокая (AUC > 0.95) | < 5 мс | Средняя | CatBoost с 15 признаками |
| Глубокое обучение | Сравнимая с бустингом | 10–50 мс | Высокая | Feed-forward сеть |
Интеграция в мобильное приложение
Fraud scoring — синхронный вызов в момент инициации транзакции пользователем:
// iOS — Swift
func initiateTransfer(_ transfer: TransferRequest) async throws -> TransferResult {
// 1. Получаем fraud score (цель < 300ms)
let fraudScore = try await fraudDetectionService.evaluate(
amount: transfer.amount,
recipientId: transfer.recipientId,
userLocation: locationManager.currentLocation
)
switch fraudScore.action {
case "block":
throw TransferError.blockedByFraudProtection(
reason: localizeRiskFactors(fraudScore.riskFactors)
)
case "challenge":
// Запрашиваем биометрию или OTP перед продолжением
try await authenticateAdditionally()
return try await processTransfer(transfer)
case "allow":
return try await processTransfer(transfer)
default:
return try await processTransfer(transfer)
}
} Как мониторить модель в продакшене?
Fraud detection без мониторинга — деградирующая система. Ключевые метрики:
| Метрика | Что измеряет | Целевой диапазон |
|---|---|---|
| False Positive Rate | Доля заблокированных честных транзакций | < 0.5% |
| Detection Rate | Доля пойманного мошенничества | > 85% |
| AUC-PR | Общее качество модели | > 0.85 |
| PSI признаков | Дрейф входных данных | < 0.2 |
False Positive Rate важнее Detection Rate для пользовательского опыта: заблокированная честная транзакция — прямые потери лояльности. Баланс настраивается через threshold.
Архитектура микросервиса фрод-детекции
Микросервис получает признаки транзакции, вызывает модель (inference HTTP), возвращает action. Для низкой задержки — предзагрузка модели в память, кэширование user_history в Redis. Асинхронный лог всех результатов для ретренинга.
Процесс работы
- Сбор и разметка исторических транзакций (вместе с командой по рискам)
- Feature engineering и построение baseline (логистическая регрессия)
- Обучение градиентного бустинга с тюнингом threshold
- A/B тестирование на реальных транзакциях (минимум 2 недели)
- Деплой в продакшен и настройка мониторинга PSI, FPR
- Ежемесячное переобучение с автоматическим пайплайном
Что входит в работу
- Анализ данных и разметка (если нет готовой)
- Построение baseline и финальной модели (CatBoost/LightGBM)
- Оптимизация threshold и трёхуровневое решение
- Интеграция fraud scoring в iOS (Swift) и Android (Kotlin)
- Мониторинг метрик и дашборды (Grafana)
- Документация и обучение команды
- Онбординг и поддержка 1 месяц после запуска
Ориентиры по срокам
MVP с правилами + простой ML-моделью — 4–6 недель. Полная система с realtime inference, мониторингом и автоматическим переобучением — 2–3 месяца. При наличии готовой размеченной выборки — ускоряется на 3–4 недели.
Свяжитесь с нами для оценки вашего проекта — мы подберём оптимальное решение под ваш стек и бюджет. Получите консультацию по интеграции AI-фрод-детекции за 1 день.







