Garrett, Monte Carlo Scripting Language Code
Status: Pre-Alpha
Brought to you by:
tbandrow
----------------------------------------------
GARRETT
A Financial Systems Evaluation Language
(c) 2006 Todd Bandrowsky
All Rights Reserved
----------------------------------------------
Horizons is a tool for asking questions about complex portfolios. It lets you
specify a set of time series curves, associate them with a collection
of contracts, and then, Spel will iterate through as many combinations
of them as you are interested in to help you better understand your
portfolio.
* Flexible iteration. Create scenarios, for example, that
explore various price ranges by season and all of
their combinations.
* Horizons handles temporal situations readily.
You specify to Horizons your rules at a series
level, and Horizons quantizes them into discrete
time series chunks as needed to run the simulation.
* Horizons handles curves easily. Roll hourly data to
monthly easily, extract by various custom defined masks,
and more.
* Horizons is fairly optimized and will only be optimized more.
Horizons's number crunching is multithreaded. With
important sections hand coded in AMD64 assembly language,
Horizons takes advantage of the both extended
SSE2 and GP registers afforded by that hardware. We're
also working on a POWER architecture port. We will use
Altivec on PowerPC 970MP and on POWER5 when available.
* Horizons is easy. You tell Horizons what you want,
and it figures out how to do it.
A Basic Horizons Scenario
-----------------------
simulation name="Market Forecast" {
curverange name = "henry hub", interpolations = 5 {
from intervalCurve( 'monthly', '5/1/2005', 42, 12, 24, 33, 17, 18, 19, 20 ),
to intervalCurve( 'monthly', '5/1/2005', 5, 8, 6, 77, 22, 33, 55, 55 )
}
curverange name = "pjm whub", interpolations = 7 {
from intervalCurve( 'monthly', '5/1/2005', 42, 12, 24, 33, 17, 18, 19, 20 ),
to intervalCurve( 'monthly', '5/1/2005', 5, 8, 6, 77, 22, 33, 55, 55 )
}
curverange name = "zoot", interpolations = 7 {
from intervalCurve( 'monthly', '5/1/2005', 42, 12, 24, 33, 17, 18, 19, 20 ) * .90,
to fromcurve * 1.10
}
curverange name = "zoot", interpolations = 7 {
from intervalCurve( 'monthly', '5/1/2005', 42, 12, 24, 33, 17, 18, 19, 20 ) * .90,
to fromcurve * 1.10
}
model name = "spark generator" {
parameters {
curve PowerPrice,
curve FuelPrice,
curve HeatRate,
curve OandM
}
outputs {
value
}
state name = "idle", start = (powerCurve < (FuelCurve * HeatRate + OandM)) =
{
remaining {
value = -OandM;
}
}
state name = "running", start = (powerCurve > (FuelCurve * HeatRate + OandM)) =
{
remaining {
value = powerCurve - (FuelCurve * HeatRate + OandM);
}
}
}
contract id="CTR0042", name="Fossil Bird 1", model="spark generator" {
PowerPrice = "pjm whub",
FuelPrice = "henry hub",
HeatRate = intervalCurve( 'monthly', '5/1/2005', 5, 8, 6, 77, 22, 33, 55, 55 ),
OandM = intervalCurve( 'monthly', '5/1/2005', 5, 8, 6, 77, 22, 33, 55, 55 )
}
contract id="CTR0043", name="Fossil Bird 2", model="spark generator", start="5/1/2005", stop="6/1/2005" {
PowerPrice = "pjm whub",
FuelPrice = "henry hub",
HeatRAte = intervalCurve( 'monthly', '5/1/2005', 5, 8, 6, 77, 22, 33, 55, 55 ),
OandM = intervalCurve( 'monthly', '5/1/2005', 5, 8, 6, 77, 22, 33, 55, 55 )
}
}
Horizons Overview
-------------
A basic Horizons scenario has a simulation outermost block, a set of curves,
a set of models, and then a list of contracts. The contracts compare
roughly to business contracts in the real world and each contract refers
to a model that describes how to calculate it.
Curves are ranges of time series data. Each curve has a from series
and a to series, both starting at the "start" date declared in the curve,
and, having the same number of data points spaced by the "interval" length.
When Horizons runs a scenario, it loops through all the permutations of the
interpolations of the curves. That is, if I have a single curve, with
interpolations set to 5, then, I will get 5 simulations. If I have two curves
with interpolations set to 5, and 4, respectively,
I will get 20 simulations.
Each simulation's curves are then fed, as needed, into the contract engine.
Each contract refers to a particular model, via the "model" tag in its line,
and also parameters to be fed into the model unique to that contract.
A model has with it a list of states. States allow the rules of the model
to change with particular circumstances in such a way as that they may
be tracked. You might think of a state as an "if then with a name".
For each discrete interval in each simulation, Spel evaluates the states
of each contract, and chooses the state which matches the conditional.
The contract is then evaluated at that state, and both the output values and
the state are recorded at each interval for subsequent analysis.
Explaining the Example
----------------------
This simple example models a very simplified set of generator contracts
for an electric utility.
In the electricity world, a benchmark way to determine the profitability of
a generator is to look at its spark spread. The generator is modelled
as a vehicle for converting a fuel price into a power price. Thus,
the expression powerCurve > FuelCurve * HeatRate + OandM, the spark spread,
models the profitability of the generator. If the power price is greater
than the fuel price times the cost to convert it, plus some fixed
O and M cost, the generator is profitable and it enters the running state.
If not, then, the generator is in the idle state. There, it can only costs,
so its value is negative.
Sample Curves
-------------
We define a basic price curve for fuel and for power. We declare, in the top
of the simulation, curves for power using "PJM W Hub" and the natural gas price
index "Henry Hub". In the real world, these prices would be gleaned from various
forecasting tools available to the industry. In this example, I made them up.
When Horizons runs a scenario, it steps through different sets of curves. Each curve
iterated through is called a curverange.
curverange name = "henry hub", interpolations = 5 {
from intervalCurve( 'monthly', '5/1/2005', 42, 12, 24, 33, 17, 18, 19, 20 ),
to intervalCurve( 'monthly', '5/1/2005', 5, 8, 6, 77, 22, 33, 55, 55 )
}
curverange name = "pjm whub", interpolations = 7 {
from intervalCurve( 'monthly', '5/1/2005', 42, 12, 24, 33, 17, 18, 19, 20 ),
to intervalCurve( 'monthly', '5/1/2005', 5, 8, 6, 77, 22, 33, 55, 55 )
}
The interpolations of 5 for the "henry hub" mean that there will be 5 curves
from the "from" curve to the "to" curve, and likewise, there are 7 curves to
be generated for pjm whub.
Sample Model
------------
Next, we define a model. A model defines shared logic to be used by contracts.
If a contract is an instance of a class, then, a model would be the class itself.
model name = "spark generator" {
parameters {
curve PowerPrice,
curve FuelPrice,
curve HeatRate,
curve OandM
}
outputs {
value
}
state name = "idle", start = (powerCurve < (FuelCurve * HeatRate + OandM))
{
remaining {
value = -OandM;
}
}
state name = "running", start = (powerCurve > (FuelCurve * HeatRate + OandM))
{
remaining {
value = powerCurve - (FuelCurve * HeatRate + OandM);
}
}
}
}
Models consist of states that are conditionally evaluated at each time
point over the span of the contract
Our model is called a "spark generator", short for spark spread generator.
It takes 4 parameters, each of which is a curve. The model outputs one series,
called value.
The model has two states, "idle", and "running". The conditional expression
assigned to each state's start parameter specifies when that state will be
valid.
The remaining tag inside each state says specifies that the rule inside is
to be applied for all of the time spent in that state. You can specify other
brackets as well. In a more complex scenario, you may want to capture the
time needed to ramp up into that new state.
state name = "running", start = (powerCurve > (FuelCurve * HeatRate + OandM))
{
schedule name="rampup", time=4, units=hours {
value = @counter * 10
}
remaining {
value = spread;
}
}
}
A state is selected in a model only when the conditional is true. Once a state
is selected, it will be evaluated for at least the time in each schedule section within
a state. Then, once the schedule advances to its remaining section, all of the states
are evaluated for every interval.
Special Variables
-----------------
You'll notice that there are some special variables that are being used in
simulations.
@counter - the number of intervals elapsed in that state
@percentage - the percentage completion of that state in a schedule slot
Note that @percentage will always be 0 for a remaining section of a state
@elapsed - the elapsed time in the state
CurveBuilders
-------------
curves are defined either by entering them or by expression. There are several ways
to make a curve. The most common is the intervalCurve( ). For example:
intervalCurve( 'monthly', '5/1/2005', 42, 12, 24, 33, 17, 18, 19, 20 ),
Establishes a curve beginning at 5/1/2005, proceeding monthly through each of the values
associated with it: 5/1/2005 = 42, 6/1/2005 = 12, 7/1/2005 = 24, and so on.
Running the Simulation
------------------------
Spel compiles the simulation into AMD64 code.
The generated code works in these steps:
1. scan all the curves in the simulation and convert to the lowest
common denominator interval length
2. determine the schedule for each contract
for each interval on the contract range, evaluate each of the start
clauses on for each contract in order and dump the schedule id to the
schedule table associated with each state
3. for each interval on the schedule table for each state
evaluate the state and update the result
4. Merge the schedule results for all the intervals into
a single schedule table
5. Attach that schedule for each contract to the simulation
6. The reporting phase then mines the simulation results
Implementation Note
-----------------------------------------------
Because GARRETT is a compiler, many classes are actually implemented as C structs with
function calls so they can be more readilly accessed via assembler.
Optimizing Floating Point Expressions with SSE2
-----------------------------------------------
The floating point expressions, because they are executed so frequently,
are heavily optimized. This section describes how that optimization works.
Transformation - parallelizing constant expressions
-----------------------------------------------------
In general, siblings of any parse tree can be resolved in parallel.
Consider the following infix expression:
powerCurve > FuelCurve * Heat Rate + OandM
We can build this dependency analysis:
A = FuelCurve * HeatRate
B = A + OandM
C = powerCurve > A
Clearly, in this case, there is not a lot that can be done in parallel. But,
what if, we did this?
powerCurve - OandM > FuelCurve * HeatRate
Then, we would have this set of twin items:
A0 = powerCurve - OandM
A1 = FuelCurve * HeatRate
Both of which could be executed in parallel