Kelick Tech Hub

Build, learn & keep up — tech, Linux & sports news included.

C++ Credit Card Fraud Detection Project

Advanced•1–2 weeks•heuristics, statistics, data analysis

Scan transaction streams and flag the ones that smell wrong — with reasons.

Share on X

What you will build

Feed the system a stream of card transactions and it raises alerts: impossible travel, velocity spikes, amounts far above the cardholder's norm. There's no machine learning here — just well-chosen rules and statistics, which is how real fraud systems started and largely still work.

What you will learn

Feature checklist

Starter code

#include <string>
#include <vector>
#include <cmath>

struct Txn { std::string card; long timeMin; std::string city; double amount; };
struct Alert { std::string card, rule, detail; };

std::vector<Alert> detect(const std::vector<Txn>& txns) {
    std::vector<Alert> alerts;
    for (size_t i = 0; i < txns.size(); ++i) {
        const Txn& t = txns[i];
        // History for this card so far:
        double sum = 0; int n = 0;
        const Txn* prev = nullptr;
        for (size_t j = 0; j < i; ++j)
            if (txns[j].card == t.card) { sum += txns[j].amount; ++n; prev = &txns[j]; }
        if (n >= 5) {
            double avg = sum / n;
            if (t.amount > 3 * avg)
                alerts.push_back({t.card, "AMOUNT_SPIKE",
                    "amount is over 3x the card average"});
        }
        if (prev && prev->city != t.city &&
            std::abs(t.timeMin - prev->timeMin) < 60)
            alerts.push_back({t.card, "IMPOSSIBLE_TRAVEL",
                "two cities within an hour"});
    }
    return alerts;
}

Full walkthrough

Step 1: Load transactions into honest data

struct Txn { cardId, timestamp (as minutes since epoch or a comparable number), city, amount } loaded from a file, SORTED by card then time — every rule below is a pattern over a card's timeline, and timelines only make sense sorted. struct Alert { txn index, rule name, evidence string } is the output type: an alert without evidence is an accusation, so evidence is a required field, not a garnish.

Step 2: Group by card and compute each card's baseline

detect() walks the transactions grouped per card (a std::map<std::string, std::vector<Txn>> or a single sorted pass). Each card gets an average amount — the baseline its own behaviour is judged against. Fraud is deviation from SELF, not from a global average: 500 is pocket money for one card and a siren for another.

Step 3: Rule 1: amount spike — over 3× the card's average

Flag any transaction whose amount exceeds 3 × the cardholder's average, with evidence naming both numbers ('amount 500 vs card average 120 — 4.2×'). Computing the average EXCLUDING the transaction being judged (or including it, deliberately) is a choice that moves borderline cases — make it consciously and document it.

Step 4: Rule 2: impossible travel

Consecutive transactions for the same card in different cities whose time gap is smaller than any plausible journey (say under 60 minutes for inter-city) cannot both be the cardholder. Evidence: both cities, both timestamps, the gap. This rule needs the sorted timeline from step 1 and nothing else — distance tables are an optional refinement.

if (b.city != a.city && b.time - a.time < 60)
    alerts.push_back({b, "impossible travel",
        a.city + " " + timeStr(a) + " -> " + b.city + " " + timeStr(b)});

Step 5: Rule 3: the small-test burst

Card thieves test stolen cards with tiny charges first. Flag 3+ transactions under a small threshold (e.g. 2.00) within a short window (e.g. 10 minutes) on one card. It is a sliding-window count over the card's timeline — two pointers, no nested loops, and a lovely small algorithm hiding inside a business rule.

Step 6: Summarise per rule — and test precision on labelled data

Print alerts grouped by rule with counts, then the honest metric: given a labelled test file (transactions marked fraud/not), precision = true alerts / all alerts. A detector that flags everything has perfect catch rate and zero usefulness; precision on labelled data is the number that tells the truth, and printing it in the summary is the checklist's demand.

Step 7: Test with a scripted story per rule

Plant: a card averaging 120 that suddenly spends 500 (rule 1); the same card in Lagos at 10:02 and London at 10:40 (rule 2 — 38 minutes); four 1.00 charges in 6 minutes (rule 3); and a control card behaving normally that must produce ZERO alerts. The control case matters most — a detector that cries wolf on the innocent card fails the only test that counts in production.

Common mistakes

Compile and run

g++ -std=c++17 -o fraud fraud.cpp

Sample run

$ ./fraud
Loaded 1,240 transactions for 86 cards (sorted per card).
Baselines computed per card. Running 3 rules...

ALERT — rule: amount spike
  card ****4417: 500.00 at MegaMart — card average is 120.00 (4.2x)
ALERT — rule: impossible travel
  card ****4417: Lagos 10:02 -> London 10:40 (38 minutes apart)
ALERT — rule: small-test burst
  card ****9023: 4 charges of 1.00 within 6 minutes (test-charge pattern)

Summary: 3 alerts — amount spike 1 · impossible travel 1 · small-test burst 1
On the labelled test set: precision 0.83 (5 of 6 alerts were true fraud)

Try it in your browser

No browser demo is included for this project. Build the C++ version below — that is the real program.

Related Advanced projects

← All 60 projects · Classic site (Learn & Sandbox)

Download Fake GPS