mirror of
https://github.com/RoboSats/robosats.git
synced 2025-09-04 01:44:17 +00:00
Merge pull request #2164 from aftermath2/aggregate_orders_metadata
Replace API values with frontend orders aggregation
This commit is contained in:
@ -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",
|
||||
|
@ -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]
|
||||
|
27
api/utils.py
27
api/utils.py
@ -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]!
|
||||
|
20
api/views.py
20
api/views.py
@ -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"))
|
||||
)
|
||||
|
@ -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,
|
||||
})}
|
||||
|
@ -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 = '';
|
||||
|
Reference in New Issue
Block a user