Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa03ed912c | ||
|
|
505399dac1 | ||
|
|
b9d3a1d626 |
@@ -7,7 +7,7 @@ import {
|
||||
NetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { useMarketList } from '@vegaprotocol/markets';
|
||||
import { formatNumber } from '@vegaprotocol/utils';
|
||||
import { formatNumber, formatNumberRounded } from '@vegaprotocol/utils';
|
||||
import { useDiscountProgramsQuery, useFeesQuery } from './__generated__/Fees';
|
||||
import { FeeCard } from './fees-card';
|
||||
import { MarketFees } from './market-fees';
|
||||
@@ -284,7 +284,7 @@ export const CurrentVolume = ({
|
||||
return (
|
||||
<div>
|
||||
<Stat
|
||||
value={formatNumber(windowLengthVolume)}
|
||||
value={formatNumberRounded(new BigNumber(windowLengthVolume))}
|
||||
text={t('Past %s epochs', epochs.toString())}
|
||||
/>
|
||||
{requiredForNextTier > 0 && (
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { getAdjustedFee } from './utils';
|
||||
|
||||
describe('getAdjustedFee', () => {
|
||||
it('simple', () => {
|
||||
const volumeDiscount = 0.5;
|
||||
const referralDiscount = 0.5;
|
||||
|
||||
const infraFee = 0.1;
|
||||
const makerFee = 0.1;
|
||||
const liqFee = 0.1;
|
||||
|
||||
const fees = [
|
||||
new BigNumber(infraFee),
|
||||
new BigNumber(makerFee),
|
||||
new BigNumber(liqFee),
|
||||
];
|
||||
|
||||
const discounts = [
|
||||
new BigNumber(volumeDiscount),
|
||||
new BigNumber(referralDiscount),
|
||||
];
|
||||
|
||||
// 1 - 0.5 - 0.5
|
||||
const v = new BigNumber(1).minus(new BigNumber(volumeDiscount));
|
||||
|
||||
// 1 - 0.5 = 0.5
|
||||
const r = new BigNumber(1).minus(new BigNumber(referralDiscount));
|
||||
|
||||
// 0.5 * 0.5 = 0.25
|
||||
// 1 - 0.25 = 0.75
|
||||
const factor = new BigNumber(1).minus(v.times(r));
|
||||
|
||||
// 0.1 + 0.1 + 0.1 = 0.3
|
||||
const totalFees = fees.reduce((sum, x) => sum.plus(x), new BigNumber(0));
|
||||
|
||||
// 0.3 * 0.75 = 0.225
|
||||
const expected = new BigNumber(totalFees).times(factor).toNumber();
|
||||
|
||||
expect(getAdjustedFee(fees, discounts)).toBe(expected);
|
||||
});
|
||||
|
||||
it('combines discount factors multiplicativly', () => {
|
||||
const volumeDiscount = 0.4;
|
||||
const referralDiscount = 0.1;
|
||||
|
||||
const infraFee = 0.0005;
|
||||
const makerFee = 0.0002;
|
||||
const liqFee = 0.01;
|
||||
|
||||
const fees = [
|
||||
new BigNumber(infraFee),
|
||||
new BigNumber(makerFee),
|
||||
new BigNumber(liqFee),
|
||||
];
|
||||
|
||||
const discounts = [
|
||||
new BigNumber(volumeDiscount),
|
||||
new BigNumber(referralDiscount),
|
||||
];
|
||||
|
||||
// formula for calculating adjusted fees
|
||||
const v = new BigNumber(1).minus(new BigNumber(volumeDiscount));
|
||||
const r = new BigNumber(1).minus(new BigNumber(referralDiscount));
|
||||
const factor = new BigNumber(1).minus(v.times(r));
|
||||
|
||||
// summed fees
|
||||
const totalFees = fees.reduce((sum, x) => sum.plus(x), new BigNumber(0));
|
||||
|
||||
const expected = new BigNumber(totalFees).times(factor).toNumber();
|
||||
|
||||
expect(getAdjustedFee(fees, discounts)).toBe(expected);
|
||||
});
|
||||
});
|
||||
@@ -88,14 +88,18 @@ export const getReferralBenefitTier = (
|
||||
/**
|
||||
* Given a set of fees and a set of discounts return
|
||||
* the adjusted fee factor
|
||||
*
|
||||
* Formula for calculating the adjusted fees
|
||||
* total_discount_factor = 1 - (1 - volumeDiscount) * (1 - referralDiscount)
|
||||
*/
|
||||
export const getAdjustedFee = (fees: BigNumber[], discounts: BigNumber[]) => {
|
||||
const totalFee = fees.reduce((sum, f) => sum.plus(f), new BigNumber(0));
|
||||
const totalDiscount = discounts.reduce(
|
||||
(sum, d) => sum.plus(d),
|
||||
new BigNumber(0)
|
||||
);
|
||||
return totalFee
|
||||
.times(BigNumber.max(0, new BigNumber(1).minus(totalDiscount)))
|
||||
.toNumber();
|
||||
|
||||
const combinedFactors = discounts.reduce((acc, d) => {
|
||||
return acc.times(new BigNumber(1).minus(d));
|
||||
}, new BigNumber(1));
|
||||
|
||||
const totalFactor = new BigNumber(1).minus(combinedFactors);
|
||||
|
||||
return totalFee.times(BigNumber.max(0, totalFactor)).toNumber();
|
||||
};
|
||||
|
||||
@@ -32,7 +32,6 @@ export const CandlesChartContainer = ({
|
||||
overlays,
|
||||
studies,
|
||||
studySizes,
|
||||
showOrders,
|
||||
setStudies,
|
||||
setStudySizes,
|
||||
setOverlays,
|
||||
@@ -48,8 +47,8 @@ export const CandlesChartContainer = ({
|
||||
);
|
||||
|
||||
const dataSource = useMemo(() => {
|
||||
return new VegaDataSource(client, marketId, pubKey, showOrders);
|
||||
}, [client, marketId, pubKey, showOrders]);
|
||||
return new VegaDataSource(client, marketId, pubKey);
|
||||
}, [client, marketId, pubKey]);
|
||||
|
||||
return (
|
||||
<AutoSizer>
|
||||
|
||||
@@ -38,26 +38,21 @@ export const CandlesMenu = () => {
|
||||
chartType,
|
||||
studies,
|
||||
overlays,
|
||||
showOrders,
|
||||
setInterval,
|
||||
setType,
|
||||
setStudies,
|
||||
setOverlays,
|
||||
setShowOrders,
|
||||
} = useCandlesChartSettings();
|
||||
const triggerClasses = 'text-xs';
|
||||
const contentAlign = 'end';
|
||||
const buttonProps = { size: 'extra-small' } as const;
|
||||
const triggerButtonProps = { size: 'extra-small' } as const;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TradingButton {...buttonProps} onClick={() => setShowOrders()}>
|
||||
{showOrders ? t('Hide orders') : t('Show orders')}
|
||||
</TradingButton>
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...buttonProps}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t(`Interval: ${intervalLabels[interval]}`)}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
@@ -86,7 +81,7 @@ export const CandlesMenu = () => {
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...buttonProps}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
<Icon name={chartTypeIcon.get(chartType) as IconName} />
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
@@ -111,7 +106,9 @@ export const CandlesMenu = () => {
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...buttonProps}>{t('Overlays')}</TradingButton>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t('Overlays')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
@@ -140,7 +137,9 @@ export const CandlesMenu = () => {
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...buttonProps}>{t('Studies')}</TradingButton>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t('Studies')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -6,19 +6,10 @@ import {
|
||||
differenceInHours,
|
||||
differenceInMinutes,
|
||||
} from 'date-fns';
|
||||
import type {
|
||||
Candle,
|
||||
DataSource,
|
||||
LabelAnnotation,
|
||||
PriceMonitoringBounds,
|
||||
} from 'pennant';
|
||||
import type { Candle, DataSource, PriceMonitoringBounds } from 'pennant';
|
||||
import { Interval as PennantInterval } from 'pennant';
|
||||
|
||||
import {
|
||||
addDecimal,
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { addDecimal } from '@vegaprotocol/utils';
|
||||
import { ChartDocument } from './__generated__/Chart';
|
||||
import type { ChartQuery, ChartQueryVariables } from './__generated__/Chart';
|
||||
import {
|
||||
@@ -34,15 +25,6 @@ import type {
|
||||
} from './__generated__/Candles';
|
||||
import type { Subscription } from 'zen-observable-ts';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { OrdersUpdateDocument } from '@vegaprotocol/orders';
|
||||
|
||||
import type {
|
||||
OrderFieldsFragment,
|
||||
OrderUpdateFieldsFragment,
|
||||
OrdersUpdateSubscription,
|
||||
OrdersUpdateSubscriptionVariables,
|
||||
} from '@vegaprotocol/orders';
|
||||
import { filterOrderUpdates } from '@vegaprotocol/orders';
|
||||
|
||||
const INTERVAL_TO_PENNANT_MAP = {
|
||||
[PennantInterval.I1M]: Schema.Interval.INTERVAL_I1M,
|
||||
@@ -65,12 +47,6 @@ const defaultConfig = {
|
||||
],
|
||||
};
|
||||
|
||||
const ACTIVE_ORDER_STATUSES = [
|
||||
Schema.OrderStatus.STATUS_ACTIVE,
|
||||
Schema.OrderStatus.STATUS_PARKED,
|
||||
];
|
||||
const ORDER_MAX = 5;
|
||||
|
||||
/**
|
||||
* A data access object that provides access to the Vega GraphQL API.
|
||||
*/
|
||||
@@ -80,11 +56,8 @@ export class VegaDataSource implements DataSource {
|
||||
partyId: null | string;
|
||||
_decimalPlaces = 0;
|
||||
_positionDecimalPlaces = 0;
|
||||
orders: OrderUpdateFieldsFragment[] = [];
|
||||
|
||||
candlesSub: Subscription | null = null;
|
||||
ordersSub: Subscription | null = null;
|
||||
showOrders = false;
|
||||
|
||||
/**
|
||||
* Indicates the number of decimal places that an integer must be shifted by in order to get a correct
|
||||
@@ -111,13 +84,11 @@ export class VegaDataSource implements DataSource {
|
||||
constructor(
|
||||
client: ApolloClient<object>,
|
||||
marketId: string,
|
||||
partyId: null | string = null,
|
||||
showOrders = false
|
||||
partyId: null | string = null
|
||||
) {
|
||||
this.client = client;
|
||||
this.marketId = marketId;
|
||||
this.partyId = partyId;
|
||||
this.showOrders = showOrders;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,7 +166,6 @@ export class VegaDataSource implements DataSource {
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
|
||||
if (data?.market?.candlesConnection?.edges) {
|
||||
const decimalPlaces = data.market.decimalPlaces;
|
||||
const positionDecimalPlaces = data.market.positionDecimalPlaces;
|
||||
@@ -253,65 +223,6 @@ export class VegaDataSource implements DataSource {
|
||||
unsubscribeData() {
|
||||
this.candlesSub && this.candlesSub.unsubscribe();
|
||||
}
|
||||
|
||||
async subscribeAnnotations(
|
||||
onSubscriptionAnnotations: (annotations: LabelAnnotation[]) => void
|
||||
) {
|
||||
if (this.partyId === null) {
|
||||
return;
|
||||
}
|
||||
if (!this.showOrders) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sub = this.client.subscribe<
|
||||
OrdersUpdateSubscription,
|
||||
OrdersUpdateSubscriptionVariables
|
||||
>({
|
||||
query: OrdersUpdateDocument,
|
||||
variables: {
|
||||
marketIds: [this.marketId],
|
||||
partyId: this.partyId,
|
||||
},
|
||||
});
|
||||
|
||||
// subscription will return a snap shot of curernt orders immediately
|
||||
// and then subsequent messages will be sent for updates
|
||||
this.ordersSub = sub.subscribe(({ data }) => {
|
||||
if (!data?.orders?.length) return;
|
||||
|
||||
const orders = filterOrderUpdates(data.orders);
|
||||
|
||||
for (const order of orders) {
|
||||
const index = this.orders.findIndex((o) => o.id === order.id) ?? -1;
|
||||
const isActive = ACTIVE_ORDER_STATUSES.includes(order.status);
|
||||
|
||||
if (index !== -1) {
|
||||
if (isActive) {
|
||||
this.orders[index] = order;
|
||||
} else {
|
||||
this.orders.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
this.orders.unshift(order);
|
||||
|
||||
if (this.orders.length > ORDER_MAX) {
|
||||
this.orders.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSubscriptionAnnotations(
|
||||
this.orders.map((o) =>
|
||||
createOrderLabel(o, this.decimalPlaces, this.positionDecimalPlaces)
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
unsubscribeAnnotations(): void {
|
||||
this.ordersSub && this.ordersSub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
const getDuration = (
|
||||
@@ -407,55 +318,3 @@ function parseCandle(
|
||||
volume: Number(addDecimal(candle.volume, positionDecimalPlaces)),
|
||||
};
|
||||
}
|
||||
|
||||
function createOrderLabel(
|
||||
order: OrderFieldsFragment | OrderUpdateFieldsFragment,
|
||||
decimalPlaces: number,
|
||||
positionDecimalPlaces: number
|
||||
): LabelAnnotation {
|
||||
const type = order.type ? Schema.OrderTypeMapping[order.type] : '-';
|
||||
const price = addDecimalsFormatNumber(order.price, decimalPlaces);
|
||||
|
||||
const prefix = order ? (order.side === Schema.Side.SIDE_BUY ? '+' : '-') : '';
|
||||
|
||||
const size =
|
||||
prefix + addDecimalsFormatNumber(order.size, positionDecimalPlaces);
|
||||
|
||||
let timeInForce = '-';
|
||||
|
||||
if (
|
||||
order.timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT &&
|
||||
order?.expiresAt
|
||||
) {
|
||||
const expiry = getDateTimeFormat().format(new Date(order.expiresAt));
|
||||
timeInForce = `${
|
||||
Schema.OrderTimeInForceCode[order.timeInForce]
|
||||
}: ${expiry}`;
|
||||
} else {
|
||||
timeInForce = order.timeInForce
|
||||
? Schema.OrderTimeInForceCode[order.timeInForce]
|
||||
: '';
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'label',
|
||||
id: order.id,
|
||||
cells: [
|
||||
{ label: type },
|
||||
{
|
||||
label: timeInForce,
|
||||
},
|
||||
{
|
||||
label: price,
|
||||
numeric: true,
|
||||
},
|
||||
{
|
||||
label: size,
|
||||
numeric: true,
|
||||
stroke: true,
|
||||
},
|
||||
],
|
||||
intent: order.side === Schema.Side.SIDE_BUY ? 'success' : 'danger',
|
||||
y: Number(addDecimal(order.price, decimalPlaces)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ interface StoredSettings {
|
||||
overlays: Overlay[];
|
||||
studies: Study[];
|
||||
studySizes: StudySizes;
|
||||
showOrders: boolean;
|
||||
}
|
||||
|
||||
export const STUDY_SIZE = 90;
|
||||
@@ -31,7 +30,6 @@ const DEFAULT_CHART_SETTINGS = {
|
||||
overlays: [Overlay.MOVING_AVERAGE],
|
||||
studies: [Study.MACD, Study.VOLUME],
|
||||
studySizes: {},
|
||||
showOrders: true,
|
||||
};
|
||||
|
||||
export const useCandlesChartSettingsStore = create<
|
||||
@@ -41,7 +39,6 @@ export const useCandlesChartSettingsStore = create<
|
||||
setOverlays: (overlays?: Overlay[]) => void;
|
||||
setStudies: (studies?: Study[]) => void;
|
||||
setStudySizes: (sizes: number[]) => void;
|
||||
setShowOrders: (show?: boolean) => void;
|
||||
}
|
||||
>()(
|
||||
persist(
|
||||
@@ -84,11 +81,6 @@ export const useCandlesChartSettingsStore = create<
|
||||
});
|
||||
});
|
||||
},
|
||||
setShowOrders: (show) => {
|
||||
set((state) => {
|
||||
state.showOrders = show === undefined ? !state.showOrders : show;
|
||||
});
|
||||
},
|
||||
})),
|
||||
{
|
||||
name: 'vega_candles_chart_store',
|
||||
@@ -135,12 +127,10 @@ export const useCandlesChartSettings = () => {
|
||||
overlays,
|
||||
studies,
|
||||
studySizes,
|
||||
showOrders: settings.showOrders,
|
||||
setInterval: settings.setInterval,
|
||||
setType: settings.setType,
|
||||
setStudies: settings.setStudies,
|
||||
setOverlays: settings.setOverlays,
|
||||
setStudySizes: settings.setStudySizes,
|
||||
setShowOrders: settings.setShowOrders,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -179,3 +179,33 @@ export const toNumberParts = (
|
||||
export const isNumeric = (
|
||||
value?: string | number | BigNumber | bigint | null
|
||||
): value is NonNullable<number | string> => /^-?\d*\.?\d+$/.test(String(value));
|
||||
|
||||
/**
|
||||
* Format a number greater than 1 million with m for million, b for billion
|
||||
* and t for trillion
|
||||
*/
|
||||
export const formatNumberRounded = (num: BigNumber) => {
|
||||
let value = '';
|
||||
|
||||
const format = (divisor: string) => {
|
||||
const result = num.dividedBy(divisor);
|
||||
return result.isInteger() ? result.toString() : result.toFixed(1);
|
||||
};
|
||||
|
||||
if (num.isGreaterThan(new BigNumber('1e14'))) {
|
||||
value = '>100t';
|
||||
} else if (num.isGreaterThanOrEqualTo(new BigNumber('1e12'))) {
|
||||
// Trillion
|
||||
value = `${format('1e12')}t`;
|
||||
} else if (num.isGreaterThanOrEqualTo(new BigNumber('1e9'))) {
|
||||
// Billion
|
||||
value = `${format('1e9')}b`;
|
||||
} else if (num.isGreaterThanOrEqualTo(new BigNumber('1e6'))) {
|
||||
// Million
|
||||
value = `${format('1e6')}m`;
|
||||
} else {
|
||||
value = formatNumber(num);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user