Trade Liquidity Volume Market Operator Access
LLGP enables, as one of its use cases, a DEX agnostic limit order system. To facilitate this limit order protocol use case, Market Makers (including Block Builders as well as more traditional MMs) will be able to access this order flow exclusively. This drives more volume and faster transaction settlement by providing incentive—aligned market making operations with users submitted transactions.
LLGP provides the ability to submit transactions without specifying a base fee in the transaction and using an ERC20 to pay for the entire transaction.
For Sushi Guard, the backrunning function in the router can be applied once the Sushi Aggregator is in production and RPC services for those networks are live.
Below is a breakdown of the mechanism audit/review done by 20squares. This provides the supporting information necessary for Market Makers to be able to take on the risk for operating in LLGP.
20Squares Mechanism Audit
Our models are written in a custom DSL compiled to haskell
. Here we give a brief description of how our software works.
The building blocks
The basic building block of our model is called open game, and can be thought of as a game-theoretic lego brick. This may represent a player, a nature draw, a payoff matrix or a complex combination of these elements. It has the following form:
gameName variables = [opengame|
inputs : a;
feedback : b;
:----------------------------:
inputs : a';
feedback : b';
operation : content;
outputs : s';
returns : t';
:----------------------------:
outputs : s;
returns : t;
|]
We can imagine this block as a box with 4 wires on its outside, on which travels information marked as:
-
inputs
, data that gets fed into the game (e.g. a player receiving information from a context). -
outputs
, data that the game feeds to the outside world (e.g. a player communicating a choice to another player). -
returns
, the returns of a player actions, which are usually directly fed to a function calculating payoffs. - The
feedback
wire which sends information back in time. If, intuitively,returns
represents the returns on a player action, one could imagine it as ‘information that an agents receive from the future’.feedback
is the dual analog of that: If a given piece of information comes from the future, someone in the future must have been sent it to the past. For additional details about thefeedback
wire please refer to the relevant literature.
The :--:
delimiters separate the outside from the inside of the box. As one can see, the interfaces inside are replicated. This is intentional as it allows for a notion of nesting. For instance, the situation depicted in the following picture:
Can be represented by the following code block:
gameName variables = [opengame|
inputs : a, a';
feedback : b;
:----------------------------:
inputs : a';
feedback : ;
operation : SubGame1;
outputs : x;
returns : t';
inputs : a, x;
feedback : b;
operation : SubGame2;
outputs : s;
returns : t;
:----------------------------:
outputs : s;
returns : t,t';
|]
In turn, Subgame1
and Subgame2
can be other games defined using the same DSL. Notice that the wire x
is internal and totally hidden from the ‘outside world’.
Exogenous parameters
An exogenous parameter is a given assumption that is not part of the model, and is fed to it externally. As such, it is treated by the model as a ‘fact’ that cannot really be modified. An example of exogenous parameter could be the market conditions at the time when a game is played.
Exogenous parameters are just defined as variables, as the field variables
in the previous code blocks testifes. These variables can in turn be fed as exogenous parameters to inside games, as in the following example:
gameName stock1Price stock2Price = [opengame|
inputs : a, a';
feedback : b;
:----------------------------:
inputs : a';
feedback : ;
operation : SubGame1 stock1Price;
outputs : x;
returns : t';
inputs : a, x;
feedback : b;
operation : SubGame2 stock2Price;
outputs : s;
returns : t;
:----------------------------:
outputs : s;
returns : t,t';
|]
Basic operations
In addition to the DSL defining the ‘piping rules’ between boxes, we provide some basic operations to populate a box, namely:
- A function, which just transforms the input in some output.
- A stochastic distribution, used to implement draws from nature.
- A strategic choice, which can be thought of as a function parametrized over strategies.
Branching
Another important operation we provide is called branching. This is useful in contexts where, say, a player choice determines which subgame is going to be played next.
Branching is represented using the operator +++
. So, for instance, if SubGame1
is defined as branch1 +++ branch2
, then we are modelling a situation where SubGame1
can actually evolve into two different games depending on input. As the input of a game can be the outcome of a strategic choice in some other game, this allows for flexible modelling of complex situations.
Graphically, branching can be represented by resorting to sheet diagrams, but as they are quite complicated to draw, this depiction is rarely used in practice.
Supplying strategies
As usual in classical game theory, a strategy conditions on the observables and assigns a (possibly randomized) action.
Every player who can make a decision in the game needs to be assigned a strategy. These individual strategies then get aggregated into a list representing the complete strategy for the whole game.
So, for instance, if our model consists of three subgames, a strategy for the whole model will just be a list:
`strGame1 ::- strGame2 ::- strGame3 ::- Nil`.
Evaluating strategies
To evaluate strategies, it is enough to just run the main
function defined in Main.hs
. This is precisely what happens when we give the command stack run
. In turn, main
invokes functions defined in Analytics.hs
which define the right notion of equilibrium to check. If you want to change strategies on the fly, just open a REPL (Cf. Interactive Execution) and give the command main
.
You can make parametric changes or even define new strategies and/or notions of equilibrium by editing the relevant files (cf. File structure). Once you save your edits, giving :r
will recompile the code on the fly. Calling main
again will evaluate the changes.
Stochasticity
Our models are Bayesian by default, meaning that they allow for reasoning in probabilitic terms.
Practically, this is obtained by relying on the Haskell Stochastic Package, which employs monadic techniques.
A consequence of this is that deterministic strategic decisions (e.g. ‘player chooses option A’) must be lifted into the stochastic monad, getting thus transformed into their probabilistic equivalent (e.g. ‘of all the options available, player chooses A with probability 1’)
A practical example of this is the following:
strategyName
:: Kleisli
Stochastic
(Parameter1, Parameter2)
Decision
strategyName = pureAction Decision1
In the example above, the player observes some parameters (Parameter1
and Parameter2
in this particular case), and then must assign an action (in this case Decision1
).
pureAction
lifts the deterministic choice Decision1
to the corresponding concept in the probabilistic realm.
The upside of assuming this little amount of overhead is that switching from pure to mixed strategies can be easily done on the fly, without having to change the model beforehand.
Branching
As a word of caution notice that, in a game with branching, we need to provide a possible strategy for each branch. For example, suppose to have the following game:
- Player 1 can choose between option A and B;
- case A: Player 2 can choose between option A1 or A2;
- case B: Player 2 can choose between option B1 or B2;
Moreover, suppose that the payoffs are as follows:
- If Player1 chooses A, and then Player2 chooses A1, then both players get $100$.
- In any other case, both players get $0$.
In this game the best strategy is clearly (A,A1). Nevertheless, we need to supply a strategy for Player2 also in the ‘B’ branch: Even if Player1 will never rationally choose B, Player2 needs to be endowed with a clear choice between B1 and B2 in case this happens.
File structure
The model is composed of several files:
-
app/Main.hs
contains all the main ingredients already set up for a run. Executing it will execute equilibrium checking on some of the most interesting strategies we defined. We suggest to start from here to get a feel of how the model analysis works. -
Model.hs
is the file where the main model is defined. -
Components.hs
is where the subgames making up the whole model are defined. -
Payoffs.hs
is where the payoff functions used in every subgame are defined. We decided to keep them all in the same file to make tweaking and fine-tuning less dispersive. -
Strategies.hs
is where the strategies we want to test are defined. -
Parametrization.hs
defines the concrete parametrizations used for the analysis: e.g. intrinsic utility of Buyer’s transaction, fixed costs for initiating a Ledger-Hedger contract etc. -
Types.hs
is where we define the types of the decisions to be taken (e.g. $Wait$ or $Initiate$ a Ledger-Hedger contract) and the types ofHLContract
andTransaction
. Here we also define the types for payoffs, gas etc. These are all aliased toDouble
. -
ActionSpaces.hs
is mainly needed for technical type-transformations. It maps a player’s decision type into the type needed to be fed in the subsequent game. -
Analytics.hs
defines the equilibrium notion for each game we want to test. -
Diagnostics.hs
is the file detailing which and how much information we want to show when strategies are tested.
Relying on the DSL Primer, parsing the code structure should be a manageable task.
Moreover, the code is divided in two different branches:
-
main
contains the standard model, and runs analytics with the parameters provided in the Ledger-Hedger paper. -
eq-breaking
instead parametrizes the risk-aversity in the utility functions, and runs stress tests to check under which conditions the equilibrium breaks.
Analytics
Now, we switch focus on analytics, which we defined as the set of techniques we employ to verify if and when a supplied results in an equilibrium. The notion of equilibrium we rely upon is the one of Nash equilibrium, which intuitively describes a situation where, for each player, unilaterally deviating from the chosen strategy results in a loss.
Reading the analytics
Analytics in our model are quite straightforward. In case a game is in equilibrium, the terminal will print Strategies are in equilibrium
.
For games with branching, there will also be a NOTHING CASE
. To understand this, consider a game (call it First Game
) that can trigger two different subgames (Subgame branch 1
, Subgame branch 2
, respectively) depending on the player’s choice. Analytics would read like this:
Game name
First Game:
Strategies are in equilibrium
Subgame branch 1:
NOTHING CASE
Subgame branch 2:
Strategies are in equilibrium
Here NOTHING CASE
signifies that the choice provided by the player results in not visiting Subgame branch 1
, which is thus never played in this senario: Evidently, the choice made by the player in First Game
resulting in the play continuing on Subgame branch 2
.
On the contrary, analytics become more expressive when the game is not in equilibrium. In this case, the engine will suggest a more profitable deviation by displaying the following prompt:
Strategies are NOT in equilibrium. Consider the following profitable deviations:
Player:
Optimal Move:
Current Strategy:
Optimal Payoff:
Current Payoff:
Observable State:
--other game--
--No more information--
Observable State
contains a dump of all the game parameters that are currenlty observable by all players. This is usually a lot of information, mainly useful for debugging purposes. All the other field names are pretty much self-describing.
Strategies employed in the analysis
Our analysis is focused on understanding when the targeted equilibrium - where the Ledger-Hedger contract gets initiated and later published by Buyer as well as accepted and confirmed by Seller - can be supported. Concretely, this means we employ the following strategies:
-- | Buyer: initiate contract strategy
initiateStrategyBuyerTarget
:: Kleisli
Stochastic
(Transaction, HLContract, GasPrice)
(InitialDecisionBuyer HLContract)
initiateStrategyBuyerTarget =
Kleisli (\(_,contract,_) -> playDeterministically $ Initiate contract)
-- | Buyer: publish strategy if no LH
noLHPublishStrategyTarget
:: Kleisli
Stochastic
(Transaction, GasPrice)
(PublishDecision Double)
noLHPublishStrategyTarget = Kleisli (\(tx,_ ) -> playDeterministically $ Publish (gasAllocTX tx))
-- | Seller: accept decision
acceptStrategyTarget
:: Kleisli
Stochastic
(Transaction, HLContract, GasPrice)
AcceptDecisionSeller
acceptStrategyTarget = pureAction Accept
-- | Buyer: publish strategy if recoup
recoupPublishTarget
:: Kleisli
Stochastic
(Transaction, GasPrice)
(PublishDecision Double)
recoupPublishTarget = Kleisli (\(tx,_ ) -> playDeterministically $ Publish (gasAllocTX tx))
-- | Buyer: recoup strategy buyer
recoupStrategyTarget
:: Kleisli
Stochastic
(Transaction, HLContract, GasPrice, GasPrice)
RecoupDecisionBuyer
recoupStrategyTarget = pureAction Refund
-- | Buyer: publish strategy part 1 if LH
lhPublishStrategyPart1Target
:: Kleisli
Stochastic
GasPrice
(PublishDecision Double)
lhPublishStrategyPart1Target = pureAction $ Publish 0.0
-- | Buyer: publish strategy part 2 if LH
lhPublishStrategyPart2Target
:: Kleisli
Stochastic
(GasPrice, Transaction, PublishDecision a1)
(PublishDecision Gas)
lhPublishStrategyPart2Target =
Kleisli
(\(pi,tx,publishDecision) ->
case publishDecision of
NoOp -> playDeterministically NoOp
Publish _ -> playDeterministically $ Publish $ gasAllocTX tx)
-- | Seller: fulfill strategy
fulfillStrategyTarget
:: Kleisli
Stochastic
(Transaction, HLContract, GasPrice,GasPrice, Gas)
FulfillDecisionSeller
fulfillStrategyTarget = pureAction Confirm
-- | Seller: noFulfill strategy
noFulfillStrategyTarget
:: Kleisli
Stochastic
(Transaction, HLContract, GasPrice, GasPrice)
FulfillDecisionSeller
noFulfillStrategyTarget = pureAction Exhaust
-- | Buyer: publish strategy if no fulfill
nofulfillPublishTarget
:: Kleisli
Stochastic
(Transaction, GasPrice)
(PublishDecision Double)
nofulfillPublishTarget = Kleisli (\(tx,_ ) -> playDeterministically $ Publish (gasAllocTX tx))
As detailed in File structure, the strategies above reside in Strategies.hs
. For more information about how to supply strategies and/or how to make changes, please refer to the section Supplying Strategies.
Running the analytics
As already stressed in Evaluating strategies, there are two main ways to run strategies. In the Normal execution mode, one just needs to give the command stack run
. This command will execute a pre-defined battery of strategies using the parameters predefined in the source code. These parameters can be varied as one pleases. Once this is done and the edits are saved, stack run
will automatically recompile the code and run the simulation with the new parameter set.
In the Interactive execution mode, the users accesses the repl via the command stack ghci
. Here one can run single functions by just calling them with the relevant parameters, as in:
functionName parameters
In particular, calling the function main
in interactive mode will result in the same behavior of calling stack run
in normal mode. Again, editing the source code and then hitting :r
will trigger recompilation on the fly.
Replicating the Ledger-Hedger paper results
These results can be found in the main
branch (see subsection File structure for more information).
To replicate the results highlighted in the Ledger-Hedger paper, we instantiated the model with the following parameters (the instantiation can be found in Parameters.hs
):
Parameter | Name in the paper | Meaning | Value |
---|---|---|---|
buyerWealth |
$W^{init}_{Buyer}$ | Initial wealth of Buyer | $10^9$ |
sellerWealth |
$W^{init}_{Seller}$ | Initial wealth of Seller | $10^9$ |
collateral |
$$col$$ | The collateral Seller must pay to accept LH contract. | $10^9$ |
piInitial |
$\pi_{initial}$ | Initial gas price | $100$ |
piContract |
$\pi_{contract}$ | The price at which Buyer buys gasAllocTX from Seller in the LH contract. |
$100$ |
payment |
$payment$ | The ampount Buyer pays to Seller in LH | gasAllocTX * piContract |
epsilon |
$\epsilon$ | Technical parameter to disincentivize unwanted behavior from Seller. | $1$ |
gasInitiation |
$g_{init}$ | Cost of opening a LH contract. | $0.1 \cdot 10^6$ |
gasAccept |
$g_{accept}$ | Cost of accepting a LH contract. | $75 \cdot 10^3$ |
gasDone |
$g_{done}$ | Cost of closing a LH contract. | $20 \cdot 10^3$ |
gasAllocTX |
$g_{alloc}$ | Gas reserved in the LH contract. | $5 \cdot 10^6$ |
gasPub |
$g_{pub}$ | Gas size of the TX if issued at current market price. | $5 \cdot 10^6$ |
These parameters were directly pulled from Sec. 6 of the Ledger-Hedger paper. As for the utility functions, we again followed what the authors did by instantiating the utility functions for both Buyer and Seller to be first $log(x)$ and then $\sqrt(x)$. These function represent risk-aversity.
Moreover, as specified in the section Assumptions made explicit, we had to postulate an explicit utility for the transaction that Buyer wants to issue. This is represented by the parameter utilityFromTX
, which has been set to $10^9$.
As for the future price distribution, we defined it in csv format in the file /probability/distribution.csv
(we also provide a constant distribution, /probability/distributionOneElement.csv
for debugging reasons). As the distribution is input through this external file, any other distribution (e.g. based on actual data) can be used. distribution.csv
is a normal distribution centered around the initial gas price. Again, we used the standard deviations suggested in the paper.
Moreover, we defined testActionSpaceGasPub
, representing the range in which $g_{pub}$ can swing. $g_{pub}$ is the gas consumed if Buyer decides to issue the transaction at market price (for more information see Ambiguous Buyer behavior). In practice, we followed the paper and equated $g_{pub}$ and $g_{alloc}$, meaning that Buyer is using Ledger-Hedger to reserve the precise amount of gas needed to execute the transaction.
As we already stated in Summary - Analytics results, we found that the equilibrium with this parameters is quite brittle, and heavily relying on both the risk-aversity of both Buyer and Seller and on the shape of the probability distribution. By this, we mean that the utility gain in using the Ledger-Hedger is really small, and even a small variation in the given parameters can result in equilibrium breaking.
The reason why the protocol is so sensible is that, Seller-side, the usage fees (gasAccept
, gasDone
) are quite high. This immediately disincentivates Seller to use the protocol, as in terms of raw payoffs doing so results in a loss.
Similarly, Buyer-side, the presence of a usage fee (gasInitiation
) and the cost defined bypayment
disincentivate Buyer to use the protocol.
This ‘operating at a loss’ result is counterbalanced uniquely by the accentuated concavity of the utility functions provided. In a nutshell, both players are so risk-averse that they are willing to pay these huge premiums to protect themselves.
Another reason why Ledger-Hedger is so sensible is also that players are fundamentally unbiased with respect to future gas price: it can go up or down, for both players, with equal probability. We are quite sure the situation would look different if players had private information available about future price distribution. Imagine the current situation:
- Seller believes price will go down in the between blocks $start$ and $end$.
- On the contrary, Buyer believes that price will go up within the same block interval.
In this scenario, both Players will be much more willing to use Ledger-Hedger, albeit for opposite reasons. Importantly, depending on how skewed these beliefs are, we may dispense of risk-aversity all together: Even for a risk-loving player hedging would make sense if the player strongly believed that prices would swing towards an unfavorable direction.
Other analyses
We ran also some other analyses these can be found in the eq-breaking
branch (see subsection File structure for more information).
First of all, and unsurprisingly, we found that running the model with the following parameters results in a much bigger utility:
Parameter | Name in the paper | Meaning | Value |
---|---|---|---|
buyerWealth |
$W^{init}_{Buyer}$ | Initial wealth of Buyer | $10^9$ |
sellerWealth |
$W^{init}_{Seller}$ | Initial wealth of Seller | $10^9$ |
collateral |
$$col$$ | The collateral Seller must pay to accept LH contract. | $10^9$ |
piInitial |
$\pi_{initial}$ | Initial gas price | $100$ |
piContract |
$\pi_{contract}$ | The price at which Buyer buys gasAllocTX from Seller in the LH contract. |
$100$ |
payment |
$payment$ | The ampount Buyer pays to Seller in LH | gasAllocTX * piContract |
epsilon |
$\epsilon$ | Technical parameter to disincentivize unwanted behavior from Seller. | $1$ |
gasInitiation |
$g_{init}$ | Cost of opening a LH contract. | $0$ |
gasAccept |
$g_{accept}$ | Cost of accepting a LH contract. | $0$ |
gasDone |
$g_{done}$ | Cost of closing a LH contract. | $0$ |
gasAllocTX |
$g_{alloc}$ | Gas reserved in the LH contract. | $5 \cdot 10^6$ |
gasPub |
$g_{pub}$ | Gas size of the TX if issued at current market price. | $5 \cdot 10^6$ |
This represents the scenario where we dispense of the platform fees altogether, and pay for gasAllocTX
exactly the current price. Most likely, this scenario hadn’t been considered in the original paper as the original work was meant to be part of some on-chain infrastructure. As such, what we call ‘platform fees’ would just be unavoidable smart contract execution costs.
Keeping everything fixed, we then turned piContract
into a parameter. In the case when both player’s utility is set to be $\sqrt x$, we verified that in this setting the equilibrium is solid within the bound:
$$98 \leq \mathtt{piContract} \leq 102$$
This represents the maximum and minimum piContract
parameters within which both Buyer and Seller judge using Ledger-Hedger convenient. This result aligns perfectly with the one highlighted in the Ledger-Hedger paper, figure 4.
Having verified this, we used both the original paper parameters and the ‘zero fees’ parameters to run a multivariate analysis on player’s risk aversity. In practice, we generalized the $\sqrt x$ function, up to now being used by both players, to the couple of functions
$$\Large x^{\frac{1}{y_{\mathbf{Buyer}}}} \qquad x^{\frac{1}{y_{\mathbf{Seller}}}}$$
The concavity/convexity of these functions is dependent on the value of $y$, as shown in the following chart:
Thus, $y_{\mathbf{Buyer}}$ and $y_{\mathbf{Seller}}$ represent the risk-aversity of both players, respectively. Positive values $< 1$ represent risk-love, $1$ represents risk-neutrality, while values $> 1$ signal risk-aversity.
Keeping all parameters fixed, the shaded region in the following graphs represents where the equilibrium holds for different risk-aversity ranges.
At $\texttt{piContract} = 98$, we get the following graph:
The blue region represents the ‘zero fees’ scenario, whereas the red region represents the fees as in the Ledger-Hedger paper. As on can see, the red region is strictly contained in the blue one. We have:
$$ \begin{alignat*}{2} \text{\color{blue} Zero fees scenario:} \qquad 0.68 \leq y_{\mathbf{Buyer}} \qquad 1.92 \leq y_{\mathbf{Seller}}\ \text{\color{red} Paper fees scenario:} \qquad 0.94 \leq y_{\mathbf{Buyer}} \qquad 7.09 \leq y_{\mathbf{Seller}} \end{alignat*} $$
This is compatible with the idea that the lower the fees the less risk averse players must be to judge the use of Ledger-Hedger convenient.
Setting $\texttt{piContract} = 99$, we get the following graph:
$$ \begin{alignat*}{2} \text{\color{blue} Zero fees scenario:} \qquad 0.79 \leq y_{\mathbf{Buyer}} \qquad 1.39 \leq y_{\mathbf{Seller}}\ \text{\color{red} Paper fees scenario:} \qquad 1.16 \leq y_{\mathbf{Buyer}} \qquad 2.93 \leq y_{\mathbf{Seller}} \end{alignat*} $$
For $\texttt{piContract} = 100$:
$$ \begin{alignat*}{2} \text{\color{blue} Zero fees scenario:} \qquad 0.94 \leq y_{\mathbf{Buyer}} \qquad 1.08 \leq y_{\mathbf{Seller}}\ \text{\color{red} Paper fees scenario:} \qquad 1.50 \leq y_{\mathbf{Buyer}} \qquad 1.85 \leq y_{\mathbf{Seller}} \end{alignat*} $$
For $\texttt{piContract} = 101$:
$$ \begin{alignat*}{2} \text{\color{blue} Zero fees scenario:} \qquad 1.16 \leq y_{\mathbf{Buyer}} \qquad 0.89 \leq y_{\mathbf{Seller}}\ \text{\color{red} Paper fees scenario:} \qquad 2.15 \leq y_{\mathbf{Buyer}} \qquad 1.35 \leq y_{\mathbf{Seller}} \end{alignat*} $$
For $\texttt{piContract} = 102$:
$$ \begin{alignat*}{3} \text{\color{blue} Zero fees scenario:} \qquad 1.50 \leq y_{\mathbf{Buyer}} \qquad 0.75 \leq y_{\mathbf{Seller}}\ \text{\color{red} Paper fees scenario:} \qquad 3.77 \leq y_{\mathbf{Buyer}} \qquad 1.06 \leq y_{\mathbf{Seller}} \end{alignat*} $$
As one can see, as piContract
increases the shaded regions migrate to the lower-right end. Again, this makes sense: As the price goes higher, Buyer is paying more and more for gasAllocTX
with respect to current price. This entails that Buyer should be more risk-averse to deemm this advantageous, and hence the region moves further to the right on the Buyer axis. Specularly, Seller is receiving an increasingly better offer compared to the current price, lowering the necessity for risk-aversity. As such, the region grows closer to the Seller axis.
Sanity checks
In addition to this, we performed some sanity checks: Using the constant distribution (see Replicating the Ledger-Hedger paper results for more information), we verified the following things:
- Setting
epsilon
to 0 results, in the ‘zero fees’ case, in equilibrium for any choice of risk-aversity for both players. This makes sense: Using the protocol results in zero extra costs. Moreover, both players know with certainty that the future gas price won’t change. Hence, risk-aversity does not matter. - With any other choice of
epsilon
and any other fees, the equilibrium breaks for any choice of risk-aversity, for both players. Again, this makes sense: Both players know with certainty that the future gas price won’t change, so payng any extra fee to hedge is disadvantageous.
Interactive perks
Finally, in the eq-breaking
branch there are more things one can do, the most important being that we expose a new function in Main.hs
that allows for better interactive queries. In interactive mode, giving:
interactiveMain piContract utilityParameterBuyer utilityParameterSeller
allows to run the Ledger-Hedger strategy on both the ‘zero fees’ and ‘paper fees’ scenarios, with customized piContract
and risk-aversity parameters for both players. In this way, the user can verify equilibria for particular choices of values without the need to recompile.