Torres1980's picture
Add synthetic training, test, and review-feedback data
2dc2b68 verified
Raw History Blame Contribute Delete
12.6 kB
"""
Synthetic statement line items for alternatives, structured notes and insurance.
Each row is the kind of free-text description that arrives from GP capital account
statements, administrator notices, custodian activity files and carrier statements,
labeled with a standardized transaction type.
To measure generalization honestly, the test split uses fund sponsors, fund names
and phrasings that never appear in training.
Run: python data/generate.py
"""
import csv
import random
from pathlib import Path
random.seed(42)
OUT = Path(__file__).parent
LABELS = [
"capital_call", "distribution_income", "distribution_return_of_capital",
"distribution_capital_gain", "recallable_distribution", "management_fee",
"incentive_fee", "fund_expense", "subscription", "redemption",
"insurance_premium", "policy_charge", "policy_loan", "withdrawal_surrender",
"index_credit", "note_coupon", "note_call_maturity", "valuation_update",
]
SPONSORS_TRAIN = ["Summit Ridge", "Harborview", "Granite", "Meridian", "Wasatch", "Keystone", "Northbeam",
"Cascade", "Bonneville", "Timberline", "Redwood", "Ironbridge", "Silverlake Trail",
"Oakmont", "Bluewater", "Highline", "Stonegate", "Pinecrest", "Copperleaf", "Westfield"]
SPONSORS_TEST = ["Lakeshore", "Aspen Hollow", "Driftwood", "Kingsley", "Marrow Creek", "Halcyon", "Tidewater"]
STRATS = ["Growth Fund", "Buyout Partners", "Direct Lending Fund", "Senior Credit Fund", "Multifamily Fund",
"Infrastructure Fund", "Opportunity Zone Fund", "Co-Invest", "Secondaries Fund", "Venture Partners",
"Real Estate Income Trust", "Private Credit Interval Fund", "Multi-Strategy Ltd", "Energy Transition Fund"]
ROMAN = ["I", "II", "III", "IV", "V", "VI", "VII", "2019", "2021", "2023"]
SUFFIX = ["", " LP", " L.P.", ", L.P.", " Fund LP", " Feeder LP", " (Offshore) Ltd", " Onshore LP"]
CARRIERS_TRAIN = ["Pinnacle Life", "Evergreen Mutual", "Atlas Annuity", "Sterling Life & Annuity", "Harbor Mutual"]
CARRIERS_TEST = ["Northstar Life", "Crestline Annuity"]
PRODUCTS = ["PPLI Policy", "PPVA Contract", "Fixed Indexed Annuity", "RILA Contract", "VUL Policy"]
ISSUERS_TRAIN = ["Global Bank AG", "Northern Securities", "Atlantic Capital Markets", "Continental Bank"]
ISSUERS_TEST = ["Pacific Rim Finance"]
UNDERLYING = ["S&P 500", "Russell 2000", "Nasdaq-100", "EURO STOXX 50", "SPX/RTY worst-of", "MSCI EAFE"]
MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
def fund(test):
s = random.choice(SPONSORS_TEST if test else SPONSORS_TRAIN)
name = f"{s} {random.choice(STRATS)} {random.choice(ROMAN)}{random.choice(SUFFIX)}"
return random.choice([name, name.upper(), name.replace("Fund", "Fd").replace("Partners", "Ptnrs")])
def policy(test):
c = random.choice(CARRIERS_TEST if test else CARRIERS_TRAIN)
return f"{c} {random.choice(PRODUCTS)} #{random.randint(10000, 99999)}"
def note(test):
i = random.choice(ISSUERS_TEST if test else ISSUERS_TRAIN)
kind = random.choice(["Autocallable", "Buffered", "Barrier", "Contingent Income", "Market-Linked CD"])
return f"{i} {kind} Note on {random.choice(UNDERLYING)} due {random.randint(2026, 2032)} CUSIP {random.randint(10000, 99999)}X{random.randint(10, 99)}"
def amt():
v = random.choice([random.uniform(500, 9999), random.uniform(10000, 250000), random.uniform(250000, 2500000)])
return random.choice([f"${v:,.2f}", f"{v:,.2f}", f"USD {v:,.2f}", f"{v:.2f}", ""])
def dt():
m, d, y = random.randint(1, 12), random.randint(1, 28), random.choice([2024, 2025, 2026])
return random.choice([f"{m:02d}/{d:02d}/{y}", f"{y}-{m:02d}-{d:02d}", f"{MONTHS[m-1]} {d}, {y}", f"Q{(m-1)//3+1} {y}", ""])
def q():
return random.choice([f"Q{random.randint(1, 4)} {random.choice([2024, 2025, 2026])}", f"{random.choice(MONTHS)} {random.choice([2025, 2026])}", "period end", ""])
# Templates: (train_phrasings, test_only_phrasings). {f}=fund, {p}=policy, {n}=note, {a}=amount, {d}=date, {q}=period, {k}=call number
T = {
"capital_call": (
["Capital Call #{k} - {f} {a}", "CAP CALL {f} DUE {d}", "Contribution - {f} - Call Notice {k}", "{f}: capital contribution {a}",
"Drawdown notice #{k} {f}", "INVESTOR CONTRIBUTION {f} {a}", "Capital call for investments - {f}", "CONTRIB {f} CALL {k}",
"{f} - Call {k} (investments & fees) {a}", "Funding request {f} due {d}"],
["Draw-down #{k} {f} payable {d}", "Paid-in capital {f} {a}", "Commitment drawn - {f}"]),
"distribution_income": (
["Distribution - Income - {f} {a}", "DIST INC {f}", "Income distribution {q} {f}", "{f} interest income distribution {a}",
"Dividend distribution - {f}", "INCOME DISTR {f} {d}", "Net investment income distribution {f}", "{f} - distribution (ordinary income)"],
["Yield distribution {f} {a}", "Current income paid - {f}", "{f} quarterly income payout"]),
"distribution_return_of_capital": (
["Distribution - Return of Capital - {f} {a}", "DIST ROC {f}", "Return of capital {f} {d}", "{f} ROC distribution {a}",
"RETURN OF CAPITAL DISTR {f}", "Capital returned - {f} - realization", "{f} distribution (return of contributed capital)"],
["Cost basis returned {f} {a}", "Non-taxable return of capital - {f}", "Capital repayment distribution {f}"]),
"distribution_capital_gain": (
["Distribution - Realized Gain - {f} {a}", "DIST LTCG {f}", "Capital gain distribution {f} {d}", "{f} gain on sale distribution",
"Realized gain distr - {f} - exit proceeds", "LONG TERM CAP GAIN DIST {f} {a}", "{f} distribution of realized gains"],
["Exit gain paid {f} {a}", "Profit distribution on realization - {f}", "{f} - proceeds in excess of cost"]),
"recallable_distribution": (
["Recallable distribution - {f} {a}", "DIST RECALLABLE {f}", "{f} recallable return of capital {d}", "Temporary distribution (subject to recall) {f}",
"RECALL DIST {f} {a}", "{f} - distribution, recallable per LPA"],
["Distribution subject to recycling {f}", "Reinvestable distribution {f} {a}"]),
"management_fee": (
["Management fee {q} - {f} {a}", "MGMT FEE {f}", "{f} management fee (outside commitment) {a}", "Mgmt fee {q} {f}",
"Advisory fee - {f} - {q}", "MANAGEMENT FEES {f} {d}", "{f} - management fee offset applied"],
["Quarterly sponsor fee {f} {a}", "Base fee {q} - {f}", "GP fee {f} {q}"]),
"incentive_fee": (
["Carried interest - {f} {a}", "INCENTIVE ALLOCATION {f}", "{f} performance fee {q}", "Carry paid to GP - {f}",
"INCENT FEE {f} {a}", "Incentive fee crystallization {f} {d}", "{f} - general partner carried interest"],
["Promote paid {f} {a}", "Performance allocation to manager - {f}"]),
"fund_expense": (
["Fund expenses {q} - {f} {a}", "ORG EXPENSES {f}", "{f} partnership expenses {a}", "Admin & audit expense - {f}",
"Organizational costs {f} {d}", "FUND EXP {f} {q}", "{f} - legal and fund administration expenses"],
["Operating expense allocation {f} {a}", "Partnership costs - {f} {q}", "Audit/tax prep charge {f}"]),
"subscription": (
["Subscription - {f} {a}", "SUB {f} {d}", "Initial subscription {f}", "{f} additional subscription {a}",
"Purchase - {f} - subscription accepted", "SUBSCR {f}", "{f} subscription settled {d}"],
["Buy-in to {f} {a}", "Shares issued - {f} {d}", "Initial investment {f}"]),
"redemption": (
["Redemption - {f} {a}", "REDEMPTION {f} {d}", "{f} partial redemption {a}", "Tender offer proceeds - {f}",
"REDEEM {f} {q}", "{f} full redemption paid", "Repurchase proceeds {f} {a}"],
["Withdrawal of shares {f} {a}", "Liquidity window payout - {f}", "Share repurchase {f} {q}"]),
"insurance_premium": (
["Premium payment - {p} {a}", "PREM PMT {p}", "{p} annual premium {d}", "Planned premium {p} {a}",
"Initial premium - {p}", "PREMIUM {p} {q}", "{p} - additional premium deposit {a}", "Purchase payment {p}"],
["Contract deposit {p} {a}", "Policy funding payment - {p}", "Single premium paid {p}"]),
"policy_charge": (
["Cost of insurance charge - {p} {a}", "COI {p} {q}", "{p} M&E charge {a}", "Policy admin fee {p}",
"MONTHLY DEDUCTION {p}", "{p} - premium load / DAC tax", "Rider charge {p} {a}", "Mortality & expense fee {p} {q}"],
["Insurance cost deduction {p} {a}", "Contract maintenance fee {p}", "Monthly policy charges - {p}"]),
"policy_loan": (
["Policy loan - {p} {a}", "LOAN ADVANCE {p}", "{p} loan disbursement {d}", "Loan repayment - {p} {a}",
"POLICY LOAN INT {p}", "{p} - loan interest charged {q}"],
["Borrowing against cash value {p} {a}", "Loan proceeds paid {p}"]),
"withdrawal_surrender": (
["Partial withdrawal - {p} {a}", "SURRENDER {p}", "{p} full surrender {d}", "Free withdrawal {p} {a}",
"PARTIAL SURR {p}", "{p} - surrender charge applied", "Required minimum distribution - {p} {a}", "Systematic withdrawal {p}"],
["Cash-out of contract {p} {a}", "Contract termination proceeds {p}", "Annuity payout {p} {q}"]),
"index_credit": (
["Index credit - {p} {a}", "INDEX CREDIT {p} {d}", "{p} annual point-to-point credit {a}", "Interest credited - {p}",
"Cap rate credit {p} {q}", "{p} - segment maturity credit", "Buffer segment credit {p} {a}"],
["Crediting strategy gain {p} {a}", "Indexed interest posted {p}", "Segment term credit {p} {q}"]),
"note_coupon": (
["Coupon payment - {n} {a}", "CPN {n}", "Contingent coupon paid {n} {d}", "Interest payment {n}",
"COUPON {n} {a}", "{n} - periodic coupon"],
["Income observation paid {n} {a}", "Fixed coupon credited {n}"]),
"note_call_maturity": (
["Autocall redemption - {n} {a}", "CALLED {n} {d}", "Maturity proceeds - {n}", "{n} called at par plus coupon",
"MATURITY {n} {a}", "Early redemption (autocall) {n}", "Principal repayment at maturity {n}"],
["Note terminated early {n} {a}", "Final settlement {n} {d}"]),
"valuation_update": (
["NAV statement {q} - {f} {a}", "Capital account balance {q} {f}", "{f} quarterly valuation {a}", "Market value update {p} {d}",
"ENDING CAPITAL {f} {q}", "Contract value as of {d} {p}", "{f} - estimated NAV {q}", "Accumulation value {p} {a}"],
["Partner's capital at {q} - {f}", "Statement value {p} {a}", "Reported fair value {f} {q}"]),
}
def noise(s):
r = random.random()
if r < 0.15:
s = s.upper()
elif r < 0.22:
s = s.lower()
if random.random() < 0.08: # small typo
i = random.randrange(len(s))
s = s[:i] + s[i + 1:]
if random.random() < 0.15:
s = random.choice(["WIRE IN ", "WIRE OUT ", "ACH ", "JNL ", "ACTIVITY: ", "REF#" + str(random.randint(100000, 999999)) + " "]) + s
return " ".join(s.split())
def render(tpl, test):
return noise(tpl.format(f=fund(test), p=policy(test), n=note(test), a=amt(), d=dt(), q=q(), k=random.randint(1, 14)))
def build(n_train=700, n_test=150):
train, test = [], []
for label in LABELS:
tr_t, te_t = T[label]
for _ in range(n_train):
train.append({"text": render(random.choice(tr_t), False), "label": label})
for _ in range(n_test):
# Test: unseen sponsors/carriers/issuers always; held-out phrasings half the time.
unseen = random.random() < 0.5
tpl = random.choice(te_t) if unseen else random.choice(tr_t)
test.append({"text": render(tpl, True), "label": label,
"condition": "unseen_phrasing" if unseen else "unseen_entities"})
# Reviewer feedback: 10 items per label in the phrasings the model has not seen, labeled by
# a person during exception review. Uses training-side entities, so test entities stay unseen.
feedback = [{"text": render(random.choice(T[label][1]), False), "label": label}
for label in LABELS for _ in range(10)]
random.shuffle(train)
random.shuffle(test)
random.shuffle(feedback)
return train, test, feedback
def write(name, rows):
with open(OUT / name, "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
w.writeheader()
w.writerows(rows)
if __name__ == "__main__":
tr, te, fb = build()
write("train.csv", tr)
write("test.csv", te)
write("review_feedback.csv", fb)
print(f"{len(tr)} train, {len(te)} test, {len(fb)} review feedback, {len(LABELS)} labels")