1
\$\begingroup\$

in my MATLAB code I describe a model for price formation with stale returns in this framework there are 2 type of trader: informed traders who always trade in the "right direction" and noise trader who make their buy/sell decision throwing a coin with likelihood of 50% for a buy/sell order. in my work the probability of arrival of an informed trader(PAIT) is fixed and equal TO 0.799, the informer trader execute their trade only when the difference between efficient price and midquote is greater in absolute value of the total costs ( bid-ask spread+ per funding cost). That's why in the first section of my code zomming on the plot of the log-prices we can see flat area : there is the arrival of an informed trader who decide to do not trade because of the higher transaction costs, in this case the price is equal to the previous one
the efficient price is given by the price at the previous instant plus a random schocks ( stocks follows a martingale process) . The midquote of the bid-ask prices at time t is equal to mid_quotes(t,:) = mid_quotes(t-1,:)+... delta*(eff_prices(t,:)-mid_quotes(t-1,:)) + ... (1-delta)*sigma_m*shocksWm(t,:); where delta is speed of learning of the market maker. the observed price is equal to the following function

function logp = flatten_eff_prices(p_start,n_min,ndays,mid_quotes,trader_type,buy_trade,sell_trade,zero_trade,nois_trade)

logp = NaN*ones(n_min,ndays);

logp(1,:)  = p_start*ones(1,ndays);

for j=2:n_min
    logp(j,:) = mid_quotes(j,:) + trader_type(j,:).*(buy_trade(j,:)+sell_trade(j,:)+zero_trade(j,:).*(logp(j-1,:)-mid_quotes(j,:)))+(1-trader_type(j,:)).*nois_trade(j,:);                             
    
end

end

in this function when trader type is equal to 1 the first part activates, that is there is an informed trader who can buy/sell or do not do anything because there is no convienence, if trader_type == 0 it means that there is a noise trader and the second part is activated((1-trader_type(j,:)).*nois_trade(j,:))

The process of generations of efficient prices and midquote is described in the following function

function [eff_prices,mid_quotes] = midquotes_and_effprices(n_min,ndays,sigma_eff,sigma_m,delta,beta,shocksWe,shocksWm,dfactor)

eff_prices = zeros(n_min,ndays);
mid_quotes = zeros(n_min,ndays);

for t=2:n_min
    eff_prices(t,:) = eff_prices(t-1,:)+sigma_eff*shocksWe(t,:)+beta*dfactor(t,:); 
    mid_quotes(t,:) = mid_quotes(t-1,:)+ delta*(eff_prices(t,:)-mid_quotes(t-1,:)) + (1-delta)*sigma_m*shocksWm(t,:);
end


end

after generating the log price in my script we can see that there are flat area . This happens because there are price repetitions, there is the arrival of an informed trader trader_type = double(shocks.U<par.PAIT) who decide to do not trade because abs(eff_prices-mid_quotes)<=c) What I want to do is to consider only the trading times with the relative prices ( like a real dataset) , roughly speaking I want to delete the flatness due to price repetitions

nrep          = 10;
n_obs_per_day = 6*60*60*100; %1/10 di sec
N_asset       = 1;


par.sigma_eff    = 0.00927;
par.sigma_m      = 0.00927;
par.delta        = 0.01;
par.bidask       = 1.905*(10^(-4));
par.beta         = 1;
par.funding_cost = 0.311*(10^(-4));
par.PAIT         = 0.799;
par.sigma_factor = 0;
shocks.U         = rand(n_obs_per_day,nrep);

shocks.We      = cell(N_asset,1);
shocks.Wm      = cell(N_asset,1);
shocks.p_eps   = cell(N_asset,1);
shocks.We      = randn(n_obs_per_day,nrep)/sqrt(n_obs_per_day);
shocks.Wm      = randn(n_obs_per_day,nrep)/sqrt(n_obs_per_day);
shocks.p_eps   = double(rand(n_obs_per_day,nrep)>(1/2));
shocks.Wfactor = zeros(n_obs_per_day,nrep)/sqrt(n_obs_per_day);
dfactor        = par.sigma_factor*shocks.Wfactor;
p_start        = 0;

[eff_prices,mid_quotes] = midquotes_and_effprices(n_obs_per_day,nrep,...
    par.sigma_eff,...
    par.sigma_m,...
    par.delta,...
    par.beta,...
    shocks.We,...
    shocks.Wm,...
    dfactor);

c            = par.bidask+par.funding_cost;
costs        = c*ones(n_obs_per_day,nrep);
buy_trade    = double((eff_prices-mid_quotes>costs).*par.bidask);
sell_trade   = double((eff_prices-mid_quotes<-costs).*(-par.bidask));
zero_trade   = double((abs(eff_prices-mid_quotes)<=c));
nois_trade   = double(par.bidask.*cumprod(2*shocks.p_eps-1));
trader_type  = double(shocks.U<par.PAIT);

logp        =  flatten_eff_prices(p_start,...
                                  n_obs_per_day,...
                                  nrep,...
                                  mid_quotes,...
                                  trader_type,...
                                  buy_trade,...
                                  sell_trade,...
                                  zero_trade,...
                                  nois_trade);

figure;
plot(logp)

this is my attempt where I create a cell array with the 10 replications and for each replications we have the instant of trading and the relative price. there is no flatnees since at each instant(not equally spaced) there is a trading which can be generated by either an informed trader or a noise trader , the only problem is that the code is computationally expensive since it takes hours to run. can someone help me to improve the speed of my code?

%%
trading_data_combined = cell(1, nrep);

for rep = 1:nrep
    trading_times_informed = [];
    trading_prices_informed = [];
    
    trading_times_noise = [];
    trading_prices_noise = [];
    
    for j = 2:n_obs_per_day
        if (shocks.U(j, rep) < par.PAIT)
            % informed
            if abs(eff_prices(j, rep) - mid_quotes(j, rep)) > c
                trading_times_informed = [trading_times_informed; j];
                trading_prices_informed = [trading_prices_informed; logp(j, rep)];
            end
        else
            %  noise trader
            trading_times_noise = [trading_times_noise; j];
            trading_prices_noise = [trading_prices_noise; logp(j, rep)];
        end
    end
    
   
    combined_data = sortrows([trading_prices_informed, trading_times_informed; trading_prices_noise, trading_times_noise], 2);
    
    
    trading_data_combined{rep} = combined_data;
end
\$\endgroup\$
0

1 Answer 1

2
\$\begingroup\$

To speed up code you have to start with two things:

  1. Pay attention to the warnings that the MATLAB Editor gives you: Four lines have red squiggles underneath, hovering over them says "Variable appears to change size on every loop iteration. Consider preallocating for speed." This comes with a button "Details", where you learn much more about why these lines of code will be slow. Fix the warnings! It is these lines:

    trading_times_informed = [trading_times_informed; j];
    trading_prices_informed = [trading_prices_informed; logp(j, rep)];
    
    trading_times_noise = [trading_times_noise; j];
    trading_prices_noise = [trading_prices_noise; logp(j, rep)];
    
  2. Run the code under the profiler. In the MATLAB Editor, there is a button "Run", if you click the bottom part it expands into a menu. The top options here is "Run and Time". This runs the code under the profiler, where you can learn how much each line of code takes to run. Because it takes for ever to run, I ran it for a short bit and stopped it. Two of the four lines indicated above each take 45% of the time (it's the two noise ones, which I guess are much more common than the informed trades). This means that 90% of the time is taken up just reallocating these arrays.

So let's fix these four lines of code as recommended by the Editor. There are different ways of doing this. One way is to allocate enough elements to never run out: we do n_obs_per_day iterations, so none of these 4 arrays will have more elements than this. We now also need two counters (one for each pair of arrays). After the loop we will cut the arrays down to the number of used rows:

    trading_times_informed = zeros(n_obs_per_day, 1);
    trading_prices_informed = zeros(n_obs_per_day, 1);
    n_informed = 0;
    
    trading_times_noise = zeros(n_obs_per_day, 1);
    trading_prices_noise = zeros(n_obs_per_day, 1);
    n_noise = 0;
    
    for j = 2:n_obs_per_day
        if (shocks.U(j, rep) < par.PAIT)
            % informed
            if abs(eff_prices(j, rep) - mid_quotes(j, rep)) > c
                n_informed = n_informed + 1;
                trading_times_informed(n_informed) = j;
                trading_prices_informed(n_informed) = logp(j, rep);
            end
        else
            % noise trader
            n_noise = n_noise + 1;
            trading_times_noise(n_noise) = j;
            trading_prices_noise(n_noise) = logp(j, rep);
        end
    end
    trading_times_informed = trading_times_informed(1:n_informed);
    trading_prices_informed = trading_prices_informed(1:n_informed);
    trading_times_noise = trading_times_noise(1:n_noise);
    trading_prices_noise = trading_prices_noise(1:n_noise);

The red squiggly lines are now gone. The code, running under the profiler, takes 4.3 seconds to run. The line if (shocks.U(j, rep) < par.PAIT) takes 33%, the next slowest line is only 8.3%, and it does down from there. I think this looks pretty even now.

Can we speed up the code more? Yes, of course. We can vectorize the loop and reduce the amount of indexing. Do we need to? I don't think so. 4 seconds seems like a short amount of time for this experiment.


The change above complicated the code a bit. In this Q&A, we learn that

trading_times_noise = [trading_times_noise; j];

is a lot more expensive way to append to an array than

trading_times_noise(end+1) = j;

What if we use that instead?

    trading_times_informed = [];
    trading_prices_informed = [];
    
    trading_times_noise = [];
    trading_prices_noise = [];
    
    for j = 2:n_obs_per_day
        if (shocks.U(j, rep) < par.PAIT)
            % informed
            if abs(eff_prices(j, rep) - mid_quotes(j, rep)) > c
                trading_times_informed(end+1, 1) = j;
                trading_prices_informed(end+1, 1) = logp(j, rep);
            end
        else
            %  noise trader
            trading_times_noise(end+1, 1) = j;
            trading_prices_noise(end+1, 1) = logp(j, rep);
        end
    end

Now the code looks as simple as it did originally (and even a bit simpler!), but we still have the red squiggles. This is less efficient than the version where we preallocate the arrays, but the total time is still very manageable, this is a good way to append to arrays.

[Note that I used trading_times_noise(end+1, 1), rather than simply trading_times_noise(end+1), because the latter creates a row vector, but this code needs a column vector. We could instead have permuted the result later.


There are other things to note about the code:

  1. I would suggest adding more spaces. Typical MATLAB style uses way too few spaces, but code is easier to read with more spaces around operators and after commas.

  2. Some lines are very long:

    logp(j,:) = mid_quotes(j,:) + trader_type(j,:).*(buy_trade(j,:)+sell_trade(j,:)+zero_trade(j,:).*(logp(j-1,:)-mid_quotes(j,:)))+(1-trader_type(j,:)).*nois_trade(j,:); 
    

    Try to break those up, computing parts separately. Or at least space out components and add line breaks:

    logp(j,:) = ...
        mid_quotes(j,:) + ...
        trader_type(j,:) .* ( ...
           buy_trade(j,:) + ...
           sell_trade(j,:) + ...
           zero_trade(j,:) .* (logp(j-1,:) - mid_quotes(j,:)) ...
        ) + ...
        (1-trader_type(j,:)) .* nois_trade(j,:);                             
    
  3. The line costs = c*ones(n_obs_per_day,nrep); is redundant. You compare one array to costs, and a different one to c directly. That is the right way to compare to a constant. Creating and using this larger constant array is inefficient.

\$\endgroup\$
5
  • \$\begingroup\$ I edited my code following your advice , the only problem is that now in the cell array at each replications I have 2000 more elements ( 2000 more trading times and trading prices with respect to the previous one replication) which is not reasonable . the first cell has more or less 2000 elements the 10th one has 23000 elements. why this happens? \$\endgroup\$
    – V013
    Oct 9, 2023 at 18:56
  • \$\begingroup\$ @V013 I don't know. The changes I made to your code didn't change its output. trading_data_combined has 10 arrays with about 435k rows each when I run it. \$\endgroup\$ Oct 9, 2023 at 19:00
  • \$\begingroup\$ oh yes, you are right , I just double checked . My fault \$\endgroup\$
    – V013
    Oct 9, 2023 at 19:11
  • \$\begingroup\$ Hey Cris , i am back. You said that we can vectorize the loop to improve efficiency. Since i have to run a Monte carlo simulation,the performance Is very important.can you please give an hint on the vectorization thing?. Plus I was thinking to not use the if loop and use logical indexing to extract only the category of traders who really execute a trader to keep improving the performance \$\endgroup\$
    – V013
    Oct 12, 2023 at 17:46
  • \$\begingroup\$ @V013 Yes, you’d use logical indexing to fish out the elements you need. \$\endgroup\$ Oct 12, 2023 at 17:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.