Skip to main content
CFDL

Energy: the DSCR cash trap and lock-up

A 300 MW capacity-price plant with a DSCR-tested dividend lock-up from a published project finance model: cash traps below 1.20x and releases on recovery, and the Base, Low and High cases run as scenarios.

Every number below is checked against an independent reference implementation on every commit — period by period, and on each metric, inside a declared tolerance. See benchmark methodology.

The case

A 300 MW plant sells capacity at a real price of CHF 90 per kW-year, indexed to inflation, over twenty years of operation. It is built over sixty months from financial close in July 2013 on a debt-first facility: each month's works, drawn on a normal-density S-curve over the construction period, are funded from the facility until its commitment is exhausted and from equity after, with interest, the upfront fee and the commitment fee capitalized into the facility balance. At conversion the facility is refinanced into a twenty-year term loan at its commitment, repaid on the sculpted schedule the structuring run produced, and a debt service reserve is funded to six months of prospective debt service and held there. Operating cost steps up every six years. Tax at forty percent falls on profit after straight-line depreciation of the plant and of the capitalized interest, amortization of the fees over ten years, and interest, with losses carried forward.

Every month the deal tests debt service cover: cash available for debt service over that month's interest and scheduled principal. Below 1.20x the month's cash after debt service, the reserve and the sweep is trapped into a lock-up account; at or above it the whole lock-up balance releases to the sponsor; a month with no debt service due is not tested. In the Low Case cover falls to 1.1374, below the 1.20x test, in July 2036 when the plant's depreciation runs out and the tax charge steps up, and it stays below 1.20x for the last 24 months of the loan, reaching its minimum of 1.1011 in June 2038: the trap holds every month's cash, CHF 3,013k in all, to retirement.

The model is written on the language alone: streams for every flow, fields for the opening positions the flows read, and five accounts moved by the streams that change them — the construction facility, the term loan, the default balance, the reserve and the lock-up. The model states the facility in the language's core statements rather than pack contracts. The covenant is a stream reading the same month's streams, which is what the reference tests; the settled-cash form of the same covenant, which lags a month by design, is pinned by the language's own cash-trap fixture, with its cure period.

The reference

Edward Bodmer's project finance model with commodity price risk, a public workbook from the "Cash Sweep and Risk versus Return" page of his site, run deterministically on the workbook's own Low Case with the cash sweep switched off. The workbook's operating case draws random price and production factors, and its Base and High cases never trip the covenant without them; the Low Case with the sweep off is the one stated setting in which the lock-up holds cash. It is recalculated with LibreOffice under a recipe of four documented transcriptions, reproducing the unchanged workbook to 1e-9. SOURCE.md gives the recipe, the URL and the assumptions.

Every figure asserted here is the workbook's own under that recipe. Two series are taken as the workbook states them: the repayment schedule its structuring run produced, and the Base Case inflation deck.

Provenance. The expectations come from the source workbook, recalculated. The figures the page names as the workbook's tie to its formulas; figures the page names as the model's own are not asserted by anything external.

What it exercises

  • A construction facility drawn debt-first against a normal-density S-curve, with interest and two fees capitalized, refinanced at conversion into a term loan at a stated commitment.
  • A sculpted repayment schedule stated on one timing and shifted by each case's construction length.
  • A debt service reserve held at six months of prospective debt service, computed forward from the schedule and the balance without a look-ahead read, funded at conversion and topped up or released monthly.
  • A 30/360 monthly rate, tax with loss carry-forward, three depreciation and amortization lines with their own caps.
  • A cash sweep, a dividend lock-up and a default balance, each an account moved by the streams that change it.
  • Scenarios: the workbook's Base and High cases with the sweep on and off, and the Low Case with the sweep on, each asserted month by month.

The result

modelreference
Minimum debt service cover, Low Case1.10111.1011
Months below 1.20x24 (July 2036 to June 2038)24
Cash trapped, total (CHF k)3,012.773,012.77
Dividends, total (CHF k)128,811.43128,811.43
Interest paid, total (CHF k)80,039.3680,039.36
Tax paid, total (CHF k)20,845.2220,845.22

Twenty-two series are asserted per month over 386 months — every cash flow, the four balances, the default balance and the inflation index — at a tolerance of 1e-5, in the deterministic case and in each of the five scenarios. All reproduce.

The delta

None in the figures the reference computes. The High Case's operations run past the workbook's last column, August 2045, and in its final five months the reserve's six-month look-ahead folds blank cells past the sheet's edge: the reserve moves by a sixth of a month's service each month and the dividend with it. Those cells are not asserted, and the High Case's dividend total is not, since it carries them; every other figure of the High Case is. Two things the reference does not offer are stated rather than claimed. The trap never releases in any deterministic case: the Low Case's cover stays below 1.20x to retirement, so the reference validates breach, trapping and accumulation, while release and the cure period remain pinned by the fixture only. And the workbook ranks a logical above a number in its covenant test, so a month with no debt service due passes; the case writes that rule as the workbook means it, and the recipe transcribes it for a spreadsheet engine that reads FALSE as zero.

Run configuration

{
  "deterministic": {
    "annual_discount_rate": 0.08
  },
  "scenarios": {
    "low_sweep_on": {
      "parameters": {
        "inputs.construction_months": 60,
        "inputs.operating_years": 20,
        "inputs.project_cost": 315000,
        "inputs.capacity_mw": 300,
        "inputs.price_real": 90,
        "inputs.opex_kw_1": 15,
        "inputs.opex_kw_2": 20,
        "inputs.opex_kw_3": 25,
        "inputs.sweep_pct": 1.0
      }
    },
    "base_sweep_on": {
      "parameters": {
        "inputs.construction_months": 50,
        "inputs.operating_years": 25,
        "inputs.project_cost": 270000,
        "inputs.capacity_mw": 320,
        "inputs.price_real": 100,
        "inputs.opex_kw_1": 10,
        "inputs.opex_kw_2": 35,
        "inputs.opex_kw_3": 40,
        "inputs.sweep_pct": 1.0
      },
      "inputs": {
        "inflation": {
          "series": {
            "interpolation": "step",
            "points": {
              "2013-01-01": 0.02,
              "2014-01-01": 0.01,
              "2015-01-01": 0.015,
              "2016-01-01": 0.02,
              "2017-01-01": 0.015,
              "2018-01-01": 0.02,
              "2045-12-01": 0.02
            }
          }
        }
      }
    },
    "base_sweep_off": {
      "parameters": {
        "inputs.construction_months": 50,
        "inputs.operating_years": 25,
        "inputs.project_cost": 270000,
        "inputs.capacity_mw": 320,
        "inputs.price_real": 100,
        "inputs.opex_kw_1": 10,
        "inputs.opex_kw_2": 35,
        "inputs.opex_kw_3": 40,
        "inputs.sweep_pct": 0.0
      },
      "inputs": {
        "inflation": {
          "series": {
            "interpolation": "step",
            "points": {
              "2013-01-01": 0.02,
              "2014-01-01": 0.01,
              "2015-01-01": 0.015,
              "2016-01-01": 0.02,
              "2017-01-01": 0.015,
              "2018-01-01": 0.02,
              "2045-12-01": 0.02
            }
          }
        }
      }
    },
    "high_sweep_on": {
      "parameters": {
        "inputs.construction_months": 45,
        "inputs.operating_years": 30,
        "inputs.project_cost": 265000,
        "inputs.capacity_mw": 330,
        "inputs.price_real": 110,
        "inputs.opex_kw_1": 8,
        "inputs.opex_kw_2": 12,
        "inputs.opex_kw_3": 18,
        "inputs.sweep_pct": 1.0
      },
      "inputs": {
        "inflation": {
          "series": {
            "interpolation": "step",
            "points": {
              "2013-01-01": 0.02,
              "2045-12-01": 0.02
            }
          }
        }
      }
    },
    "high_sweep_off": {
      "parameters": {
        "inputs.construction_months": 45,
        "inputs.operating_years": 30,
        "inputs.project_cost": 265000,
        "inputs.capacity_mw": 330,
        "inputs.price_real": 110,
        "inputs.opex_kw_1": 8,
        "inputs.opex_kw_2": 12,
        "inputs.opex_kw_3": 18,
        "inputs.sweep_pct": 0.0
      },
      "inputs": {
        "inflation": {
          "series": {
            "interpolation": "step",
            "points": {
              "2013-01-01": 0.02,
              "2045-12-01": 0.02
            }
          }
        }
      }
    }
  }
}

Verified results

Checked period by period: 22 series across 386 periods — 8492 values in all, each within ±0.00001 of the reference.

  • plant.revenue
  • plant.opex
  • plant.capex
  • debt.draw
  • equity.injection
  • debt.idc
  • debt.upfront_fee
  • debt.commit_fee
  • debt.interest
  • debt.repayment
  • debt.sweep
  • dsra.change
  • plant.tax
  • trap.in
  • trap.out
  • dist.dividend
  • account.asset.plant.cdebt
  • account.asset.plant.pdebt
  • account.asset.plant.dsra
  • account.asset.plant.lockup
  • account.asset.plant.default
  • asset.plant.index

Checked per scenario, each a full run under its own parameters:

Scenariostream.dist.dividend.totalstream.trap.in.totalstream.debt.sweep.totalstream.plant.tax.totalstream.debt.interest.total
low_sweep_on-146,535.960-105,023.74-32,661.57-50,498.47
base_sweep_on-323,353.220-122,531.38-178,224.77-43,000.54
base_sweep_off-301,129.9200-163,409.24-80,039.36
high_sweep_on—0-147,603.64-370,446.21-36,779.78
high_sweep_off—00-353,142.37-80,039.36

Summary metrics for the base run:

MetricValueTolerance
stream.dist.dividend.total-128,811.43±0.001
stream.trap.in.total-3,012.77±0.001
stream.debt.sweep.total0±0.001
stream.plant.tax.total-20,845.22±0.001
stream.debt.interest.total-80,039.36±0.001