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