Skip to content

Commit 94e6cff

Browse files
committed
ready for release 0.41.x
1 parent c9b5830 commit 94e6cff

File tree

8 files changed

+538
-312
lines changed

8 files changed

+538
-312
lines changed

absbox/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from importlib.metadata import version
1717
from absbox.local.cf import readBondsCf,readToCf,readFeesCf,readAccsCf,readPoolsCf,readFlowsByScenarios,readMultiFlowsByScenarios,readFieldsByScenarios
1818
from absbox.local.cf import readInspect, readLedgers, readTriggers
19+
from absbox.local.cf import BondCfHeader
1920

2021
from absbox.report import toHtml,OutputType,toExcel
2122

absbox/local/cf.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pandas as pd
22
import toolz as tz
33
from lenses import lens
4+
import enum
45
from absbox.validation import vInt, vDate, vFloat, vBool
56
from absbox.validation import vDict, vList, vStr, vNum
67
from absbox import unifyTs
@@ -44,6 +45,12 @@ def filterCols(xs, columnsToKeep):
4445
return [_[columnsToKeep] for _ in xs]
4546

4647

48+
class BondCfHeader(enum.Enum):
49+
SIMPLE = ['rate','cash','intDue','intOverInt','factor','memo']+["利率","本息合计","应付利息","罚息","本金系数","备注"]
50+
STANDARD = ['intOverInt','factor','memo']+["罚息","本金系数","备注"]
51+
FULL = []
52+
53+
4754
def readBondsCf(bMap, popColumns=["factor","memo","本金系数","备注","应付利息","罚息","intDue","intOverInt"]) -> pd.DataFrame:
4855
def isBondGroup(k, v) -> bool:
4956
if isinstance(k,str) and isinstance(v,dict):
@@ -86,8 +93,7 @@ def filterCols(x:dict, columnsToKeep) -> pd.DataFrame:
8693
cfFrame.append(v)
8794

8895
header = pd.MultiIndex.from_tuples(indexes
89-
, names=["BondGroup",'Bond',"Field"]
90-
)
96+
, names=["BondGroup",'Bond',"Field"])
9197

9298
df = pd.concat(cfFrame,axis=1)
9399
df.columns = header
@@ -164,12 +170,21 @@ def readInspect(r:dict) -> pd.DataFrame:
164170
else:
165171
u = pd.DataFrame()
166172

167-
w = r['waterfallInspect'].to_records() if ('waterfallInspect' in r and r['waterfallInspect'] and (not r['waterfallInspect'].empty)) else []
173+
if not 'waterfallInspect' in r:
174+
return pd.concat([u],axis=1).sort_index()
175+
176+
if r['waterfallInspect'] is None:
177+
return pd.concat([u],axis=1).sort_index()
178+
179+
if r['waterfallInspect'].empty:
180+
return pd.concat([u],axis=1).sort_index()
181+
182+
w = r['waterfallInspect'].to_records()
168183
wdf = tz.pipe(tz.groupby(lambda x:tz.nth(2,x), w)
169184
,lambda m: tz.valmap(lambda xs: [ (_[1],_[4]) for _ in xs],m)
170185
,lambda m: tz.valmap(lambda xs: pd.DataFrame(xs, columns =['Date', 'val']).set_index('Date'),m)
171186
,lambda m: {k:v.rename({"val":k},axis=1) for k,v in m.items() }
172-
)
187+
)
173188
return pd.concat([u,*wdf.values()],axis=1).sort_index()
174189

175190

absbox/local/component.py

Lines changed: 371 additions & 284 deletions
Large diffs are not rendered by default.

absbox/local/generic.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ def readBondStmt(respBond):
2020
case {'tag':'Bond', **singleBndMap }:
2121
bStmt = mapNone(singleBndMap.get('bndStmt',[]),[])
2222
return pd.DataFrame(list(tz.pluck("contents", bStmt)), columns=english_bondflow_fields).set_index("date")
23+
case {'tag':'MultiIntBond', **singleBndMap }:
24+
bStmt = mapNone(singleBndMap.get('bndStmt',[]),[])
25+
return pd.DataFrame(list(tz.pluck("contents", bStmt)), columns=english_bondflow_fields).set_index("date")
2326
case _:
2427
raise RuntimeError("Failed to read bond flow from resp",respBond)
2528

absbox/local/util.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def mapNone(x, v):
1515
return x
1616

1717

18-
def lmap(f, xs):
18+
def lmap(f: callable, xs) -> list:
1919
''' just make it looks more functional '''
2020
return list(map(f, xs))
2121

@@ -58,18 +58,23 @@ def readTagMap(x:dict) -> str:
5858
case _ :
5959
return f"<{x}>"
6060

61+
6162
def readTag(x: dict):
6263
return f"<{x['tag']}:{','.join(x['contents'])}>"
6364

65+
6466
def isDate(x):
6567
return re.match(r"\d{4}\-\d{2}\-\d{2}", x)
6668

69+
6770
def allList(xs):
6871
return all([isinstance(x, list) for x in xs])
6972

73+
7074
def mkTs(n, vs):
7175
return mkTag((n, vs))
7276

77+
7378
def mkTbl(n, vs):
7479
return mkTag((n, vs))
7580

@@ -106,20 +111,6 @@ def unifyTs(xs):
106111
return r.sort_index()
107112

108113

109-
def aggCFby(_df, interval, cols):
110-
df = _df.copy()
111-
idx = None
112-
dummy_col = '_index'
113-
df[dummy_col] = df.index
114-
_mapping = {"月份": "M", "Month": "M", "M": "M", "month": "M"}
115-
if df.index.name == "日期":
116-
idx = "日期"
117-
else:
118-
idx = "date"
119-
df[dummy_col] = pd.to_datetime(df[dummy_col]).dt.to_period(_mapping[interval])
120-
return df.groupby([dummy_col])[cols].sum().rename_axis(idx)
121-
122-
123114
def update_deal(d, i, c): #Deprecated ,to be replace with Lens
124115
"A patch function to update a deal data component list in immuntable way"
125116
_d = d.copy()
@@ -218,13 +209,15 @@ def renameKs2(m: dict, kmapping:dict) -> dict:
218209
assert set(m.keys()).issubset(set(kmapping.keys())), f"{m.keys()} not in {kmapping.keys()}"
219210
return {kmapping[k]: v for k, v in m.items()}
220211

212+
221213
def updateKs(m: dict, kmapping:dict) -> dict:
222214
''' Given a map, update ks from a key-mapping '''
223215
assert isinstance(m, dict), "M is not a map"
224216
assert isinstance(kmapping, dict), f"Mapping is not a map: {kmapping}"
225217

226218
return {kmapping.get(k,k):v for k,v in m.items()}
227219

220+
228221
def ensure100(xs, msg=""):
229222
assert sum(xs) == 1.0, f"Doesn't not sum up 100%: {msg}"
230223

@@ -456,6 +449,4 @@ def patchDicts(dict1:dict,dict2:dict)-> dict:
456449
Merge two dictionaries, if a key exists in both dictionaries,use the value from dict2
457450
if key only exists in either one, use the only one value
458451
"""
459-
return tz.merge_with(lambda xs: xs[1] if len(xs)==2 else xs[0]
460-
,dict1
461-
,dict2)
452+
return tz.merge_with(lambda xs: xs[1] if len(xs)==2 else xs[0] ,dict1 ,dict2)

absbox/tests/benchmark/china/test22.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,19 @@
88
]
99
,"封包日":"2021-01-04"}
1010

11-
from absbox.local.util import aggCFby
11+
def aggCFby(_df, interval, cols):
12+
df = _df.copy()
13+
idx = None
14+
dummy_col = '_index'
15+
df[dummy_col] = df.index
16+
_mapping = {"月份": "M", "Month": "M", "M": "M", "month": "M"}
17+
if df.index.name == "日期":
18+
idx = "日期"
19+
else:
20+
idx = "date"
21+
df[dummy_col] = pd.to_datetime(df[dummy_col]).dt.to_period(_mapping[interval])
22+
return df.groupby([dummy_col])[cols].sum().rename_axis(idx)
23+
1224
p = localAPI.runPool(mypool
1325
,assumptions=[{"租赁截止日":"2023-02-01"}]
1426
,read=True)

absbox/validation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ def isValidUrl(url: str) -> str | None:
2020
def vList(x, t, msg:str = None) -> list:
2121
return Schema([t]).validate(x)
2222

23+
def vTuple(x, t, msg:str = None) -> tuple:
24+
return Schema((t,)).validate(x)
2325

2426
def vDict(x, msg:str = None) -> dict:
2527
pass
@@ -32,7 +34,6 @@ def vStr(x, msg:str = None) -> str:
3234
def vNum(x, msg:str = None) -> float:
3335
return Schema(Or(float, int)).validate(x)
3436

35-
3637
def vFloat(x, msg:str = None) -> float:
3738
return Schema(float).validate(x)
3839

docs/source/modeling.rst

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ To model them via sequential parameter ( *Not Recommend* ):
8787
,<Collection Rule>
8888
,<Liquidation facilities>
8989
,<Interest Rate Swap>
90+
,None # reserve for currency swap
9091
,<Triggers>
9192
,<Deal Status>
9293
,<Custom data> # optional
@@ -330,7 +331,7 @@ Ratio Type
330331
Bool Type
331332
^^^^^^^^^^
332333
* ``("trigger", loc ,<trigger name>)`` -> status of trigger with name ``<trigger name>`` at :ref:`Trigger Locations`
333-
* ``("isMostSenior","A",["B","C"])`` -> True if the bond "A" is oustanding and "B" and "C" are not outstanding
334+
* ``("isMostSenior","A",("B","C"))`` -> True if the bond "A" is oustanding and "B" and "C" are not outstanding
334335
* ``("status", <deal status>)`` -> True if current deal status is :ref:`Deal Status`
335336
* ``("rateTest", <formula>, <cmp>, rate)`` -> True if :ref:`Formula` compare with a rate value
336337
* ``("allTest", True|False, <boolean type ds>....)`` -> True if all boolean type ds are True/False
@@ -1877,6 +1878,11 @@ syntax
18771878
,{"fixReserve":100}]}
18781879
,{"fixReserve":150}]})
18791880
1881+
("reserveAcc",{"type":("when",[("isPaidOff","A","B","C","D"),True]
1882+
,("fix",0)
1883+
,("target",("*",("poolBalance",),0.0035)))
1884+
,"balance":begReserveBal}),
1885+
18801886
Reinvestment Setup
18811887
^^^^^^^^^^^^^^^^^^^^^^^^^^
18821888
@@ -1915,6 +1921,8 @@ Fields:
19151921
,"rate": 0.03
19161922
,"lastSettleDate":"2022-11-02"}})
19171923
1924+
1925+
19181926
19191927
19201928
Bonds/Tranches
@@ -1971,6 +1979,17 @@ syntax
19711979
19721980
:code:`"rateType":{"fix":0.0569,"dayCount":"DC_ACT_365"}`
19731981
1982+
:code:`"rateType":["fix":0.0569]`
1983+
1984+
:code:`"rateType":("fix":0.0569)`
1985+
1986+
.. versionadded:: 0.40.10
1987+
1988+
syntax
1989+
:code:`"rateType":("fix", 0.0569, "DC_ACT_365")`
1990+
1991+
:code:`"rateType":["fix", 0.0569, "DC_ACT_365"]`
1992+
19741993
Float Rate
19751994
""""""""""""""""
19761995
@@ -1983,6 +2002,13 @@ syntax
19832002
19842003
:code:`"rateType":{"floater":[0.05, "SOFR1Y",-0.0169,"MonthEnd"], "dayCount":"DC_ACT_365"}`
19852004
2005+
:code:`"rateType":("floor", 0.01, <floater rate>)`
2006+
2007+
:code:`"rateType":("floor", 0.01, {"floater":[0.05, "SOFR1Y",-0.0169,"MonthEnd"]})`
2008+
2009+
:code:`"rateType":("cap", 0.10, <floater rate>)`
2010+
2011+
:code:`"rateType":("cap", 0.10, {"floater":[0.05, "SOFR1Y",-0.0169,"MonthEnd"]})`
19862012
19872013
Step-Up Rate
19882014
""""""""""""""
@@ -2466,6 +2492,20 @@ PayInt
24662492
* ``limit`` -> :ref:`<limit>`
24672493
* ``support`` -> :ref:`<support>`
24682494
2495+
PayIntAndBook
2496+
.. versionadded:: 0.40.10
2497+
pay interest to bond and book the ledger
2498+
2499+
syntax
2500+
``["payInt", {Account}, [<Bonds>], m, "book", <Direction>, <Ledger>]``
2501+
2502+
`m`is just a map same in the `payFee` , which has keys :
2503+
2504+
* ``limit`` -> :ref:`<limit>`
2505+
* ``support`` -> :ref:`<support>`
2506+
2507+
2508+
24692509
PayIntBySeq
24702510
.. versionadded:: 0.23.4
24712511
pay interest to bonds sequentially
@@ -2834,6 +2874,9 @@ Liquidity Repay
28342874
* ``["liqRepay","bal", <Account>, <liqProvider>, <Limit>]``
28352875
* ``["liqRepay","int", <Account>, <liqProvider>, <Limit>]``
28362876
* ``["liqRepay","premium", <Account>, <liqProvider>, <Limit>]``
2877+
* ``["liqRepay",["int","bal","premium"], <Account>, <liqProvider>, <Limit>]``
2878+
2879+
repay interest first then balance, then premium
28372880
28382881
Compensation
28392882
Use all cash from the account and pay back to liquidity provider as compensation,no limit amount.
@@ -2848,6 +2891,31 @@ Accrue interest & fee
28482891
``["liqAccrue", <liqProvider>]``
28492892
28502893
2894+
Rate Swap
2895+
^^^^^^^^^^^^
2896+
2897+
Settle
2898+
.. versionadded:: 0.40.10
2899+
syntax
2900+
``["settleSwap", <AccountName>, <SwapName>]``
2901+
2902+
Pay Out
2903+
.. versionadded:: 0.40.10
2904+
syntax
2905+
``["paySwap", <AccountName>, <SwapName>]``
2906+
2907+
Collect
2908+
.. versionadded:: 0.40.10
2909+
syntax
2910+
``["receiveSwap", <AccountName>, <SwapName>]``
2911+
2912+
2913+
2914+
Rate Hedge
2915+
^^^^^^^^^^^^
2916+
2917+
2918+
28512919
Conditional Action
28522920
^^^^^^^^^^^^^^^^^^^^
28532921
@@ -2940,9 +3008,26 @@ syntax
29403008
* ``["formula",<ledger name>,<Debit|Credit>,<formula>]``
29413009
29423010
The most generic booking type ,which just book a :ref:`Formula` value to ledger ``<ledger name>`` with either ``Debit`` or ``Credit``.
2943-
..
2944-
* ``["AccountDraw",<ledger name>]``
2945-
It was used in ``Support``,when there is insufficent interest or fee payment from account A and account B cures the shortfall ,that amount draw from account B was call "Account Draw" and booked in the ledger.
3011+
3012+
* ``["till",<ledger name>,<Debit|Credit>,<formula>]``
3013+
3014+
Book the value of formula to ledger till the value equals to the formula.
3015+
i.e
3016+
3017+
* ledger => credit ,10, input: formula => 20,credit
3018+
3019+
* then 10 credit will be booked to ledger
3020+
* ledger => credit ,10, input: formula => 10,credit
3021+
3022+
* then nothing will be booked
3023+
* ledger => credit ,10, input: formula => 20,debit
3024+
3025+
* then 30 debit will be booked to ledger
3026+
* ledger => credit ,10, input: formula => 0,debit/debit
3027+
3028+
* then 10 debit will be booked
3029+
3030+
29463031
29473032
.. seealso::
29483033
@@ -3345,6 +3430,37 @@ Liquidity Provider(Facility) Example
33453430
}
33463431
}
33473432
3433+
Example-1
3434+
""""""""""""""""""""""""""""""""
3435+
3436+
3437+
Cash Advance Facility Maximum Amount means, on each date
3438+
3439+
* (a) as long as any of the Class A Notes or Class B Notes are outstanding, an amount equal to
3440+
3441+
* (i) the greater of 0.50 per cent. of the Principal Amount Outstanding of the Class A Notes and Class B Notes on such date and
3442+
* (ii) 0.20 per cent. of the Principal Amount Outstanding of the Class A Notes and Class B Notes as at the Closing Date and
3443+
* (b) on the Notes Payment Date on which the Class A Notes and Class B Notes have been or are to be redeemed in full, zero;
3444+
3445+
.. code-block:: python
3446+
3447+
{"warehouse":{
3448+
"credit" : 2000
3449+
,"balance":300
3450+
,"type": {"formula":("max", ("factor",("bondBalance","A","B"),0.005)
3451+
, ("factor",("originalBondBalance","A","B"),0.002))}
3452+
,"creditCalc":"IncludeDueInt"
3453+
,"start": "2021-06-15"
3454+
,"rateType": ("fix", 0.05)
3455+
,"feeType": ("fix", 0.03)
3456+
,"rate":0.03
3457+
,"fee":0.01
3458+
}
3459+
}
3460+
3461+
3462+
3463+
33483464
Interest Rate Hedge
33493465
---------------------
33503466

0 commit comments

Comments
 (0)