1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-09 15:35:22 +02:00

rollback more stuff

This commit is contained in:
Tyler Myracle 2024-01-19 19:34:34 -06:00
parent 18562477bf
commit 187b84e1c5
4 changed files with 271 additions and 262 deletions

View file

@ -99,21 +99,7 @@ export async function createTestInvestmentAccount(
(s) => s.date === it.date && s.ticker === it.ticker (s) => s.date === it.date && s.ticker === it.ticker
)?.price )?.price
function getTransactionCategory(type: string) { const isCashFlow = it.type === 'DEPOSIT' || it.type === 'WITHDRAW'
switch (type) {
case 'BUY':
return 'buy'
case 'SELL':
return 'sell'
case 'DIVIDEND':
return 'dividend'
case 'DEPOSIT':
case 'WITHDRAW':
return 'transfer'
default:
return undefined
}
}
return { return {
securityId: securities.find((s) => it.ticker === s.symbol)?.id, securityId: securities.find((s) => it.ticker === s.symbol)?.id,
@ -122,7 +108,26 @@ export async function createTestInvestmentAccount(
amount: price ? new Prisma.Decimal(price).times(it.qty) : it.qty, amount: price ? new Prisma.Decimal(price).times(it.qty) : it.qty,
quantity: price ? it.qty : 0, quantity: price ? it.qty : 0,
price: price ?? 0, price: price ?? 0,
category: getTransactionCategory(it.type), plaidType:
isCashFlow || it.type === 'DIVIDEND'
? 'cash'
: it.type === 'BUY'
? 'buy'
: it.type === 'SELL'
? 'sell'
: undefined,
plaidSubtype:
it.type === 'DEPOSIT'
? 'deposit'
: it.type === 'WITHDRAW'
? 'withdrawal'
: it.type === 'DIVIDEND'
? 'dividend'
: it.type === 'BUY'
? 'buy'
: it.type === 'SELL'
? 'sell'
: undefined,
} }
}), }),
}, },

View file

@ -70,7 +70,8 @@ export class InvestmentTransactionBalanceSyncStrategy extends BalanceSyncStrateg
it.account_id = ${pAccountId} it.account_id = ${pAccountId}
AND it.date BETWEEN ${pStart} AND now() AND it.date BETWEEN ${pStart} AND now()
AND ( -- filter for transactions that modify a position AND ( -- filter for transactions that modify a position
it.category IN ('buy', 'sell', 'transfer') it.plaid_type IN ('buy', 'sell', 'transfer')
OR it.finicity_transaction_id IS NOT NULL
) )
GROUP BY GROUP BY
1, 2 1, 2

View file

@ -117,33 +117,33 @@ export class AccountQueryService implements IAccountQueryService {
} }
>( >(
sql` sql`
SELECT SELECT
h.id, h.id,
h.security_id, h.security_id,
s.name, s.name,
s.symbol, s.symbol,
s.shares_per_contract, s.shares_per_contract,
he.quantity, he.quantity,
he.value, he.value,
he.cost_basis, he.cost_basis,
h.cost_basis_user, h.cost_basis_user,
h.cost_basis_provider, h.cost_basis_provider,
he.cost_basis_per_share, he.cost_basis_per_share,
he.price, he.price,
he.price_prev, he.price_prev,
he.excluded he.excluded
FROM FROM
holdings_enriched he holdings_enriched he
INNER JOIN security s ON s.id = he.security_id INNER JOIN security s ON s.id = he.security_id
INNER JOIN holding h ON h.id = he.id INNER JOIN holding h ON h.id = he.id
WHERE WHERE
he.account_id = ${accountId} he.account_id = ${accountId}
ORDER BY ORDER BY
he.excluded ASC, he.excluded ASC,
he.value DESC he.value DESC
OFFSET ${page * pageSize} OFFSET ${page * pageSize}
LIMIT ${pageSize}; LIMIT ${pageSize};
` `
) )
return rows return rows
@ -158,53 +158,53 @@ export class AccountQueryService implements IAccountQueryService {
const { rows } = await this.pg.pool.query<ValuationTrend>( const { rows } = await this.pg.pool.query<ValuationTrend>(
sql` sql`
WITH valuation_trends AS ( WITH valuation_trends AS (
SELECT
date,
COALESCE(interpolated::numeric, filled) AS amount
FROM (
SELECT
time_bucket_gapfill('1d', v.date) AS date,
interpolate(avg(v.amount)) AS interpolated,
locf(avg(v.amount)) AS filled
FROM
valuation v
WHERE
v.account_id = ${accountId}
AND v.date BETWEEN ${pStart} AND ${pEnd}
GROUP BY
1
) valuations_gapfilled
WHERE
to_char(date, 'MM-DD') = '01-01'
), valuations_combined AS (
SELECT
COALESCE(v.date, vt.date) AS date,
COALESCE(v.amount, vt.amount) AS amount,
v.id AS valuation_id
FROM
(SELECT * FROM valuation WHERE account_id = ${accountId}) v
FULL OUTER JOIN valuation_trends vt ON vt.date = v.date
)
SELECT SELECT
v.date, date,
v.amount, COALESCE(interpolated::numeric, filled) AS amount
v.valuation_id,
v.amount - v.prev_amount AS period_change,
ROUND((v.amount - v.prev_amount)::numeric / NULLIF(v.prev_amount, 0), 4) AS period_change_pct,
v.amount - v.first_amount AS total_change,
ROUND((v.amount - v.first_amount)::numeric / NULLIF(v.first_amount, 0), 4) AS total_change_pct
FROM ( FROM (
SELECT SELECT
*, time_bucket_gapfill('1d', v.date) AS date,
LAG(amount, 1) OVER (ORDER BY date ASC) AS prev_amount, interpolate(avg(v.amount)) AS interpolated,
(SELECT amount FROM valuations_combined ORDER BY date ASC LIMIT 1) AS first_amount locf(avg(v.amount)) AS filled
FROM FROM
valuations_combined valuation v
) v WHERE
ORDER BY v.account_id = ${accountId}
v.date ASC AND v.date BETWEEN ${pStart} AND ${pEnd}
` GROUP BY
1
) valuations_gapfilled
WHERE
to_char(date, 'MM-DD') = '01-01'
), valuations_combined AS (
SELECT
COALESCE(v.date, vt.date) AS date,
COALESCE(v.amount, vt.amount) AS amount,
v.id AS valuation_id
FROM
(SELECT * FROM valuation WHERE account_id = ${accountId}) v
FULL OUTER JOIN valuation_trends vt ON vt.date = v.date
)
SELECT
v.date,
v.amount,
v.valuation_id,
v.amount - v.prev_amount AS period_change,
ROUND((v.amount - v.prev_amount)::numeric / NULLIF(v.prev_amount, 0), 4) AS period_change_pct,
v.amount - v.first_amount AS total_change,
ROUND((v.amount - v.first_amount)::numeric / NULLIF(v.first_amount, 0), 4) AS total_change_pct
FROM (
SELECT
*,
LAG(amount, 1) OVER (ORDER BY date ASC) AS prev_amount,
(SELECT amount FROM valuations_combined ORDER BY date ASC LIMIT 1) AS first_amount
FROM
valuations_combined
) v
ORDER BY
v.date ASC
`
) )
return rows return rows
@ -220,77 +220,80 @@ export class AccountQueryService implements IAccountQueryService {
const { rows } = await this.pg.pool.query<ReturnSeries>( const { rows } = await this.pg.pool.query<ReturnSeries>(
sql` sql`
WITH start_date AS ( WITH start_date AS (
SELECT
a.id AS "account_id",
GREATEST(account_value_start_date(a.id), a.start_date) AS "start_date"
FROM
account a
WHERE
a.id = ANY(${pAccountIds})
GROUP BY
1
), external_flows AS (
SELECT
it.account_id,
it.date,
SUM(it.amount) AS "amount"
FROM
investment_transaction it
LEFT JOIN start_date sd ON sd.account_id = it.account_id
WHERE
it.account_id = ANY(${pAccountIds})
AND it.date BETWEEN sd.start_date AND ${pEnd}
-- filter for investment_transactions that represent external flows
AND (
it.category = 'transfer'
)
GROUP BY
1, 2
), external_flow_totals AS (
SELECT
account_id,
SUM(amount) as "amount"
FROM
external_flows
GROUP BY
1
), balances AS (
SELECT
abg.account_id,
abg.date,
abg.balance,
0 - SUM(COALESCE(ef.amount, 0)) OVER (PARTITION BY abg.account_id ORDER BY abg.date ASC) AS "contributions_period",
COALESCE(-1 * (eft.amount - coalesce(SUM(ef.amount) OVER (PARTITION BY abg.account_id ORDER BY abg.date DESC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0)), 0) AS "contributions"
FROM
account_balances_gapfilled(
${pStart},
${pEnd},
'1d',
${pAccountIds}
) abg
LEFT JOIN external_flows ef ON ef.account_id = abg.account_id AND ef.date = abg.date
LEFT JOIN external_flow_totals eft ON eft.account_id = abg.account_id
)
SELECT SELECT
b.account_id, a.id AS "account_id",
b.date, GREATEST(account_value_start_date(a.id), a.start_date) AS "start_date"
b.balance,
b.contributions,
b.contributions_period,
COALESCE(ROUND((b.balance - b0.balance - b.contributions_period) / COALESCE(NULLIF(b0.balance, 0), NULLIF(b.contributions_period, 0)), 4), 0) AS "rate_of_return"
FROM FROM
balances b account a
LEFT JOIN ( WHERE
SELECT DISTINCT ON (account_id) a.id = ANY(${pAccountIds})
account_id, GROUP BY
balance 1
FROM ), external_flows AS (
balances SELECT
ORDER BY it.account_id,
account_id, date ASC it.date,
) b0 ON b0.account_id = b.account_id SUM(it.amount) AS "amount"
` FROM
investment_transaction it
LEFT JOIN start_date sd ON sd.account_id = it.account_id
WHERE
it.account_id = ANY(${pAccountIds})
AND it.date BETWEEN sd.start_date AND ${pEnd}
-- filter for investment_transactions that represent external flows
AND (
(it.plaid_type = 'cash' AND it.plaid_subtype IN ('contribution', 'deposit', 'withdrawal'))
OR (it.plaid_type = 'transfer' AND it.plaid_subtype IN ('transfer'))
OR (it.plaid_type = 'buy' AND it.plaid_subtype IN ('contribution'))
OR (it.finicity_transaction_id IS NOT NULL AND it.finicity_investment_transaction_type IN ('contribution', 'deposit', 'transfer'))
)
GROUP BY
1, 2
), external_flow_totals AS (
SELECT
account_id,
SUM(amount) as "amount"
FROM
external_flows
GROUP BY
1
), balances AS (
SELECT
abg.account_id,
abg.date,
abg.balance,
0 - SUM(COALESCE(ef.amount, 0)) OVER (PARTITION BY abg.account_id ORDER BY abg.date ASC) AS "contributions_period",
COALESCE(-1 * (eft.amount - coalesce(SUM(ef.amount) OVER (PARTITION BY abg.account_id ORDER BY abg.date DESC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0)), 0) AS "contributions"
FROM
account_balances_gapfilled(
${pStart},
${pEnd},
'1d',
${pAccountIds}
) abg
LEFT JOIN external_flows ef ON ef.account_id = abg.account_id AND ef.date = abg.date
LEFT JOIN external_flow_totals eft ON eft.account_id = abg.account_id
)
SELECT
b.account_id,
b.date,
b.balance,
b.contributions,
b.contributions_period,
COALESCE(ROUND((b.balance - b0.balance - b.contributions_period) / COALESCE(NULLIF(b0.balance, 0), NULLIF(b.contributions_period, 0)), 4), 0) AS "rate_of_return"
FROM
balances b
LEFT JOIN (
SELECT DISTINCT ON (account_id)
account_id,
balance
FROM
balances
ORDER BY
account_id, date ASC
) b0 ON b0.account_id = b.account_id
`
) )
return rows return rows
@ -310,18 +313,18 @@ export class AccountQueryService implements IAccountQueryService {
const { rows } = await this.pg.pool.query<BalanceSeries>( const { rows } = await this.pg.pool.query<BalanceSeries>(
sql` sql`
SELECT SELECT
abg.account_id, abg.account_id,
abg.date, abg.date,
abg.balance abg.balance
FROM FROM
account_balances_gapfilled( account_balances_gapfilled(
${pStart}, ${pStart},
${pEnd}, ${pEnd},
${pInterval}, ${pInterval},
${pAccountIds} ${pAccountIds}
) abg ) abg
` `
) )
return rows return rows
@ -341,15 +344,15 @@ export class AccountQueryService implements IAccountQueryService {
'accountIds' in id 'accountIds' in id
? id.accountIds ? id.accountIds
: sql`( : sql`(
SELECT SELECT
array_agg(a.id) array_agg(a.id)
FROM FROM
account a account a
LEFT JOIN account_connection ac ON ac.id = a.account_connection_id LEFT JOIN account_connection ac ON ac.id = a.account_connection_id
WHERE WHERE
(a.user_id = ${id.userId} OR ac.user_id = ${id.userId}) (a.user_id = ${id.userId} OR ac.user_id = ${id.userId})
AND a.is_active AND a.is_active
)` )`
const pStart = raw(`'${start}'`) const pStart = raw(`'${start}'`)
const pEnd = raw(`'${end}'`) const pEnd = raw(`'${end}'`)
@ -376,27 +379,27 @@ export class AccountQueryService implements IAccountQueryService {
} }
>( >(
sql` sql`
SELECT SELECT
abg.date, abg.date,
a.category, a.category,
a.classification, a.classification,
SUM(CASE WHEN a.classification = 'asset' THEN abg.balance ELSE -abg.balance END) AS balance SUM(CASE WHEN a.classification = 'asset' THEN abg.balance ELSE -abg.balance END) AS balance
FROM FROM
account_balances_gapfilled( account_balances_gapfilled(
${pStart}, ${pStart},
${pEnd}, ${pEnd},
${pInterval}, ${pInterval},
${pAccountIds} ${pAccountIds}
) abg ) abg
INNER JOIN account a ON a.id = abg.account_id INNER JOIN account a ON a.id = abg.account_id
GROUP BY GROUP BY
GROUPING SETS ( GROUPING SETS (
(abg.date, a.classification, a.category), (abg.date, a.classification, a.category),
(abg.date, a.classification), (abg.date, a.classification),
(abg.date) (abg.date)
) )
ORDER BY date ASC; ORDER BY date ASC;
` `
) )
// Group independent rows into NetWorthSeries objects // Group independent rows into NetWorthSeries objects
@ -450,15 +453,15 @@ export class AccountQueryService implements IAccountQueryService {
'accountId' in id 'accountId' in id
? [id.accountId] ? [id.accountId]
: sql`( : sql`(
SELECT SELECT
array_agg(a.id) array_agg(a.id)
FROM FROM
account a account a
LEFT JOIN account_connection ac ON ac.id = a.account_connection_id LEFT JOIN account_connection ac ON ac.id = a.account_connection_id
WHERE WHERE
(a.user_id = ${id.userId} OR ac.user_id = ${id.userId}) (a.user_id = ${id.userId} OR ac.user_id = ${id.userId})
AND a.is_active AND a.is_active
)` )`
const pStart = raw(`'${start}'`) const pStart = raw(`'${start}'`)
const pEnd = raw(`'${end}'`) const pEnd = raw(`'${end}'`)
@ -466,59 +469,59 @@ export class AccountQueryService implements IAccountQueryService {
const { rows } = await this.pg.pool.query<AccountRollup>( const { rows } = await this.pg.pool.query<AccountRollup>(
sql` sql`
WITH account_rollup AS ( WITH account_rollup AS (
SELECT
abg.date,
a.classification,
a.category,
a.id,
SUM(abg.balance) AS balance,
CASE GROUPING(abg.date, a.classification, a.category, a.id)
WHEN 3 THEN 'classification'
WHEN 1 THEN 'category'
WHEN 0 THEN 'account'
ELSE NULL
END AS grouping
FROM
account_balances_gapfilled(
${pStart},
${pEnd},
${pInterval},
${pAccountIds}
) abg
INNER JOIN account a ON a.id = abg.account_id
GROUP BY
GROUPING SETS (
(abg.date, a.classification, a.category, a.id),
(abg.date, a.classification, a.category),
(abg.date, a.classification)
)
)
SELECT SELECT
ar.date, abg.date,
ar.classification, a.classification,
ar.category, a.category,
ar.id, a.id,
ar.balance, SUM(abg.balance) AS balance,
ar.grouping, CASE GROUPING(abg.date, a.classification, a.category, a.id)
CASE WHEN 3 THEN 'classification'
WHEN a.id IS NULL THEN NULL WHEN 1 THEN 'category'
ELSE json_build_object('id', a.id, 'name', a.name, 'mask', a.mask, 'syncStatus', a.sync_status, 'connection', CASE WHEN ac.id IS NULL THEN NULL ELSE json_build_object('name', ac.name, 'syncStatus', ac.sync_status) END) WHEN 0 THEN 'account'
END AS account, ELSE NULL
ROUND( END AS grouping
CASE ar.grouping
WHEN 'account' THEN COALESCE(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date, ar.classification, ar.category), 0)
WHEN 'category' THEN COALESCE(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date, ar.classification), 0)
WHEN 'classification' THEN COALESCE(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date), 0)
END, 4) AS rollup_pct,
ROUND(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date), 4) AS total_pct
FROM FROM
account_rollup ar account_balances_gapfilled(
LEFT JOIN account a ON a.id = ar.id ${pStart},
LEFT JOIN account_connection ac ON ac.id = a.account_connection_id ${pEnd},
ORDER BY ${pInterval},
ar.classification, ar.category, ar.id, ar.date; ${pAccountIds}
` ) abg
INNER JOIN account a ON a.id = abg.account_id
GROUP BY
GROUPING SETS (
(abg.date, a.classification, a.category, a.id),
(abg.date, a.classification, a.category),
(abg.date, a.classification)
)
)
SELECT
ar.date,
ar.classification,
ar.category,
ar.id,
ar.balance,
ar.grouping,
CASE
WHEN a.id IS NULL THEN NULL
ELSE json_build_object('id', a.id, 'name', a.name, 'mask', a.mask, 'syncStatus', a.sync_status, 'connection', CASE WHEN ac.id IS NULL THEN NULL ELSE json_build_object('name', ac.name, 'syncStatus', ac.sync_status) END)
END AS account,
ROUND(
CASE ar.grouping
WHEN 'account' THEN COALESCE(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date, ar.classification, ar.category), 0)
WHEN 'category' THEN COALESCE(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date, ar.classification), 0)
WHEN 'classification' THEN COALESCE(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date), 0)
END, 4) AS rollup_pct,
ROUND(ar.balance / SUM(NULLIF(ar.balance, 0)) OVER (PARTITION BY ar.grouping, ar.date), 4) AS total_pct
FROM
account_rollup ar
LEFT JOIN account a ON a.id = ar.id
LEFT JOIN account_connection ac ON ac.id = a.account_connection_id
ORDER BY
ar.classification, ar.category, ar.id, ar.date;
`
) )
return rows return rows

View file

@ -315,9 +315,6 @@ export class InsightService implements IInsightService {
{ {
finicityInvestmentTransactionType: 'dividend', finicityInvestmentTransactionType: 'dividend',
}, },
{
category: 'dividend',
},
], ],
}, },
}), }),
@ -750,7 +747,10 @@ export class InsightService implements IInsightService {
WHERE WHERE
it.account_id = ${accountId} it.account_id = ${accountId}
AND ( AND (
it.category = 'transfer' (it.plaid_type = 'cash' AND it.plaid_subtype IN ('contribution', 'deposit', 'withdrawal'))
OR (it.plaid_type = 'transfer' AND it.plaid_subtype IN ('transfer', 'send', 'request'))
OR (it.plaid_type = 'buy' AND it.plaid_subtype IN ('contribution'))
OR (it.finicity_transaction_id IS NOT NULL AND it.finicity_investment_transaction_type IN ('contribution', 'deposit', 'transfer'))
) )
-- Exclude any contributions made prior to the start date since balances will be 0 -- Exclude any contributions made prior to the start date since balances will be 0
AND (a.start_date is NULL OR it.date >= a.start_date) AND (a.start_date is NULL OR it.date >= a.start_date)