Market simulation

The purpose of this tutorial is to show a more in-depth guide on how to simulate a market using Garpar, along with all the available tweaks for the process.

Currently, the system supports market simulation using one of the following distributions: normal, uniform, or Lévy stable. We will use the normal distribution for this tutorial.

We can run our first simulation by doing the following:

[1]:
from garpar.datasets.risso import make_risso_normal

make_risso_normal()

[1]:
Stocks S0[W 1.0, H 0.5] S1[W 1.0, H 0.5] S2[W 1.0, H 0.5] S3[W 1.0, H 0.5] S4[W 1.0, H 0.5] S5[W 1.0, H 0.5] S6[W 1.0, H 0.5] S7[W 1.0, H 0.5] S8[W 1.0, H 0.5] S9[W 1.0, H 0.5]
Days
0 100.000000 100.000000 100.000000 100.000000 100.000000 100.000000 100.000000 100.000000 100.000000 100.000000
1 99.217459 101.011179 99.051546 100.939962 99.027175 100.674693 101.057617 99.107305 100.552594 101.057419
2 98.175032 99.714135 99.801658 101.887271 99.462664 101.759334 100.195162 97.976316 101.542128 102.223276
3 97.100092 98.461286 100.567572 102.884586 98.575600 100.882997 99.288656 97.183166 102.520327 100.957083
4 97.943529 99.635107 99.716944 101.733750 97.965693 99.743022 97.926802 98.152265 101.775098 99.926287
... ... ... ... ... ... ... ... ... ... ...
361 77.011462 107.790311 102.358754 75.009370 86.030695 99.154258 108.431197 101.740880 168.732165 135.961668
362 77.677567 108.842242 101.299433 75.597476 85.052443 99.891277 109.497674 102.730757 167.391853 137.225215
363 76.968017 109.897206 102.178140 74.798019 84.301589 99.021719 108.480965 104.087446 169.255572 136.123678
364 76.338357 111.396875 103.043883 75.654148 83.612694 98.106395 107.582160 105.030917 167.362275 134.653284
365 77.132471 112.222480 103.973727 74.924523 84.618482 99.181151 106.541846 106.398680 168.841089 133.593830
366 days x 10 stocks - W.Size 5

By default, the simulation generates 10 stocks with 365 days of prices for each one. These prices follow the same distribution.

For this tutorial, we will focus on three sections of the StocksSet:

  1. Entropy (H): Located in the top section, representing the entropy of the prices of each stock. By default, this value is 0.5.

  2. Stock Prices: The section that displays the value of each stock for each day.

  3. Market Data: Found at the bottom, this section includes:

    • Total days

    • Number of stocks

    • Window size (we will explain its significance as we progress).

Parameters of a Simulation

There are several parameters that influence a simulation. This section will demonstrate how each parameter affects the results.

Let’s start with a simple example. Suppose we want to simulate 5 stocks over 10 days, each starting at a price of 110. This can be achieved with the following function call:

[2]:
make_risso_normal(days=10, stocks=5, price=110)

[2]:
Stocks S0[W 1.0, H 0.5] S1[W 1.0, H 0.5] S2[W 1.0, H 0.5] S3[W 1.0, H 0.5] S4[W 1.0, H 0.5]
Days
0 110.000000 110.000000 110.000000 110.000000 110.000000
1 109.100791 110.985320 108.592367 110.751179 111.111873
2 110.081776 109.794453 107.904694 112.181642 111.891958
3 111.101074 110.902450 108.837916 113.198713 110.238611
4 109.861787 112.095493 107.662382 114.604822 111.213248
5 108.765900 111.473659 108.702145 113.678955 112.281496
6 108.036743 112.605383 107.578590 114.698660 111.473552
7 109.277220 113.624004 106.613202 116.054480 110.283692
8 108.131517 114.565859 105.687997 117.402401 111.530358
9 107.039655 115.685478 104.677679 116.326258 110.528993
10 106.211737 116.839084 105.957010 117.293314 111.529207
11 days x 5 stocks - W.Size 5

Sometimes, we might want a more ‘controlled’ or even deterministic environment. We can achieve this by using a seed. The random_state parameter represents this seed, as shown in the following example:

[3]:
make_risso_normal(days=10, stocks=5, random_state=42)

[3]:
Stocks S0[W 1.0, H 0.5] S1[W 1.0, H 0.5] S2[W 1.0, H 0.5] S3[W 1.0, H 0.5] S4[W 1.0, H 0.5]
Days
0 100.000000 100.000000 100.000000 100.000000 100.000000
1 98.805864 100.935769 99.326480 100.900464 100.649803
2 99.723073 99.709073 98.300671 99.884634 101.809271
3 100.481068 98.845525 99.570943 98.878556 100.721910
4 99.870674 97.846148 100.461281 99.783308 99.528206
5 101.124337 96.897283 101.310699 98.637364 100.581171
6 102.006936 95.770467 100.074504 99.746865 101.208367
7 103.332098 94.844957 101.280570 100.450735 100.356336
8 104.518001 94.306958 102.217987 99.473017 101.303750
9 105.458859 93.176509 100.824345 100.715559 100.136298
10 106.641517 92.403867 101.862962 99.497536 99.235579
11 days x 5 stocks - W.Size 5

Feel free to try this with different values of random_state.

Now, let’s talk about the H value. This represents the entropy of a given stock, which can be thought of as the informational efficiency of the market. In simple terms, it describes how quickly information spreads throughout the market. Entropy is essentially a measure of unpredictability. When the entropy value is closer to 1.0, it means that the chances of winning or losing on any given day are nearly equal—making the stock’s movements highly unpredictable. On the other hand, when entropy is closer to 0.0, it suggests that the chances of winning or losing are imbalanced, meaning the stock’s behavior is more predictable or skewed in one direction.

To test this, lets try using a high entropy value and a low entropy value. This will show how this concept is visualized:

[4]:
make_risso_normal(stocks=1, entropy=1.0, random_state=702).plot.line(
    palette="flare"
)
make_risso_normal(stocks=1, entropy=0.0, random_state=702).plot.line(
    palette="viridis"
)

[4]:
<Axes: title={'center': 'Price'}, xlabel='Days'>
../_images/tutorials_market_simulation_7_1.png

We can see that the stock following the red line doesn’t show a clear, sustained trend of either dropping or rising, while the one with the teal color tends to drop over time.

Note that we used a method line to create the graph, the attribute plot is known as an accessor and we will explain them in one of the lasts sections of this tutorial.

Another important parameter is the window_size, which represents how long an investor will hold a stock. By default, we’ve set it to 5 days. This value is combined with the entropy to determine the loss probability each day. So for example, lets assume the same entropy value and see how the stocks values diverge:

[5]:
make_risso_normal(
    stocks=1, entropy=0.5, window_size=5, random_state=702
).plot.line(palette="flare")
make_risso_normal(
    stocks=1, entropy=0.5, window_size=7, random_state=702
).plot.line(palette="viridis")

[5]:
<Axes: title={'center': 'Price'}, xlabel='Days'>
../_images/tutorials_market_simulation_9_1.png

As we can see, the simulation produces different results based on the window_size.

We’ve also included some validations. For example, if we try to simulate a market with fewer days than the window_size, the simulation will fail. In such cases, we issue a warning to inform the user about what’s happening:

[6]:
make_risso_normal(days=10, stocks=4, random_state=42, window_size=20)

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[6], line 1
----> 1 make_risso_normal(days=10, stocks=4, random_state=42, window_size=20)

File ~/checkouts/readthedocs.org/user_builds/garpar/envs/latest/lib/python3.12/site-packages/garpar/datasets/risso.py:384, in make_risso_normal(mu, sigma, entropy, random_state, n_jobs, verbose, **kwargs)
    344 """Create a StocksSet instance using RissoNormal maker.
    345
    346 This function is intended to create a StocksSet instance using the
   (...)    373     Generated stocks set instance.
    374 """
    375 maker = RissoNormal(
    376     mu=mu,
    377     sigma=sigma,
   (...)    381     verbose=verbose,
    382 )
--> 384 return maker.make_stocks_set(**kwargs)

File ~/checkouts/readthedocs.org/user_builds/garpar/envs/latest/lib/python3.12/site-packages/garpar/datasets/ds_base.py:349, in RandomEntropyStocksSetMakerABC.make_stocks_set(self, window_size, days, stocks, price, weights)
    326 """Create a StocksSet instance with random prices.
    327
    328 Parameters
   (...)    346
    347 """
    348 if window_size <= 0 or window_size > days:
--> 349     raise ValueError("'window_size' must be in the interval (0, days]")
    351 initial_prices = self._coerce_price(stocks, price)
    353 loss_probability = self.get_window_loss_probability(
    354     window_size, self.entropy
    355 )

ValueError: 'window_size' must be in the interval (0, days]

As we mentioned, there are other distributions that a market prices follow. Feel free to test them. In the current version we have the functions make_risso_unifom and make_risso_levy_stable appart from the make_risso_normal shown in the previous steps.

There are other parameters that can be used in a simulation, such as n_jobs, which represents the number of concurrent ‘workers’ used for the simulation, or the verbose parameter, which, when set to 1, shows details of such ‘workers’.

Combining simulations

We can use the make_multisector function to run the simulation with different distribution or simulation values at the same time. For example, if we want to see how the prices change with a particular seed and the three distributions, we can do something like the following:

[7]:
from garpar.datasets import (
    RissoUniform,
    RissoNormal,
    RissoLevyStable,
    make_multisector,
)

make_multisector(
    RissoNormal(random_state=42),
    RissoUniform(random_state=42),
    RissoLevyStable(random_state=42),
    days=10,
    stocks=3,
)

[7]:
Stocks rissonormal_S0[W 1.0, H 0.5] rissouniform_S0[W 1.0, H 0.5] rissolevystable_S0[W 1.0, H 0.5]
Days
0 100.000000 100.000000 100.000000
1 98.805864 97.632493 99.783564
2 99.723073 101.690331 100.449422
3 100.481068 105.711307 101.042132
4 99.870674 100.473104 100.711882
5 101.124337 104.818773 102.554182
6 102.006936 108.857429 103.839387
7 103.332098 114.204546 104.271329
8 104.518001 118.185062 106.170553
9 105.458859 120.886469 106.377469
10 106.641517 122.287369 106.493258
11 days x 3 stocks - W.Size 5

The classes RissoUniform, RissoNormal and RissoLevyStable are known as makers in our system. Objects of these classes are in charge of creating StocksSet, the difference between each one is the distribution a generated StocksSet prices follow.

The accessors

The accessors are properties that allow an object to be treated as a particular tipe. When we introduce the entropy value, we utilized the property plot, this property had an interface inside to make different plots of the StocksSet. We used only the line plot, but if we want to make a boxplot; we will simply make something like this:

[8]:
make_risso_normal(
    stocks=1, entropy=0.5, window_size=7, random_state=702
).plot.box(palette="viridis")

[8]:
<Axes: title={'center': 'Price'}, xlabel='Stocks'>
../_images/tutorials_market_simulation_16_1.png

There are many other accessors. Feel free to look for the Core subpackage in the API section, where we published every function prototype alongside its documentation!

Now that you know how the simulation works, we recommend you to follow this tutorial.