11. Transaction Monitoring¶
This microservice is intended to monitor financial transactions reported by a third-party application. The purpose of this service is to be able to detect unusual transactions in order to immediately alert the interested parties of the financial institution.
The monitoring is done using a rule-based system.
11.1 Transaction Monitoring rules¶
The transactions that are reported by the entity to the Trak.e monitoring service are evaluated by means of certain configurable rules in the service, for the automatic detection of unusual operations in order to generate alerts. These alerts are then studied by analysts from the aforementioned entity for better analysis and decision making.
In particular, Trak.e employs a self-developed rule-based system. A rule-based system is one that uses rules defined by human experts in a certain domain to make deductions and perform actions. They are also called simply expert systems: they are considered a way of representing the knowledge of experts within an automatic system. Rules are usually in the form of
if-then
statements; that is, they are of the formif A then B
.
Each active rule is executed once for each injected transaction. It is allowed to add any number of file monitoring rules, but at most 50 can be active at the same time.
11.2 Implementation¶
The rules follow the scheme seen for the risk matrix and the calculation of the transactional profile: rules written in Python language.
11.2.1 Parameters¶
The available variables are:
-
profile
: When evaluating the rule, the variable is placed in the execution context.profile
contains the digital profile that is currently being processed. All the attributes detailed in the section Digital Profiles is available on this object. -
transaction
: Contains the information of the transaction that is currently being processing. All attributes reported by the entity through the transaction API are available. -
hist_trxs
: If the rule includes information about the transactionality of the digital profile, you can access the transaction history of the profile with this name. The variable is placed in the execution context in the format of pandas.DataFrame, where each row of the DataFrame represents a transaction and the columns of the DataFrame are the attributes of transactions (using underscore as separator in case of being nested attributes). It is possible that the history is empty (no rows or columns) if the customer does not record transactions.
11.2.2 Rule result¶
The rule must define a variable named SHOULD_RAISE
whose value must be
a truth value indicating whether it should generate a new alert. The possible values and their interpretation are:
True
: The rule was evaluated and unusual behavior was determined, so that an alert will be raised in trak.e so that an analyst of the entity manage and solve about the anomaly.False
: The rule was evaluated and it was determined that there were no behaviors unusual.None
: The rule was not evaluated. Either it does not apply, or some critical value is missing to do it.
Note
Context:
In addition to the True
, False
and None
values used to interpret the evaluation of the rule, the context of the evaluation is returned, particularly those defined public variables. This allows saving values calculated during the execution of the rule and that they are available at the time of viewing the results.
Note
For security reasons, it is not possible to use the complete Python language when defining a rule, but only a subset of the language. In particular, import
s are not allowed. However, for the convenience of the programmer, some modules, classes, and functions included in Python can be used.
Note
Modules, classes and functions allowed when defining rules:
Decimal
class of thedecimal
module: This class allows handling decimal numbers with fixed and arbitrary precision. You can check the documentation for this class here.pandas
module: This module allows data analysis to be carried out in a fast, powerful, flexible and simple way. In particular, it is necessary to work with the history of transactions. Note that, as in most of the examples on the page, in the rules one refers to this module using the abbreviationpd
. The module documentation can be found here.datetime
class of thedatetime
module: This class allows you to flexibly represent and work with dates and times. Note that in trak.e all the dates and times are represented in milliseconds since the Epoch, so after working the dates must be converted to this format before making comparisons with the attributes of the file or transactions. Documentation for this class can be found at here.timedelta
class of thedatetime
module: This class allows representing and working with time intervals. As an example, the difference between two objects of typedatetime
will be an object of typetimedelta
. The documentation for the class can be found here.strptime
function: This function allows to grammatically parse a date and time in text format and return the correspondingdatetime
object. The function receives two arguments; the date and time, and the string that describes the format. Some examples:>>> strptime("20-06-2021", "%d-%m-%Y") datetime.datetime(2021, 6, 20, 0, 0) >>> strptime("20-06-21", "%d-%m-%y") datetime.datetime(2021, 6, 20, 0, 0) >>> strptime("20-06-21, 20:08", "%d-%m-%y, %H:%M") datetime.datetime(2021, 6, 20, 20, 8)
json
module: This module contains functions to work comfortably with JSON objects. The module documentation can be found here.math
module: Contains several useful mathematical functions. The documentation is here.- In addition, some useful functions can be used:
max
,min
,sum
,all
,any
,round
,len
,isinstance
andrange
. - In addition, the standard constructors
str
,int
,float
,list
,tuple
,dict
,set
andbool
can be used. - The standard exceptions
IndexError
andKeyError
are also defined in the context, so you can handle them in your code.
11.3 Examples¶
11.3.1 Exceeds number of transactions¶
- Description: Something very common is to evaluate the number of transactions that I make a client in a certain period. A basic rule to evaluate this is the following:
init = datetime.now().replace(hour=0, minute=0, second=0,
microsecond=0) - timedelta(days=30)
init_timestamp = int(init.timestamp()) * 1000
cant_trx = hist_trxs[(hist_trxs["timestamp"] >= init_timestamp) & (
hist_trxs["side"] == transaction.side)].shape[0]
SHOULD_RAISE = cant_trx >= 20
11.3.2 Exceeds fixed amount¶
- Description: This rule evaluates if the cumulative total of transactions exceeds a predetermined fixed amount.
init = datetime.now().replace(hour=0, minute=0, second=0,
microsecond=0) - timedelta(days=30)
init_timestamp = int(init.timestamp()) * 1000
total_amount = hist_trxs[
(hist_trxs["timestamp"] >= init_timestamp) & (
hist_trxs["side"] == transaction.side)
].amount.sum().item()
SHOULD_RAISE = total_amount + transaction.amount >= 1e7
11.3.3 Exceeds transactional profile¶
- Description: This rule evaluates the transactions and verifies that, accumulating the amount with the rest of the operations of the period, the profile does not exceeds its assigned transactional profile.
transactional_profile = profile["transactional_profile_amount"]
if not transactional_profile:
SHOULD_RAISE = None
transactional_profile_period = 31536000000 # 365*24*60*60*1000
now = transaction["timestamp"]
from_ = now - transactional_profile_period
sum_amount_deposit = sum(hist_trxs[hist_trxs["side"] == "deposit"][hist_trxs["timestamp"] > from_]["amount"])
sum_amount_extraction = sum(hist_trxs[hist_trxs["side"] == "extraction"][hist_trxs["timestamp"] > from_]["amount"])
if transaction.side == "deposit":
sum_amount_deposit += transaction.amount
elif transaction.side == "extraction":
sum_amount_extraction += transaction.amount
if sum_amount_deposit + sum_amount_extraction > transactional_profile:
SHOULD_RAISE = True
else:
SHOULD_RAISE = False
11.3.4 Sudden profile change¶
-
Description: This rule evaluates the transactions looking for anomalies in the amounts operated by the client. To do this, it compares the current month with its historical average of deposits and withdrawals made.
-
Parameters:
Parameter | Meaning | Type | Default value |
---|---|---|---|
side |
The rule can be set up to work with deposit s or with extraction |
Literal "deposit" or "extraction" |
"deposit" |
profile_change_min_seniority |
This rule need the profile to have a certain seniority, a history of transaction in the entity that allows us to determine a reliable historical average. The rule will only be evaluated for those profiles that have been created some time ago | integer, representing a time in milliseconds | int(1.814e10) (210 days) |
profile_change_lookback_period |
This parameters controls how much time will we consider in order to define de average behavior. | integer, representing a time in milliseconds | int(1.555e10) (180 days) |
profile_change_min_threshold |
This parameter represents a minimum amount of interest, and serves to avoid having an excess of alerts for small amounts of money. If the behavior of the current month is less than this value the rule will not be evaluated. | Floating, representing an amount of money. | 350000 |
profile_change_tolerable_deviation |
Dictionary that represents, for natural and legal persons and according to the level of risk, how much transactionality is allowed to deviate from the average behavior. | Dictionary of dictionary of floats, representing a percentage change. | *0.6 for low-risk legal persons *0.4 for medium-risk legal persons *0.25 for high-risk legal persons *0.8 for low-risk natural persons *0.6 for medium-risk natural persons *0.4 for high risk natural persons |
# allowed sides: "deposit"/"extraction"
side = "deposit"
# profile_change_min_seniority: Profile newer than this will not trigger the
# rule. Defaults to 210 days i.e. 7 months
profile_change_min_seniority: int = int(1.814e10)
# profile_change_lookback_period. How long will the rule look back in order to
# define a usual behavior. Defaults to 180 days, i.e. 6 months
profile_change_lookback_period: int = int(1.555e10)
# Minimum amount of money that will trigger the rule.If current month behavior
# is less than this threshold, the rule is not applied.
profile_change_min_threshold: float = 350000
# threshold for each person type and risk value. These are floating point
# numbers between 0 and 1 representing a percentage that the profile might
# overcome each month.
tolerable_deviation = dict(
legal_person=dict(
low=0.6,
medium=0.4,
high=0.25,
),
natural_person=dict(
low=0.8,
medium=0.6,
high=0.4,
),
)
timestamp = transaction.timestamp
created_at = profile.created_at
person_type = profile.person_type
risk = profile.risk
if not timestamp or not created_at or not person_type or not risk:
SHOULD_RAISE = None
elif transaction.side != side:
SHOULD_RAISE = None
elif timestamp - created_at < profile_change_min_seniority:
# Not enough seniority to consider this profile
SHOULD_RAISE = None
else:
trx_now = datetime.fromtimestamp(timestamp // 1000)
period_end = int(trx_now.replace(day=1, hour=0, minute=0, second=0, microsecond=0).timestamp() * 1000)
period_init = period_end - profile_change_lookback_period
one_month = int(2.592e9)
this_month_behavior = (
hist_trxs.loc[(hist_trxs["timestamp"] >= period_end) & (hist_trxs["side"] == side)].amount.sum()
+ transaction["amount"]
)
if this_month_behavior < profile_change_min_threshold:
SHOULD_RAISE = None
else:
average_behavior = (
hist_trxs.loc[
(period_init <= hist_trxs["timestamp"])
& (hist_trxs["timestamp"] < period_end)
& (hist_trxs["side"] == side)
].amount.sum()
* one_month
/ profile_change_lookback_period
)
deviation = (this_month_behavior - average_behavior) / this_month_behavior
SHOULD_RAISE = bool(deviation > tolerable_deviation[person_type][risk])
Resources |
---|
transaction_transaction |
transaction_rule |
transaction_summary |