Does the randi function have a good level of pseudorandomness?

1 view (last 30 days)
I have the following code which simulates how the price of a cryptocurrency changes.
% Cryptocurrency is a class whose objects represent different
% cryptocurrencies.
classdef Cryptocurrency < handle
% Property of the Cryptocurrency class.
% Only the market class can modify the attribute.
properties (GetAccess = public, SetAccess = {?Market})
price (1,1) double {mustBeNonnegative} = 0
end
% The initialSupply property is a constant and therefore can only
% be modified by the constructor.
properties (GetAccess = public, SetAccess = immutable)
initialSupply (1,1) double {mustBePositive} = 1
initialPrice (1,1) double {mustBePositive} = 1
end
% methods of the Cryptocurrency class.
methods
% Constructor of the Cryptocurrency class. Takes the initial price
% and the inizial supply of the cryptocurrency as input.
function obj = Cryptocurrency(inputPrice, inputSupply)
obj.price = inputPrice;
obj.initialPrice = inputPrice;
obj.initialSupply = inputSupply;
end
end
methods (Access = {?Market})
function obj = modifyPrice(obj, supply)
obj.price = (obj.initialSupply * obj.initialPrice) ...
/ supply;
end
end
end
The Market class takes two cryptocurrencies as input and allows the exchange between them. Each exchange leads to a change in the price of the two tokens.
% this class simulates the price dynamics of two cryptocurrencies based on
% the rules of Automated Market Makers.
% The class allows you to buy token A by paying a certain amount of
% token B and vice versa.
classdef Market < handle
% These properties can only be changed by the constructor.
% - assetA and assetB represent the two cryptocurrencies whose price
% dynamics are simulated by this class.
% - supplyA and supplyB indicate the quantity of the two tokens
% in the liquidity pool.
properties (GetAccess = public, SetAccess = private)
assetA (1,1) Cryptocurrency = Cryptocurrency(1,1)
assetB (1,1) Cryptocurrency = Cryptocurrency(1,1)
supplyA (1,1) double {mustBeNonnegative} = 0
supplyB (1,1) double {mustBeNonnegative} = 0
end
% - k is the constant used in AMMs based on the law k = x * y, where x
% is the initial amount of token A and y is the initial amount of
% token B. The value of k must remain constant.
properties (GetAccess = public, SetAccess = immutable)
k (1,1) double {mustBePositive} = 1
end
methods
% Constructor of the Market class. It takes as input two objects
% of Cryptocurrency class
function obj = Market(cryptoA, cryptoB)
arguments (Input)
cryptoA (1,1) Cryptocurrency
cryptoB (1,1) Cryptocurrency
end
obj.assetA = cryptoA;
obj.assetB = cryptoB;
% Formula used in AMMs. k must remain constant.
obj.k = cryptoA.initialSupply * cryptoB.initialSupply;
% the supply in the AMM is not the same as that of the crypto
% in the market.
obj.supplyA = cryptoA.initialSupply;
obj.supplyB = cryptoB.initialSupply;
end
% method that allows you to buy assetA by providing assetB in exchange.
% To buy a quantity of assetA, a quantity of assetB must be
% deposited in the pool.
% In this way the price of assetA increases as there is less
% availability in the pool and the price of assetB decreases as
% there is a greater quantity.
% The method takes as input the quantity of token A to be purchased.
function obj = buyCryptoA(obj, transactionVolume)
arguments (Input)
obj (1,1) Market
transactionVolume (1,1) double {mustBeNonnegative}
end
% An amount of tokenA greater than the amount of tokenA in the
% pool cannot be purchased.
if transactionVolume >= obj.supplyA
fprintf("\nThe transaction cannot be executed.\n")
else
% The amount of token A in the pool decreases.
obj.supplyA = obj.supplyA - transactionVolume;
% The amount of token B varies so that k remains constant.
obj.supplyB = obj.k / obj.supplyA;
% The price of token A rises as it becomes scarcer.
obj.assetA = obj.assetA.modifyPrice(obj.supplyA);
% The price of the token B drops as there is more of it
obj.assetB = obj.assetB.modifyPrice(obj.supplyB);
end
end
% method that allows you to buy assetB by providing assetA in exchange.
% To buy a quantity of assetB, a quantity of assetA must be
% deposited in the pool (sold).
% In this way the price of assetB increases as there is less
% availability in the pool and the price of assetA decreases as
% there is a greater quantity.
% The method takes as input the quantity of token A to be sold.
function obj = sellCryptoA(obj, transactionVolume)
arguments (Input)
obj (1,1) Market
transactionVolume (1,1) double {mustBeNonnegative}
end
% The amount of token A in the pool increases.
obj.supplyA = obj.supplyA + transactionVolume;
obj.supplyB = obj.k / obj.supplyA;
% The price of token B rises as it becomes scarcer.
% The price of the token A drops as there is more of it.
obj.assetB = obj.assetB.modifyPrice(obj.supplyB);
% The price of the token A drops as there is more of it.
obj.assetA = obj.assetA.modifyPrice(obj.supplyA);
end
end
end
In the main code, I start the simulation in which every 100 steps I arrive at a final price. I calculate this final price 10,000 times to see how the price is distributed.
close all % all open windows are closed
clear % all variables in the workspace are deleted
clc % the shell is cleaned up
% Price distribution evaluation experiment
% assetA is the token we want to stabilize at the price of $1.
% the dynamics of the price of A is established by simulating an automated
% market maker. For this reason, token A is accompanied by a second
% token, token B.
% Its initial price is supposed to be $1.
priceA = 1;
% the initial circulating supply of assetA is input by the user
supplyA = 10000;
% The user inputs the market value of token B at the start of simulation
priceB = 1;
% Following the rules of the Automated Market Makers, the
% circulating supply of token B based on the entered values is calculated
supplyB = 10000;
% pricesA contains all the final prices at the end of each simulation
pricesA = [];
% Start of the simulation. At each iteration a purchase or sale of token A
% is made, based on a 50% probability.
for j = 1:10000
assetA = Cryptocurrency(priceA, supplyA);
assetB = Cryptocurrency(priceB, supplyB);
% the market is created between the two tokens, which allows the price
% to be regulated following a sale.
% The price of the two tokens is established by simulating
% the behavior of an Automated Market Maker.
marketAB = Market(assetA, assetB);
for i = 1:100
% randomValue is used to determine whether to buy or sell Token A.
randomValue = randi(10);
% randomVolume is used to randomly choose the volume of each transaction.
randomVolume = randi(1000);
% If randomValue is even, a purchase of tokenA is made.
if mod(randomValue, 2) == 0
marketAB = buyCryptoA(marketAB, randomVolume);
else
% If random Value is odd, a sale of token A is made.
marketAB = sellCryptoA(marketAB, randomVolume);
end
end
disp(assetA.price);
pricesA = [pricesA, assetA.price];
end
% histogram(X, edges) sorts X into bins with the bin edges specified by the
% vector edges. Each bin includes the left edge, but does not include the
% right edge, except for the last bin which includes both edges.
priceRanges = 0:0.02:2;
histogram(pricesA, priceRanges);
xlabel('Final price')
ylabel('Number of observation')
The problem is that from the resulting graph the final price never falls below the price of 0.5, while it rises above the price of two dollars. Could this be due to a problem with the randi function? Or to the logic of my code?

Answers (1)

John D'Errico
John D'Errico on 5 Jun 2023
Edited: John D'Errico on 5 Jun 2023
randi is random. At least as good as is mathematically possible to make it so, based on the random number generation tools we have today, and they are pretty good. Actually, we see this sort of question come up so often on the forum. Someone has a complex piece of code, and as soon as they see a result they don't understand, it is easy to wonder if there may be a bug in some supplied piece of code, even though that code has had a huge amount of testing and validation over the years. In fact, the odds are hugely better there is a bug in the complex code you wrote.
Regardless, I see only two calls to randi in that code, and they have me wonder why you are using randi at all, at least for one of them.
% randomValue is used to determine whether to buy or sell Token A.
randomValue = randi(10);
% randomVolume is used to randomly choose the volume of each transaction.
randomVolume = randi(1000);
How are you using those numbers? Looking at randomvalue, we see this:
if mod(randomValue, 2) == 0
marketAB = buyCryptoA(marketAB, randomVolume);
else
% If random Value is odd, a sale of token A is made.
marketAB = sellCryptoA(marketAB, randomVolume);
end
So essentially, you are testing the parity of a random integer from 1 to 10. If it is even, then you do one thing, if odd you make another choice. Why not simply use rand?
if rand() > 0.5
do_stuff
else
do_other_stuff
end
The point is, when you test the parity of a number, you are testing the lower level bits of the number, and it is down in the least significant bits of the generator that IF there were a problem, you might be more likely to see one. After all, the high order bits would be the ones most extensively tested, and so are the ones least likely to have a problem that nobody has ever noticed.
The test of rand itself however, essentially tests the MOST significant bit of a random number. If there were any flaw in the generator, it would be most obvious there.
The other use of randi is to generate a random volume between 1 and 1000. I wonder if there may be some subtle problem here in what you are doing too, in the assumption the volume must be an integer amount between 1 and 1000. I can't go any more deeply into that, because I've not got any modeling expertise in this area. But I would re-think that assumption carefully if I were you.
As for the rest of the code, only someone with skills and expertise in that field would be qualified to validate your simulation. That is not me, but it seems far more likely there may be some sort of flaw in the model, rather than immediately assuming the problem lies in a random number generator that has been used and tested extensively.

Tags

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!