Merge pull request #2164 from aftermath2/aggregate_orders_metadata

Replace API values with frontend orders aggregation
This commit is contained in:
KoalaSat
2025-08-13 10:22:40 +00:00
committed by GitHub
6 changed files with 34 additions and 98 deletions

View File

@ -237,14 +237,6 @@ class OrderDetailSerializer(serializers.ModelSerializer):
premium_now = serializers.FloatField(
required=False, help_text="Premium over the CEX price at the current time"
)
premium_percentile = serializers.FloatField(
required=False,
help_text="(Only if `is_maker`) Premium percentile of your order compared to other public orders in the same currency currently in the order book",
)
num_similar_orders = serializers.IntegerField(
required=False,
help_text="(Only if `is_maker`) The number of public orders of the same currency currently in the order book",
)
tg_enabled = serializers.BooleanField(
required=False,
help_text="(Only if `is_maker`) Whether Telegram notification is enabled or not",
@ -446,8 +438,6 @@ class OrderDetailSerializer(serializers.ModelSerializer):
"maker_status",
"taker_status",
"price_now",
"premium_percentile",
"num_similar_orders",
"tg_enabled",
"tg_token",
"tg_bot_name",

View File

@ -8,7 +8,6 @@ from api.models import Order
from api.utils import (
base91_to_hex,
bitcoind_rpc,
compute_premium_percentile,
get_cln_version,
get_exchange_rates,
get_lnd_version,
@ -123,43 +122,6 @@ class TestUtils(TestCase):
# Assert that the read method of the file object was called
mock_file().read.assert_called_once()
@patch("api.utils.Order.objects.filter")
def test_compute_premium_percentile(self, mock_filter):
# Mock the filter method to return a mock queryset
mock_queryset = MagicMock()
mock_filter.return_value = mock_queryset
# Mock the exclude method of the queryset to return the same mock queryset
mock_queryset.exclude.return_value = mock_queryset
# Mock the count method of the queryset to return a specific number
mock_queryset.count.return_value = 2
# Mock the order object
order = MagicMock()
order.currency = "USD"
order.status = Order.Status.PUB
order.type = "type"
order.id = 1
order.amount = 1000
order.has_range = False
order.max_amount = 2000
order.last_satoshis = 10000
# Call the compute_premium_percentile function with the mock order object
percentile = compute_premium_percentile(order)
# Assert that the function returns a float
self.assertIsInstance(percentile, float)
# Assert that the filter method of the queryset was called with the correct arguments
mock_filter.assert_called_once_with(
currency=order.currency, status=Order.Status.PUB, type=order.type
)
# Assert that the exclude method of the queryset was called with the correct arguments
mock_queryset.exclude.assert_called_once_with(id=order.id)
def test_weighted_median(self):
values = [1, 2, 3, 4, 5]
weights = [1, 1, 1, 1, 1]

View File

@ -304,33 +304,6 @@ def get_robosats_commit():
return commit_hash
premium_percentile = {}
@ring.dict(premium_percentile, expire=300)
def compute_premium_percentile(order):
queryset = Order.objects.filter(
currency=order.currency, status=Order.Status.PUB, type=order.type
).exclude(id=order.id)
if len(queryset) <= 1:
return 0.5
amount = order.amount if not order.has_range else order.max_amount
order_rate = float(order.last_satoshis) / float(amount)
rates = []
for similar_order in queryset:
similar_order_amount = (
similar_order.amount
if not similar_order.has_range
else similar_order.max_amount
)
rates.append(float(similar_order.last_satoshis) / float(similar_order_amount))
rates = np.array(rates)
return round(np.sum(rates < order_rate) / len(rates), 2)
def weighted_median(values, sample_weight=None, quantiles=0.5, values_sorted=False):
"""Very close to numpy.percentile, but it supports weights.
NOTE: quantiles should be in [0, 1]!

View File

@ -59,7 +59,6 @@ from api.serializers import (
)
from api.utils import (
compute_avg_premium,
compute_premium_percentile,
get_cln_version,
get_lnd_version,
get_robosats_commit,
@ -269,19 +268,6 @@ class OrderView(viewsets.ViewSet):
else:
data["satoshis_now"] = Logics.satoshis_now(order)
# 4. a) If maker and Public/Paused, add premium percentile
# num similar orders, and maker information to enable telegram notifications.
if data["is_maker"] and order.status in [
Order.Status.PUB,
Order.Status.PAU,
]:
data["premium_percentile"] = compute_premium_percentile(order)
data["num_similar_orders"] = len(
Order.objects.filter(
currency=order.currency, status=Order.Status.PUB
)
)
# For participants add positions, nicks and status as a message and hold invoices status
data["is_buyer"] = Logics.is_buyer(order, request.user)
data["is_seller"] = Logics.is_seller(order, request.user)
@ -568,7 +554,6 @@ class OrderView(viewsets.ViewSet):
# 3) If action is 'update invoice'
elif action == "update_invoice":
# DEPRECATE post v0.5.1.
valid_signature, invoice = verify_signed_message(
request.user.robot.public_key, pgp_invoice
)
@ -620,7 +605,7 @@ class OrderView(viewsets.ViewSet):
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
# 7) If action is rate
# 7) If action is rate_user
elif action == "rate_user" and rating:
"""No user rating"""
pass
@ -631,7 +616,7 @@ class OrderView(viewsets.ViewSet):
if not valid:
return Response(context, status.HTTP_400_BAD_REQUEST)
# 9) If action is rate_platform
# 9) If action is pause
elif action == "pause":
valid, context = Logics.pause_unpause_public_order(order, request.user)
if not valid:
@ -821,7 +806,6 @@ class InfoView(viewsets.ViewSet):
context["node_id"] = config("NODE_ID")
context["network"] = config("NETWORK", cast=str, default="mainnet")
context["maker_fee"] = float(config("FEE")) * float(config("MAKER_FEE_SPLIT"))
context["maker_fee"] = float(config("FEE")) * float(config("MAKER_FEE_SPLIT"))
context["taker_fee"] = float(config("FEE")) * (
1 - float(config("MAKER_FEE_SPLIT"))
)

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useContext, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import {
List,
@ -15,6 +15,7 @@ import { LoadingButton } from '@mui/lab';
import currencies from '../../../../static/assets/currencies.json';
import { type Order } from '../../../models';
import { FederationContext, type UseFederationStoreType } from '../../../contexts/FederationContext';
import { PauseCircle, Storefront, Percent } from '@mui/icons-material';
interface PublicWaitPrompProps {
@ -29,6 +30,8 @@ export const PublicWaitPrompt = ({
onClickPauseOrder,
}: PublicWaitPrompProps): React.JSX.Element => {
const { t } = useTranslation();
const { federation, federationUpdatedAt } = useContext<UseFederationStoreType>(FederationContext);
const currencyCode = currencies[order.currency.toString()];
const depositHoursMinutes = function (): {
@ -41,6 +44,32 @@ export const PublicWaitPrompt = ({
return dict;
};
const numSimilarOrders = useMemo((): number => {
const orders = Object.values(federation.book) ?? [];
const similarOrders = orders.filter(bookOrder =>
// Public -> 1
bookOrder?.currency == order.currency && order.status == 1
);
return similarOrders.length;
}, [federationUpdatedAt]);
const premiumPercentile = useMemo((): number => {
const orders = Object.values(federation.book) ?? [];
const querySet = orders.filter(bookOrder =>
bookOrder?.currency == order.currency && order.status == 1 && bookOrder.type == order.type
);
if (querySet.length <= 1) {
return 0.5;
}
const premiums = querySet.map(similarOrder => parseFloat(similarOrder?.premium ?? '0'));
const sumPremium = premiums.reduce((sum, rate) => sum + (rate < order.premium ? 1 : 0), 0);
const percentile = (sumPremium / premiums.length) * 100;
return Math.floor(parseFloat(percentile.toFixed(2)));
}, [federationUpdatedAt]);
return (
<List dense={true}>
<Divider />
@ -69,7 +98,7 @@ export const PublicWaitPrompt = ({
<Storefront />
</ListItemIcon>
<ListItemText
primary={order.num_similar_orders}
primary={numSimilarOrders}
secondary={t('Public orders for {{currencyCode}}', {
currencyCode,
})}
@ -102,7 +131,7 @@ export const PublicWaitPrompt = ({
<Percent />
</ListItemIcon>
<ListItemText
primary={`${t('Premium rank')} ${Math.floor(order.premium_percentile * 100)}%`}
primary={`${t('Premium rank')} ${premiumPercentile}%`}
secondary={t('Among public {{currencyCode}} orders (higher is cheaper)', {
currencyCode,
})}

View File

@ -87,8 +87,6 @@ class Order {
longitude: number = 0;
password: string | undefined = undefined;
premium_now: number | undefined = undefined;
premium_percentile: number = 0;
num_similar_orders: number = 0;
tg_enabled: boolean = false; // deprecated
tg_token: string = '';
tg_bot_name: string = '';