OpenRedukti Scripting With Ravi

OpenRedukti comes with a scripting language Ravi that is a dialect of Lua. It is much easier to play with and understand how OpenRedukti works by running small examples in the scripting language.

Once you have built OpenRedukti, you will find a commandline program called dylan. To start the scripting environment just invoke this command. It works the same way as the Lua command line.

To exit the scripting environment enter ^Z on Windows, and ^D on Unix systems.

The scripting API is a subset of the OpenRedukti C++ API, therefore not every feature is available.

Date Types

Dates are represented as integer values. The api for dates is as follows:

Converting to Date type,month,year)
takes day, month, year and returns a date
Parse a string representation of date. It will detect seperator character ‘/’ or ‘-‘. The formats acceptable are yyyy/mm/dd, dd/mm/yyyy, yyyy-mm-dd, or dd-mm-yyyy{ d,m,y }
takes table and converts to date
returns today’s date


local t = {}
t.effective_date ={1,1,2016}
t.termination_date ={1,1,2017}

Decomposing a Date type

takes a date and returns day, month, year


function print_date(d)
    local d,m,y = redukti.date_parts( d )
    return string.format("%02d/%02d/%d", d, m, y)


Date arithmetic

As a date value represents the number of days since 1899/12/31, you can simply add or subtract from it to get to another date. Following api is provided for adding a period to date.

redukti.addperiod(date, period)
Adds period to date and returns resulting date Periods are entities like 1D, 4W, 1M or 15Y.


date =
print(redukti.addperiod(date, '5Y'))    -- add 5 years to the date
print(redukti.addperiod(date, '-1D'))   -- subtract 1 day from date

Holiday Calendars

The api for obtaining and working with calendars is as follows:

Returns a calendar object. Suported business centers are AUSY, USNY, GBLO, EUTA, JPTO and BRSP. You can specify more than one center by separating the values by a comma.
calendar:advance(date, period [, business_day_convention])
Advances the given date by the given period and adjusts it to be on a business day. Business day convention defaults to FOLLOWING.
calendar:advance(date, days [, business_day_convention])
Moves the date forward or back by specified number of business days. Business day convention defaults to FOLLOWING.


cal = redukti.calendar('USNY,GBLO')     -- joint calendar
today =  -- today's date
month_ago = cal:advance(today, "-1M", "FOLLOWING")
month_hence = cal:advance(today, "1M", "FOLLOWING")
print(today, month_ago, month_hence)

Day Count Fractions

Returns a Day Count Fraction object for the specified name - name must be ISDA / FpML compatible.
dayfraction:fraction(d1, d2)
Calculates the day fraction between two dates.


fraction = redukti.dayfraction('ACT/360')
today =
two_years_later = redukti.addperiod(today, "2Y")
print(fraction:fraction(today, two_years_later))


redukti.index(currency, index_family, tenor)
Returns an index object
redukti.index(isda_index, tenor)
Returns an index object
Returns maturity date given the value date, as per the index
index:adjust_date(date, days)
Adds/subtracts the number of business days specified and adjusts the resulting date as per fixing calendar
Computes fixing date, value date and maturity date given the start date and returns all three


idx = redukti.index('USD', 'LIBOR', '1W')
dt =,10,2016)
adjusted = idx:adjust_date(dt, 1)
assert(adjusted ==,10,2016))
fixing_dt, value_dt, maturity_dt = idx:date_components(adjusted)
assert(maturity_dt ==,10,2016))
assert(value_dt == adjusted)

Automatic Differentiation

redukti.adouble1{ n1, n2, … }
Returns an adouble object for each array value. The assumption is that the array values are part of a multivariate function, and therefore each value is treated as variable. The total number of variables is equal to the numeber of values in the array. The adouble objects are set up for first order derrivative computation. Note that the maximum number of allowed variables is 100 to keep memory usage in check.
redukti.adouble1(n1, n2, …)
Same as above but input is in the form of parameters rather than an array.
redukti.adouble2{ n1, n2, … }
As above, but the returned adouble objects are set up to compute second order derivatives too.
redukti.adouble2(n1, n2, …)
Same as above but input is in the form of parameters rather than an array.
Returns the gradient as an array
Only available if second order derivatives are being calculated. The hessian is returned as a table of arrays where each row is an array.
Returns the absolute value
Returns adouble object raised to power n
Return square root of adouble object
Returns exp(adouble)
Returns log(adouble), where log is natural logarithm
Returns cos(adouble)
Return sin(adouble)
Returns tan(adouble)


-- compute derivate of x^2, where x = 5

x = redukti.adouble1(5.0)
ans = x:pow(2)

Above outputs:

  firstorder = {
    [1] = 10.0

This tells you that the value of x^2 is 25.0, and derivative is 10.0 - i.e. 2*x, as you would expect.

Here is another example:

x, y, z = redukti.adouble2 {5.0, 3.0, 6.0}
-- compute x + y + z
added = x + y + z
multiplied = x * y * z


Results in following output:

  firstorder = {
    [1] = 1.0,
    [2] = 1.0,
    [3] = 1.0
  secondorder = {
  firstorder = {
    [1] = 18.0,
    [2] = 30.0,
    [3] = 15.0
  secondorder = {
    [1, 2] = 6.0,
    [1, 3] = 3.0,
    [2, 1] = 6.0,
    [2, 3] = 5.0,
    [3, 1] = 3.0,
    [3, 2] = 5.0

Since the matrix data for a second order derivative can grow very large, the scripting api restricts the number of allowed variables in a single object to 100.

Another example:

x, y = redukti.adouble2 {6, 7}
ans = x:pow(2) * y:pow(2)

Results in:

  firstorder = {
    [1] = 588.0,
    [2] = 504.0
  secondorder = {
    [1, 1] = 98.0,
    [1, 2] = 168.0,
    [2, 1] = 168.0,
    [2, 2] = 72.0

Calculation Schedules

redukti.schedule { parameters }

Builds a calculation schedule and returns 3 arrays : adjusted start dates, adjusted end dates, adjusted payment dates. Note that some payment dates may be set to 0 - this means that there is no payment in that calculation period. For instance when compounding, or in zero coupon streams, payments do not occur with every period.

The parameters may include following:

(required) unadjusted effective date - this defines the start of the schedule
(required if term not present) unadjusted termination date - this defines the end date of the schedule.
(required if termination_date not present) term is the length of the transaction, e.g. 5Y.
(required) the frequency of payment, may be 1T for single payment, or supported tenor values upto 1Y.
BusinessDayConvention to be used to adjust payment dates
Business Centers to be use for computing payment holidays
FpML defined RollConvention for deciding how to calculate period start/end dates
the frequency of calculating accruals, also equal to the frequency at which resets occur for non-OIS streams. Must be <= payment frequency
the BusinessDayConvention to be used for adjusting calculation period dates
Business Centers to be used for computing holidays when adjusting calculation period dates
unadjusted start date of the first regular period, implies front stub
unadjusted end date of the last regular period, implies back stub
used if first payment does not occur at the first possible payment date
used if last regular period payment date does not occur at the last possible regular payment date


t = {}
t.effective_date ={1,1,2016}
t.termination_date ={1,1,2017}
t.payment_frequency = "3M"
t.payment_business_centers = "GBLO,USNY"
t.payment_day_convention = "MODFOLLOWING"

starts, ends, pays = redukti.schedule(t)

function print_date(d)
    local d,m,y = redukti.date_parts( d )
    return string.format("%02d/%02d/%d", d, m, y)

for i=1,#starts do
        print(i, '  adjusted start date ' .. print_date(starts[i]))
        print(i, '    adjusted end date ' .. print_date(ends[i]))
        if pays[i] ~= 0 then
                print(i, 'adjusted payment date ' .. print_date(ends[i]))
                print(i, '           no payment ')

This outputs:

1         adjusted start date 04/01/2016
1           adjusted end date 01/04/2016
1       adjusted payment date 01/04/2016
2         adjusted start date 01/04/2016
2           adjusted end date 01/07/2016
2       adjusted payment date 01/07/2016
3         adjusted start date 01/07/2016
3           adjusted end date 03/10/2016
3       adjusted payment date 03/10/2016
4         adjusted start date 03/10/2016
4           adjusted end date 03/01/2017
4       adjusted payment date 03/01/2017

Another example:

t = {}
t.effective_date ={25,11,2016}
t.termination_date ={25,11,2017}
t.payment_frequency = "3M"
t.payment_business_centers = "GBLO,USNY"
t.payment_day_convention = "MODFOLLOWING"
t.calculation_frequency = "1M"
t.calculation_business_centers = "GBLO,USNY"
t.calculation_day_convention = "MODFOLLOWING"

starts, ends, pays = redukti.schedule(t)

This time the output is:

1         adjusted start date 25/11/2016
1           adjusted end date 28/12/2016
1                  no payment
2         adjusted start date 28/12/2016
2           adjusted end date 25/01/2017
2                  no payment
3         adjusted start date 25/01/2017
3           adjusted end date 27/02/2017
3       adjusted payment date 27/02/2017
4         adjusted start date 27/02/2017
4           adjusted end date 27/03/2017
4                  no payment
5         adjusted start date 27/03/2017
5           adjusted end date 25/04/2017
5                  no payment
6         adjusted start date 25/04/2017
6           adjusted end date 25/05/2017
6       adjusted payment date 25/05/2017
7         adjusted start date 25/05/2017
7           adjusted end date 26/06/2017
7                  no payment
8         adjusted start date 26/06/2017
8           adjusted end date 25/07/2017
8                  no payment
9         adjusted start date 25/07/2017
9           adjusted end date 25/08/2017
9       adjusted payment date 25/08/2017
10        adjusted start date 25/08/2017
10          adjusted end date 25/09/2017
10                 no payment
11        adjusted start date 25/09/2017
11          adjusted end date 25/10/2017
11                 no payment
12        adjusted start date 25/10/2017
12          adjusted end date 27/11/2017
12      adjusted payment date 27/11/2017

Note that in the examples above I did not specify roll convention so this was inferred.


redukti.interpolator { parameters }

Returns an interpolator object

Parameters can be following:

values to be used for x axis
values to be used for y axis (interpolation occurs in y axis)
1 if first order derivatives are needed; 2 will generate second order derivatives also
The type of interpolator, e.g. Linear, LogLinear, CubicSplineNatural, LogCubicSplineNatural, MonotoneConvex
interpolator:interpolate(x [, n])
Returns the interpolated value. If the optional parameter n is 1 the return value is an adouble containing the interpolated value as well as the derivatives computed using automatic differentiation. If the optional parameter n is 2 then the return value is an adouble but computed using numeric differentiation. The latter is for testing purposes only.


x = {0.01, 0.02, 0.03, 0.04, 0.05}

y = {1000000.0, 20004.0, 300000.5, 4000000.0, 900000.0}

interp1 = redukti.interpolator {
        x = x,
        y = y,
        interpolator = 'CubicSplineNatural',
        order = 2

interp2 = redukti.interpolator {
        x = x,
        y = y,
        interpolator = 'MonotoneConvex',
        order = 2

print(interp1:interpolate(0.035, 1))

print(interp2:interpolate(0.035, 1))

Interest Rate Curves

redukti.curve { parameters }

Sets up an interest rate curve. The parameters are as follows.

The date of the curve, all maturities are with respect to this date
An array of maturity dates
An array of numbers representing zero rates or discount factors
‘ZeroRate’ or ‘DiscountFactor’ - indicates the type of value
Interpolator type. For ZeroRate curves, use Linear, CubicSplineNatural, MonotoneConvex. For discount factor curves, use LogLinear, LogCubicSplineNatural
Currency, forms part of curve’s id
IndexFamily, forms part of curve’s id
Curve’s tenor, forms part of curve’s id
If 1 first order derivatives will be computed, if 2 additionally second order derivatives will be computed
‘Forward’ or ‘Discount’ - this is a logical marker for how the curve will be used, forms part of curve’s id
Returns the zero rate
Returns the discount factor
Computes and returns sensitivities for given date with respect to the curve pillars.
Returns three arrays - maturies, zero rates and discount factors


As creating a curve manually is tedious, often it is easier to import data from files. For an example of this please see the the example in test_zerocurve.lua.

Time Series / Fixings

redukti.fixing_service { data }
Creates a FixingService object. The data must be a table containing values indexed by Index type. Each value must be a table that has fixings indexed by date. That is, of the form:
        [index1] = {
                [date1] = index1value1,
                [date2] = index1value2
        [index2] = {
                [date1] = index2value1,
                [date2] = index2value2
fixing_service:fixing(index, date)
Returns the fixing for given index and date. If not found then returns nil


This too is easier to load from files, hence I will refer to the following script as an example.


redukti.cashflows { data }
Sets up a CFCollection object and returns it.

The contents of { data } mirrors the CFCollection protocol buffers type, except that it is expressed as a Lua table. Here is an example:

t =
{ -- Collection
        -- first stream
                -- cashflow
                        type = 'simple',
                        currency = 'USD',
                        amount = 40523611.1111111,
                        payment_date =, 7, 2017)
                -- cashflow
                        type = 'ois',
                        accrual_start_date =, 7, 2016),
                        accrual_end_date =, 7, 2017),
                        notional = 815000000,
                        index = 'USD-Federal Funds-H.15-OIS-COMPOUND',
                        day_count_fraction = 'ACT/360',
                        payment_date =, 7, 2017)
                -- cashflow
                        type = 'floating',
                        compounding_method = 'FLAT',
                        currency = 'USD',
                        day_count_fraction = 'ACT/360',
                        payment_date =,10,2017),
                        periods = {
                                        notional = 10000000,
                                        accrual_start_date =, 7, 2017),
                                        accrual_end_date =, 8, 2017),
                                        index = 'USD-LIBOR-BBA',
                                        tenor = '1M',
                                        spread = -0.0009
                                        notional = 10000000,
                                        accrual_start_date =, 8, 2017),
                                        accrual_end_date =, 9, 2017),
                                        index = 'USD-LIBOR-BBA',
                                        tenor = '1M',
                                        spread = -0.0009
                                        notional = 10000000,
                                        accrual_start_date =, 9, 2017),
                                        accrual_end_date =, 10, 2017),
                                        index = 'USD-LIBOR-BBA',
                                        tenor = '1M',
                                        spread = -0.0009
        -- second stream
                -- cashflow
                        type = 'fra',
                        currency = 'USD',
                        day_count_fraction = 'ACT/360',
                        payment_date =, 9, 2014),
                        notional = 15000000,
                        fixed_rate = 0.015,
                        accrual_start_date =, 9, 2014),
                        accrual_end_date =, 11, 2014),
                        index = 'USD-LIBOR-BBA',
                        tenor = '2M',
                        index2 = 'USD-LIBOR-BBA',
                        tenor2 = '3M',

flows = redukti.cashflows(t)

When you run this the output will dump the generated protocol buffers value in JSON like format.

Utility for Loading Data

The scripting api contains following utility for loading data from CSV files.

redukti.loadcsv { parameters }

Loads data from a CSV file. Returns a table where each element represents a row in the CSV file.

Following parameters are supported:

Specifies the path and name of the file to read from

Contains a string where each character represents a conversion rule for a column. Following rules are allowed:

Intepret as string field
Interpret as number field
Interpret as integer field
Interpret as date field
Ignore column, field set to nil
A value of true means that the source file has headings
A value of true means that each field will be named by the column heading

Suppose that a file contains:


Then we can load this using following:

fixings = redukti.loadcsv { file=filename, conversion='ssdn', heading=true, fields=true }

Here is a dump of the first three lines of the table:

> table_print(fixings)
[1] => table
       [tenor] => 1D
       [index] => EUR-EONIA-OIS-COMPOUND
       [fixing] => 0.036
       [date] => 39084
[2] => table
       [tenor] => 1W
       [index] => EUR-EURIBOR-Reuters
       [fixing] => 0.03614
       [date] => 39084
[3] => table
       [tenor] => 2W
       [index] => EUR-EURIBOR-Reuters
       [fixing] => 0.03615
       [date] => 39084

Building Curves

redukti.build_curves( business_date, curve_definitions, par_rates [, pricing_script] )

Builds a set of Zero Rate curves from par rates. Requires curve definitions and par rates as input.


The curve definitions must be presented as a table that mirrors the IRCurveDefinition protocol buffers message type. Values must be keyed by the Each value must be a table conatining the fields corresponding to an IRCurveDefinition. These are:

ID of the curve
Interpolator to be used, e.g. Linear
Currency of the curve
‘ZeroRate’ or ‘DiscountFactor’
Tenor of the curve
Specifies how maturities for instruments are derived
Optional list of tenor values used if maturities are derived from fixed tenors

The input par rates must be presented as a table. The elements in the table must have following fields.

a that is defined in the curve definitions
The type of instrument. This is mapped to a Ravi / Lua scripting function name.
The id of the instrument - this has to be in a specific format.
The par rate
forward_curve_id of the curve to be used for forward rates
discount_curve_id of the curve to be used for discounting
The tenor to be used on floating leg of the instrument.
This is a Lua script that is responsible for generating the cashflow structure for each instrument used in the curve. See the default script named pricing.lua that is supplied with OpenRedukti. If a name isn’t supplied the script defaults to the one used when building OpenRedukti; this is useful for testing but not very good for deployment as the path to the script is baked in at compile time!


Please see the function build_curves in utils.lua.

Cashflow Pricing

This is complex process involving several steps.

  1. First of all you need a set of zero curves, either bootstrapped from par rates, or obtained from another source.
  2. You need to setup a curve mapper.
  3. You need fixings.
  4. You need to setup a ValuationContext.
  5. You then convert the Cashflows to an internal format ready for pricing.
  6. Next you need to setup a curve provider.
  7. Finally you invoke the pricing function to compute the PV and the sensitivities of the cashflows.

Curve Mapper

The purpose of the Curve Mapper is to allow mapping of logical curves, so that the cashflow generator can reference curves without knowing how these curves will actually be delivered. So this provides a level of indirection.

The api is described below.

Sets up a curve mapper object
redukti.pricing_curve_id { parameters }

Returns a logical curve id. The parameters are:

‘Forward’ or ‘Discount’
Optional tenor of the curve
curve_mapper:add_mapping( from_id, to_id )
This sets up a mapping from ‘from_id’ to ‘to_id’. Both ids must be pricing_curve_ids.


curve_mapper = redukti.curve_mapper()
f_eonia_id = redukti.pricing_curve_id { curve_type = 'F', currency = 'EUR', index_family = 'EONIA'}
d_eonia_id = redukti.pricing_curve_id { curve_type = 'D', currency = 'EUR' }
d_euribor_id = redukti.pricing_curve_id { curve_type = 'D', currency = 'EUR', index_family = 'EURIBOR'}
f_euribor_1m_id = redukti.pricing_curve_id { curve_type = 'F', currency = 'EUR', index_family = 'EURIBOR', tenor = '1M'}
f_euribor_3m_id = redukti.pricing_curve_id { curve_type = 'F', currency = 'EUR', index_family = 'EURIBOR', tenor = '3M'}
f_euribor_6m_id = redukti.pricing_curve_id { curve_type = 'F', currency = 'EUR', index_family = 'EURIBOR', tenor = '6M'}
f_euribor_12m_id = redukti.pricing_curve_id { curve_type = 'F', currency = 'EUR', index_family = 'EURIBOR', tenor = '12M'}
d_euribor_1m_id = redukti.pricing_curve_id { curve_type = 'D', currency = 'EUR', index_family = 'EURIBOR', tenor = '1M'}
d_euribor_3m_id = redukti.pricing_curve_id { curve_type = 'D', currency = 'EUR', index_family = 'EURIBOR', tenor = '3M'}
d_euribor_6m_id = redukti.pricing_curve_id { curve_type = 'D', currency = 'EUR', index_family = 'EURIBOR', tenor = '6M'}
d_euribor_12m_id = redukti.pricing_curve_id { curve_type = 'D', currency = 'EUR', index_family = 'EURIBOR', tenor = '12M'}
f_euribor_id = redukti.pricing_curve_id { curve_type = 'F', currency = 'EUR', index_family = 'EURIBOR'}

-- map request for forward EONIA curve to discount curve
curve_mapper:add_mapping( f_eonia_id, d_eonia_id )
-- map any request for EURIBOR Discount to EONIA discount
curve_mapper:add_mapping( d_euribor_id, d_eonia_id )
-- map euribor 1m to generic
curve_mapper:add_mapping( f_euribor_1m_id, f_euribor_id )
-- map euribor 12m to generic
curve_mapper:add_mapping( f_euribor_1m_id, f_euribor_id )


redukti.valuation_context( parameters, fixing_service)

Sets up a ValuationContex object.


Table containing parameters. Parameters are:

Business date
If 1 first order derivatives will be computed, if 2, second order derivatives will be computed as well.
A FixingService object


utils = assert(require('utils'))
fixing_service = utils.load_fixings('../testdata/20121211/fixings.csv')
business_date ='2012/12/11')
valuation_context = redukti.valuation_context({ reference_date = business_date, order = 1 }, fixing_service)

Cashflow Conversion

redukti.prepare_cashflows_for_pricing(valuation_context, curve_mapper, cashflows)
Converts the supplied cashflows to a format suitable for pricing.


flows = deposit_rate(, 7, 2013), 'EUR', 'EURIBOR', '1Y', 0.00140)
pricing_cashflows = redukti.prepare_cashflows_for_pricing(valuation_context, curve_mapper, flows)


The CurveProvider resolves logical curve ids and maps these to actual curve objects.

Creates a curve_provider object.
curve_provider:add_mapping(pricing_curve_id, curve)
Adds a mapping from a pricing_curve_id to a Zero Curve object.


Please see the script test_pricing.lua.

Calculate NPV and Sensitivities

Finally you can invoke:

redukti.present_value(valuation_context, pricing_cashflows, curve_provider)
Computes the NPV and sensitivities and returns a PricingResult object.
Tells if you pricing was successful
Returns an array of curve identifiers used for pricing
Given a curve id, returns the first order sensitivities as an table of values keyed by curve pillars.


Please see the script test_pricing.lua.