Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38f6f75ef9 | ||
|
|
eb4b82b6d2 | ||
|
|
05391a13c6 | ||
|
|
acd6211912 | ||
|
|
e1cc899fa7 | ||
|
|
ac6c3a6ba2 |
@@ -7,7 +7,7 @@ import {
|
||||
NetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { useMarketList } from '@vegaprotocol/markets';
|
||||
import { formatNumber, formatNumberRounded } from '@vegaprotocol/utils';
|
||||
import { formatNumber } 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={formatNumberRounded(new BigNumber(windowLengthVolume))}
|
||||
value={formatNumber(windowLengthVolume)}
|
||||
text={t('Past %s epochs', epochs.toString())}
|
||||
/>
|
||||
{requiredForNextTier > 0 && (
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
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,18 +88,14 @@ 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 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();
|
||||
const totalDiscount = discounts.reduce(
|
||||
(sum, d) => sum.plus(d),
|
||||
new BigNumber(0)
|
||||
);
|
||||
return totalFee
|
||||
.times(BigNumber.max(0, new BigNumber(1).minus(totalDiscount)))
|
||||
.toNumber();
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@ export const CandlesChartContainer = ({
|
||||
overlays,
|
||||
studies,
|
||||
studySizes,
|
||||
showOrders,
|
||||
setStudies,
|
||||
setStudySizes,
|
||||
setOverlays,
|
||||
@@ -47,8 +48,8 @@ export const CandlesChartContainer = ({
|
||||
);
|
||||
|
||||
const dataSource = useMemo(() => {
|
||||
return new VegaDataSource(client, marketId, pubKey);
|
||||
}, [client, marketId, pubKey]);
|
||||
return new VegaDataSource(client, marketId, pubKey, showOrders);
|
||||
}, [client, marketId, pubKey, showOrders]);
|
||||
|
||||
return (
|
||||
<AutoSizer>
|
||||
|
||||
@@ -38,21 +38,26 @@ export const CandlesMenu = () => {
|
||||
chartType,
|
||||
studies,
|
||||
overlays,
|
||||
showOrders,
|
||||
setInterval,
|
||||
setType,
|
||||
setStudies,
|
||||
setOverlays,
|
||||
setShowOrders,
|
||||
} = useCandlesChartSettings();
|
||||
const triggerClasses = 'text-xs';
|
||||
const contentAlign = 'end';
|
||||
const triggerButtonProps = { size: 'extra-small' } as const;
|
||||
const buttonProps = { size: 'extra-small' } as const;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TradingButton {...buttonProps} onClick={() => setShowOrders()}>
|
||||
{showOrders ? t('Hide orders') : t('Show orders')}
|
||||
</TradingButton>
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
<TradingButton {...buttonProps}>
|
||||
{t(`Interval: ${intervalLabels[interval]}`)}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
@@ -81,7 +86,7 @@ export const CandlesMenu = () => {
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
<TradingButton {...buttonProps}>
|
||||
<Icon name={chartTypeIcon.get(chartType) as IconName} />
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
@@ -106,9 +111,7 @@ export const CandlesMenu = () => {
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t('Overlays')}
|
||||
</TradingButton>
|
||||
<TradingButton {...buttonProps}>{t('Overlays')}</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
@@ -137,9 +140,7 @@ export const CandlesMenu = () => {
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t('Studies')}
|
||||
</TradingButton>
|
||||
<TradingButton {...buttonProps}>{t('Studies')}</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -6,10 +6,19 @@ import {
|
||||
differenceInHours,
|
||||
differenceInMinutes,
|
||||
} from 'date-fns';
|
||||
import type { Candle, DataSource, PriceMonitoringBounds } from 'pennant';
|
||||
import type {
|
||||
Candle,
|
||||
DataSource,
|
||||
LabelAnnotation,
|
||||
PriceMonitoringBounds,
|
||||
} from 'pennant';
|
||||
import { Interval as PennantInterval } from 'pennant';
|
||||
|
||||
import { addDecimal } from '@vegaprotocol/utils';
|
||||
import {
|
||||
addDecimal,
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { ChartDocument } from './__generated__/Chart';
|
||||
import type { ChartQuery, ChartQueryVariables } from './__generated__/Chart';
|
||||
import {
|
||||
@@ -25,6 +34,15 @@ 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,
|
||||
@@ -47,6 +65,12 @@ 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.
|
||||
*/
|
||||
@@ -56,8 +80,11 @@ 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
|
||||
@@ -84,11 +111,13 @@ export class VegaDataSource implements DataSource {
|
||||
constructor(
|
||||
client: ApolloClient<object>,
|
||||
marketId: string,
|
||||
partyId: null | string = null
|
||||
partyId: null | string = null,
|
||||
showOrders = false
|
||||
) {
|
||||
this.client = client;
|
||||
this.marketId = marketId;
|
||||
this.partyId = partyId;
|
||||
this.showOrders = showOrders;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,6 +195,7 @@ export class VegaDataSource implements DataSource {
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
|
||||
if (data?.market?.candlesConnection?.edges) {
|
||||
const decimalPlaces = data.market.decimalPlaces;
|
||||
const positionDecimalPlaces = data.market.positionDecimalPlaces;
|
||||
@@ -223,6 +253,65 @@ 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 = (
|
||||
@@ -318,3 +407,55 @@ 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,6 +13,7 @@ interface StoredSettings {
|
||||
overlays: Overlay[];
|
||||
studies: Study[];
|
||||
studySizes: StudySizes;
|
||||
showOrders: boolean;
|
||||
}
|
||||
|
||||
export const STUDY_SIZE = 90;
|
||||
@@ -30,6 +31,7 @@ const DEFAULT_CHART_SETTINGS = {
|
||||
overlays: [Overlay.MOVING_AVERAGE],
|
||||
studies: [Study.MACD, Study.VOLUME],
|
||||
studySizes: {},
|
||||
showOrders: true,
|
||||
};
|
||||
|
||||
export const useCandlesChartSettingsStore = create<
|
||||
@@ -39,6 +41,7 @@ export const useCandlesChartSettingsStore = create<
|
||||
setOverlays: (overlays?: Overlay[]) => void;
|
||||
setStudies: (studies?: Study[]) => void;
|
||||
setStudySizes: (sizes: number[]) => void;
|
||||
setShowOrders: (show?: boolean) => void;
|
||||
}
|
||||
>()(
|
||||
persist(
|
||||
@@ -81,6 +84,11 @@ export const useCandlesChartSettingsStore = create<
|
||||
});
|
||||
});
|
||||
},
|
||||
setShowOrders: (show) => {
|
||||
set((state) => {
|
||||
state.showOrders = show === undefined ? !state.showOrders : show;
|
||||
});
|
||||
},
|
||||
})),
|
||||
{
|
||||
name: 'vega_candles_chart_store',
|
||||
@@ -127,10 +135,12 @@ 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,33 +179,3 @@ 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