Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.finwatch.finance/llms.txt

Use this file to discover all available pages before exploring further.

6.1 Naming Conventions

Rules:
  • Use PascalCase or PascalCase_With_Underscores.
  • Name should describe the fraud pattern, not the implementation detail.
  • Good: HighFrequencyDestination, DormantAccountActivity
  • Bad: CheckAmount, Rule_v2_final
Variables:
  • Use $snake_case.
  • Good: $sanctioned_countries, $high_risk_bins
  • Bad: $myList, $SC

6.2 One Rule, One Purpose

Each .ws file should contain a single rule that detects a single, specific fraud pattern. This provides:
  • Clear audit trails: When a transaction is blocked, you know exactly which pattern triggered it.
  • Independent tuning: You can adjust the score of one rule without affecting others.
  • Clean version history: Git diffs show exactly what changed and why.
Don’t do this:
// BAD: Trying to detect multiple unrelated patterns in one rule
rule EverythingCheck {
    when amount > 10000
      or metadata.country in $sanctioned_countries
      or hour_of_day(timestamp) > 23
    then block score 0.9 reason "Something is wrong"
}
Do this instead: Write three separate rules, each with its own score, reason, and description.

6.3 Writing Clear Descriptions

Your description should answer three questions:
  1. What does this rule detect?
  2. Why is this pattern suspicious?
  3. What is the expected action?
// Excellent: Answers all three questions.
description "Detects more than 10 payments to the same destination within 24 hours, which may indicate account compromise or automated fraud."

6.4 Performance Considerations

Order your and conditions wisely. Place cheap checks before expensive checks. The interpreter uses short-circuit evaluation, so if the first condition fails, the expensive ones are never executed.
// GOOD: Cheap 'amount' check first, expensive 'count()' second.
when amount > 100
 and count(when destination == $current.destination, "PT24H") > 10

// BAD: Expensive 'count()' runs even for $1 transactions.
when count(when destination == $current.destination, "PT24H") > 10
 and amount > 100
Keep time windows as small as possible. "PT1H" is much faster to query than "P30D". Use the smallest window that effectively catches the pattern. Avoid overly complex regex. Catastrophic backtracking in regex can cause severe performance degradation. Keep patterns simple and specific. Avoid nested quantifiers like (.+)+.

6.5 Scoring Strategy

Adopt a consistent scoring philosophy across your entire rule set:
CategoryScore RangeExample Rules
Informational0.1 - 0.3Unusual time, minor pattern
Suspicious0.4 - 0.6High velocity, dormant account
High Risk0.7 - 0.8Failed previous tx + high amount
Critical0.9 - 1.0Sanctioned country, known fraud entity
The risk consolidator aggregates scores. If multiple rules fire with 0.3 scores, the combined risk may still escalate the transaction to a block. Design your scores with this aggregation in mind.

6.6 Common Pitfalls

PitfallConsequenceFix
Typo in field name (ammount vs amount)Rule never triggers, no errorDouble-check field names against your transaction schema
Missing descriptionAnalysts can’t understand why a transaction was flaggedAlways write a description
Using or without understanding precedenceUnexpected rule behaviorKeep or usage simple, or split into multiple rules
Very large time windows (P365D)Slow query performanceUse the smallest effective window
Score of 0.0Rule fires but has no impact on risk consolidationAlways assign a meaningful score
Regex without (?i)Misses case variationsUse (?i) for case-insensitive matching
Missing reasonAnalysts see “No reason provided”Always write a specific reason