diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx index e82b38929..df65f4183 100644 --- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx +++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx @@ -1003,7 +1003,7 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => { name="oco" label={ {t('One cancels another')}} + description={{t('One cancels the other')}} > <>{t('OCO')} diff --git a/libs/deal-ticket/src/hooks/use-form-values.ts b/libs/deal-ticket/src/hooks/use-form-values.ts index aeb7c09be..93952d599 100644 --- a/libs/deal-ticket/src/hooks/use-form-values.ts +++ b/libs/deal-ticket/src/hooks/use-form-values.ts @@ -31,13 +31,13 @@ export interface StopOrderFormValues { oco?: boolean; - ocoTriggerType?: 'price' | 'trailingPercentOffset'; + ocoTriggerType: 'price' | 'trailingPercentOffset'; ocoTriggerPrice?: string; ocoTriggerTrailingPercentOffset?: string; - ocoType?: OrderType; - ocoSize?: string; - ocoTimeInForce?: OrderTimeInForce; + ocoType: OrderType; + ocoSize: string; + ocoTimeInForce: OrderTimeInForce; ocoPrice?: string; } diff --git a/libs/deal-ticket/src/utils/map-form-values-to-submission.ts b/libs/deal-ticket/src/utils/map-form-values-to-submission.ts index 5a1d0ea82..efaee17f5 100644 --- a/libs/deal-ticket/src/utils/map-form-values-to-submission.ts +++ b/libs/deal-ticket/src/utils/map-form-values-to-submission.ts @@ -112,7 +112,7 @@ export const mapFormValuesToStopOrdersSubmission = ( decimalPlaces ); let oppositeStopOrderSetup: StopOrderSetup | undefined = undefined; - if (data.oco && data.ocoType && data.ocoSize && data.ocoTimeInForce) { + if (data.oco) { oppositeStopOrderSetup = { orderSubmission: mapFormValuesToOrderSubmission( { diff --git a/libs/deal-ticket/src/utils/map-form-values-to-submisstion.spec.ts b/libs/deal-ticket/src/utils/map-form-values-to-submisstion.spec.ts index a8ecc5560..548ddfa89 100644 --- a/libs/deal-ticket/src/utils/map-form-values-to-submisstion.spec.ts +++ b/libs/deal-ticket/src/utils/map-form-values-to-submisstion.spec.ts @@ -1,8 +1,12 @@ import type { OrderSubmissionBody } from '@vegaprotocol/wallet'; -import { mapFormValuesToOrderSubmission } from './map-form-values-to-submission'; +import { + mapFormValuesToOrderSubmission, + mapFormValuesToTakeProfitAndStopLoss, +} from './map-form-values-to-submission'; import * as Schema from '@vegaprotocol/types'; import { OrderTimeInForce, OrderType } from '@vegaprotocol/types'; import type { OrderFormValues } from '../hooks'; +import { type MarketFieldsFragment } from '@vegaprotocol/markets'; describe('mapFormValuesToOrderSubmission', () => { it('sets and formats price only for limit orders', () => { @@ -186,3 +190,239 @@ describe('mapFormValuesToOrderSubmission', () => { } ); }); + +const market: MarketFieldsFragment = { + __typename: 'Market', + id: 'marketId', + decimalPlaces: 1, + positionDecimalPlaces: 4, + state: Schema.MarketState.STATE_ACTIVE, + tradingMode: Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS, + fees: { + __typename: 'Fees', + factors: { + __typename: 'FeeFactors', + makerFee: '0.0002', + infrastructureFee: '0.0005', + liquidityFee: '0.0001', + }, + liquidityFeeSettings: { + __typename: 'LiquidityFeeSettings', + feeConstant: null, + method: Schema.LiquidityFeeMethod.METHOD_MARGINAL_COST, + }, + }, + tradableInstrument: { + __typename: 'TradableInstrument', + instrument: { + __typename: 'Instrument', + id: '', + name: 'Bitcoin / Tether USD (Perpetual)', + code: 'BTC/USDT', + metadata: { + __typename: 'InstrumentMetadata', + tags: [ + 'base:BTC', + 'quote:USDT', + 'oracle:pyth', + 'oracleChain:gnosis', + 'class:fx/crypto', + 'perpetual', + 'sector:defi', + 'enactment:2023-12-01T18:00:00Z', + ], + }, + product: { + __typename: 'Perpetual', + quoteName: 'USDT', + fundingRateScalingFactor: '1', + fundingRateLowerBound: '-0.001', + fundingRateUpperBound: '0.001', + settlementAsset: { + __typename: 'Asset', + id: '8ba0b10971f0c4747746cd01ff05a53ae75ca91eba1d4d050b527910c983e27e', + symbol: 'USDT', + name: 'Tether USD', + decimals: 6, + quantum: '1000000', + }, + dataSourceSpecForSettlementData: { + __typename: 'DataSourceSpec', + id: '6c3df6eb28ff3e7db9e6b2b27c62f43b30673df4837ba5e54814063527228939', + data: { + __typename: 'DataSourceDefinition', + sourceType: { + __typename: 'DataSourceDefinitionExternal', + sourceType: { + __typename: 'EthCallSpec', + abi: [ + '[{"inputs": [{"internalType": "bytes32", "name": "id", "type": "bytes32"}], "name": "getPrice", "outputs": [{"internalType": "int256", "name": "", "type": "int256" }], "stateMutability": "view", "type": "function"}]', + ], + address: '0x719abd606155442c21b7d561426d42bd0e40a776', + args: ['"5i32yLSoX+GmfbRNwS3l2zMPesZrctxliv7fD0pBW0M="'], + method: 'getPrice', + requiredConfirmations: 3, + normalisers: [ + { + __typename: 'Normaliser', + name: 'btc.price', + expression: '$[0]', + }, + ], + trigger: { + __typename: 'EthCallTrigger', + trigger: { + __typename: 'EthTimeTrigger', + initial: '2024-02-23T21:44:12Z', + every: 60, + until: null, + }, + }, + filters: [ + { + __typename: 'Filter', + key: { + __typename: 'PropertyKey', + name: 'btc.price', + type: Schema.PropertyKeyType.TYPE_INTEGER, + numberDecimalPlaces: 18, + }, + conditions: [ + { + __typename: 'Condition', + value: '0', + operator: + Schema.ConditionOperator.OPERATOR_GREATER_THAN, + }, + ], + }, + ], + }, + }, + }, + }, + dataSourceSpecForSettlementSchedule: { + __typename: 'DataSourceSpec', + id: '5c45c686b8cf0b5bd85a5c39608cbec3d0e6e49a318264eaaf8c3f45d37255dc', + data: { + __typename: 'DataSourceDefinition', + sourceType: { + __typename: 'DataSourceDefinitionInternal', + sourceType: { + __typename: 'DataSourceSpecConfigurationTimeTrigger', + triggers: [ + { + __typename: 'InternalTimeTrigger', + initial: 1708724652, + every: 28800, + }, + ], + conditions: [ + { + __typename: 'Condition', + operator: Schema.ConditionOperator.OPERATOR_GREATER_THAN, + value: '0', + }, + ], + }, + }, + }, + }, + dataSourceSpecBinding: { + __typename: 'DataSourceSpecPerpetualBinding', + settlementDataProperty: 'btc.price', + settlementScheduleProperty: 'vegaprotocol.builtin.timetrigger', + }, + }, + }, + }, + marketTimestamps: { + __typename: 'MarketTimestamps', + open: '2024-02-16T20:25:45.340803198Z', + close: null, + }, +}; + +const orderFormValues: OrderFormValues = { + type: OrderType.TYPE_LIMIT, + side: Schema.Side.SIDE_BUY, + timeInForce: OrderTimeInForce.TIME_IN_FORCE_GTC, + size: '1', + price: '66300', + postOnly: false, + reduceOnly: false, + iceberg: false, + tpSl: true, + takeProfit: '70000', + stopLoss: '60000', +}; + +describe('mapFormValuesToTakeProfitAndStopLoss', () => { + it('creates batch market instructions for a normal order created with TP and SL', () => { + const result = mapFormValuesToTakeProfitAndStopLoss( + orderFormValues, + market, + 'reference' + ); + + const expected = { + stopOrdersSubmission: [ + { + fallsBelow: undefined, + risesAbove: { + orderSubmission: { + expiresAt: undefined, + icebergOpts: undefined, + marketId: 'marketId', + postOnly: false, + price: undefined, + reduceOnly: true, + reference: 'reference', + side: 'SIDE_SELL', + size: '10000', + timeInForce: 'TIME_IN_FORCE_FOK', + type: 'TYPE_MARKET', + }, + price: '700000', + }, + }, + { + fallsBelow: { + orderSubmission: { + expiresAt: undefined, + icebergOpts: undefined, + marketId: 'marketId', + postOnly: false, + price: undefined, + reduceOnly: false, + reference: 'reference', + side: 'SIDE_SELL', + size: '10000', + timeInForce: 'TIME_IN_FORCE_GTC', + type: 'TYPE_MARKET', + }, + price: '600000', + }, + risesAbove: undefined, + }, + ], + submissions: [ + { + expiresAt: undefined, + icebergOpts: undefined, + marketId: 'marketId', + postOnly: false, + price: '663000', + reduceOnly: false, + reference: 'reference', + side: 'SIDE_BUY', + size: '10000', + timeInForce: 'TIME_IN_FORCE_GTC', + type: 'TYPE_LIMIT', + }, + ], + }; + + expect(result).toStrictEqual(expected); + }); +}); diff --git a/libs/i18n/src/locales/en/deal-ticket.json b/libs/i18n/src/locales/en/deal-ticket.json index ebf37c430..31f39c8ec 100644 --- a/libs/i18n/src/locales/en/deal-ticket.json +++ b/libs/i18n/src/locales/en/deal-ticket.json @@ -66,7 +66,7 @@ "Notional": "Notional", "NOTIONAL_SIZE_TOOLTIP_TEXT": "The notional size represents the position size in the settlement asset {{quoteName}} of the futures contract. This is calculated by multiplying the number of contracts by the prices of the contract. For example 10 contracts traded at a price of $50 has a notional size of $500.", "OCO": "OCO", - "One cancels another": "One cancels another", + "One cancels the other": "One cancels the other", "Only limit orders are permitted when market is in auction": "Only limit orders are permitted when market is in auction", "Only your allocated margin will be used to fund this position, and if the maintenance margin is breached you will be closed out.": "Only your allocated margin will be used to fund this position, and if the maintenance margin is breached you will be closed out.", "You have an existing position on this market.": "You have an existing position on this market.",