Skip to content

Commit

Permalink
Akmal | Likhith / feat: vanilla financials (binary-com#8355)
Browse files Browse the repository at this point in the history
* Akmal / content updates for EU clients (#38)

* feat: convert vanilla options component

* Revert "Akmal / content updates for EU clients (#38)"

* fix: import from shared

* likhith / feat: ✨ incorporate vanilla financials

* fix: content changes for EU

* feat: add unit tests for vanilla options

* fix: reuse types

* fix: add empty line

* fix: remove commented line

* chore: refactor test cases

* chore: fix datatypes according to latest changes

* fix: datatypes warnings

* chore: fix formatting

* fix: return missing props

* feat: update infobox longcode description

* feat: remove all payout formatting for vanillas

* fix: circle ci warning

* fix: execute sell even if bid price is zero

* fix: show appropriate error message when trade falls on weekend

* chore: make use of useTraderStore hook

* feat: trigger vercel

* feat: trigger vercel

* chore: remove unused logic

* feat: content updates for vanilla financial markets

* fix: type

* fix: circle ci errors after merge

* fix: other circle ci issues

* feat: improve solution

* feat: refactor test cases

* feat: small improvements

* feat: trigger circle ci

* feat: trigger circle ci

* fix: code formatting

* fix: code formatting

* fix: merge conflict

* fix: review comments

* fix: mobile bug

* chore: format code

* chore: code suggestions

* chore: code suggestions

* fix: remove unused variable

* fix: data type

* fix: resolve issues after merge

* fix: circle ci issue

* feat: trigger circle ci

* feat: modify test cases

* chore: remove unused function

* feat: refactor vanilla fx logic for contract glossary and description

* fix: apply circle ci suggestions

* fix: add a key

* fix: remove unused variables

* feat: add a type

* fix: apply review suggestions

* fix: new label for trade categories

* fix: typescript types

* fix: issues after resolving conflicts

* fix: test case

* feat: refractor the code

* fix: circle ci warning

* feat: add observer

* fix: issues after merge

* chore: fix import

* fix: refactor logic after conflicts

* chore: resolve conflicts
  • Loading branch information
akmal-deriv committed Oct 25, 2023
1 parent d99a695 commit 55b29ba
Show file tree
Hide file tree
Showing 19 changed files with 363 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import { render, RenderResult } from '@testing-library/react';
import VanillaOptionsCardBody, { TVanillaOptionsCardBodyProps } from '../vanilla-options-card-body';

describe('VanillaOptionsCardBody', () => {
const mock_props: TVanillaOptionsCardBodyProps = {
contract_info: {
buy_price: 100,
bid_price: 105,
entry_spot_display_value: '110',
barrier: '120',
sell_price: 95,
profit: -5,
status: 'loss',
},
currency: 'USD',
getCardLabels: () => ({
CONTRACT_VALUE: 'Contract Value',
ENTRY_SPOT: 'Entry Spot',
PURCHASE_PRICE: 'Purchase Price',
STRIKE: 'Strike',
TOTAL_PROFIT_LOSS: 'Total Profit/Loss',
}),
is_sold: true,
progress_slider: null,
status: 'loss',
};
it('should render the correct content for a sold contract', async () => {
// Render the component with the provided props
const { getByText, getByTestId }: RenderResult = render(
<VanillaOptionsCardBody {...mock_props}/>
);

const indicative_movement = getByTestId('dc-contract-card__indicative--movement');

// Test that the correct elements are present in the component
expect(getByText(mock_props.getCardLabels().CONTRACT_VALUE)).toBeInTheDocument();
expect(getByText(mock_props.getCardLabels().ENTRY_SPOT)).toBeInTheDocument();
expect(getByText(mock_props.getCardLabels().PURCHASE_PRICE)).toBeInTheDocument();
expect(getByText(mock_props.getCardLabels().STRIKE)).toBeInTheDocument();
expect(getByText(mock_props.getCardLabels().TOTAL_PROFIT_LOSS)).toBeInTheDocument();
expect(indicative_movement).toHaveClass('dc-contract-card__indicative--movement-complete');
expect(indicative_movement.querySelector('.dc-icon.dc-icon--red')).toBeInTheDocument();
});

it('should render the correct content for an unsold contract', async () => {
mock_props.contract_info.profit = 5;
mock_props.contract_info.status = 'profit';
mock_props.is_sold = false;
mock_props.progress_slider = <div />;
mock_props.status = 'profit';
delete mock_props.contract_info.sell_price;


// Render the component with the provided props
const { getByText, getByTestId }: RenderResult = render(
<VanillaOptionsCardBody {...mock_props}/>
);

const indicative_movement = getByTestId('dc-contract-card__indicative--movement');

// Test that the correct elements are present in the component
expect(getByText(mock_props. getCardLabels().CONTRACT_VALUE)).toBeInTheDocument();
expect(getByText(mock_props. getCardLabels().ENTRY_SPOT)).toBeInTheDocument();
expect(getByText(mock_props. getCardLabels().PURCHASE_PRICE)).toBeInTheDocument();
expect(getByText(mock_props. getCardLabels().STRIKE)).toBeInTheDocument();
expect(getByText(mock_props. getCardLabels().TOTAL_PROFIT_LOSS)).toBeInTheDocument();
expect(indicative_movement).not.toHaveClass('dc-contract-card__indicative--movement-complete');
expect(indicative_movement.querySelector('.dc-icon.dc-icon--green')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import { getDisplayStatus, isCryptocurrency } from '@deriv/shared';
import { TContractInfo } from '@deriv/shared/src/utils/contract/contract-types';
import DesktopWrapper from '../../desktop-wrapper';
import MobileWrapper from '../../mobile-wrapper';
import ContractCardItem from './contract-card-item';
import Icon from '../../icon';
import Money from '../../money';
import { ResultStatusIcon } from '../result-overlay/result-overlay';
import { TGetCardLables } from '../../types';

const VanillaOptionsCardBody = ({ contract_info, currency, getCardLabels, is_sold, progress_slider, status }) => {
const { buy_price, bid_price, entry_spot_display_value, barrier, sell_price, profit } = contract_info;
export type TVanillaOptionsCardBodyProps = {
contract_info: TContractInfo;
currency: string;
getCardLabels: TGetCardLables;
is_sold: boolean;
progress_slider: React.ReactNode;
status?: string;
};

const VanillaOptionsCardBody: React.FC<TVanillaOptionsCardBodyProps> = ({
contract_info,
currency,
getCardLabels,
is_sold,
progress_slider,
status,
}) => {
const { buy_price, bid_price, entry_spot_display_value, barrier, sell_price, profit }: TContractInfo =
contract_info;
const contract_value = is_sold ? sell_price : bid_price;
const { CONTRACT_VALUE, ENTRY_SPOT, PURCHASE_PRICE, STRIKE, TOTAL_PROFIT_LOSS } = getCardLabels();

Expand All @@ -27,25 +45,26 @@ const VanillaOptionsCardBody = ({ contract_info, currency, getCardLabels, is_sol
</ContractCardItem>

<ContractCardItem header={ENTRY_SPOT}>
<Money amount={entry_spot_display_value} />
{entry_spot_display_value && <Money amount={entry_spot_display_value} />}
</ContractCardItem>

<ContractCardItem header={STRIKE}>
<Money amount={barrier} />
{barrier && <Money amount={barrier} />}
</ContractCardItem>
</div>
<ContractCardItem
className='dc-contract-card-item__total-profit-loss'
header={TOTAL_PROFIT_LOSS}
is_crypto={isCryptocurrency(currency)}
is_loss={+profit < 0}
is_won={+profit > 0}
is_loss={Number(profit) < 0}
is_won={Number(profit) > 0}
>
<Money amount={profit} currency={currency} />
<div
className={classNames('dc-contract-card__indicative--movement', {
'dc-contract-card__indicative--movement-complete': is_sold,
})}
data-testid='dc-contract-card__indicative--movement'
>
{status === 'profit' && <Icon icon='IcProfit' />}
{status === 'loss' && <Icon icon='IcLoss' />}
Expand All @@ -60,7 +79,9 @@ const VanillaOptionsCardBody = ({ contract_info, currency, getCardLabels, is_sol
</ContractCardItem>

<ContractCardItem header={ENTRY_SPOT}>
<Money amount={entry_spot_display_value} currency={currency} />
{entry_spot_display_value && (
<Money amount={entry_spot_display_value} currency={currency} />
)}
</ContractCardItem>
</div>

Expand All @@ -70,7 +91,7 @@ const VanillaOptionsCardBody = ({ contract_info, currency, getCardLabels, is_sol
</ContractCardItem>

<ContractCardItem header={STRIKE}>
<Money amount={barrier} currency={currency} />
{barrier && <Money amount={barrier} currency={currency} />}
</ContractCardItem>
</div>

Expand All @@ -86,14 +107,15 @@ const VanillaOptionsCardBody = ({ contract_info, currency, getCardLabels, is_sol
className='dc-contract-card-item__total-profit-loss'
header={TOTAL_PROFIT_LOSS}
is_crypto={isCryptocurrency(currency)}
is_loss={+profit < 0}
is_won={+profit > 0}
is_loss={Number(profit) < 0}
is_won={Number(profit) > 0}
>
<Money amount={profit} currency={currency} />
<div
className={classNames('dc-contract-card__indicative--movement', {
'dc-contract-card__indicative--movement-complete': is_sold,
})}
data-testid='dc-contract-card__indicative--movement-mobile'
>
{status === 'profit' && <Icon icon='IcProfit' />}
{status === 'loss' && <Icon icon='IcLoss' />}
Expand All @@ -105,13 +127,4 @@ const VanillaOptionsCardBody = ({ contract_info, currency, getCardLabels, is_sol
);
};

VanillaOptionsCardBody.propTypes = {
contract_info: PropTypes.object,
currency: PropTypes.string,
getCardLabels: PropTypes.func,
is_sold: PropTypes.bool,
progress_slider: PropTypes.node,
status: PropTypes.string,
};

export default React.memo(VanillaOptionsCardBody);
2 changes: 1 addition & 1 deletion packages/core/src/Stores/contract-replay-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ export default class ContractReplayStore extends BaseStore {

onClickSell(contract_id) {
const { bid_price } = this.contract_info;
if (contract_id && bid_price) {
if (contract_id && (bid_price || bid_price === 0)) {
this.is_sell_requested = true;
WS.sell(contract_id, bid_price).then(this.handleSell);
}
Expand Down
5 changes: 3 additions & 2 deletions packages/shared/src/utils/constants/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { shouldShowCancellation, shouldShowExpiration, TURBOS, VANILLALONG } fro
export const getLocalizedBasis = () =>
({
accumulator: localize('Accumulators'),
payout: localize('Payout'),
multiplier: localize('Multiplier'),
payout_per_pip: localize('Payout per pip'),
payout_per_point: localize('Payout per point'),
payout: localize('Payout'),
stake: localize('Stake'),
multiplier: localize('Multiplier'),
turbos: localize('Turbos'),
} as const);

Expand Down
15 changes: 15 additions & 0 deletions packages/shared/src/utils/contract/contract.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ type TGetAccuBarriersDTraderTimeout = (params: {
underlying: string;
}) => number;

// Trade types that are considered as vanilla financials
export const VANILLA_FX_SYMBOLS = [
'frxAUDUSD',
'frxEURUSD',
'frxGBPUSD',
'frxUSDCAD',
'frxUSDJPY',
'frxXAUUSD',
'frxXAGUSD',
] as const;

// animation correction time is an interval in ms between ticks receival from API and their actual visual update on the chart
export const ANIMATION_CORRECTION_TIME = 200;
export const DELAY_TIME_1S_SYMBOL = 500;
Expand All @@ -26,6 +37,7 @@ export const TURBOS = {
export const VANILLALONG = {
CALL: 'vanillalongcall',
PUT: 'vanillalongput',
FX: 'vanilla_fx',
} as const;

export const getContractStatus = ({ contract_type, exit_tick_time, profit, status }: TContractInfo) => {
Expand Down Expand Up @@ -81,6 +93,9 @@ export const isTurbosContract = (contract_type = '') => /TURBOS/i.test(contract_

export const isVanillaContract = (contract_type = '') => /VANILLA/i.test(contract_type);

export const isVanillaFxContract = (contract_type = '', symbol = '') =>
isVanillaContract(contract_type) && VANILLA_FX_SYMBOLS.includes(symbol as typeof VANILLA_FX_SYMBOLS[number]);

export const isSmartTraderContract = (contract_type = '') => /RUN|EXPIRY|RANGE|UPORDOWN|ASIAN/i.test(contract_type);

export const isAsiansContract = (contract_type = '') => /ASIAN/i.test(contract_type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ import {
epochToMoment,
getCancellationPrice,
getCurrencyDisplayCode,
getLocalizedBasis,
hasTwoBarriers,
isAccumulatorContract,
isEndedBeforeCancellationExpired,
isMobile,
isMultiplierContract,
isSmartTraderContract,
isAsiansContract,
isTurbosContract,
isUserSold,
isEndedBeforeCancellationExpired,
isUserCancelled,
isUserSold,
isVanillaFxContract,
toGMTFormat,
TContractInfo,
} from '@deriv/shared';
Expand Down Expand Up @@ -49,17 +51,18 @@ const ContractDetails = ({
commission,
contract_type,
currency,
date_start,
display_number_of_contracts,
entry_spot_display_value,
entry_tick_time,
exit_tick_time,
high_barrier,
low_barrier,
profit,
date_start,
tick_count,
tick_passed,
transaction_ids: { buy, sell } = {},
low_barrier,
display_number_of_contracts,
underlying,
} = contract_info;

const is_profit = Number(profit) >= 0;
Expand All @@ -74,6 +77,10 @@ const ContractDetails = ({
: `${tick_count} ${ticks_label}`;
const { action_names, event_names, form_names, form_sources } = getRudderstackConfig();

const vanilla_payout_text = isVanillaFxContract(contract_type, underlying)
? getLocalizedBasis().payout_per_pip
: getLocalizedBasis().payout_per_point;

const getLabel = () => {
if (isUserSold(contract_info) && isEndedBeforeCancellationExpired(contract_info))
return localize('Deal cancellation');
Expand Down Expand Up @@ -170,7 +177,7 @@ const ContractDetails = ({
<ContractAuditItem
id='dt_bt_label'
icon={<Icon icon='IcContractPayout' size={24} />}
label={localize('Payout per point')}
label={vanilla_payout_text}
value={
display_number_of_contracts
? `${display_number_of_contracts} ${getCurrencyDisplayCode(currency)}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,22 +71,22 @@ const ContractDrawer = observer(

const contract_audit = (
<ContractAudit
contract_end_time={getEndTime(contract_info)}
contract_info={contract_info}
contract_update_history={contract_update_history}
contract_end_time={getEndTime(contract_info)}
is_accumulator={is_accumulator}
is_dark_theme={is_dark_theme}
is_multiplier={is_multiplier}
is_open
is_turbos={is_turbos}
duration={getDurationTime(contract_info)}
duration_unit={getDurationUnitText(getDurationPeriod(contract_info))}
duration={getDurationTime(contract_info)}
exit_spot={exit_spot}
has_result={
!!is_sold || is_multiplier || is_vanilla || is_turbos || is_accumulator || is_smarttrader_contract
}
toggleHistoryTab={toggleHistoryTab}
is_accumulator={is_accumulator}
is_dark_theme={is_dark_theme}
is_multiplier={is_multiplier}
is_open
is_turbos={is_turbos}
is_vanilla={is_vanilla}
toggleHistoryTab={toggleHistoryTab}
/>
);

Expand Down
Loading

0 comments on commit 55b29ba

Please sign in to comment.