Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions backend/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ impl From<db::Portfolio> for websocket_api::Portfolio {
available_balance,
market_exposures,
owner_credits,
traded_market_ids,
}: db::Portfolio,
) -> Self {
Self {
Expand All @@ -39,6 +40,7 @@ impl From<db::Portfolio> for websocket_api::Portfolio {
credit: credit.credit.0.try_into().unwrap(),
})
.collect(),
traded_market_ids,
}
}
}
Expand Down
25 changes: 25 additions & 0 deletions backend/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,29 @@ impl DB {
}
}

/// Get all market IDs an account has ever traded in or redeemed from.
///
/// # Errors
/// Returns an error if the database query fails.
pub async fn get_traded_market_ids(&self, account_id: i64) -> SqlxResult<Vec<i64>> {
let rows = sqlx::query_scalar!(
r#"
SELECT DISTINCT market_id as "market_id!"
FROM (
SELECT market_id FROM trade WHERE buyer_id = ?1
UNION
SELECT market_id FROM trade WHERE seller_id = ?1
UNION
SELECT fund_id AS market_id FROM redemption WHERE redeemer_id = ?1
)
"#,
account_id
)
.fetch_all(&self.pool)
.await?;
Ok(rows)
}

#[must_use]
pub fn get_all_accounts(&self) -> BoxStream<'_, SqlxResult<Account>> {
sqlx::query_as!(
Expand Down Expand Up @@ -3633,6 +3656,7 @@ async fn get_portfolio(
available_balance,
market_exposures,
owner_credits: vec![],
traded_market_ids: vec![],
}))
}

Expand Down Expand Up @@ -4061,6 +4085,7 @@ pub struct Portfolio {
pub available_balance: Decimal,
pub market_exposures: Vec<MarketExposure>,
pub owner_credits: Vec<OwnerCredit>,
pub traded_market_ids: Vec<i64>,
}

#[derive(Debug)]
Expand Down
3 changes: 2 additions & 1 deletion backend/src/handle_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,10 +278,11 @@ async fn send_initial_private_data(
let mut transfers = Vec::new();
let mut portfolios = Vec::new();
for &account_id in accounts {
let Some(portfolio) = db.get_portfolio(account_id).await? else {
let Some(mut portfolio) = db.get_portfolio(account_id).await? else {
tracing::warn!("Account {account_id} not found");
continue;
};
portfolio.traded_market_ids = db.get_traded_market_ids(account_id).await?;
portfolios.push(Portfolio::from(portfolio));
transfers.extend(
db.get_transfers(account_id)
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/lib/api.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const serverState = $state({
marketGroups: new SvelteMap<number, websocket_api.IMarketGroup>(),
auctions: new SvelteMap<number, websocket_api.IAuction>(),
universes: new SvelteMap<number, websocket_api.IUniverse>(),
tradedMarketIds: new SvelteMap<number, Set<number>>(),
lastKnownTransactionId: 0,
arborPixieAccountId: undefined as number | undefined
});
Expand Down Expand Up @@ -258,9 +259,16 @@ socket.onmessage = (event: MessageEvent) => {
if (msg.portfolios) {
if (!msg.portfolios.areNewOwnerships) {
serverState.portfolios.clear();
serverState.tradedMarketIds.clear();
}
for (const p of msg.portfolios.portfolios || []) {
serverState.portfolios.set(p.accountId, p);
if (p.tradedMarketIds?.length) {
serverState.tradedMarketIds.set(
p.accountId as number,
new Set(p.tradedMarketIds.map(Number))
);
}
if (p.accountId == serverState.actingAs) {
serverState.portfolio = p;
}
Expand Down
23 changes: 19 additions & 4 deletions frontend/src/lib/pnlMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,10 @@ export function computePnLOverTime(
if (!cashFlowByMarket.has(cid)) cashFlowByMarket.set(cid, 0);
}

// Track redeem fee
totalRedeemFees += event.redeemFee * amount;
// Track redeem fee (attributed to the fund market)
if (!filterMarketIds || filterMarketIds.has(fundId)) {
totalRedeemFees += event.redeemFee * amount;
}

const { totalCash, totalMtM } = computePnLAtPoint();
dataPoints.push({
Expand Down Expand Up @@ -466,12 +468,25 @@ export function computePnLOverTime(
export function getMarketsNeedingHistory(
accountId: number,
markets: Map<number, MarketData>,
portfolio: websocket_api.IPortfolio | undefined
portfolio: websocket_api.IPortfolio | undefined,
tradedMarketIds: Set<number> | undefined
): number[] {
const needed: number[] = [];
const seen = new Set<number>();

// Markets with current exposure
// Markets the backend told us this account has traded/redeemed
if (tradedMarketIds) {
for (const marketId of tradedMarketIds) {
const marketData = markets.get(marketId);
if (marketData && !marketData.hasFullTradeHistory && !seen.has(marketId)) {
seen.add(marketId);
needed.push(marketId);
}
}
}

// Markets with current exposure (covers markets not yet in traded_market_ids,
// e.g. from redemption constituents the account never directly traded)
for (const exposure of portfolio?.marketExposures || []) {
const marketId = Number(exposure.marketId ?? 0);
const marketData = markets.get(marketId);
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/routes/performance/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,13 @@
}

const portfolio = serverState.portfolios.get(accountId);
const needed = getMarketsNeedingHistory(accountId, serverState.markets, portfolio);
const tradedMarketIds = serverState.tradedMarketIds.get(accountId);
const needed = getMarketsNeedingHistory(
accountId,
serverState.markets,
portfolio,
tradedMarketIds
);

let delay = 0;
for (const marketId of needed) {
Expand Down
6 changes: 6 additions & 0 deletions schema-js/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1567,6 +1567,9 @@ export namespace websocket_api {

/** Portfolio ownerCredits */
ownerCredits?: (websocket_api.Portfolio.IOwnerCredit[]|null);

/** Portfolio tradedMarketIds */
tradedMarketIds?: ((number|Long)[]|null);
}

/** Represents a Portfolio. */
Expand All @@ -1593,6 +1596,9 @@ export namespace websocket_api {
/** Portfolio ownerCredits. */
public ownerCredits: websocket_api.Portfolio.IOwnerCredit[];

/** Portfolio tradedMarketIds. */
public tradedMarketIds: (number|Long)[];

/**
* Creates a new Portfolio instance using the specified properties.
* @param [properties] Properties to set
Expand Down
57 changes: 57 additions & 0 deletions schema-js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4159,6 +4159,7 @@ $root.websocket_api = (function() {
* @property {number|null} [availableBalance] Portfolio availableBalance
* @property {Array.<websocket_api.Portfolio.IMarketExposure>|null} [marketExposures] Portfolio marketExposures
* @property {Array.<websocket_api.Portfolio.IOwnerCredit>|null} [ownerCredits] Portfolio ownerCredits
* @property {Array.<number|Long>|null} [tradedMarketIds] Portfolio tradedMarketIds
*/

/**
Expand All @@ -4172,6 +4173,7 @@ $root.websocket_api = (function() {
function Portfolio(properties) {
this.marketExposures = [];
this.ownerCredits = [];
this.tradedMarketIds = [];
if (properties)
for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i)
if (properties[keys[i]] != null)
Expand Down Expand Up @@ -4218,6 +4220,14 @@ $root.websocket_api = (function() {
*/
Portfolio.prototype.ownerCredits = $util.emptyArray;

/**
* Portfolio tradedMarketIds.
* @member {Array.<number|Long>} tradedMarketIds
* @memberof websocket_api.Portfolio
* @instance
*/
Portfolio.prototype.tradedMarketIds = $util.emptyArray;

/**
* Creates a new Portfolio instance using the specified properties.
* @function create
Expand Down Expand Up @@ -4254,6 +4264,12 @@ $root.websocket_api = (function() {
if (message.ownerCredits != null && message.ownerCredits.length)
for (var i = 0; i < message.ownerCredits.length; ++i)
$root.websocket_api.Portfolio.OwnerCredit.encode(message.ownerCredits[i], writer.uint32(/* id 5, wireType 2 =*/42).fork()).ldelim();
if (message.tradedMarketIds != null && message.tradedMarketIds.length) {
writer.uint32(/* id 6, wireType 2 =*/50).fork();
for (var i = 0; i < message.tradedMarketIds.length; ++i)
writer.int64(message.tradedMarketIds[i]);
writer.ldelim();
}
return writer;
};

Expand Down Expand Up @@ -4312,6 +4328,17 @@ $root.websocket_api = (function() {
message.ownerCredits.push($root.websocket_api.Portfolio.OwnerCredit.decode(reader, reader.uint32()));
break;
}
case 6: {
if (!(message.tradedMarketIds && message.tradedMarketIds.length))
message.tradedMarketIds = [];
if ((tag & 7) === 2) {
var end2 = reader.uint32() + reader.pos;
while (reader.pos < end2)
message.tradedMarketIds.push(reader.int64());
} else
message.tradedMarketIds.push(reader.int64());
break;
}
default:
reader.skipType(tag & 7);
break;
Expand Down Expand Up @@ -4374,6 +4401,13 @@ $root.websocket_api = (function() {
return "ownerCredits." + error;
}
}
if (message.tradedMarketIds != null && message.hasOwnProperty("tradedMarketIds")) {
if (!Array.isArray(message.tradedMarketIds))
return "tradedMarketIds: array expected";
for (var i = 0; i < message.tradedMarketIds.length; ++i)
if (!$util.isInteger(message.tradedMarketIds[i]) && !(message.tradedMarketIds[i] && $util.isInteger(message.tradedMarketIds[i].low) && $util.isInteger(message.tradedMarketIds[i].high)))
return "tradedMarketIds: integer|Long[] expected";
}
return null;
};

Expand Down Expand Up @@ -4422,6 +4456,20 @@ $root.websocket_api = (function() {
message.ownerCredits[i] = $root.websocket_api.Portfolio.OwnerCredit.fromObject(object.ownerCredits[i]);
}
}
if (object.tradedMarketIds) {
if (!Array.isArray(object.tradedMarketIds))
throw TypeError(".websocket_api.Portfolio.tradedMarketIds: array expected");
message.tradedMarketIds = [];
for (var i = 0; i < object.tradedMarketIds.length; ++i)
if ($util.Long)
(message.tradedMarketIds[i] = $util.Long.fromValue(object.tradedMarketIds[i])).unsigned = false;
else if (typeof object.tradedMarketIds[i] === "string")
message.tradedMarketIds[i] = parseInt(object.tradedMarketIds[i], 10);
else if (typeof object.tradedMarketIds[i] === "number")
message.tradedMarketIds[i] = object.tradedMarketIds[i];
else if (typeof object.tradedMarketIds[i] === "object")
message.tradedMarketIds[i] = new $util.LongBits(object.tradedMarketIds[i].low >>> 0, object.tradedMarketIds[i].high >>> 0).toNumber();
}
return message;
};

Expand All @@ -4441,6 +4489,7 @@ $root.websocket_api = (function() {
if (options.arrays || options.defaults) {
object.marketExposures = [];
object.ownerCredits = [];
object.tradedMarketIds = [];
}
if (options.defaults) {
if ($util.Long) {
Expand Down Expand Up @@ -4470,6 +4519,14 @@ $root.websocket_api = (function() {
for (var j = 0; j < message.ownerCredits.length; ++j)
object.ownerCredits[j] = $root.websocket_api.Portfolio.OwnerCredit.toObject(message.ownerCredits[j], options);
}
if (message.tradedMarketIds && message.tradedMarketIds.length) {
object.tradedMarketIds = [];
for (var j = 0; j < message.tradedMarketIds.length; ++j)
if (typeof message.tradedMarketIds[j] === "number")
object.tradedMarketIds[j] = options.longs === String ? String(message.tradedMarketIds[j]) : message.tradedMarketIds[j];
else
object.tradedMarketIds[j] = options.longs === String ? $util.Long.prototype.toString.call(message.tradedMarketIds[j]) : options.longs === Number ? new $util.LongBits(message.tradedMarketIds[j].low >>> 0, message.tradedMarketIds[j].high >>> 0).toNumber() : message.tradedMarketIds[j];
}
return object;
};

Expand Down
1 change: 1 addition & 0 deletions schema/portfolio.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ message Portfolio {
double available_balance = 3;
repeated MarketExposure market_exposures = 4;
repeated OwnerCredit owner_credits = 5;
repeated int64 traded_market_ids = 6;

message MarketExposure {
int64 market_id = 1;
Expand Down