Skip to content

Instantly share code, notes, and snippets.

@nikhilwoodruff
Created August 26, 2021 17:23
Show Gist options
  • Save nikhilwoodruff/4c6320a02c66577757c495a79453213c to your computer and use it in GitHub Desktop.
Save nikhilwoodruff/4c6320a02c66577757c495a79453213c to your computer and use it in GitHub Desktop.
/* PTM+v2 model */
/* inctax.sas module */
/* objective: calculate income tax liabilities */
/* define various output variable arrays */
/* note: variables contained in each array are listed in macro variables appearing in array definitions */
/* e.g. the tivars macro variable appearing in first array definition may list variables ti_ST ti_NS and ti_DV */
/* these macro variable lists are created in the macvars module according to tax parameters specified by user */
/* i.e. they will depend on the number of streams and tax bands, and their names, as set in the tax parameters */
array tis(&PTAX_NSTREAMS.) &tivars.; /* total income by income stream */
array ads(&PTAX_NSTREAMS.) &advars.; /* allowances & deductions allocated by income stream */
array taxincs(&PTAX_NSTREAMS.) &taxincvars.; /* total income after allowances & deductions by income stream */
array taxlim(&PTAX_NBANDS.); /* width of each tax band */
array allow(&PTAX_NSTREAMS.) &allowvars.; /* stream-specific allowances due by income stream */
/* (e.g. allow(2) would be savings allowance due */
array taxinc1(&PTAX_NSTREAMS.,&PTAX_NBANDS.) &incvars1.; /* 2D array: taxable income before stream allowances by (income stream,tax band) */ array taxinc2(&PTAX_NSTREAMS.,&PTAX_NBANDS.) &incvars2.; /* 2D array: taxable income after stream allowances by (income stream,tax band) */
array taxrate(&PTAX_NSTREAMS.,&PTAX_NBANDS.) &ratevars.; /* 2D array: tax rates by (income stream,tax band) */
array taxpre(&PTAX_NSTREAMS.,&PTAX_NBANDS.) &taxprevars.; /* 2D array: liabilities before tax credits by (income stream,tax band) */
array tax(&PTAX_NSTREAMS.,&PTAX_NBANDS.) &taxvars.; /* 2D array: liabilities after tax credits by (income stream,tax band) */
array taxb(&PTAX_NBANDS.) &taxvarsb.; /* liabilities after tax credits by tax band */
array tptax(&PTAX_NBANDS.) &tptaxvars.; /* taxpayers' TOTAL liability recorded against the tax band corresponding to their highest marginal rate */
/* e.g. if tax band 3 is the HR band, then tptax(3) would hold total liabilities of HR taxpayers */
array penchrate(&PTAX_NBANDS.);
/* pensions charge tax rates */
OFFICIAL
/* note: not bothering to name the elements in this array as these variables will not be kept */
/* compute tax at last record for each individual */
if last.sref then do;
/* initialise flags for cases within aged, MCA and univeral tapers */
agetaper = 0;
mcataper = 0;
pataper = 0;
/* initialise total income */
ti = 0;
/* assign elements in the ti by income stream array; and sum total income */
/* note: this code is written in macro format so that %do ... %end macro language can be used (it is not valid outside a macro) the %do ... %end formulation creates a looping macro variable &i. which can then be used in the RHS double
resolution that assigns the income streams */
/* this macro formulation is ONLY required for assignments of this type */
%macro assign();
%do i = 1 %to &PTAX_NSTREAMS.;
tis(&i.) = &&PTAX_STREAMDEF&i.; /* double
resolves to each income stream definition as step through loop */ /*
e.g. when macro variable i = 1, RHS first resolves to &PTAX_STREAMDEF1
which subsequently resolves to its value determined by the corresponding parameter value */
ti = sum(ti,tis(&i.));
%end;
%mend assign;
%assign;
/* create some income variables of interest */
/* note: output variables only - not used in tax calculations */ ppp = tis(1) - INCPROP; /* NSND excluding net income from property */
pdi = tis(2) + tis(3) + INCPROP; /* Investment (savings/dividends) income including net income from property */
/* note: this code clearly assumes that NSND, savings and dividends are income streams 1, 2 & 3 respectively */
/* total deductions */
deds = sum(EMP_SCH_, RAR, STK_RELI, SUPANN, COVNTS, GIFTAID, GIFTPROP, GIFT_INV, MOTHDEDS, DEFICIEN);
/* income after deductions */
iad = max(0,ti-deds);
OFFICIAL
/* assign PAs */
pas = 0;
if TAGE < &PTAX_AGED. then pas = &PTAX_PA.; /* normal PA*/
else if TAGE < &PTAX_SAGED. then pas = &PTAX_APA.; /* aged PA */
else pas = &PTAX_SAPA.;
/* higher aged PA */
/* age-related taper */
if pas > &PTAX_PA. then do;
pas0 = pas;
if iad >= &PTAX_AILT. then pas = max(&PTAX_PA.,ceil(pas-((iad- &PTAX_AILT.)*&PTAX_AILTR.))); /* use ceil to match PTM model */ if pas0 > pas > &PTAX_PA. then agetaper = 1; /* flag cases within aged taper */
end;
/* assign MCA */
/* do it here before variable pas affected by universal PA taper */
/* note: input variable MCAPERC shows proportion of MCA due based on proportion of year married,
but is not a true proportion; has been scaled by factor 1000 for formatting purposes */
mcas = 0;
if MCAPERC > 0 and MAXAGE > =&PTAX_MCA_MA. then do;
/* males (note: MCA taper applies only to males) */
if SEX = 1 then do;
/* compute pas_taper such that PTAX_AILT. + pas_taper
= taper start point for MCA */
pas_taper = 0;
if (TAGE >= &PTAX_AGED. and TAGE < &PTAX_SAGED.) then
pas_taper = (&PTAX_APA.-pas)/&PTAX_AILTR.;
else if TAGE >= &PTAX_SAGED. then pas_taper =
(&PTAX_SAPA.-pas)/&PTAX_AILTR.;
/* apply taper as required */
if MAXAGE >= &PTAX_SAGED. and SEX = 1 then do;
if iad >= &PTAX_AILT. + pas_taper then
mcasdue =
max(&PTAX_MMCA.,&PTAX_MCA_75.-(iad-&PTAX_AILT.-pas_taper)*&PTAX_MCATR.) * (MCAPERC/10000);
else mcasdue = &PTAX_MCA_75. * (MCAPERC/10000);
if 0 < mcasdue < &PTAX_MCA_75. * (MCAPERC/10000)
then mcataper = 1; /* flag cases within MCA taper */
end;
else if (MAXAGE >= &PTAX_AGED. and MAXAGE < &PTAX_SAGED.) and SEX = 1 then do;
if iad >= &PTAX_AILT. + pas_taper then
OFFICIAL
mcasdue =
max(&PTAX_MMCA.,&PTAX_MCA_65.-(iad-&PTAX_AILT.-pas_taper)*&PTAX_MCATR.) * (MCAPERC/10000);
else mcasdue =&PTAX_MCA_65. * (MCAPERC/10000);
if 0 < mcasdue < &PTAX_MCA_65. * (MCAPERC/10000)
then mcataper = 1; /* flag cases within MCA taper */ end;
if MCATYPE = 0 then mcas = round(mcasdue,0.01);
/* No transfer */
else if MCATYPE = 2 then mcas = round(max(0,mcasdue- (&PTAX_MMCA./2)),0.01); /* Half transfer */
else if MCATYPE = 1 then mcas = round(max(0,mcasdue- &PTAX_MMCA.),0.01); /* Full transfer */
if mcas = 0 then mcataper = 0;
end; /* sex = 1 */
/* females */
else if SEX = 2 and MAXAGE >=&PTAX_SAGED. then mcas =
round(&PTAX_MCA_75.*(MCAPERC/10000),0.01);
else if SEX = 2 and MAXAGE >=&PTAX_AGED. then mcas =
round(&PTAX_MCA_65.*(MCAPERC/10000),0.01);
end; /* MCAPERC > 0 and MAXAGE > =&PTAX_MCA_MA. */
/* assign BPA using BPAPERC (note: like MCAPERC this is scaled up by factor 1000 on input dataset) */
if bpaperc > 0 then bpadue = round(&PTAX_BPA.*(BPAPERC/10000),1); else bpadue = 0;
/* PA universal "£100k" taper */
pas0 = pas;
if iad >= &PTAX_PAT. then pas = max(0,pas-int((iad-
&PTAX_PAT.)*&PTAX_PATR.));
if pas0 > pas > 0 then pataper = 1; /* flag cases within PA taper */
/* remove allowances for non-residents */
if NR_NOPA = "Y" then do;
pas = 0;
mcas = 0;
bpadue = 0;
agetaper = 0;
pataper = 0;
mcataper = 0;
end;
/* marriage allowance (transferrable tax allowance) */
/* code assumes that age of both individuals in couple is the same (i.e. so get same PA) */
tta = 0;
OFFICIAL
/* derive HR limit - determines who can receive a PA transfer and can differ for Scottish taxpayers */
if gor_code ne 'S99999999' then brlimit = &&PTAX_LIM&PTAX_BRLIM.; else if gor_code = 'S99999999' then brlimit =
&&PTAX_LIM&PTAX_BRLIM_S.;
/* Bringing this inctax module forward will mean matched data is automatically checked for eligibility before inclusion, even in future base years. */
/* Total number of cases with an increased tta will be the same (or similar, due to randomness) because we are still imputing to hit the same target. */
/* There will simply be a slight increase in the number of imputed cases. */
/* initialise variables needed for marriage allowance */
ma_inc_cond_receive = 0;
ma_inc_cond_give = 0;
ma_flag = 0;
matched_eligible = 0;
/* set criteria for receiving PA, store as binary variable */ if iad-sum(pas,bpadue)<=brlimit then do;
if (TAGE<&PTAX_AGED. and PARTNER_IAD<=&PTAX_PA.)
or (TAGE>=&PTAX_AGED. and TAGE<&PTAX_SAGED. and
PARTNER_IAD<=&PTAX_APA.)
or (TAGE>=&PTAX_SAGED. and
PARTNER_IAD<=&PTAX_SAPA.)
then do;
ma_inc_cond_receive = 1;
end;
end;
/* set criteria for giving PA, store as binary variable */
if iad<=max(&PTAX_PA.,pas) and PARTNER_IAD<=&PTAX_PA.+brlimit then do;
if (TAGE<&PTAX_AGED. and PARTNER_IAD>&PTAX_PA.)
or (TAGE>=&PTAX_AGED. and TAGE<&PTAX_SAGED. and
PARTNER_IAD>&PTAX_APA.)
or (TAGE>=&PTAX_SAGED. and PARTNER_IAD>&PTAX_SAPA.)
then do;
ma_inc_cond_give = 1;
end;
end;
/* high earner recieving PA transfer */
/* check if flag exists and the case meets criteria. If so, increase TTA by the transferrable portion of PA */
if iad-sum(pas,bpadue)<=brlimit and m_2018=1 and &TAKEUP_TTA > 0 then do;
OFFICIAL
/* amount transferred = normal PA * transferrable
proportion */
tta=ceil((&PTAX_PA.*&PTAX_TTA_PAPROP.)/10)*10; /*
multiplied and divided by 10 to ensure that tta rounds to nearest 10 */ pas=pas+tta;
ma_flag = 1;
matched_eligible=1;
end;
/* low earner transferring PA */
if iad<=max(&PTAX_PA.,pas) and m_2018 = 0 and n_2018=1 and &TAKEUP_TTA > 0 then do;
tta=floor((&PTAX_PA.*-&PTAX_TTA_PAPROP.)/10)*10; /*
multiplied and divided by 10 to ensure that tta rounds to nearest 10 */ pas=max(0,pas+tta);
ma_flag = 2;
matched_eligible = 2;
end;
/* now impute to those left who are eligible who did not have the flag */
/* */
if ma_inc_cond_receive = 1 and m_2018 = 0 and
TTA_MARRIED_HIGH_EARNER=1 and ranuni(2222)<=&TAKEUP_TTA. then do;
/* amount transferred = normal PA * transferrable
proportion */
tta=ceil((&PTAX_PA.*&PTAX_TTA_PAPROP.)/10)*10; /*
multiplied and divided by 10 to ensure that tta rounds to nearest 10 */ pas=pas+tta;
ma_flag = 1;
end;
if ma_inc_cond_give = 1 and n_2018 = 0 and TTA_MARRIED_LOW_EARNER=1 and ranuni(2222)<=&TAKEUP_TTA. then do;
tta=floor((&PTAX_PA.*-&PTAX_TTA_PAPROP.)/10)*10; /*
multiplied and divided by 10 to ensure that tta rounds to nearest 10 */ pas=max(0,pas+tta);
ma_flag = 2;
end;
/* total allowances due at marginal rate ('full') relief */ hpasfull = sum(pas,bpadue);
/* total allowances and deductions */
tot_ad = sum(hpasfull,deds);
/* taxable income */
taxinc = max(0,ti-tot_ad);
/* assign elements in the allowances/deductions and taxable income by income stream arrays */
/* initialise temporary variable to keep track of
allowances/deductions remaining */
OFFICIAL
/* note: this loop can be written in standard code as standard loop counter i works, macro variable &i. not needed */
tempad = tot_ad;
do i = 1 to &PTAX_NSTREAMS.;
ads(i) = min(tis(i),tempad);
tempad = tempad - ads(i);
taxincs(i) = tis(i) - ads(i);
end;
/* assign elements in the taxlim by tax band array */
/* note: this array is initialised with the 'width' of each tax band, i.e. upper limit of band minus
the upper limit of the previous band (according to parameter values); but it is later amended during
tax calculations to show width of each band remaining after incomes have been allocated from bottom up */
%macro assign();
/* temporary variable to record upper limit of previous tax band, initialise at zero */
previous = 0;
%do i = 1 %to &PTAX_NBANDS.;
/* note: double resolution of macro variable needed in
assignment below, so this code written in macro format */
lim = &&PTAX_LIM&i.;
taxlim(&i.) = lim - previous; /* gives effective width of each tax band */
previous = lim;
%end;
%mend assign;
%assign;
/* assign elements in 2D arrays for taxable income, by (income stream,tax band) */
/* note: computing taxable income before stream specific allowances (e.g. savings and dividends allowances) */
/* initialise marginal rate indicator (needed below to assign correct stream specific allowances */
taxtype = 0;
/* step through income streams in 'stack' order */
do s = 1 to &PTAX_NSTREAMS.;
/* set income equal to taxable income for this stream */
tempinc = taxincs(s);
/* for each stream, step through tax bands */
do b = 1 to &PTAX_NBANDS.;
/* assign taxable income (before stream allowances) for
this (stream,band) */
taxinc1(s,b) = min(tempinc,taxlim(b));
/* update marginal rate indicator */
if taxinc1(s,b) > 0 and b > taxtype then taxtype = b;
tempinc = tempinc - taxinc1(s,b); /* update
income remaining for this stream */
taxlim(b) = taxlim(b) - taxinc1(s,b); /* update width of band remaining */
end;
OFFICIAL
end;
/* assign stream specific allowances to taxable income for each stream from bottom tax band upwards */
/* then compute tax before tax credits */
/* tax credits means all tax reducers granted at fixed relief rate and computed later (e.g. MCA) */
/* however, we do take account of dividends tax credit in calculations of tax before tax credits below */
/* this prevents tax credits later being allocated against dividends tax liabilities that are covered by
dividends tax credit when this is not zero */
tottaxpre = 0;
%macro assign();
/* step through income streams in 'stack' order */
%do s = 1 %to &PTAX_NSTREAMS.;
/* assign stream allowance due according to taxpayer
marginal rate */
allow(&s.) = 0;
%do b = 1 %to &PTAX_NBANDS.;
/* note: double resolution of macro variable needed
in assignment below (and later),
so this code written in macro format */
if &b. <= taxtype then allow(&s.) =
&&PTAX_ALLOW_S&s._B&b.;
%end;
/* create temporary variable to keep track of stream
allowance due during allocation to tax bands */
tempallow = allow(&s.);
%do b = 1 %to &PTAX_NBANDS.;
/* calculate tax rate applicable to this
(stream,band) */
if gor_code NE 'S99999999' and gor_code NE
'W99999999' then taxrate(&s.,&b.) = &&PTAX_RATE_S&s._B&b.; /* applicable tax rate rUK */
if gor_code = 'S99999999' then taxrate(&s.,&b.) =
&&PTAX_RATE_S&s._B&b._S; /* applicable tax rate Scotland only - note this can be chaged to use the flag once in the dataset*/
if gor_code = 'W99999999' then taxrate(&s.,&b.) =
&&PTAX_RATE_S&s._B&b._W; /* applicable tax rate Wales only - note this can be chaged to use the flag once in the dataset */ /* if stream = dividends stream use after dividend
TC rate */
if &s. = &PTAX_DIVSTREAM. then taxrate(&s.,&b.) =
max(0,taxrate(&s.,&b.)- &PTAX_DTCR.);
/* only allocate stream allowance due to this band
if a tax liability would arise */
/* e.g. prevents savings allowance being allocated
to LR band if LR savings tax rate is zero */
if taxrate(&s.,&b.) > 0 then allowused =
min(tempallow,taxinc1(&s.,&b.));
else allowused = 0;
/* assign taxable income (after stream allowances)
for this (stream,band) */
OFFICIAL
taxinc2(&s.,&b.) = taxinc1(&s.,&b.) - allowused;
tempallow = tempallow - allowused;
/* update stream allowance remaining */
taxpre(&s.,&b.) =
taxinc2(&s.,&b.)*taxrate(&s.,&b.); /* assign tax (before tax credits) for this (stream,band) */
taxpres(&s.) =
round(sum(taxpres(&s.),taxpre(&s.,&b.)),0.01); /* assign tax (before tax credits) for this (stream only) - new for NSND tax credit */ tottaxpre = sum(tottaxpre,taxpre(&s.,&b.));
/* update total tax */
%end;
%end;
%mend assign;
%assign;
/*LLIR_resttricted calc */
/*For 18-19 SPI, 50% of LLIR is now given at 20% tax credit. */ /*Modify below to reflect split in policy */
/*LLIR_AMT_c = LLIR_AMT_50c;
LLIR_RES_AMT_TOT_0c=0;
LLIR_RES_AMT_TOT_50c=
max(0,LLIR_RES_AMT_50_1+LLIR_RES_AMT_50_2+LLIR_RES_AMT_50_3+LLIR_RES_AMT_50 _4+LLIR_RES_AMT_50_5+LLIR_RES_AMT_50_6+LLIR_RES_AMT_50_7); LLIR_RES_AMT_TOT = LLIR_RES_AMT_TOT_50c;*/
/* The above is only needed in the SPI base year, is calculated in step 2, stage 4 of the IPS process for projections*/
LLIR_RESTRICT_INC=FLOOR(MAX(0,TI_ST-PAS-GIFTPROP-GIFT_INV-MOTHDEDS)); LLIR_RESTRICT_AMT_TOT=CEIL(MIN(LLIR_RESTRICT_INC,LLIR_RES_AMT_TOT));
/* tax relief value of tax credits/reducers */
taxcr =
sum(MCAS*&PTAX_MCAR.,CITR_TOT*&PTAX_CITR.,ALMY_NEW*&PTAX_ALMY.,VENT_CAP*&PT AX_VENT.,EIS_REL_*&PTAX_EIS.,SEED_EIS*&PTAX_SEIS.,FI_TAXCR,SINVRTOT*&PTAX_S ITR.,LLIR_RESTRICT_AMT_TOT*&PTAX_LLIR.);
/* total tax after tax credits/reducers */
tottaxpost = max(0,tottaxpre-taxcr);
/* initialise total tax after tax credits */
tottax = 0;
/* re-initialise marginal rate indicator */
/* will be re-computed below with respect to tax due (not taxable income as above) */
/* e.g. if taxpayer has taxable income in HR band, but tax credits/reducers cover the HR tax liability due
then the taxpayer will be assigned as BR */
taxtype = 0;
/* marginal rate on earnings */
mtr_tottax = 0;
/* assign 2D elements in array for tax liabilities (after tax credits) by (income stream,band);
and sum tottax and assign marginal rate indicator */
do s = 1 to &PTAX_NSTREAMS.;
OFFICIAL
do b = 1 to &PTAX_NBANDS.;
/* allocate tax credits relief proportionately by
(stream,band) */
if tottaxpre > 0 and s = 1 then tax(s,b) =
round(((taxpre(s,b)/tottaxpre)*tottaxpost),0.01);
else if tottaxpre > 0 and s > 1 then tax(s,b) =
round((taxpre(s,b)/tottaxpre)*tottaxpost,0.01);
else tax(s,b) = 0;
/* if stream = dividends stream and user has specified
liabilities basis via %ptmrun option #divtc{false},
add dividend tax covered by the dividend tax credit
*/
if s = &PTAX_DIVSTREAM. and &divtc. = 0 then tax(s,b) =
round(sum(tax(s,b),taxinc2(s,b)*&PTAX_DTCR.),0.01);
/* update total tax and tax in this tax band variables */
tottax = round(sum(tottax,tax(s,b)),0.01);
taxb(b) = round(sum(taxb(b),tax(s,b)),0.01);
/* update marginal rate indicator */
if tax(s,b) > 0 and b > taxtype then taxtype = b;
/* assign MTR variable (for earnings) */
if s = 1 and tax(s,b) > 0 then mtr_tottax =
100*taxrate(1,b);
end;
end;
/* revised pension tax code */
/* charge applies to any excess of pension savings over the annual limit (shown in input variable PSAV_XS) */
/* in this implementation, PTM+ 'stacks' excess pensions saving on top of other taxable incomes
in order to determine the tax rate that will apply */
%macro tax_pench();
/* initialise tax on excess pension savings at zero */
tax_pench = 0;
if PSAV_XS > 0 then do;
temp_psav_xs = PSAV_XS;
/* step through tax bands */
%do b = 1 %to &PTAX_NBANDS.;
/* allocate excess pension savings to bands from
bottom up */
/* note: at this point, array taxlim holds
remaining widths of bands available
following earlier allocation of other taxable
incomes */
psav_xs_in_band = min(temp_psav_xs,taxlim(&b.));
temp_psav_xs = temp_psav_xs - psav_xs_in_band;
/* determine tax rate to be applied */
/* note: code assumes UK NSND rates are relevant,
and further assumes these are stream one */
/* uses UK NSND basic rate for tax bands below BRL,
*/
if &b. < &PTAX_BRLIM. then tax_pench_rate =
&&PTAX_RATE_S1_B&PTAX_BRLIM.;
else tax_pench_rate = &&PTAX_RATE_S1_B&b.;
/* compute tax applying to excess savings in this
tax band and add to relevant output variables */
tax_psav_xs_in_band = psav_xs_in_band *
tax_pench_rate;
tax_pench = tax_pench + tax_psav_xs_in_band;
tax(1,&b.) = tax(1,&b.) + tax_psav_xs_in_band;
OFFICIAL
taxb(&b.) = taxb(&b.) + tax_psav_xs_in_band;
%end;
end;
/* add excess pensions saving charge to tottax */
tottax = tottax + tax_pench;
%mend tax_pench;
%tax_pench;
/* assign elements in the tptax by tax band array */
do i = 1 to &PTAX_NBANDS.;
if taxtype = i then tptax(i) = tottax;
else tptax(i) = 0;
end;
/* adjust MTR variable for tapering effects */
if agetaper = 1 then mtr_tottax = mtr_tottax * (1+&PTAX_AILTR.); if pataper = 1 then mtr_tottax = mtr_tottax * (1+&PTAX_PATR.);
if mcataper = 1 then mtr_tottax = mtr_tottax +
100*&PTAX_MCAR.*&PTAX_MCATR.;
/* set PTM/NS marginal rate indicator to zero */
/* assigned in mrgcode module called next */
mrg_code = " ";
/* ## HICB from 2012-13 Jan 2013 (Does not affect marginal rate) * Must be placed at end of INCTAX Module ## */
%macro HICB();
if iad > &PTAX_HICB_LIMIT. then do;
if CB_CHRGE > 0 then do;
NO_STEPS = INT((iad -
&PTAX_HICB_LIMIT.)/&PTAX_HICB_STEP.);
HICB_RATE = MIN(1, &PTAX_HICB_STEP_RATE. * NO_STEPS);
TAX_HICB = CB_CHRGE * HICB_RATE;
/* tottax = tottax + TAX_HICB; DE-ACTIVATED BUT LEFT SO CAN SWITCH BACK IN */
end;
end;
%mend HICB;
%HICB;
end; /* last.sref */
OFFICIAL
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment