Register
Login
Resources
Docs Blog Datasets Glossary Case Studies Tutorials & Webinars
Product
Data Engine LLMs Platform Enterprise
Pricing Explore
Connect to our Discord channel

portfolio_simulating.py 5.1 KB

You have to be logged in to leave a comment. Sign In
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
  1. import numpy as np
  2. from numba import jit
  3. @jit
  4. def make_loan_array(l_type, funded, pmt, month, cfs, rem_princp_array, end_m=None):
  5. '''
  6. it takes 5 months to go from nonpaying to charged_off (90 days late, 120 default, 150 charged off)
  7. 3 l_types:
  8. A) default immediately, take 5 months
  9. B) Fully Prepay between 6-12 months.
  10. C) Fully Prepay/pay between 24-36 months
  11. month: what month the loan was invested in (starts at 0 aka 0 indexed)
  12. end_m is what month the loan willl end relative to month.
  13. '''
  14. # if theoretic:
  15. # add_m = np.random.choice(np.arange(-3,4))
  16. # if l_type == 'A':
  17. # add_cfs = np.zeros(1)
  18. # else:
  19. # if l_type == 'B':
  20. # n_m = 9+add_m+1
  21. # print(n_m)
  22. # elif l_type == 'C':
  23. # n_m = 33+add_m+1
  24. # per = np.arange(n_m) + 1
  25. # add_cfs = np.tile(pmt, (n_m+1))
  26. # rem_princp = (1 - np.ppmt(rate, per, term, funded, 0).sum()).round(2)
  27. # add_cfs[-1] += rem_princp
  28. # add_cfs[0] = funded
  29. # cfs[month:month+len(add_cfs)] += add_cfs
  30. # return cfs
  31. # else:
  32. assert end_m is not None, "Pass the months on books/months recorded as end_m"
  33. # Only 2 l_types 'A' default and 'B' (pre)paid, use end_m
  34. if l_type == 'charged_off':
  35. n_m = end_m
  36. add_cfs = np.array([pmt] * int(n_m - 4))
  37. elif l_type == 'paid':
  38. n_m = end_m
  39. add_cfs = np.array([pmt] * int(n_m+1))
  40. rem_princp = -funded - rem_princp_array[:end_m].sum()
  41. add_cfs[-1] += rem_princp
  42. else:
  43. print('unrecognized loan type')
  44. add_cfs[0] = funded
  45. cfs[month:month+len(add_cfs)] += add_cfs
  46. return cfs
  47. def sim_portfolio(end_d_by_term_stat_grade, sel_loans, ppmt_by_term_rate, n_months=120, n_inv_loans=2000, cfs=None, funded=-1,
  48. verbose=False, longest_winddown=76, wait_for_cont=False):
  49. '''
  50. Simulate a portfolio for INVESTING for n_months. Actual portfolio winddown
  51. may take up to 36 months from last investment date
  52. funded is the loan amount that will be used (e.g. -1 is one dollar funded to loan)
  53. Args:
  54. end_d_by_term_stat_grade: dict pickled from 11_portfolio_simulating.ipynb \
  55. keys are (term, l_stat, grade) and value = tuple(end_m, probability)
  56. sel_loans: loans that you can possibly invest in. Samples from this for (term,l_stat, grade)
  57. funded: should be negative
  58. '''
  59. # limit it to relevant columns
  60. sel_loans = sel_loans[['loan_status', 'grade', 'term', 'int_rate']]
  61. cfs = np.zeros(n_months + longest_winddown) if cfs is None else cfs
  62. end_i = 0
  63. for month in range(n_months):
  64. # end_i keeps track of when you have enough money to reinvest
  65. if month == end_i:
  66. if verbose:
  67. print('month is {0} *************************'.format(month))
  68. # iterate over loans and construct the cfs array
  69. n_inv_loans = int(np.floor(n_inv_loans))
  70. starting_money = abs(n_inv_loans*funded)
  71. #loans = sel_loans.sample(n=n_inv_loans, replace=True, )
  72. #li_tups = [tuple(x) for x in loans[['term', 'loan_status', 'grade', 'int_rate']].values]
  73. #li_tups = [((l.term, l.loan_status, l.grade), l.int_rate) for l in loans.itertuples()]
  74. loans = sel_loans.sample(n=n_inv_loans, replace=True, ).to_dict('records')
  75. li_tups = [((l['term'], l['loan_status'], l['grade']), l['int_rate']) for l in loans]
  76. for l in li_tups:
  77. # pick the end_m
  78. #key, rate = l[:3], l[3]
  79. key, rate = l
  80. ms, probs = end_d_by_term_stat_grade[key]
  81. end_m = int(np.random.choice(ms, replace=True, p=probs))
  82. # key is term, stat, grade
  83. term, stat, grade = key
  84. pmt = np.pmt(rate/12, term, funded, 0)
  85. cfs = make_loan_array(stat, funded, pmt, month, cfs, ppmt_by_term_rate[term, str(round(rate, 4))], end_m=end_m)
  86. if verbose:
  87. print('picking a loan with term: {0}, status: {1}, grade: {2}, and rate: {3}'.format(*key, rate))
  88. print('loan life is {0} ending in month {1}'.format(end_m, end_m+month))
  89. if wait_for_cont:
  90. import ipdb; ipdb.set_trace()
  91. if verbose:
  92. print('invested in {0} loans this month'.format(n_inv_loans))
  93. print('current cfs: {0}'.format(cfs))
  94. print('current IRR: {0}'.format(np.irr(cfs)*12))
  95. print('current unadjusted net money: {0}'.format(np.sum(cfs)))
  96. # calculate which month can reinvest next in
  97. leftover_money = starting_money - (n_inv_loans * abs(funded))
  98. assert leftover_money >= 0, 'somehow negative leftover money'
  99. end_i = month+1
  100. while (leftover_money < abs(funded)) and (end_i < len(cfs)):
  101. leftover_money += cfs[end_i]
  102. end_i += 1
  103. n_inv_loans = abs(leftover_money/funded)
  104. # returns the cfs, the net money made, and the annualized irr
  105. return cfs, np.sum(cfs), np.irr(cfs)*12
Tip!

Press p or to see the previous file or, n or to see the next file

Comments

Loading...