BCW2011 Liquidation Walkthrough#
Work through this page after Getting Started and BCW2011 Case Study.
The code discussed here is:
src/example/BCW2011Liquidation.py
What To Watch#
By the end of this page, you should be able to move in both directions:
from BCW’s liquidation equations to the FinHJB implementation,
from the FinHJB classes back to the economics they represent.
This is the cleanest entry point because the case has:
one state variable,
one control,
one endogenous boundary target,
no refinancing and no regime switching.
Reproduction#
Run this example from the repository root:
MPLBACKEND=Agg uv run python src/example/BCW2011Liquidation.py
The BCW scripts are documented and supported as repository-root examples. They are not designed around local-directory execution inside src/example/.
Economic Setup And State Reduction#
The original BCW problem is written in firm capital K and cash W. FinHJB solves the one-dimensional reduced form after BCW’s homogeneity step:
This reduction is what makes the example fit the current one-dimensional FinHJB interface.
The paper’s key objects become:
In repository terms:
grid.sstores the state grid forw,grid.vstores the solved value-capital ratiop(w),grid.dvstores the marginal value of cashp'(w),grid.d2vstores the curvaturep''(w).
Paper Equations Used In This Case#
The liquidation case uses BCW’s internal-financing HJB and the liquidation/payout boundaries.
HJB: Eq. (13)#
With BCW’s quadratic adjustment cost,
Investment FOC: Eq. (14)#
Boundary Conditions: Eq. (16)-(18)#
At the payout boundary \bar w:
At the liquidation boundary:
How Those Equations Become FinHJB Objects#
The repository implementation follows a stable object mapping:
Economic object |
FinHJB object |
What it does in this case |
|---|---|---|
benchmark parameters |
|
stores Table I values such as |
left and right boundary values |
|
pins |
control container |
|
stores the single control |
investment update |
|
imposes Eq. (14) as an implicit policy residual |
HJB residual |
|
imposes Eq. (13) on the interior grid |
In code, the exact decomposition is:
Eq. (14) is implemented through
investment_rule_residual(...)and exposed inPolicy.cal_investment(...).Eq. (13) is implemented through
standard_hjb_residual(...)and called fromModel.hjb_residual(...).Boundary.compute_v_left(...)returns the liquidation valuel.Boundary.compute_v_right(...)uses the payout-side closed form implied by Eq. (16).
This is the clean pattern FinHJB expects for a one-control continuous-time model:
put primitive parameters in
Parameter,put boundary values in
Boundary,put controls in
PolicyDict,encode the FOC in
Policy,encode the HJB in
Model.
Why The Numerical Workflow Is boundary_search(method="bisection")#
The liquidation case has one endogenous object to solve for numerically:
the payout boundary
\bar w.
The left boundary is fixed at w = 0 with p(0)=l. The right boundary is pinned by the super-contact condition:
Numerically, that becomes:
search over
s_max = \bar w,evaluate
grid.d2v[-1],stop when the right-tail curvature is approximately zero.
That is exactly why the script uses a one-target boundary_search() with bisection:
there is only one unknown boundary target,
the search bracket is economically well-behaved,
the root condition is scalar and monotone enough for bisection to be robust.
Boundary Logic In Repository Terms#
This case uses three layers of boundary information:
Left boundary:
Boundary.compute_v_left(...) = lRight boundary value: the script uses the payout-side closed-form value consistent with BCW’s payout region and
p'(\bar w)=1Right boundary optimality target:
super_contact_residual(grid) = grid.d2v[-1]
This separation matters. In FinHJB, the boundary value and the boundary optimality condition are not the same object:
the boundary value tells the solver what value to pin at the grid edge,
the boundary target tells the outer search how to move the edge itself.
Figure 2: How To Read The Output Economically#
Panel A: p(w)#
The value-capital ratio starts at liquidation value l=0.9, stays above the liquidation line l+w, and bends into the payout boundary near \bar w \approx 0.22.
That is BCW’s statement that the firm does not liquidate early even when refinancing is unavailable.
Panel B: p'(w)#
The marginal value of cash explodes as w \to 0. In this case, extra cash is valuable because it delays forced liquidation.
Panel C: i(w)#
Investment becomes negative near zero cash. In BCW’s language, the firm sells assets to move away from the liquidation boundary.
Panel D: i'(w)#
Investment rises with cash, but not linearly. This is the right object to inspect when you want to discuss investment-cash sensitivity rather than just the investment level.
Stable Quantitative Targets#
Healthy runs in this repository usually look like:
\bar w \approx 0.22,p'(0) \approx 30,i(\bar w) \approx 10.5%,strongly negative investment close to
w=0,p''(\bar w)numerically close to zero.
Those are the right first checks before you read deeper meaning into minor grid-level differences.
Code Inspection Pattern#
from src.example.BCW2011Liquidation import run_case
bundle = run_case(number=1000)
result = bundle["results"]["baseline"]
print(result["summary"])
print(result["grid"].df.head())
print(result["grid"].df.tail())
The key quantities to inspect are:
result["summary"]["payout_boundary"],result["summary"]["dv_at_zero"],result["grid"].d2v[-1],result["grid"].policy["investment"].
How To Adapt This Pattern#
Start from this case if your own model still has:
one reduced state variable,
one control,
a fixed left boundary,
a single endogenous payout boundary on the right.
Only move to the later BCW examples if your model genuinely needs:
issuance with value matching,
multiple controls,
or a piecewise residual across regions.