Skip to content

Commit ac919de

Browse files
authored
Apr25 (#274)
* include Pre: HasPass/IsOutstanding * move away from stack * Lift pool current balance query * add eq to result component * Using DL * refactor Z-bond due prin * enable maxSpread * [root finder] finalise new spread finder * Expose bal in FixedAsset; Expose extend periods in FixedAsset * Finalized DDB in Fixed Asset * Expose default on lease * fix first loss algo * LEASE: fix current balance * fix default on rental algo * expose rental change vec
1 parent 0e098be commit ac919de

54 files changed

Lines changed: 1272 additions & 991 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docker-image-dev-by-tag.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,5 @@ jobs:
5353
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/hastructure:dev, ${{ steps.meta.outputs.tags }}
5454
cache-from: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/hastructure:buildcache
5555
cache-to: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/hastructure:buildcache,mode=max
56+
57+

.github/workflows/docker-image-dev.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,5 @@ jobs:
5757
context: "{{defaultContext}}"
5858
push: true
5959
tags: ${{ steps.meta.outputs.tags }}
60-
labels: ${{ steps.meta.outputs.labels }}
60+
labels: ${{ steps.meta.outputs.labels }}
61+

.github/workflows/docker-image.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,4 @@ jobs:
157157
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/hastructure:latest, ${{ steps.meta.outputs.tags }}
158158
cache-from: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/hastructure:buildcache
159159
cache-to: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/hastructure:buildcache,mode=max
160-
160+

.github/workflows/haskell.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ jobs:
3535
3636
- name: Install dependencies
3737
run: |
38-
stack update
39-
stack build
38+
cabal update
4039
- name: Build
41-
run: stack build
40+
run: cabal build
4241
- name: Run tests
43-
run: stack test
42+
run: cabal test
4443

4544
- name: Badge Action
4645
uses: emibcn/badge-action@v1.2.4
46+
47+

CHANGELOG.md

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,39 @@
22

33
<!-- towncrier release notes start -->
44

5+
## 0.45.7
6+
### 2025-05-26
7+
* ENHANCE: add `BaseByVec` for vector-based rental change
8+
9+
10+
## 0.45.5
11+
### 2025-05-20
12+
* NEW: `MaxSpread` feature for structuring stage: get max possible bond coupon rate !
13+
* ENHANCE: Transfer from `stack` to `cabal` as build tool
14+
* ENHANCE: Apply `DList` to trigger log
15+
* ENHANCE: Enable `Double Decline Balance` in `FixedAsset`
16+
* REFACTOR: Refactor `Leasing` asset type
17+
* Add `Default` assumption
18+
* Add `Period-based` rental ,in addition to `Day-based` rental calculation
19+
20+
21+
## 0.45.2
22+
### 2025-04-01
23+
* ENHANCE: Performance optimization by replace `List` with `DList`.
24+
* ENHANCE: In `inspection` ,expose `IsOutstanding` `HasPassedMaturity` in `Pre`
25+
526
## 0.45.1
627
### 2025-03-25
7-
* BREAK:
828
* FIX: in `Pricing/IRR`, error when holding position is too small
9-
* NEW:
1029
* ENHANCE: engine will auto patch `interest start date` for bonds if it is not modeled. In `PreClosing` status, engine will use `closing date` as bond interest begin date ; In `Non-PreClosing` status, it defaults to use last waterfall distribution date as bond interest begin date.
1130

12-
1331
## 0.45.0
1432
### 2025-03-21
1533
* BREAK: remove unused `DealDates` : `FixInterval`, `CustomDates` and `PatternInterval`. Since all these can be replace by new `GenericDates` in type `DateDesp`
1634
* ENHANCE: now bond with `No last interest accure day` will begin accrue interest from `closing date` if the deal is in `PreClosing` mode, while the bond will use `last bond day` otherwise.
1735
* FIX: `IsPaidOff` now can be queried in inspection formula
1836

1937

20-
21-
22-
2338
## 0.44.0
2439
### 2025-03-11
2540
* BREAK: Add `PAC` `PAC Anchor` to `BondGroup`, now `BondGroup` is `Map String L.Bond (Maybe PrinType)`

Dockerfile

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
FROM fpco/stack-build:lts-22.6 as build
1+
FROM haskell:9.8.4-slim-bullseye as build
22
RUN mkdir /opt/build
33
COPY . /opt/build
4-
RUN cd /opt/build && stack build --copy-bins \
5-
--local-bin-path /opt/build --resolver lts-22.6 # --system-ghc
4+
RUN cd /opt/build && cabal update && cabal install
65

76

8-
FROM --platform=linux/amd64 ubuntu:22.04
7+
FROM --platform=linux/amd64 ubuntu:25.04
98
RUN mkdir -p /opt/myapp
109
ARG BINARY_PATH
1110
WORKDIR /opt/myapp
@@ -15,7 +14,7 @@ RUN apt-get update && apt-get install -y \
1514
# NOTICE THIS LINE
1615

1716

18-
COPY --from=build /opt/build/Hastructure-exe .
17+
COPY --from=build /root/.local/bin/Hastructure-exe .
1918
COPY --from=build /opt/build/config.yml .
2019
COPY --from=build /opt/build/swagger.json .
2120
#COPY config.yml /opt/myapp

Hastructure.cabal

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cabal-version: 1.12
1+
cabal-version: 3.0
22

33
-- This file has been generated from package.yaml by hpack version 0.37.0.
44
--
@@ -13,7 +13,7 @@ bug-reports: https://github.com/yellowbean/Hastructure/issues
1313
author: Xiaoyu
1414
maintainer: always.zhang@gmail.com
1515
copyright: 2025 Xiaoyu, Zhang
16-
license: BSD3
16+
license: BSD-3-Clause
1717
license-file: LICENSE
1818
build-type: Simple
1919
extra-source-files:
@@ -24,6 +24,8 @@ source-repository head
2424
type: git
2525
location: https://github.com/yellowbean/Hastructure
2626

27+
with-compiler: ghc-9.8.2
28+
2729
library
2830
exposed-modules:
2931
Accounts
@@ -67,14 +69,21 @@ library
6769
Util
6870
Validation
6971
Waterfall
70-
WebUI
7172
other-modules:
7273
Paths_Hastructure
7374
hs-source-dirs:
7475
src
7576
build-depends:
7677
Decimal
78+
, base-compat
79+
, attoparsec
80+
, string-conversions
81+
, warp
82+
, wai-cors
83+
, http-types
84+
, exceptions
7785
, aeson
86+
, attoparsec-aeson
7887
, aeson-pretty
7988
, base
8089
, bytestring
@@ -84,7 +93,6 @@ library
8493
, hashable
8594
, ieee754
8695
, lens
87-
, lucid
8896
, math-functions
8997
, monad-loops
9098
, mtl
@@ -104,9 +112,12 @@ library
104112
, template-haskell
105113
, text
106114
, time
107-
, vector
108115
, wai
109116
, yaml
117+
, vector
118+
, MissingH
119+
, dlist
120+
-- , proto3-wire
110121
default-language: Haskell2010
111122

112123
executable Hastructure-exe
@@ -135,7 +146,6 @@ executable Hastructure-exe
135146
, http-types
136147
, ieee754
137148
, lens
138-
, lucid
139149
, math-functions
140150
, monad-loops
141151
, mtl
@@ -158,11 +168,12 @@ executable Hastructure-exe
158168
, text
159169
, time
160170
, unordered-containers
161-
, vector
162171
, wai
163172
, wai-cors
164173
, warp
165174
, yaml
175+
, dlist
176+
-- , proto3-suite
166177
default-language: Haskell2010
167178

168179
test-suite Hastructure-test
@@ -205,7 +216,6 @@ test-suite Hastructure-test
205216
, hashable
206217
, ieee754
207218
, lens
208-
, lucid
209219
, math-functions
210220
, monad-loops
211221
, mtl
@@ -229,7 +239,9 @@ test-suite Hastructure-test
229239
, template-haskell
230240
, text
231241
, time
232-
, vector
233242
, wai
234243
, yaml
244+
, vector
245+
, MissingH
246+
, dlist
235247
default-language: Haskell2010

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright Xiaoyu Zhang (always.zhang A_T gmail ) (c) 2022-2024
1+
Copyright Xiaoyu Zhang (always.zhang A_T gmail ) (c) 2022-2025
22

33
All rights reserved.
44

app/Main.hs

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import GHC.Generics
4444
import GHC.Real
4545
import qualified Data.ByteString.Lazy.Char8 as BL8
4646
import qualified Data.ByteString.Char8 as BS
47-
import Lucid hiding (type_)
4847
import Network.Wai
4948
import Network.Wai.Handler.Warp
5049
import Network.Wai.Middleware.Cors
@@ -101,7 +100,7 @@ debug = flip Debug.Trace.trace
101100

102101

103102
version1 :: Version
104-
version1 = Version "0.44.2"
103+
version1 = Version "0.45.7"
105104

106105

107106
wrapRun :: DealType -> Maybe AP.ApplyAssumptionType -> AP.NonPerfAssumption -> RunResp
@@ -241,16 +240,6 @@ stressRevovlingPerf r (Just (AP.AvailableAssets rp applyAssumpType))
241240
stressRevovlingPerf r (Just (AP.AvailableAssetsBy m))
242241
= Just (AP.AvailableAssetsBy (Map.map (over (_2 . AP.applyAssumptionTypeAssetPerf . _1) (stressAssetPerf r)) m))
243242

244-
dtToBonds :: DealType -> Map.Map BondName L.Bond
245-
dtToBonds (MDeal d) = DB.bonds d
246-
dtToBonds (RDeal d) = DB.bonds d
247-
dtToBonds (IDeal d) = DB.bonds d
248-
dtToBonds (LDeal d) = DB.bonds d
249-
dtToBonds (FDeal d) = DB.bonds d
250-
dtToBonds (UDeal d) = DB.bonds d
251-
dtToBonds (VDeal d) = DB.bonds d
252-
dtToBonds (PDeal d) = DB.bonds d
253-
254243
modifyDealType :: DM.ModifyType -> Double -> DealType -> DealType
255244
modifyDealType dm f (MDeal d) = MDeal $ DM.modDeal dm f d
256245
modifyDealType dm f (RDeal d) = RDeal $ DM.modDeal dm f d
@@ -281,42 +270,67 @@ queryDealTypeBool (UDeal _d) d s = Q.queryDealBool _d s d
281270
queryDealTypeBool (VDeal _d) d s = Q.queryDealBool _d s d
282271
queryDealTypeBool (PDeal _d) d s = Q.queryDealBool _d s d
283272

273+
getDealBondMap :: DealType -> Map.Map BondName L.Bond
274+
getDealBondMap (MDeal d) = DB.bonds d
275+
getDealBondMap (RDeal d) = DB.bonds d
276+
getDealBondMap (IDeal d) = DB.bonds d
277+
getDealBondMap (LDeal d) = DB.bonds d
278+
getDealBondMap (FDeal d) = DB.bonds d
279+
getDealBondMap (UDeal d) = DB.bonds d
280+
getDealBondMap (VDeal d) = DB.bonds d
281+
getDealBondMap (PDeal d) = DB.bonds d
282+
283+
getDealFeeMap :: DealType -> Map.Map FeeName F.Fee
284+
getDealFeeMap (MDeal d) = DB.fees d
285+
getDealFeeMap (RDeal d) = DB.fees d
286+
getDealFeeMap (IDeal d) = DB.fees d
287+
getDealFeeMap (LDeal d) = DB.fees d
288+
getDealFeeMap (FDeal d) = DB.fees d
289+
getDealFeeMap (UDeal d) = DB.fees d
290+
getDealFeeMap (VDeal d) = DB.fees d
291+
getDealFeeMap (PDeal d) = DB.fees d
284292

285293
-- stress the pool performance, till a bond suffers first loss
286294
testByDefault :: DealType -> AP.ApplyAssumptionType -> AP.NonPerfAssumption -> BondName -> Double -> Double
287295
testByDefault dt assumps nonPerfAssump@AP.NonPerfAssumption{AP.revolving = mRevolving} bn r
288296
= let
289297
stressed = over (AP.applyAssumptionTypeAssetPerf . _1 ) (stressAssetPerf (toRational r)) assumps
290298
stressedNonPerf = nonPerfAssump {AP.revolving = stressRevovlingPerf (toRational r) mRevolving }
291-
runResult = wrapRun dt (Just stressed) stressedNonPerf
299+
runResult = wrapRun dt (Just stressed) stressedNonPerf -- `debug` ("running stress "++ show stressed)
292300
in
293301
case runResult of
294302
Right (d,mPoolCfMap,mResult,mPricing) ->
295303
let
296-
bondBal = L.getOutstandingAmount $ (dtToBonds d) Map.! bn
304+
bondBal = L.getOutstandingAmount $ (getDealBondMap d) Map.! bn
297305
in
298-
(fromRational (toRational bondBal) - 0.01)
306+
(fromRational (toRational bondBal) - 0.01) -- `debug` (">>> test run result"++ show (fromRational (toRational bondBal) - 0.01))
299307
Left errorMsg -> error $ "Error in test fun for first loss" ++ show errorMsg
300308

301309

302310
-- add spread to bonds till PV of bond (discounted by pricing assumption) equals to face value
303311
-- with constraint that all liabilities are paid off
304-
testBySpread :: DealRunInput -> (BondName,[BondName]) -> Double -> Double
305-
testBySpread (dt,mPAssump,runAssump) (bn,bnds) f
312+
testBySpread :: DealRunInput -> (BondName,Bool,Bool) -> Double -> Double
313+
testBySpread (dt,mPAssump,runAssump) (bn,otherBondFlag,otherFeeFlag) f
306314
= let
307-
runResult = wrapRun (modifyDealType (DM.AddSpreadToBonds bnds) f dt) mPAssump runAssump
315+
runResult = wrapRun (modifyDealType (DM.AddSpreadToBonds bn) f dt) mPAssump runAssump
308316
in
309317
case runResult of
310-
Right (d, mPoolCfMap, mResult, pResult) ->
318+
Right (dt, mPoolCfMap, mResult, pResult) ->
311319
let
320+
-- bnds
321+
otherBondsName = []
322+
-- check fees/other bonds
323+
otherBondOustanding True = sum $ L.getOutstandingAmount <$> Map.elems (getDealBondMap dt)
324+
otherBondOustanding False = 0.0
325+
feeOutstanding True = sum $ L.getOutstandingAmount <$> Map.elems (getDealFeeMap dt)
326+
feeOutstanding False = 0.0
312327
v = getPriceValue $ pResult Map.! bn
313-
bond = dtToBonds d Map.! bn
328+
bondBal = L.getOriginBalance $ (getDealBondMap dt) Map.! bn
314329
in
315-
-- if L.getCurBalance bond > 0 then
316-
if True then
317-
1.0
330+
if (otherBondOustanding otherBondFlag+feeOutstanding otherFeeFlag) > 0 then
331+
-1
318332
else
319-
(fromRational . toRational) (v - L.getOriginBalance bond)
333+
(fromRational . toRational) $ bondBal - v -- `debug` ("rate"++ show f ++ "bondBal:"++ show bondBal++"v:"++ show v)
320334
Left errorMsg -> error $ "Error in test fun for spread testing" ++ show errorMsg
321335

322336
runRootFinderBy :: RootFindReq -> Handler (Either String RootFindResp)
@@ -335,17 +349,17 @@ runRootFinderBy (FirstLossReq (dt,Just assumps,nonPerfAssump@AP.NonPerfAssumptio
335349
NotBracketed -> Left "Not able to bracket the root"
336350
SearchFailed -> Left "Not able to find the root"
337351

338-
runRootFinderBy (MaxSpreadToFaceReq (dt,pAssump,dAssump) (bn,bnds))
352+
runRootFinderBy (MaxSpreadToFaceReq (dt,pAssump,dAssump) bns chkOtherBnds chkOtherFees)
339353
= return $
340354
let
341355
itertimes = 500
342356
def = RiddersParam { riddersMaxIter = itertimes, riddersTol = RelTol 0.0001}
343357
in
344-
case ridders def (0.00,200.0) (testBySpread (dt,pAssump,dAssump) (bn,bnds)) of
358+
case ridders def (0.00,200.0) (testBySpread (dt,pAssump,dAssump) (bns,chkOtherBnds,chkOtherFees)) of
345359
Root r -> let
346-
dt' = modifyDealType (DM.AddSpreadToBonds bnds) r dt
360+
dt' = modifyDealType (DM.AddSpreadToBonds bns) r dt
347361
in
348-
Right $ BestSpreadResult r (dtToBonds dt') dt'
362+
Right $ BestSpreadResult r (getDealBondMap dt') dt'
349363
NotBracketed -> Left "Not able to bracket the root"
350364
SearchFailed -> Left "Not able to find the root"
351365

0 commit comments

Comments
 (0)