1
1
package paysim .actors ;
2
2
3
+ import java .util .Arrays ;
4
+ import java .util .HashMap ;
3
5
import java .util .Map ;
4
6
import static java .lang .Math .max ;
5
7
6
8
import ec .util .MersenneTwisterFast ;
7
- import paysim .parameters .ActionTypes ;
8
- import paysim .parameters .BalancesClients ;
9
9
import sim .engine .SimState ;
10
10
import sim .engine .Steppable ;
11
11
import sim .util .distribution .Binomial ;
12
12
13
13
import paysim .PaySim ;
14
+
14
15
import paysim .base .ClientActionProfile ;
15
16
import paysim .base .ClientProfile ;
16
17
import paysim .base .StepActionProfile ;
17
18
import paysim .base .Transaction ;
19
+
20
+ import paysim .parameters .ActionTypes ;
18
21
import paysim .parameters .Parameters ;
22
+ import paysim .parameters .BalancesClients ;
23
+
19
24
import paysim .utils .RandomCollection ;
20
25
21
26
@@ -29,20 +34,22 @@ public class Client extends SuperActor implements Steppable {
29
34
private double clientWeight ;
30
35
private double balanceMax = 0 ;
31
36
private int countTransferTransactions = 0 ;
37
+ private double expectedAvgTransaction = 0 ;
38
+ private double initialBalance ;
32
39
33
40
Client (String name , Bank bank ) {
34
41
super (CLIENT_IDENTIFIER + name );
35
42
this .bank = bank ;
36
43
}
37
44
38
- public Client (String name , Bank bank , Map < String , ClientActionProfile > profile , double initBalance ,
39
- MersenneTwisterFast random , int totalTargetCount ) {
40
- super ( CLIENT_IDENTIFIER + name );
41
- this .bank = bank ;
42
- this .clientProfile = new ClientProfile ( profile , random );
43
- this .clientWeight = (( double ) clientProfile . getClientTargetCount ()) / totalTargetCount ;
44
- this .balance = initBalance ;
45
- this .overdraftLimit = pickOverdraftLimit (random );
45
+ public Client (PaySim paySim ) {
46
+ super ( CLIENT_IDENTIFIER + paySim . generateId ());
47
+ this . bank = paySim . pickRandomBank ( );
48
+ this .clientProfile = new ClientProfile ( paySim . pickNextClientProfile (), paySim . random ) ;
49
+ this .clientWeight = (( double ) clientProfile . getClientTargetCount ()) / Parameters . stepsProfiles . getTotalTargetCount ( );
50
+ this .initialBalance = BalancesClients . pickNextBalance ( paySim . random ) ;
51
+ this .balance = initialBalance ;
52
+ this .overdraftLimit = pickOverdraftLimit (paySim . random );
46
53
}
47
54
48
55
@ Override
@@ -74,26 +81,80 @@ private int pickCount(MersenneTwisterFast random, int targetStepCount) {
74
81
75
82
private String pickAction (MersenneTwisterFast random , Map <String , Double > stepActionProb ) {
76
83
Map <String , Double > clientProbabilities = clientProfile .getActionProbability ();
84
+ Map <String , Double > rawProbabilities = new HashMap <>();
77
85
RandomCollection <String > actionPicker = new RandomCollection <>(random );
78
86
87
+ // Pick the compromise between the Step distribution and the Client distribution
79
88
for (Map .Entry <String , Double > clientEntry : clientProbabilities .entrySet ()) {
80
89
String action = clientEntry .getKey ();
81
90
double clientProbability = clientEntry .getValue ();
82
- double finalProbability ;
91
+ double rawProbability ;
83
92
84
93
if (stepActionProb .containsKey (action )) {
85
94
double stepProbability = stepActionProb .get (action );
86
95
87
- finalProbability = (clientProbability + stepProbability ) / 2 ;
96
+ rawProbability = (clientProbability + stepProbability ) / 2 ;
88
97
} else {
89
- finalProbability = clientProbability ;
98
+ rawProbability = clientProbability ;
99
+ }
100
+ rawProbabilities .put (action , rawProbability );
101
+ }
102
+
103
+ // Correct the distribution so the balance of the account do not diverge too much
104
+ double probInflow = 0 ;
105
+ for (Map .Entry <String , Double > rawEntry : rawProbabilities .entrySet ()) {
106
+ String action = rawEntry .getKey ();
107
+ if (isInflow (action )) {
108
+ probInflow += rawEntry .getValue ();
109
+ }
110
+ }
111
+ double probOutflow = 1 - probInflow ;
112
+ double newProbInflow = computeProbWithSpring (probInflow , probOutflow , balance );
113
+ double newProbOutflow = 1 - newProbInflow ;
114
+
115
+ for (Map .Entry <String , Double > rawEntry : rawProbabilities .entrySet ()) {
116
+ String action = rawEntry .getKey ();
117
+ double rawProbability = rawEntry .getValue ();
118
+ double finalProbability ;
119
+
120
+ if (isInflow (action )) {
121
+ finalProbability = rawProbability * newProbInflow / probInflow ;
122
+ } else {
123
+ finalProbability = rawProbability * newProbOutflow / probOutflow ;
90
124
}
91
125
actionPicker .add (finalProbability , action );
92
126
}
93
127
94
128
return actionPicker .next ();
95
129
}
96
130
131
+ /**
132
+ * The Biased Bernoulli Walk we were doing can go far to the equilibrium of an account
133
+ * To avoid this we conceptually add a spring that would be attached to the equilibrium position of the account
134
+ */
135
+ private double computeProbWithSpring (double probUp , double probDown , double currentBalance ){
136
+ double equilibrium = 40 * expectedAvgTransaction ; // Could also be the initial balance in other models
137
+ double correctionStrength = 3 * Math .pow (10 , -5 ); // In a physical model it would be 1 / 2 * kB * T
138
+ double characteristicLengthSpring = equilibrium ;
139
+ double k = 1 / characteristicLengthSpring ;
140
+ double springForce = k * (equilibrium - currentBalance );
141
+ double newProbUp = 0.5d * ( 1d + (expectedAvgTransaction * correctionStrength ) * springForce + (probUp - probDown ));
142
+
143
+ if (newProbUp > 1 ){
144
+ newProbUp = 1 ;
145
+ } else if (newProbUp < 0 ){
146
+ newProbUp = 0 ;
147
+ }
148
+ return newProbUp ;
149
+
150
+ }
151
+
152
+ private boolean isInflow (String action ){
153
+ String [] inflowActions = {CASH_IN , DEPOSIT };
154
+ return Arrays .stream (inflowActions )
155
+ .anyMatch (action ::equals );
156
+ }
157
+
97
158
private double pickAmount (MersenneTwisterFast random , String action , StepActionProfile stepAmountProfile ) {
98
159
ClientActionProfile clientAmountProfile = clientProfile .getProfilePerAction (action );
99
160
@@ -135,7 +196,7 @@ private void makeTransaction(PaySim state, int step, String action, double amoun
135
196
double reducedAmount = amount ;
136
197
boolean lastTransferFailed = false ;
137
198
while (reducedAmount > Parameters .transferLimit && !lastTransferFailed ) {
138
- lastTransferFailed = handleTransfer (state , step , Parameters .transferLimit , clientTo );
199
+ lastTransferFailed = ! handleTransfer (state , step , Parameters .transferLimit , clientTo );
139
200
reducedAmount -= Parameters .transferLimit ;
140
201
}
141
202
if (reducedAmount > 0 && !lastTransferFailed ) {
@@ -150,7 +211,7 @@ private void makeTransaction(PaySim state, int step, String action, double amoun
150
211
}
151
212
}
152
213
153
- private void handleCashIn (PaySim paysim , int step , double amount ) {
214
+ protected void handleCashIn (PaySim paysim , int step , double amount ) {
154
215
Merchant merchantTo = paysim .pickRandomMerchant ();
155
216
String nameOrig = this .getName ();
156
217
String nameDest = merchantTo .getName ();
@@ -167,7 +228,7 @@ private void handleCashIn(PaySim paysim, int step, double amount) {
167
228
paysim .getTransactions ().add (t );
168
229
}
169
230
170
- private void handleCashOut (PaySim paysim , int step , double amount ) {
231
+ protected void handleCashOut (PaySim paysim , int step , double amount ) {
171
232
Merchant merchantTo = paysim .pickRandomMerchant ();
172
233
String nameOrig = this .getName ();
173
234
String nameDest = merchantTo .getName ();
@@ -187,7 +248,7 @@ private void handleCashOut(PaySim paysim, int step, double amount) {
187
248
paysim .getTransactions ().add (t );
188
249
}
189
250
190
- private void handleDebit (PaySim paysim , int step , double amount ) {
251
+ protected void handleDebit (PaySim paysim , int step , double amount ) {
191
252
String nameOrig = this .getName ();
192
253
String nameDest = this .bank .getName ();
193
254
double oldBalanceOrig = this .getBalance ();
@@ -205,7 +266,7 @@ private void handleDebit(PaySim paysim, int step, double amount) {
205
266
paysim .getTransactions ().add (t );
206
267
}
207
268
208
- private void handlePayment (PaySim paysim , int step , double amount ) {
269
+ protected void handlePayment (PaySim paysim , int step , double amount ) {
209
270
Merchant merchantTo = paysim .pickRandomMerchant ();
210
271
211
272
String nameOrig = this .getName ();
@@ -228,17 +289,17 @@ private void handlePayment(PaySim paysim, int step, double amount) {
228
289
paysim .getTransactions ().add (t );
229
290
}
230
291
231
- boolean handleTransfer (PaySim paysim , int step , double amount , Client clientTo ) {
292
+ protected boolean handleTransfer (PaySim paysim , int step , double amount , Client clientTo ) {
232
293
String nameOrig = this .getName ();
233
294
String nameDest = clientTo .getName ();
234
295
double oldBalanceOrig = this .getBalance ();
235
296
double oldBalanceDest = clientTo .getBalance ();
236
297
237
- boolean transferFailed ;
298
+ boolean transferSuccessful ;
238
299
if (!isDetectedAsFraud (amount )) {
239
300
boolean isUnauthorizedOverdraft = this .withdraw (amount );
240
- transferFailed = isUnauthorizedOverdraft ;
241
- if (! isUnauthorizedOverdraft ) {
301
+ transferSuccessful = ! isUnauthorizedOverdraft ;
302
+ if (transferSuccessful ) {
242
303
clientTo .deposit (amount );
243
304
}
244
305
@@ -252,7 +313,7 @@ boolean handleTransfer(PaySim paysim, int step, double amount, Client clientTo)
252
313
t .setFraud (this .isFraud ());
253
314
paysim .getTransactions ().add (t );
254
315
} else { // create the transaction but don't move any money as the transaction was detected as fraudulent
255
- transferFailed = true ;
316
+ transferSuccessful = false ;
256
317
double newBalanceOrig = this .getBalance ();
257
318
double newBalanceDest = clientTo .getBalance ();
258
319
@@ -263,10 +324,10 @@ boolean handleTransfer(PaySim paysim, int step, double amount, Client clientTo)
263
324
t .setFraud (this .isFraud ());
264
325
paysim .getTransactions ().add (t );
265
326
}
266
- return transferFailed ;
327
+ return transferSuccessful ;
267
328
}
268
329
269
- private void handleDeposit (PaySim paysim , int step , double amount ) {
330
+ protected void handleDeposit (PaySim paysim , int step , double amount ) {
270
331
String nameOrig = this .getName ();
271
332
String nameDest = this .bank .getName ();
272
333
double oldBalanceOrig = this .getBalance ();
@@ -297,17 +358,17 @@ private boolean isDetectedAsFraud(double amount) {
297
358
}
298
359
299
360
private double pickOverdraftLimit (MersenneTwisterFast random ){
300
- double averageTransaction = 0 , stdTransaction = 0 ;
361
+ double stdTransaction = 0 ;
301
362
302
363
for (String action : ActionTypes .getActions ()){
303
364
double actionProbability = clientProfile .getActionProbability ().get (action );
304
365
ClientActionProfile actionProfile = clientProfile .getProfilePerAction (action );
305
- averageTransaction += actionProfile .getAvgAmount () * actionProbability ;
366
+ expectedAvgTransaction += actionProfile .getAvgAmount () * actionProbability ;
306
367
stdTransaction += Math .pow (actionProfile .getStdAmount () * actionProbability , 2 );
307
368
}
308
369
stdTransaction = Math .sqrt (stdTransaction );
309
370
310
- double randomizedMeanTransaction = random .nextGaussian () * stdTransaction + averageTransaction ;
371
+ double randomizedMeanTransaction = random .nextGaussian () * stdTransaction + expectedAvgTransaction ;
311
372
312
373
return BalancesClients .getOverdraftLimit (randomizedMeanTransaction );
313
374
}
0 commit comments