Skip to content

Commit d8ddfff

Browse files
committed
update case
1 parent 69faa95 commit d8ddfff

File tree

11 files changed

+109
-89
lines changed

11 files changed

+109
-89
lines changed

absbox/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from .library import LIBRARY, LibraryEndpoints, LibraryPath
99
from .local.util import *
1010
from .local.base import *
11-
from .local.cmp import compResult
11+
from .local.cmp import compResult,compDf
1212
from .local.china import 信贷ABS, SPV
1313
from .local.generic import Generic
1414
from .deal import mkDeal, mkDealsBy, setDealsBy, prodDealsBy, setAssumpsBy, prodAssumpsBy

absbox/local/china.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -127,21 +127,22 @@ def read(resp):
127127
output['agg_accounts'] = aggAccs(output['accounts'], 'chinese')
128128

129129
output['pool'] = {}
130-
mFutureFlow = tz.get_in(['pool','contents','futureCf'], deal_content, default=None)
131-
if mFutureFlow is None:
132-
output['pool']['flow'] = None
130+
poolMap = deal_content['pool']['contents']
131+
132+
if deal_content['pool']['tag']=='MultiPool':
133+
output['pool']['flow'] = tz.valmap(lambda v: readPoolCf(v['futureCf'][0]['contents']) if (not v['futureCf'] is None) else pd.DataFrame(), poolMap)
134+
output['pool']['breakdown'] = tz.valmap(lambda v: list(tz.map(readPoolCf, v['futureCf'][1] & lens.Each()['contents'].collect() )) if (v['futureCf'] and (not v['futureCf'][1] is None)) else [], poolMap)
135+
elif deal_content['pool']['tag']=='ResecDeal':
136+
output['pool']['flow'] = {tz.get([1,2,4],k.split(":")): readPoolCf(v['futureCf']['contents']) for (k,v) in poolMap.items() }
133137
else:
134-
_pool_cf_header, _, expandFlag = guess_pool_flow_header(mFutureFlow['contents'][1][0], "chinese")
135-
if not expandFlag:
136-
output['pool']['flow'] = pd.DataFrame([_['contents'] for _ in mFutureFlow['contents'][1]]
137-
, columns=_pool_cf_header)
138-
else:
139-
output['pool']['flow'] = pd.DataFrame([_['contents'][:-1]+mapNone(_['contents'][-1],[None]*6) for _ in mFutureFlow['contents'][1]]
140-
, columns=_pool_cf_header)
141-
142-
pool_idx = "日期"
143-
output['pool']['flow'] = output['pool']['flow'].set_index(pool_idx)
144-
output['pool']['flow'].index.rename(pool_idx, inplace=True)
138+
raise RuntimeError(f"Failed to match deal pool type:{deal_content['pool']['tag']}")
139+
140+
outstanding_pool_flow = {k:{"flow": readPoolCf(aggFlow['contents'])
141+
,"breakdown": [ readPoolCf(_['contents']) for _ in breakdownFlows]}
142+
for k,(aggFlow,breakdownFlows) in resp[4].items()}
143+
output['pool_outstanding'] = {"flow": { k:v['flow'] for k,v in outstanding_pool_flow.items() }
144+
,"breakdown": { k:v['breakdown'] for k,v in outstanding_pool_flow.items() } }
145+
145146

146147
output['pricing'] = readPricingResult(resp[3], 'cn')
147148
output['result'] = readRunSummary(resp[2], 'cn')

absbox/local/cmp.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
11
import pandas as pd
2+
from lenses import lens
3+
4+
5+
def compDf(x:pd.DataFrame, y:pd.DataFrame, names) -> pd.DataFrame:
6+
""" Compare two dataframe ,return the dataframe with difference """
7+
8+
(nameA,nameB) = names
9+
x, y = x.align(y, fill_value=pd.NA)
10+
cmp = x.compare(y, result_names=(nameA,nameB))
11+
result = cmp.copy()
12+
for column in cmp.columns.levels[0]:
13+
if pd.api.types.is_numeric_dtype(cmp[column][nameA]) and pd.api.types.is_numeric_dtype(cmp[column][nameB]):
14+
result[column, 'diff'] = cmp[column][nameA].fillna(0) - cmp[column][nameB].fillna(0)
15+
return result.reindex(axis=1, level=1, labels=[nameA, nameB, 'diff']).sort_index(axis=1,level=0)
16+
217

318
def compResult(r1:dict, r2:dict, names=("Left", "Right")):
419
""" compare first to second result
@@ -10,19 +25,6 @@ def compResult(r1:dict, r2:dict, names=("Left", "Right")):
1025
:param names: name of the results, defaults to ("Left", "Right")
1126
:type names: tuple, optional
1227
"""
13-
14-
def compDf(x:pd.DataFrame, y:pd.DataFrame) -> pd.DataFrame:
15-
""" Compare two dataframe ,return the dataframe with difference """
16-
17-
(nameA,nameB) = names
18-
x, y = x.align(y, fill_value=pd.NA)
19-
cmp = x.compare(y, result_names=(nameA,nameB))
20-
result = cmp.copy()
21-
for column in cmp.columns.levels[0]:
22-
if pd.api.types.is_numeric_dtype(cmp[column][nameA]) and pd.api.types.is_numeric_dtype(cmp[column][nameB]):
23-
result[column, 'diff'] = cmp[column][nameA].fillna(0) - cmp[column][nameB].fillna(0)
24-
return result.reindex(axis=1, level=1, labels=[nameA, nameB, 'diff']).sort_index(axis=1,level=0)
25-
2628
def cmpMap(x:dict, y:dict) -> dict:
2729
""" compare two map with the same keys """
2830
assert set(x.keys())==set(y.keys()), f"keys not match {x.keys()} vs {y.keys()}"
@@ -31,7 +33,7 @@ def cmpMap(x:dict, y:dict) -> dict:
3133
assert isinstance(v, pd.DataFrame), f"expecting DataFrame, got {type(v)}"
3234
assert isinstance(y[k], pd.DataFrame), f"expecting DataFrame, got {type(y[k])}"
3335
if not v.equals(y[k]):
34-
cmp[k] = compDf(v, y[k])
36+
cmp[k] = compDf(v, y[k], names)
3537
else:
3638
cmp[k] = pd.DataFrame()
3739
return cmp
@@ -72,7 +74,10 @@ def cmpMap(x:dict, y:dict) -> dict:
7274

7375
return comp_result
7476

77+
7578
def compTwoEngine(xEngine, yEngine, d, pAssump, rAssump):
7679
rx = xEngine.run(d, read=True, poolAssump=pAssump, runAssump=rAssump)
7780
ry = xEngine.run(d, read=True, poolAssump=pAssump, runAssump=rAssump)
78-
return compResult(rx, ry)
81+
return compResult(rx, ry)
82+
83+

absbox/tests/benchmark/china/resp/test07.out.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8941,6 +8941,10 @@
89418941
"2025-05-01",
89428942
"No Pool Cashflow/All Account is zero/Not revolving"
89438943
]
8944+
},
8945+
{
8946+
"tag": "WarningMsg",
8947+
"contents": "Oustanding pool cashflow hasn't been collected yetfromList [(PoolConsol,427)]"
89448948
}
89458949
],
89468950
{},

absbox/tests/benchmark/china/resp/test12.out.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91442,6 +91442,10 @@
9144291442
"2049-06-30",
9144391443
"No Pool Cashflow/All Account is zero/Not revolving"
9144491444
]
91445+
},
91446+
{
91447+
"tag": "WarningMsg",
91448+
"contents": "Oustanding pool cashflow hasn't been collected yetfromList [(PoolConsol,138)]"
9144591449
}
9144691450
],
9144791451
{},

absbox/tests/benchmark/china/resp/test24.out.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14478,6 +14478,10 @@
1447814478
"2027-09-30",
1447914479
"No Pool Cashflow/All Account is zero/Not revolving"
1448014480
]
14481+
},
14482+
{
14483+
"tag": "WarningMsg",
14484+
"contents": "Oustanding pool cashflow hasn't been collected yetfromList [(PoolConsol,399)]"
1448114485
}
1448214486
],
1448314487
{},

absbox/tests/benchmark/china/resp/test25.out.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41283,6 +41283,10 @@
4128341283
"2034-10-31",
4128441284
"No Pool Cashflow/All Account is zero/Not revolving"
4128541285
]
41286+
},
41287+
{
41288+
"tag": "WarningMsg",
41289+
"contents": "Oustanding pool cashflow hasn't been collected yetfromList [(PoolConsol,314)]"
4128641290
}
4128741291
],
4128841292
{},

absbox/tests/benchmark/china/test24.py

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -134,52 +134,52 @@
134134
)
135135

136136

137-
if __name__ == '__main__':
138-
from absbox import API
139-
localAPI = API("https://absbox.org/api/latest")
140-
141-
142-
revol_asset = ["Mortgage"
143-
,{"originBalance":220,"originRate":["fix",0.043],"originTerm":48
144-
,"freq":"Monthly","type":"Level","originDate":"2021-07-01"}
145-
,{"currentBalance":220
146-
,"currentRate":0.043
147-
,"remainTerm":36
148-
,"status":"current"}]
149-
150-
r = localAPI.run(德宝天元202301,
151-
runAssump = [
152-
("revolving"
153-
,["constant",revol_asset]
154-
,("Pool",("Mortgage",{"CDR":0.01},None,None,None)
155-
,None
156-
,None))
157-
,("call",{"bondFactor":0.05})
158-
,("report",{"dates":"MonthEnd"})
159-
],
160-
poolAssump = ("Pool",("Mortgage",{"CDR":0.0012},None,None,None)
161-
,None
162-
,None)
163-
,read=True)
164-
165-
# view pool balance changes
166-
r['pool']['flow']['余额'].iloc[2:,].plot.area(rot=45
167-
,title="循环资产池余额变化"
168-
,ylabel="资产池余额"
169-
)
170-
# view balance sheet
171-
bs = r['result']['report']['balanceSheet']
172-
173-
# view capital structure
174-
col_to_keep = [('asset', 'Pool Performing')
175-
,('asset', '现金储备分账户')
176-
,('liability', 'A')
177-
,('liability', '次级')
178-
#,('ratio',"A-OC")
179-
]
180-
start_index = 25
181-
chart = bs.iloc[start_index:50,][col_to_keep].plot.area(rot=45,stacked=True,secondary_y=['OC-A'])
182-
chart.set_title("资产负债结构")
137+
# if __name__ == '__main__':
138+
# from absbox import API
139+
# localAPI = API("https://absbox.org/api/latest")
140+
#
141+
#
142+
# revol_asset = ["Mortgage"
143+
# ,{"originBalance":220,"originRate":["fix",0.043],"originTerm":48
144+
# ,"freq":"Monthly","type":"Level","originDate":"2021-07-01"}
145+
# ,{"currentBalance":220
146+
# ,"currentRate":0.043
147+
# ,"remainTerm":36
148+
# ,"status":"current"}]
149+
#
150+
# r = localAPI.run(德宝天元202301,
151+
# runAssump = [
152+
# ("revolving"
153+
# ,["constant",revol_asset]
154+
# ,("Pool",("Mortgage",{"CDR":0.01},None,None,None)
155+
# ,None
156+
# ,None))
157+
# ,("call",{"bondFactor":0.05})
158+
# ,("report",{"dates":"MonthEnd"})
159+
# ],
160+
# poolAssump = ("Pool",("Mortgage",{"CDR":0.0012},None,None,None)
161+
# ,None
162+
# ,None)
163+
# ,read=True)
164+
#
165+
# # view pool balance changes
166+
# r['pool']['flow']['余额'].iloc[2:,].plot.area(rot=45
167+
# ,title="循环资产池余额变化"
168+
# ,ylabel="资产池余额"
169+
# )
170+
# # view balance sheet
171+
# bs = r['result']['report']['balanceSheet']
172+
#
173+
# # view capital structure
174+
# col_to_keep = [('asset', 'Pool Performing')
175+
# ,('asset', '现金储备分账户')
176+
# ,('liability', 'A')
177+
# ,('liability', '次级')
178+
# #,('ratio',"A-OC")
179+
# ]
180+
# start_index = 25
181+
# chart = bs.iloc[start_index:50,][col_to_keep].plot.area(rot=45,stacked=True,secondary_y=['OC-A'])
182+
# chart.set_title("资产负债结构")
183183

184184

185185
## scenario analysis on revolving
@@ -231,4 +231,4 @@
231231
# ,"次级超额收益"
232232
# ,"原始权益人剩余收益"
233233
# ,"优先级平均期限"])
234-
234+

absbox/tests/regression/test_main.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,9 @@ def test_collect_outstanding(setup_api):
392392
eqDataFrame(complete['pool']['flow']['PoolConsol'], combined)
393393

394394
assert rWithOsPoolFlow['pool_outstanding']['flow']['PoolConsol'].shape == (44, 16), "Outstanding pool cashflow should be non empty"
395-
assert rWithOsPoolFlow['result']['logs'].to_dict(orient="records")[-1]['Comment'] == "Oustanding pool cashflow hasn't been collected yet", "Outstanding pool cashflow should have logs"
395+
assert rWithOsPoolFlow['result']['logs'].to_dict(orient="records")[-1]['Comment'].startswith("Oustanding pool cashflow hasn't been collected yet"), "Outstanding pool cashflow should have logs"
396+
397+
assert complete['result']['logs'] is None , "in a complete run, there shouldn't be oustanding pool warning logs"
396398

397399
@pytest.mark.pool
398400
def test_collect_pool_loanlevel_cashflow(setup_api):
@@ -439,16 +441,8 @@ def test_reports(setup_api):
439441
def test_revolving_01(setup_api):
440442
""" Test revolving pool with collection """
441443
revol_asset = ["Mortgage",{
442-
"originBalance": 1400,
443-
"originRate": ["fix", 0.045],
444-
"originTerm": 30,
445-
"freq": "Monthly",
446-
"type": "Level",
447-
"originDate": "2021-02-01",},
448-
{"currentBalance": 1400,
449-
"currentRate": 0.08,
450-
"remainTerm": 30,
451-
"status": "current",}, ]
444+
"originBalance": 1400, "originRate": ["fix", 0.045], "originTerm": 30, "freq": "Monthly", "type": "Level", "originDate": "2021-02-01",},
445+
{"currentBalance": 1400, "currentRate": 0.08, "remainTerm": 30, "status": "current",}, ]
452446
r = setup_api.run(test05, read=True, runAssump =[("revolving"
453447
,["constant",revol_asset]
454448
,("Pool",("Mortgage",None,None,None,None)

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = absbox
3-
version = 0.46.5
3+
version = 0.50.0
44
description_file = README.md
55
author = xiaoyu, zhang
66
author_email = [email protected]

0 commit comments

Comments
 (0)