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 |
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:
Entropy (
H): Located in the top section, representing the entropy of the prices of each stock. By default, this value is0.5.Stock Prices: The section that displays the value of each stock for each day.
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 |
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 |
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'>
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
lineto create the graph, the attributeplotis 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'>
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 |
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'>
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.