Skip to content

Commit

Permalink
Move a lot of calculations from flipside into backend
Browse files Browse the repository at this point in the history
  • Loading branch information
Playwo committed Sep 14, 2022
1 parent b3374c0 commit edd8431
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 54 deletions.
2 changes: 1 addition & 1 deletion Thorsight/ClientApp/src/app/core/models/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class PositionSnapshot {
assetPrice!: number;
currentStakeUnits!: number;
totalStakeUnits!: number;
breakEvenPrice!: number;
breakEvenValue!: number;
valueUSD!: number;
assetAmount!: number;
runeAmount!: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ export class AssetWorthGraph extends BaseComponent implements AfterViewInit {
this.poolValues = this.pools.map(pool => {
const values = this.viewCache.positionHistories!.filter(stat => stat.poolName == pool).map((position, i, _) => {
breakEvenPrices[i] = breakEvenPrices[i] > 0
? breakEvenPrices[i] + position.breakEvenPrice * position.currentStakeUnits
: position.breakEvenPrice * position.currentStakeUnits;
? breakEvenPrices[i] + position.breakEvenValue
: position.breakEvenValue;

return position.valueUSD;
});
Expand Down
2 changes: 1 addition & 1 deletion Thorsight/Controllers/QueryController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public async Task<IEnumerable<OpenPositionDto>> GetOpenPositionsAsync([FromRoute

[HttpGet("PositionHistory/{address}")]
[ResponseCache(Duration = 120, Location = ResponseCacheLocation.Any)]
public async Task<IEnumerable<PositionSnapshotDto>> GetPositionHistoryAsync([FromRoute] string address, [FromQuery][Range(7, 180)] uint days,
public async Task<IEnumerable<PositionSnapshotDto>> GetPositionHistoryAsync([FromRoute] string address, [FromQuery][Range(7, 180)] int days,
CancellationToken cancellationToken)
=> (await QueryClient.GetPositionHistoryAsync(address, days, cancellationToken))
.OrderBy(x => x.Timestamp)
Expand Down
20 changes: 16 additions & 4 deletions Thorsight/Models/Dtos/PositionSnapshotDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ public class PositionSnapshotDto
public string PoolName { get; private set; }
public decimal AssetPrice { get; private set; }

public ulong CurrentStakeUnits { get; private set; }
public long CurrentStakeUnits { get; private set; }
public decimal TotalStakeUnits { get; private set; }

public decimal ValueUSD { get; private set; }
public decimal BreakEvenPrice { get; private set; }
public decimal BreakEvenValue { get; private set; }

public decimal AssetAmount { get; private set; }
public decimal RuneAmount { get; private set; }

public decimal DepositRuneValue { get; private set; }
public decimal DepositAssetValue { get; private set; }

public PositionSnapshotDto(DateTimeOffset timestamp, string poolName, decimal assetPrice, ulong currentStakeUnits, decimal totalStakeUnits,
public PositionSnapshotDto(DateTimeOffset timestamp, string poolName, decimal assetPrice, long currentStakeUnits, decimal totalStakeUnits,
decimal valueUSD, decimal breakEvenPrice, decimal assetAmount, decimal runeAmount, decimal depositRuneValue, decimal depositAssetValue)
{
Timestamp = timestamp;
Expand All @@ -27,10 +27,22 @@ public PositionSnapshotDto(DateTimeOffset timestamp, string poolName, decimal as
CurrentStakeUnits = currentStakeUnits;
TotalStakeUnits = totalStakeUnits;
ValueUSD = valueUSD;
BreakEvenPrice = breakEvenPrice;
BreakEvenValue = breakEvenPrice;
AssetAmount = assetAmount;
RuneAmount = runeAmount;
DepositRuneValue = depositRuneValue;
DepositAssetValue = depositAssetValue;
}

private PositionSnapshotDto(string poolName, long currentStakeUnits, decimal breakEvenPrice, decimal depositRuneAmount, decimal depositAssetAmount)
{
PoolName = poolName;
CurrentStakeUnits = currentStakeUnits;
BreakEvenValue = breakEvenPrice;
DepositRuneValue = depositRuneAmount;
DepositAssetValue = depositAssetAmount;
}

public static PositionSnapshotDto Initial(string poolName, long currentStakeUnits, decimal breakEvenPrice, decimal depositRuneAmount, decimal depositAssetAmount)
=> new PositionSnapshotDto(poolName, currentStakeUnits, breakEvenPrice, depositRuneAmount, depositAssetAmount);
}
33 changes: 33 additions & 0 deletions Thorsight/Models/QueryObjects/LiquidityUpdate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Globalization;

namespace Thorsight.Models.QueryObjects;

public class LiquidityUpdate : FlipsideObject
{
public DateTimeOffset BlockTimestamp { get; private set; }
public string Action { get; private set; } = null!;
public string PoolName { get; private set; } = null!;

public decimal PricePerUnit { get; private set; }
public long Units { get; private set; }

public decimal DepositRuneValue { get; private set; }
public decimal DepositAssetValue { get; private set; }

public long WithdrawBasisPoints { get; private set; }

public override void SetValues(string[] rawValues)
{
BlockTimestamp = DateTimeOffset.Parse(rawValues[0]);
Action = rawValues[1];
PoolName = rawValues[2];

PricePerUnit = decimal.Parse(rawValues[3], NumberStyles.Float);
Units = long.Parse(rawValues[4]);

DepositRuneValue = decimal.Parse(rawValues[5], NumberStyles.Float);
DepositAssetValue = decimal.Parse(rawValues[6], NumberStyles.Float);

WithdrawBasisPoints = long.Parse(rawValues[7]);
}
}
6 changes: 4 additions & 2 deletions Thorsight/Services/MidgardClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ public async Task<PoolInfo[]> GetPoolsAsync()
}
}

public async Task<PoolDepthPriceSnapshot[]?> GetPoolDepthPriceHistory(string asset, uint days)
public async Task<PoolDepthPriceSnapshot[]?> GetPoolDepthPriceHistory(string asset, int days,
CancellationToken cancellationToken)
{
try
{
var response = await Client.GetFromJsonAsync<PoolDepthPriceHistoryResponse>($"https://midgard.thorchain.info/v2/history/depths/{asset}?interval=day&count={days + 1}");
var response = await Client.GetFromJsonAsync<PoolDepthPriceHistoryResponse>(
$"https://midgard.thorchain.info/v2/history/depths/{asset}?interval=day&count={days + 1}", cancellationToken);
return response?.Intervals;
}
catch
Expand Down
124 changes: 80 additions & 44 deletions Thorsight/Services/QueryClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,71 +62,107 @@ public async Task<OpenPositionDto[]> GetCurrentPositionsAsync(string address, Ca
return positionDtos.ToArray();
}

public async Task<PositionSnapshotDto[]> GetPositionHistoryAsync(string address, uint days, CancellationToken cancellationToken)
public async Task<PositionSnapshotDto[]> GetPositionHistoryAsync(string address, int days, CancellationToken cancellationToken)
{
string sql =
"WITH lp_actions AS ( " +
"SELECT a.block_timestamp, lp_action, a.pool_name, (a.rune_amount_usd + a.asset_amount_usd) / stake_units AS price_per_unit, stake_units AS units, " +
"(a.asset_amount / 2) * (b.rune_amount / b.asset_amount) + (a.rune_amount / 2) AS deposit_rune_value, " +
"(a.rune_amount / 2) * (b.asset_amount / b.rune_amount) + (a.asset_amount / 2) AS deposit_asset_value " +
"FROM flipside_prod_db.thorchain.liquidity_actions a " +
"JOIN flipside_prod_db.thorchain.pool_block_balances b ON a.block_id = b.block_id AND a.pool_name = b.pool_name " +
$"WHERE from_address = '{address}' ), " +
"pools AS ( " +
"SELECT DISTINCT pool_name " +
"FROM lp_actions )," +
"days AS ( " +
"SELECT date_day AS day " +
"FROM crosschain.core.dim_dates " +
$"WHERE date_day > CURRENT_DATE - {days + 1} AND date_day < CURRENT_DATE ) " +
"SELECT day, p.pool_name, " +
"COALESCE((SELECT sum(CASE WHEN LP_ACTION = 'add_liquidity' THEN units ELSE -units END) FROM lp_actions a WHERE block_timestamp <= day AND a.pool_name = p.pool_name), 0) AS current_stake_units, " +
"CASE WHEN current_stake_units = 0 THEN 0 ELSE COALESCE((SELECT sum(CASE WHEN LP_ACTION = 'add_liquidity' THEN units * price_per_unit ELSE -units * price_per_unit END) FROM lp_actions a WHERE block_timestamp <= day AND a.pool_name = p.pool_name), 0) / current_stake_units END AS break_even_price_per_unit, " +
"COALESCE((SELECT sum(CASE WHEN LP_ACTION = 'add_liquidity' THEN deposit_rune_value ELSE -deposit_rune_value END) FROM lp_actions a WHERE block_timestamp <= day AND a.pool_name = p.pool_name), 0) AS deposit_rune_value, " +
"COALESCE((SELECT sum(CASE WHEN LP_ACTION = 'add_liquidity' THEN deposit_asset_value ELSE -deposit_asset_value END) FROM lp_actions a WHERE block_timestamp <= day AND a.pool_name = p.pool_name), 0) AS deposit_asset_value " +
"FROM days JOIN pools p " +
"GROUP BY day, p.pool_name";

var positionHistory = await Flipside.RunQueryAsync<PositionSnapshot>(sql, cancellationToken: cancellationToken);

var pools = new Dictionary<string, PoolInfo?>();
var positionDtos = new List<PositionSnapshotDto>();


foreach(var poolTypeGrouping in positionHistory.GroupBy(x => x.PoolName))
"SELECT a.block_timestamp, lp_action, a.pool_name, (a.rune_amount_usd + a.asset_amount_usd) / a.stake_units AS price_per_unit, a.stake_units AS units, " +
"(a.asset_amount / 2) * (b.rune_amount / b.asset_amount) + (a.rune_amount / 2) AS deposit_rune_value, " +
"(a.rune_amount / 2) * (b.asset_amount / b.rune_amount) + (a.asset_amount / 2) AS deposit_asset_value, " +
"COALESCE(u.basis_points, 0) AS withdraw_basis_points " +
"FROM flipside_prod_db.thorchain.liquidity_actions a " +
"JOIN flipside_prod_db.thorchain.pool_block_balances b ON a.block_id = b.block_id AND a.pool_name = b.pool_name " +
"LEFT JOIN flipside_prod_db.thorchain.unstake_events u ON a.block_id = u.block_id AND a.tx_id = u.tx_id AND a.pool_name = u.pool_name AND a.to_address = u.to_address AND a.from_address = u.from_address AND a.stake_units = u.stake_units " +
$"WHERE a.from_address = '{address}'";

var now = DateTimeOffset.UtcNow;
var currentDay = now.Subtract(now.TimeOfDay);
var timeframe = Enumerable.Range(0, days)
.Select(x => x - days)
.Select(x => currentDay.AddDays(x))
.ToArray();

var liquidityActions = (await Flipside.RunQueryAsync<LiquidityUpdate>(sql, cancellationToken: cancellationToken))
.OrderBy(x => x.BlockTimestamp)
.ToArray(); ;

var poolNames = liquidityActions.Select(x => x.PoolName).Distinct();

var positionSnapshots = new List<PositionSnapshotDto>();

foreach (string poolName in poolNames)
{
string poolName = poolTypeGrouping.Key;
var poolHistory = await Midgard.GetPoolDepthPriceHistory(poolName, days);
var poolHistory = await Midgard.GetPoolDepthPriceHistory(poolName, days, cancellationToken);

if (poolHistory is null)
{
continue;
}

foreach(var position in poolTypeGrouping)
var previousLpActions = liquidityActions
.Where(x => x.PoolName == poolName && x.BlockTimestamp.Date < timeframe[0].Date)
.ToArray();

var initialPosition = PositionSnapshotDto.Initial(poolName,
previousLpActions.Sum(x => x.Action == "add_liquidity" ? x.Units : -x.Units),
previousLpActions.Sum(x => x.Action == "add_liquidity" ? x.Units * x.PricePerUnit : -x.Units * x.PricePerUnit),
previousLpActions.Aggregate<LiquidityUpdate, decimal>(0, (current, update) => update.Action == "add_liquidity" ? current + update.DepositRuneValue : current - (current * update.WithdrawBasisPoints / 10000)),
previousLpActions.Aggregate<LiquidityUpdate, decimal>(0, (current, update) => update.Action == "add_liquidity" ? current + update.DepositAssetValue : current - (current * update.WithdrawBasisPoints / 10000))
);

foreach (var day in timeframe)
{
var poolBalance = poolHistory.FirstOrDefault(x => x.StartTime.DateTime == position.Timestamp.DateTime);
var currentPoolStats = poolHistory.Where(x => x.StartTime.DateTime == day.DateTime).SingleOrDefault();

if (poolBalance is null)
if (currentPoolStats is null)
{
Logger.LogWarning("Missing pool history value!");
Logger.LogWarning("Missing pool history value: {day} - {poolName}", day.Date.ToShortDateString(), poolName);
continue;
}

decimal poolShare = (decimal)position.CurrentStakeUnits / poolBalance.Units;
var positionAtStart = positionSnapshots.LastOrDefault(x => x.PoolName == poolName, initialPosition);

decimal runeAmount = poolBalance.RuneDepth * poolShare / 100000000;
decimal assetAmount = runeAmount / poolBalance.AssetPrice;
long currentStakeUnits = positionAtStart.CurrentStakeUnits;
decimal breakEvenValue = positionAtStart.BreakEvenValue;
decimal depositRuneValue = positionAtStart.DepositRuneValue;
decimal depositAssetValue = positionAtStart.DepositAssetValue;

decimal valueUSD = 2 * assetAmount * poolBalance.AssetPriceUSD;
var lpActions = liquidityActions
.Where(x => x.PoolName == poolName && x.BlockTimestamp.Date == day.Date);

var dto = new PositionSnapshotDto(position.Timestamp, position.PoolName, poolBalance.AssetPrice, position.CurrentStakeUnits, poolBalance.Units,
valueUSD, position.BreakEvenPrice, assetAmount, runeAmount, position.DepositRuneValue, position.DepositAssetValue);

positionDtos.Add(dto);
foreach (var lpAction in lpActions)
{
if (lpAction.Action == "add_liquidity")
{
currentStakeUnits += lpAction.Units;
breakEvenValue += lpAction.Units * lpAction.PricePerUnit;
depositRuneValue += lpAction.DepositRuneValue;
depositAssetValue += lpAction.DepositAssetValue;
}
if (lpAction.Action == "remove_liquidity")
{
currentStakeUnits -= lpAction.Units;
breakEvenValue -= lpAction.Units * lpAction.PricePerUnit;
depositRuneValue -= depositRuneValue * lpAction.WithdrawBasisPoints / 10000;
depositAssetValue -= depositAssetValue * lpAction.WithdrawBasisPoints / 10000;
}
}

decimal poolShare = (decimal)currentStakeUnits / currentPoolStats.Units;
decimal runeAmount = currentPoolStats.RuneDepth * poolShare / 100000000;
decimal assetAmount = runeAmount / currentPoolStats.AssetPrice;

decimal valueUSD = 2 * assetAmount * currentPoolStats.AssetPriceUSD;

var positionAtEnd = new PositionSnapshotDto(
day, poolName, currentPoolStats.AssetPrice, currentStakeUnits,
currentPoolStats.Units, valueUSD, breakEvenValue, assetAmount, runeAmount,
depositRuneValue, depositAssetValue);

positionSnapshots.Add(positionAtEnd);
}
}

return positionDtos.ToArray();
return positionSnapshots.ToArray();
}
}

0 comments on commit edd8431

Please sign in to comment.