Feat/129 pennant chart (#214)
* initial commit for adding chartt lib with pennant chart * add pennant package, fix dynamic import of chart * use updated pennant library * Create separate chart and depth-chart libs * Remove leftover generated files * Use more targeted queries and subscriptions * Fix jestConfig value for depth-chart * Add jest-canvas-mock * Refactor updateDepthUpdate function * Add updateDpethUpdate test * Add jest-canvas-mock to chart tests * Avoid using any type in test * Use correct casing for gql queries and subscriptions * Make ButtonRadio generic in option value type * Add padding and margin to chart container * Remove unused subscriptions and methods from data source * Use correct React imports Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
parent
dbd0514515
commit
f721a21d0f
38
apps/trading/components/chart-container/button-radio.tsx
Normal file
38
apps/trading/components/chart-container/button-radio.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
|
interface ButtonRadioProps<T> {
|
||||||
|
name: string;
|
||||||
|
options: Array<{ value: T; text: string }>;
|
||||||
|
currentOption: T | null;
|
||||||
|
onSelect: (option: T) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ButtonRadio = <T extends string>({
|
||||||
|
name,
|
||||||
|
options,
|
||||||
|
currentOption,
|
||||||
|
onSelect,
|
||||||
|
}: ButtonRadioProps<T>) => {
|
||||||
|
return (
|
||||||
|
<div className="flex gap-8">
|
||||||
|
{options.map((option) => {
|
||||||
|
const isSelected = option.value === currentOption;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={() => onSelect(option.value)}
|
||||||
|
className="flex-1"
|
||||||
|
variant={isSelected ? 'accent' : undefined}
|
||||||
|
data-testid={
|
||||||
|
isSelected
|
||||||
|
? `${name}-${option.value}-selected`
|
||||||
|
: `${name}-${option.value}`
|
||||||
|
}
|
||||||
|
key={option.value}
|
||||||
|
>
|
||||||
|
{option.text}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
39
apps/trading/components/chart-container/chart-container.tsx
Normal file
39
apps/trading/components/chart-container/chart-container.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { ButtonRadio } from './button-radio';
|
||||||
|
import { DepthChartContainer } from '@vegaprotocol/depth-chart';
|
||||||
|
import { TradingChartContainer } from '@vegaprotocol/chart';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
type ChartType = 'depth' | 'trading';
|
||||||
|
|
||||||
|
interface ChartContainerProps {
|
||||||
|
marketId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChartContainer = ({ marketId }: ChartContainerProps) => {
|
||||||
|
const [chartType, setChartType] = useState<ChartType>('trading');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-4 py-8 flex flex-col h-full">
|
||||||
|
<div className="mb-4">
|
||||||
|
<ButtonRadio
|
||||||
|
name="chart-type"
|
||||||
|
options={[
|
||||||
|
{ text: 'Trading view', value: 'trading' },
|
||||||
|
{ text: 'Depth', value: 'depth' },
|
||||||
|
]}
|
||||||
|
currentOption={chartType}
|
||||||
|
onSelect={(value) => {
|
||||||
|
setChartType(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
{chartType === 'trading' ? (
|
||||||
|
<TradingChartContainer marketId={marketId} />
|
||||||
|
) : (
|
||||||
|
<DepthChartContainer marketId={marketId} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
1
apps/trading/components/chart-container/index.ts
Normal file
1
apps/trading/components/chart-container/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { ChartContainer } from './chart-container';
|
@ -5,6 +5,7 @@ import { useState } from 'react';
|
|||||||
import { GridTab, GridTabs } from './grid-tabs';
|
import { GridTab, GridTabs } from './grid-tabs';
|
||||||
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
|
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
|
||||||
import { OrderListContainer } from '@vegaprotocol/order-list';
|
import { OrderListContainer } from '@vegaprotocol/order-list';
|
||||||
|
import { ChartContainer } from '../../components/chart-container';
|
||||||
import { TradesContainer } from '@vegaprotocol/trades';
|
import { TradesContainer } from '@vegaprotocol/trades';
|
||||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||||
@ -12,11 +13,6 @@ import type { Market_market } from './__generated__/Market';
|
|||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import { AccountsContainer } from '@vegaprotocol/accounts';
|
import { AccountsContainer } from '@vegaprotocol/accounts';
|
||||||
|
|
||||||
const Chart = () => (
|
|
||||||
<Splash>
|
|
||||||
<p>{t('Chart')}</p>
|
|
||||||
</Splash>
|
|
||||||
);
|
|
||||||
const Orderbook = () => (
|
const Orderbook = () => (
|
||||||
<Splash>
|
<Splash>
|
||||||
<p>{t('Orderbook')}</p>
|
<p>{t('Orderbook')}</p>
|
||||||
@ -24,7 +20,7 @@ const Orderbook = () => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const TradingViews = {
|
const TradingViews = {
|
||||||
Chart: Chart,
|
Chart: ChartContainer,
|
||||||
Ticket: DealTicketContainer,
|
Ticket: DealTicketContainer,
|
||||||
Orderbook: Orderbook,
|
Orderbook: Orderbook,
|
||||||
Orders: OrderListContainer,
|
Orders: OrderListContainer,
|
||||||
@ -54,7 +50,7 @@ export const TradeGrid = ({ market }: TradeGridProps) => {
|
|||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
<TradeGridChild className="col-start-1 col-end-2">
|
<TradeGridChild className="col-start-1 col-end-2">
|
||||||
<TradingViews.Chart />
|
<TradingViews.Chart marketId={market.id} />
|
||||||
</TradeGridChild>
|
</TradeGridChild>
|
||||||
<TradeGridChild className="row-start-1 row-end-3">
|
<TradeGridChild className="row-start-1 row-end-3">
|
||||||
<TradingViews.Ticket marketId={market.id} />
|
<TradingViews.Ticket marketId={market.id} />
|
||||||
|
12
libs/chart/.babelrc
Normal file
12
libs/chart/.babelrc
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@nrwl/react/babel",
|
||||||
|
{
|
||||||
|
"runtime": "automatic",
|
||||||
|
"useBuiltIns": "usage"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"plugins": []
|
||||||
|
}
|
18
libs/chart/.eslintrc.json
Normal file
18
libs/chart/.eslintrc.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||||
|
"ignorePatterns": ["!**/*", "__generated__"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
7
libs/chart/README.md
Normal file
7
libs/chart/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# chart
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test chart` to execute the unit tests via [Jest](https://jestjs.io).
|
10
libs/chart/jest.config.js
Normal file
10
libs/chart/jest.config.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
displayName: 'chart',
|
||||||
|
preset: '../../jest.preset.js',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||||
|
},
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||||
|
coverageDirectory: '../../coverage/libs/chart',
|
||||||
|
setupFiles: ['jest-canvas-mock'],
|
||||||
|
};
|
4
libs/chart/package.json
Normal file
4
libs/chart/package.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "@vegaprotocol/chart",
|
||||||
|
"version": "0.0.1"
|
||||||
|
}
|
43
libs/chart/project.json
Normal file
43
libs/chart/project.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"root": "libs/chart",
|
||||||
|
"sourceRoot": "libs/chart/src",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": [],
|
||||||
|
"targets": {
|
||||||
|
"build": {
|
||||||
|
"executor": "@nrwl/web:rollup",
|
||||||
|
"outputs": ["{options.outputPath}"],
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/libs/chart",
|
||||||
|
"tsConfig": "libs/chart/tsconfig.lib.json",
|
||||||
|
"project": "libs/chart/package.json",
|
||||||
|
"entryFile": "libs/chart/src/index.ts",
|
||||||
|
"external": ["react/jsx-runtime"],
|
||||||
|
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
|
||||||
|
"compiler": "babel",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "libs/chart/README.md",
|
||||||
|
"input": ".",
|
||||||
|
"output": "."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"outputs": ["{options.outputFile}"],
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": ["libs/chart/**/*.{ts,tsx,js,jsx}"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"executor": "@nrwl/jest:jest",
|
||||||
|
"outputs": ["coverage/libs/chart"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "libs/chart/jest.config.js",
|
||||||
|
"passWithNoTests": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
libs/chart/src/index.ts
Normal file
1
libs/chart/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './lib/trading-chart';
|
36
libs/chart/src/lib/__generated__/CandleFields.ts
generated
Normal file
36
libs/chart/src/lib/__generated__/CandleFields.ts
generated
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL fragment: CandleFields
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface CandleFields {
|
||||||
|
__typename: "Candle";
|
||||||
|
/**
|
||||||
|
* RFC3339Nano formatted date and time for the candle
|
||||||
|
*/
|
||||||
|
datetime: string;
|
||||||
|
/**
|
||||||
|
* High price (uint64)
|
||||||
|
*/
|
||||||
|
high: string;
|
||||||
|
/**
|
||||||
|
* Low price (uint64)
|
||||||
|
*/
|
||||||
|
low: string;
|
||||||
|
/**
|
||||||
|
* Open price (uint64)
|
||||||
|
*/
|
||||||
|
open: string;
|
||||||
|
/**
|
||||||
|
* Close price (uint64)
|
||||||
|
*/
|
||||||
|
close: string;
|
||||||
|
/**
|
||||||
|
* Volume price (uint64)
|
||||||
|
*/
|
||||||
|
volume: string;
|
||||||
|
}
|
108
libs/chart/src/lib/__generated__/Candles.ts
generated
Normal file
108
libs/chart/src/lib/__generated__/Candles.ts
generated
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { Interval } from "@vegaprotocol/types";
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL query operation: Candles
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface Candles_market_tradableInstrument_instrument {
|
||||||
|
__typename: 'Instrument';
|
||||||
|
/**
|
||||||
|
* Uniquely identify an instrument across all instruments available on Vega (string)
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* Full and fairly descriptive name for the instrument
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
|
||||||
|
*/
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Candles_market_tradableInstrument {
|
||||||
|
__typename: 'TradableInstrument';
|
||||||
|
/**
|
||||||
|
* An instance of or reference to a fully specified instrument.
|
||||||
|
*/
|
||||||
|
instrument: Candles_market_tradableInstrument_instrument;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Candles_market_candles {
|
||||||
|
__typename: 'Candle';
|
||||||
|
/**
|
||||||
|
* RFC3339Nano formatted date and time for the candle
|
||||||
|
*/
|
||||||
|
datetime: string;
|
||||||
|
/**
|
||||||
|
* High price (uint64)
|
||||||
|
*/
|
||||||
|
high: string;
|
||||||
|
/**
|
||||||
|
* Low price (uint64)
|
||||||
|
*/
|
||||||
|
low: string;
|
||||||
|
/**
|
||||||
|
* Open price (uint64)
|
||||||
|
*/
|
||||||
|
open: string;
|
||||||
|
/**
|
||||||
|
* Close price (uint64)
|
||||||
|
*/
|
||||||
|
close: string;
|
||||||
|
/**
|
||||||
|
* Volume price (uint64)
|
||||||
|
*/
|
||||||
|
volume: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Candles_market {
|
||||||
|
__typename: 'Market';
|
||||||
|
/**
|
||||||
|
* Market ID
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
|
||||||
|
* number denominated in the currency of the Market. (uint64)
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* Currency Balance decimalPlaces Real Balance
|
||||||
|
* GBP 100 0 GBP 100
|
||||||
|
* GBP 100 2 GBP 1.00
|
||||||
|
* GBP 100 4 GBP 0.01
|
||||||
|
* GBP 1 4 GBP 0.0001 ( 0.01p )
|
||||||
|
*
|
||||||
|
* GBX (pence) 100 0 GBP 1.00 (100p )
|
||||||
|
* GBX (pence) 100 2 GBP 0.01 ( 1p )
|
||||||
|
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
|
||||||
|
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
|
||||||
|
*/
|
||||||
|
decimalPlaces: number;
|
||||||
|
/**
|
||||||
|
* An instance of or reference to a tradable instrument.
|
||||||
|
*/
|
||||||
|
tradableInstrument: Candles_market_tradableInstrument;
|
||||||
|
/**
|
||||||
|
* Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by params
|
||||||
|
*/
|
||||||
|
candles: (Candles_market_candles | null)[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Candles {
|
||||||
|
/**
|
||||||
|
* An instrument that is trading on the VEGA network
|
||||||
|
*/
|
||||||
|
market: Candles_market | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CandlesVariables {
|
||||||
|
marketId: string;
|
||||||
|
interval: Interval;
|
||||||
|
since: string;
|
||||||
|
}
|
50
libs/chart/src/lib/__generated__/CandlesSub.ts
generated
Normal file
50
libs/chart/src/lib/__generated__/CandlesSub.ts
generated
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { Interval } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL subscription operation: CandlesSub
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface CandlesSub_candles {
|
||||||
|
__typename: "Candle";
|
||||||
|
/**
|
||||||
|
* RFC3339Nano formatted date and time for the candle
|
||||||
|
*/
|
||||||
|
datetime: string;
|
||||||
|
/**
|
||||||
|
* High price (uint64)
|
||||||
|
*/
|
||||||
|
high: string;
|
||||||
|
/**
|
||||||
|
* Low price (uint64)
|
||||||
|
*/
|
||||||
|
low: string;
|
||||||
|
/**
|
||||||
|
* Open price (uint64)
|
||||||
|
*/
|
||||||
|
open: string;
|
||||||
|
/**
|
||||||
|
* Close price (uint64)
|
||||||
|
*/
|
||||||
|
close: string;
|
||||||
|
/**
|
||||||
|
* Volume price (uint64)
|
||||||
|
*/
|
||||||
|
volume: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CandlesSub {
|
||||||
|
/**
|
||||||
|
* Subscribe to the candles updates
|
||||||
|
*/
|
||||||
|
candles: CandlesSub_candles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CandlesSubVariables {
|
||||||
|
marketId: string;
|
||||||
|
interval: Interval;
|
||||||
|
}
|
68
libs/chart/src/lib/__generated__/Chart.ts
generated
Normal file
68
libs/chart/src/lib/__generated__/Chart.ts
generated
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL query operation: Chart
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface Chart_market_data_priceMonitoringBounds {
|
||||||
|
__typename: "PriceMonitoringBounds";
|
||||||
|
/**
|
||||||
|
* Minimum price that isn't currently breaching the specified price monitoring trigger
|
||||||
|
*/
|
||||||
|
minValidPrice: string;
|
||||||
|
/**
|
||||||
|
* Maximum price that isn't currently breaching the specified price monitoring trigger
|
||||||
|
*/
|
||||||
|
maxValidPrice: string;
|
||||||
|
/**
|
||||||
|
* Reference price used to calculate the valid price range
|
||||||
|
*/
|
||||||
|
referencePrice: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Chart_market_data {
|
||||||
|
__typename: "MarketData";
|
||||||
|
/**
|
||||||
|
* A list of valid price ranges per associated trigger
|
||||||
|
*/
|
||||||
|
priceMonitoringBounds: Chart_market_data_priceMonitoringBounds[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Chart_market {
|
||||||
|
__typename: "Market";
|
||||||
|
/**
|
||||||
|
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
|
||||||
|
* number denominated in the currency of the Market. (uint64)
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* Currency Balance decimalPlaces Real Balance
|
||||||
|
* GBP 100 0 GBP 100
|
||||||
|
* GBP 100 2 GBP 1.00
|
||||||
|
* GBP 100 4 GBP 0.01
|
||||||
|
* GBP 1 4 GBP 0.0001 ( 0.01p )
|
||||||
|
*
|
||||||
|
* GBX (pence) 100 0 GBP 1.00 (100p )
|
||||||
|
* GBX (pence) 100 2 GBP 0.01 ( 1p )
|
||||||
|
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
|
||||||
|
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
|
||||||
|
*/
|
||||||
|
decimalPlaces: number;
|
||||||
|
/**
|
||||||
|
* marketData for the given market
|
||||||
|
*/
|
||||||
|
data: Chart_market_data | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Chart {
|
||||||
|
/**
|
||||||
|
* An instrument that is trading on the VEGA network
|
||||||
|
*/
|
||||||
|
market: Chart_market | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChartVariables {
|
||||||
|
marketId: string;
|
||||||
|
}
|
235
libs/chart/src/lib/data-source.ts
Normal file
235
libs/chart/src/lib/data-source.ts
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
import type { ApolloClient } from '@apollo/client';
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
import type { Candle, DataSource } from 'pennant';
|
||||||
|
import { Interval } from 'pennant';
|
||||||
|
|
||||||
|
import { addDecimal } from '@vegaprotocol/react-helpers';
|
||||||
|
import type { Chart, ChartVariables } from './__generated__/Chart';
|
||||||
|
import type { Candles, CandlesVariables } from './__generated__/Candles';
|
||||||
|
import type { CandleFields } from './__generated__/CandleFields';
|
||||||
|
import type {
|
||||||
|
CandlesSub,
|
||||||
|
CandlesSubVariables,
|
||||||
|
} from './__generated__/CandlesSub';
|
||||||
|
import type { Subscription } from 'zen-observable-ts';
|
||||||
|
|
||||||
|
export const CANDLE_FRAGMENT = gql`
|
||||||
|
fragment CandleFields on Candle {
|
||||||
|
datetime
|
||||||
|
high
|
||||||
|
low
|
||||||
|
open
|
||||||
|
close
|
||||||
|
volume
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CANDLES_QUERY = gql`
|
||||||
|
${CANDLE_FRAGMENT}
|
||||||
|
query Candles($marketId: ID!, $interval: Interval!, $since: String!) {
|
||||||
|
market(id: $marketId) {
|
||||||
|
id
|
||||||
|
decimalPlaces
|
||||||
|
tradableInstrument {
|
||||||
|
instrument {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
candles(interval: $interval, since: $since) {
|
||||||
|
...CandleFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CANDLES_SUB = gql`
|
||||||
|
${CANDLE_FRAGMENT}
|
||||||
|
subscription CandlesSub($marketId: ID!, $interval: Interval!) {
|
||||||
|
candles(marketId: $marketId, interval: $interval) {
|
||||||
|
...CandleFields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CHART_QUERY = gql`
|
||||||
|
query Chart($marketId: ID!) {
|
||||||
|
market(id: $marketId) {
|
||||||
|
decimalPlaces
|
||||||
|
data {
|
||||||
|
priceMonitoringBounds {
|
||||||
|
minValidPrice
|
||||||
|
maxValidPrice
|
||||||
|
referencePrice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const defaultConfig = {
|
||||||
|
decimalPlaces: 5,
|
||||||
|
supportedIntervals: [
|
||||||
|
Interval.I1D,
|
||||||
|
Interval.I6H,
|
||||||
|
Interval.I1H,
|
||||||
|
Interval.I15M,
|
||||||
|
Interval.I5M,
|
||||||
|
Interval.I1M,
|
||||||
|
],
|
||||||
|
priceMonitoringBounds: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data access object that provides access to the Vega GraphQL API.
|
||||||
|
*/
|
||||||
|
export class VegaDataSource implements DataSource {
|
||||||
|
client: ApolloClient<object>;
|
||||||
|
marketId: string;
|
||||||
|
partyId: null | string;
|
||||||
|
_decimalPlaces = 0;
|
||||||
|
|
||||||
|
candlesSub: Subscription | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the number of decimal places that an integer must be shifted by in order to get a correct
|
||||||
|
* number denominated in the currency of the Market.
|
||||||
|
*/
|
||||||
|
get decimalPlaces(): number {
|
||||||
|
return this._decimalPlaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param client - An ApolloClient instance.
|
||||||
|
* @param marketId - Market identifier.
|
||||||
|
* @param partyId - Party identifier.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
client: ApolloClient<object>,
|
||||||
|
marketId: string,
|
||||||
|
partyId: null | string = null
|
||||||
|
) {
|
||||||
|
this.client = client;
|
||||||
|
this.marketId = marketId;
|
||||||
|
this.partyId = partyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by the charting library to initialize itself.
|
||||||
|
*/
|
||||||
|
async onReady() {
|
||||||
|
try {
|
||||||
|
const { data } = await this.client.query<Chart, ChartVariables>({
|
||||||
|
query: CHART_QUERY,
|
||||||
|
variables: {
|
||||||
|
marketId: this.marketId,
|
||||||
|
},
|
||||||
|
fetchPolicy: 'no-cache',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data && data.market && data.market.data) {
|
||||||
|
this._decimalPlaces = data.market.decimalPlaces;
|
||||||
|
|
||||||
|
return {
|
||||||
|
decimalPlaces: this._decimalPlaces,
|
||||||
|
supportedIntervals: [
|
||||||
|
Interval.I1D,
|
||||||
|
Interval.I6H,
|
||||||
|
Interval.I1H,
|
||||||
|
Interval.I15M,
|
||||||
|
Interval.I5M,
|
||||||
|
Interval.I1M,
|
||||||
|
],
|
||||||
|
priceMonitoringBounds:
|
||||||
|
data.market.data.priceMonitoringBounds?.map((bounds) => ({
|
||||||
|
maxValidPrice: Number(
|
||||||
|
addDecimal(bounds.maxValidPrice, this._decimalPlaces)
|
||||||
|
),
|
||||||
|
minValidPrice: Number(
|
||||||
|
addDecimal(bounds.minValidPrice, this._decimalPlaces)
|
||||||
|
),
|
||||||
|
referencePrice: Number(
|
||||||
|
addDecimal(bounds.referencePrice, this._decimalPlaces)
|
||||||
|
),
|
||||||
|
})) ?? [],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by the charting library to get historical data.
|
||||||
|
*/
|
||||||
|
async query(interval: Interval, from: string) {
|
||||||
|
try {
|
||||||
|
const { data } = await this.client.query<Candles, CandlesVariables>({
|
||||||
|
query: CANDLES_QUERY,
|
||||||
|
variables: {
|
||||||
|
marketId: this.marketId,
|
||||||
|
interval,
|
||||||
|
since: from,
|
||||||
|
},
|
||||||
|
fetchPolicy: 'no-cache',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data && data.market && data.market.candles) {
|
||||||
|
const decimalPlaces = data.market.decimalPlaces;
|
||||||
|
|
||||||
|
const candles = data.market.candles
|
||||||
|
.filter((d): d is CandleFields => d !== null)
|
||||||
|
.map((d) => parseCandle(d, decimalPlaces));
|
||||||
|
|
||||||
|
return candles;
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by the charting library to create a subscription to streaming data.
|
||||||
|
*/
|
||||||
|
subscribeData(
|
||||||
|
interval: Interval,
|
||||||
|
onSubscriptionData: (data: Candle) => void
|
||||||
|
) {
|
||||||
|
const res = this.client.subscribe<CandlesSub, CandlesSubVariables>({
|
||||||
|
query: CANDLES_SUB,
|
||||||
|
variables: { marketId: this.marketId, interval },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.candlesSub = res.subscribe(({ data }) => {
|
||||||
|
if (data) {
|
||||||
|
const candle = parseCandle(data.candles, this.decimalPlaces);
|
||||||
|
|
||||||
|
onSubscriptionData(candle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by the charting library to clean-up a subscription to streaming data.
|
||||||
|
*/
|
||||||
|
unsubscribeData() {
|
||||||
|
this.candlesSub && this.candlesSub.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCandle(candle: CandleFields, decimalPlaces: number): Candle {
|
||||||
|
return {
|
||||||
|
date: new Date(candle.datetime),
|
||||||
|
high: Number(addDecimal(candle.high, decimalPlaces)),
|
||||||
|
low: Number(addDecimal(candle.low, decimalPlaces)),
|
||||||
|
open: Number(addDecimal(candle.open, decimalPlaces)),
|
||||||
|
close: Number(addDecimal(candle.close, decimalPlaces)),
|
||||||
|
volume: Number(candle.volume),
|
||||||
|
};
|
||||||
|
}
|
17
libs/chart/src/lib/trading-chart.spec.tsx
Normal file
17
libs/chart/src/lib/trading-chart.spec.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { TradingChartContainer } from './trading-chart';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
|
describe('TradingChart', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(
|
||||||
|
<MockedProvider>
|
||||||
|
<VegaWalletContext.Provider value={{} as never}>
|
||||||
|
<TradingChartContainer marketId={'market-id'} />
|
||||||
|
</VegaWalletContext.Provider>
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
expect(baseElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
32
libs/chart/src/lib/trading-chart.tsx
Normal file
32
libs/chart/src/lib/trading-chart.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import 'pennant/dist/style.css';
|
||||||
|
|
||||||
|
import { Chart as TradingChart, Interval } from 'pennant';
|
||||||
|
import { VegaDataSource } from './data-source';
|
||||||
|
import { useApolloClient } from '@apollo/client';
|
||||||
|
import { useContext, useMemo } from 'react';
|
||||||
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
|
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
|
export type TradingChartContainerProps = {
|
||||||
|
marketId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TradingChartContainer = ({
|
||||||
|
marketId,
|
||||||
|
}: TradingChartContainerProps) => {
|
||||||
|
const client = useApolloClient();
|
||||||
|
const { keypair } = useVegaWallet();
|
||||||
|
const theme = useContext(ThemeContext);
|
||||||
|
|
||||||
|
const dataSource = useMemo(() => {
|
||||||
|
return new VegaDataSource(client, marketId, keypair?.pub);
|
||||||
|
}, [client, marketId, keypair]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TradingChart
|
||||||
|
dataSource={dataSource}
|
||||||
|
interval={Interval.I15M}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
25
libs/chart/tsconfig.json
Normal file
25
libs/chart/tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
22
libs/chart/tsconfig.lib.json
Normal file
22
libs/chart/tsconfig.lib.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||||
|
"../../node_modules/@nrwl/react/typings/image.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.spec.tsx",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"**/*.spec.js",
|
||||||
|
"**/*.test.js",
|
||||||
|
"**/*.spec.jsx",
|
||||||
|
"**/*.test.jsx"
|
||||||
|
],
|
||||||
|
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||||
|
}
|
19
libs/chart/tsconfig.spec.json
Normal file
19
libs/chart/tsconfig.spec.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"module": "commonjs",
|
||||||
|
"types": ["jest", "node"]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"**/*.spec.tsx",
|
||||||
|
"**/*.test.js",
|
||||||
|
"**/*.spec.js",
|
||||||
|
"**/*.test.jsx",
|
||||||
|
"**/*.spec.jsx",
|
||||||
|
"**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
12
libs/depth-chart/.babelrc
Normal file
12
libs/depth-chart/.babelrc
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@nrwl/react/babel",
|
||||||
|
{
|
||||||
|
"runtime": "automatic",
|
||||||
|
"useBuiltIns": "usage"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"plugins": []
|
||||||
|
}
|
18
libs/depth-chart/.eslintrc.json
Normal file
18
libs/depth-chart/.eslintrc.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||||
|
"ignorePatterns": ["!**/*", "__generated__"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.js", "*.jsx"],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
11
libs/depth-chart/README.md
Normal file
11
libs/depth-chart/README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# depth-chart
|
||||||
|
|
||||||
|
This library was generated with [Nx](https://nx.dev).
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Run `nx build depth-chart` to build the library.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `nx test depth-chart` to execute the unit tests via [Jest](https://jestjs.io).
|
10
libs/depth-chart/jest.config.js
Normal file
10
libs/depth-chart/jest.config.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module.exports = {
|
||||||
|
displayName: 'depth-chart',
|
||||||
|
preset: '../../jest.preset.js',
|
||||||
|
transform: {
|
||||||
|
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||||
|
},
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||||
|
coverageDirectory: '../../coverage/libs/depth-chart',
|
||||||
|
setupFiles: ['jest-canvas-mock'],
|
||||||
|
};
|
4
libs/depth-chart/package.json
Normal file
4
libs/depth-chart/package.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "@vegaprotocol/depth-chart",
|
||||||
|
"version": "0.0.1"
|
||||||
|
}
|
43
libs/depth-chart/project.json
Normal file
43
libs/depth-chart/project.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"root": "libs/depth-chart",
|
||||||
|
"sourceRoot": "libs/depth-chart/src",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": [],
|
||||||
|
"targets": {
|
||||||
|
"build": {
|
||||||
|
"executor": "@nrwl/web:rollup",
|
||||||
|
"outputs": ["{options.outputPath}"],
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/libs/depth-chart",
|
||||||
|
"tsConfig": "libs/depth-chart/tsconfig.lib.json",
|
||||||
|
"project": "libs/depth-chart/package.json",
|
||||||
|
"entryFile": "libs/depth-chart/src/index.ts",
|
||||||
|
"external": ["react/jsx-runtime"],
|
||||||
|
"rollupConfig": "@nrwl/react/plugins/bundle-rollup",
|
||||||
|
"compiler": "babel",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "libs/depth-chart/README.md",
|
||||||
|
"input": ".",
|
||||||
|
"output": "."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"outputs": ["{options.outputFile}"],
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": ["libs/depth-chart/**/*.{ts,tsx,js,jsx}"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"executor": "@nrwl/jest:jest",
|
||||||
|
"outputs": ["coverage/libs/depth-chart"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "libs/depth-chart/jest.config.js",
|
||||||
|
"passWithNoTests": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
libs/depth-chart/src/index.ts
Normal file
1
libs/depth-chart/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './lib/depth-chart';
|
120
libs/depth-chart/src/lib/__generated__/marketDepth.ts
generated
Normal file
120
libs/depth-chart/src/lib/__generated__/marketDepth.ts
generated
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL query operation: marketDepth
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface marketDepth_market_data {
|
||||||
|
__typename: "MarketData";
|
||||||
|
/**
|
||||||
|
* the arithmetic average of the best bid price and best offer price.
|
||||||
|
*/
|
||||||
|
midPrice: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepth_market_depth_lastTrade {
|
||||||
|
__typename: "Trade";
|
||||||
|
/**
|
||||||
|
* The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64)
|
||||||
|
*/
|
||||||
|
price: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepth_market_depth_sell {
|
||||||
|
__typename: "PriceLevel";
|
||||||
|
/**
|
||||||
|
* The price of all the orders at this level (uint64)
|
||||||
|
*/
|
||||||
|
price: string;
|
||||||
|
/**
|
||||||
|
* The total remaining size of all orders at this level (uint64)
|
||||||
|
*/
|
||||||
|
volume: string;
|
||||||
|
/**
|
||||||
|
* The number of orders at this price level (uint64)
|
||||||
|
*/
|
||||||
|
numberOfOrders: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepth_market_depth_buy {
|
||||||
|
__typename: "PriceLevel";
|
||||||
|
/**
|
||||||
|
* The price of all the orders at this level (uint64)
|
||||||
|
*/
|
||||||
|
price: string;
|
||||||
|
/**
|
||||||
|
* The total remaining size of all orders at this level (uint64)
|
||||||
|
*/
|
||||||
|
volume: string;
|
||||||
|
/**
|
||||||
|
* The number of orders at this price level (uint64)
|
||||||
|
*/
|
||||||
|
numberOfOrders: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepth_market_depth {
|
||||||
|
__typename: "MarketDepth";
|
||||||
|
/**
|
||||||
|
* Last trade for the given market (if available)
|
||||||
|
*/
|
||||||
|
lastTrade: marketDepth_market_depth_lastTrade | null;
|
||||||
|
/**
|
||||||
|
* Sell side price levels (if available)
|
||||||
|
*/
|
||||||
|
sell: marketDepth_market_depth_sell[] | null;
|
||||||
|
/**
|
||||||
|
* Buy side price levels (if available)
|
||||||
|
*/
|
||||||
|
buy: marketDepth_market_depth_buy[] | null;
|
||||||
|
/**
|
||||||
|
* Sequence number for the current snapshot of the market depth
|
||||||
|
*/
|
||||||
|
sequenceNumber: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepth_market {
|
||||||
|
__typename: "Market";
|
||||||
|
/**
|
||||||
|
* Market ID
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
|
||||||
|
* number denominated in the currency of the Market. (uint64)
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* Currency Balance decimalPlaces Real Balance
|
||||||
|
* GBP 100 0 GBP 100
|
||||||
|
* GBP 100 2 GBP 1.00
|
||||||
|
* GBP 100 4 GBP 0.01
|
||||||
|
* GBP 1 4 GBP 0.0001 ( 0.01p )
|
||||||
|
*
|
||||||
|
* GBX (pence) 100 0 GBP 1.00 (100p )
|
||||||
|
* GBX (pence) 100 2 GBP 0.01 ( 1p )
|
||||||
|
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
|
||||||
|
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
|
||||||
|
*/
|
||||||
|
decimalPlaces: number;
|
||||||
|
/**
|
||||||
|
* marketData for the given market
|
||||||
|
*/
|
||||||
|
data: marketDepth_market_data | null;
|
||||||
|
/**
|
||||||
|
* Current depth on the orderbook for this market
|
||||||
|
*/
|
||||||
|
depth: marketDepth_market_depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepth {
|
||||||
|
/**
|
||||||
|
* An instrument that is trading on the VEGA network
|
||||||
|
*/
|
||||||
|
market: marketDepth_market | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepthVariables {
|
||||||
|
marketId: string;
|
||||||
|
}
|
67
libs/depth-chart/src/lib/__generated__/marketDepthSubscribe.ts
generated
Normal file
67
libs/depth-chart/src/lib/__generated__/marketDepthSubscribe.ts
generated
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL subscription operation: marketDepthSubscribe
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface marketDepthSubscribe_marketDepth_sell {
|
||||||
|
__typename: "PriceLevel";
|
||||||
|
/**
|
||||||
|
* The price of all the orders at this level (uint64)
|
||||||
|
*/
|
||||||
|
price: string;
|
||||||
|
/**
|
||||||
|
* The total remaining size of all orders at this level (uint64)
|
||||||
|
*/
|
||||||
|
volume: string;
|
||||||
|
/**
|
||||||
|
* The number of orders at this price level (uint64)
|
||||||
|
*/
|
||||||
|
numberOfOrders: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepthSubscribe_marketDepth_buy {
|
||||||
|
__typename: "PriceLevel";
|
||||||
|
/**
|
||||||
|
* The price of all the orders at this level (uint64)
|
||||||
|
*/
|
||||||
|
price: string;
|
||||||
|
/**
|
||||||
|
* The total remaining size of all orders at this level (uint64)
|
||||||
|
*/
|
||||||
|
volume: string;
|
||||||
|
/**
|
||||||
|
* The number of orders at this price level (uint64)
|
||||||
|
*/
|
||||||
|
numberOfOrders: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepthSubscribe_marketDepth {
|
||||||
|
__typename: "MarketDepth";
|
||||||
|
/**
|
||||||
|
* Sell side price levels (if available)
|
||||||
|
*/
|
||||||
|
sell: marketDepthSubscribe_marketDepth_sell[] | null;
|
||||||
|
/**
|
||||||
|
* Buy side price levels (if available)
|
||||||
|
*/
|
||||||
|
buy: marketDepthSubscribe_marketDepth_buy[] | null;
|
||||||
|
/**
|
||||||
|
* Sequence number for the current snapshot of the market depth
|
||||||
|
*/
|
||||||
|
sequenceNumber: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepthSubscribe {
|
||||||
|
/**
|
||||||
|
* Subscribe to the market depths update
|
||||||
|
*/
|
||||||
|
marketDepth: marketDepthSubscribe_marketDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepthSubscribeVariables {
|
||||||
|
marketId: string;
|
||||||
|
}
|
91
libs/depth-chart/src/lib/__generated__/marketDepthUpdateSubscribe.ts
generated
Normal file
91
libs/depth-chart/src/lib/__generated__/marketDepthUpdateSubscribe.ts
generated
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL subscription operation: marketDepthUpdateSubscribe
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface marketDepthUpdateSubscribe_marketDepthUpdate_market_data {
|
||||||
|
__typename: "MarketData";
|
||||||
|
/**
|
||||||
|
* the arithmetic average of the best bid price and best offer price.
|
||||||
|
*/
|
||||||
|
midPrice: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepthUpdateSubscribe_marketDepthUpdate_market {
|
||||||
|
__typename: "Market";
|
||||||
|
/**
|
||||||
|
* Market ID
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* marketData for the given market
|
||||||
|
*/
|
||||||
|
data: marketDepthUpdateSubscribe_marketDepthUpdate_market_data | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepthUpdateSubscribe_marketDepthUpdate_sell {
|
||||||
|
__typename: "PriceLevel";
|
||||||
|
/**
|
||||||
|
* The price of all the orders at this level (uint64)
|
||||||
|
*/
|
||||||
|
price: string;
|
||||||
|
/**
|
||||||
|
* The total remaining size of all orders at this level (uint64)
|
||||||
|
*/
|
||||||
|
volume: string;
|
||||||
|
/**
|
||||||
|
* The number of orders at this price level (uint64)
|
||||||
|
*/
|
||||||
|
numberOfOrders: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepthUpdateSubscribe_marketDepthUpdate_buy {
|
||||||
|
__typename: "PriceLevel";
|
||||||
|
/**
|
||||||
|
* The price of all the orders at this level (uint64)
|
||||||
|
*/
|
||||||
|
price: string;
|
||||||
|
/**
|
||||||
|
* The total remaining size of all orders at this level (uint64)
|
||||||
|
*/
|
||||||
|
volume: string;
|
||||||
|
/**
|
||||||
|
* The number of orders at this price level (uint64)
|
||||||
|
*/
|
||||||
|
numberOfOrders: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepthUpdateSubscribe_marketDepthUpdate {
|
||||||
|
__typename: "MarketDepthUpdate";
|
||||||
|
/**
|
||||||
|
* Market id
|
||||||
|
*/
|
||||||
|
market: marketDepthUpdateSubscribe_marketDepthUpdate_market;
|
||||||
|
/**
|
||||||
|
* Sell side price levels (if available)
|
||||||
|
*/
|
||||||
|
sell: marketDepthUpdateSubscribe_marketDepthUpdate_sell[] | null;
|
||||||
|
/**
|
||||||
|
* Buy side price levels (if available)
|
||||||
|
*/
|
||||||
|
buy: marketDepthUpdateSubscribe_marketDepthUpdate_buy[] | null;
|
||||||
|
/**
|
||||||
|
* Sequence number for the current snapshot of the market depth
|
||||||
|
*/
|
||||||
|
sequenceNumber: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepthUpdateSubscribe {
|
||||||
|
/**
|
||||||
|
* Subscribe to price level market depth updates
|
||||||
|
*/
|
||||||
|
marketDepthUpdate: marketDepthUpdateSubscribe_marketDepthUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface marketDepthUpdateSubscribeVariables {
|
||||||
|
marketId: string;
|
||||||
|
}
|
14
libs/depth-chart/src/lib/depth-chart.spec.tsx
Normal file
14
libs/depth-chart/src/lib/depth-chart.spec.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { DepthChartContainer } from './depth-chart';
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
|
|
||||||
|
describe('DepthChart', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(
|
||||||
|
<MockedProvider>
|
||||||
|
<DepthChartContainer marketId={'market-id'} />
|
||||||
|
</MockedProvider>
|
||||||
|
);
|
||||||
|
expect(baseElement).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
65
libs/depth-chart/src/lib/depth-chart.tsx
Normal file
65
libs/depth-chart/src/lib/depth-chart.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import 'pennant/dist/style.css';
|
||||||
|
|
||||||
|
import { addDecimal, ThemeContext } from '@vegaprotocol/react-helpers';
|
||||||
|
import { DepthChart } from 'pennant';
|
||||||
|
import type { DepthChartProps } from 'pennant';
|
||||||
|
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { marketDepthUpdateSubscribe_marketDepthUpdate_sell } from './__generated__/marketDepthUpdateSubscribe';
|
||||||
|
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { useDepthUpdate } from './hooks/use-depth-update';
|
||||||
|
|
||||||
|
type PriceLevel = Pick<
|
||||||
|
marketDepthUpdateSubscribe_marketDepthUpdate_sell,
|
||||||
|
'price' | 'volume'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type DepthChartContainerProps = {
|
||||||
|
marketId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DepthChartContainer = ({ marketId }: DepthChartContainerProps) => {
|
||||||
|
const theme = useContext(ThemeContext);
|
||||||
|
|
||||||
|
const { data, loading, error } = useDepthUpdate({ marketId }, 500);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <Splash>Error</Splash>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Splash>Loading...</Splash>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || !data.market) {
|
||||||
|
return <Splash>No Data</Splash>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const market = data.market;
|
||||||
|
const decimalPlaces = data.market.decimalPlaces;
|
||||||
|
const depthData: DepthChartProps['data'] = { buy: [], sell: [] };
|
||||||
|
|
||||||
|
if (market.depth) {
|
||||||
|
if (market.depth.buy) {
|
||||||
|
depthData.buy = market?.depth.buy?.map((priceLevel: PriceLevel) => ({
|
||||||
|
price: Number(addDecimal(priceLevel.price, decimalPlaces)),
|
||||||
|
volume: Number(priceLevel.volume),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (market.depth.sell) {
|
||||||
|
depthData.sell = market?.depth.sell?.map((priceLevel: PriceLevel) => ({
|
||||||
|
price: Number(addDecimal(priceLevel.price, decimalPlaces)),
|
||||||
|
volume: Number(priceLevel.volume),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let midPrice: number | undefined = undefined;
|
||||||
|
|
||||||
|
if (market.data?.midPrice) {
|
||||||
|
midPrice = Number(addDecimal(market.data.midPrice, decimalPlaces));
|
||||||
|
}
|
||||||
|
|
||||||
|
return <DepthChart data={depthData} midPrice={midPrice} theme={theme} />;
|
||||||
|
};
|
1
libs/depth-chart/src/lib/helpers/index.ts
Normal file
1
libs/depth-chart/src/lib/helpers/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './update-depth-update';
|
110
libs/depth-chart/src/lib/helpers/update-depth-update.spec.ts
Normal file
110
libs/depth-chart/src/lib/helpers/update-depth-update.spec.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { updateDepthUpdate } from './update-depth-update';
|
||||||
|
|
||||||
|
describe('updateDepthUpdate', () => {
|
||||||
|
it('Updates typical case', () => {
|
||||||
|
const prev = createMarketDepth([{ price: '100', volume: '10' }], null);
|
||||||
|
const update = createMarketDepthUpdate(
|
||||||
|
[{ price: '200', volume: '20' }],
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
const expected = createMarketDepth(
|
||||||
|
[
|
||||||
|
{ price: '200', volume: '20' },
|
||||||
|
{ price: '100', volume: '10' },
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(updateDepthUpdate(prev, update)).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Removes price level', () => {
|
||||||
|
const prev = createMarketDepth(
|
||||||
|
[
|
||||||
|
{ price: '200', volume: '20' },
|
||||||
|
{ price: '100', volume: '10' },
|
||||||
|
],
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
const update = createMarketDepthUpdate(
|
||||||
|
[{ price: '200', volume: '0' }],
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
const expected = createMarketDepth([{ price: '100', volume: '10' }], []);
|
||||||
|
|
||||||
|
expect(updateDepthUpdate(prev, update)).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createMarketDepth(
|
||||||
|
buy: { price: string; volume: string }[] | null,
|
||||||
|
sell: { price: string; volume: string }[] | null
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
market: {
|
||||||
|
__typename: 'Market' as const,
|
||||||
|
id: 'id',
|
||||||
|
decimalPlaces: 0,
|
||||||
|
data: { __typename: 'MarketData' as const, midPrice: '100' },
|
||||||
|
depth: {
|
||||||
|
__typename: 'MarketDepth' as const,
|
||||||
|
lastTrade: { __typename: 'Trade' as const, price: '100' },
|
||||||
|
sell: sell
|
||||||
|
? sell.map((priceLevel) => ({
|
||||||
|
__typename: 'PriceLevel' as const,
|
||||||
|
price: priceLevel.price,
|
||||||
|
volume: priceLevel.volume,
|
||||||
|
numberOfOrders: '20',
|
||||||
|
}))
|
||||||
|
: null,
|
||||||
|
buy: buy
|
||||||
|
? buy.map((priceLevel) => ({
|
||||||
|
__typename: 'PriceLevel' as const,
|
||||||
|
price: priceLevel.price,
|
||||||
|
volume: priceLevel.volume,
|
||||||
|
numberOfOrders: '20',
|
||||||
|
}))
|
||||||
|
: null,
|
||||||
|
sequenceNumber: '0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMarketDepthUpdate(
|
||||||
|
buy: { price: string; volume: string }[] | null,
|
||||||
|
sell: { price: string; volume: string }[] | null
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
marketDepthUpdate: {
|
||||||
|
__typename: 'MarketDepthUpdate' as const,
|
||||||
|
market: {
|
||||||
|
__typename: 'Market' as const,
|
||||||
|
id: 'id',
|
||||||
|
data: { __typename: 'MarketData' as const, midPrice: '100' },
|
||||||
|
},
|
||||||
|
sell: sell
|
||||||
|
? sell.map((priceLevel) => ({
|
||||||
|
__typename: 'PriceLevel' as const,
|
||||||
|
price: priceLevel.price,
|
||||||
|
volume: priceLevel.volume,
|
||||||
|
numberOfOrders: '20',
|
||||||
|
}))
|
||||||
|
: null,
|
||||||
|
buy: buy
|
||||||
|
? buy.map((priceLevel) => ({
|
||||||
|
__typename: 'PriceLevel' as const,
|
||||||
|
price: priceLevel.price,
|
||||||
|
volume: priceLevel.volume,
|
||||||
|
numberOfOrders: '20',
|
||||||
|
}))
|
||||||
|
: null,
|
||||||
|
sequenceNumber: '1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
90
libs/depth-chart/src/lib/helpers/update-depth-update.ts
Normal file
90
libs/depth-chart/src/lib/helpers/update-depth-update.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import type {
|
||||||
|
marketDepth,
|
||||||
|
marketDepth_market_depth,
|
||||||
|
} from '../__generated__/marketDepth';
|
||||||
|
import type { marketDepthUpdateSubscribe } from '../__generated__/marketDepthUpdateSubscribe';
|
||||||
|
import sortBy from 'lodash/sortBy';
|
||||||
|
|
||||||
|
type MarketDepth = Pick<marketDepth_market_depth, 'buy' | 'sell'>;
|
||||||
|
|
||||||
|
export function updateDepthUpdate(
|
||||||
|
prev: marketDepth,
|
||||||
|
subscriptionData: { data: marketDepthUpdateSubscribe }
|
||||||
|
): marketDepth {
|
||||||
|
if (!subscriptionData.data.marketDepthUpdate || !prev.market) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
market: {
|
||||||
|
...prev.market,
|
||||||
|
...(prev.market.data && {
|
||||||
|
data: {
|
||||||
|
...prev.market.data,
|
||||||
|
midPrice:
|
||||||
|
subscriptionData.data.marketDepthUpdate.market.data?.midPrice ??
|
||||||
|
prev.market.data.midPrice,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
depth: {
|
||||||
|
...prev.market.depth,
|
||||||
|
...merge(prev.market.depth, subscriptionData.data.marketDepthUpdate),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function merge(snapshot: MarketDepth, update: MarketDepth): MarketDepth {
|
||||||
|
let buy = snapshot.buy ? [...snapshot.buy] : null;
|
||||||
|
let sell = snapshot.sell ? [...snapshot.sell] : null;
|
||||||
|
|
||||||
|
if (buy !== null) {
|
||||||
|
if (update.buy !== null) {
|
||||||
|
for (const priceLevel of update.buy) {
|
||||||
|
const index = buy.findIndex(
|
||||||
|
(level) => level.price === priceLevel.price
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
if (priceLevel.volume !== '0') {
|
||||||
|
buy.splice(index, 1, priceLevel);
|
||||||
|
} else {
|
||||||
|
buy.splice(index, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buy.push(priceLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buy = update.buy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sell !== null) {
|
||||||
|
if (update.sell !== null) {
|
||||||
|
for (const priceLevel of update.sell) {
|
||||||
|
const index = sell.findIndex(
|
||||||
|
(level) => level.price === priceLevel.price
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
if (priceLevel.volume !== '0') {
|
||||||
|
sell.splice(index, 1, priceLevel);
|
||||||
|
} else {
|
||||||
|
sell.splice(index, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sell.push(priceLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sell = update.sell;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
buy: sortBy(buy, (d) => -parseInt(d.price)),
|
||||||
|
sell: sortBy(sell, (d) => parseInt(d.price)),
|
||||||
|
};
|
||||||
|
}
|
135
libs/depth-chart/src/lib/hooks/use-depth-update.ts
Normal file
135
libs/depth-chart/src/lib/hooks/use-depth-update.ts
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import type { ApolloError } from '@apollo/client';
|
||||||
|
import { useApolloClient } from '@apollo/client';
|
||||||
|
import throttle from 'lodash/throttle';
|
||||||
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { updateDepthUpdate } from '../helpers';
|
||||||
|
import {
|
||||||
|
MARKET_DEPTH_QUERY,
|
||||||
|
MARKET_DEPTH_UPDATE_SUB,
|
||||||
|
} from '../queries/market-depth';
|
||||||
|
import type {
|
||||||
|
marketDepth,
|
||||||
|
marketDepthVariables,
|
||||||
|
} from '../__generated__/marketDepth';
|
||||||
|
import type {
|
||||||
|
marketDepthUpdateSubscribe,
|
||||||
|
marketDepthUpdateSubscribeVariables,
|
||||||
|
} from '../__generated__/marketDepthUpdateSubscribe';
|
||||||
|
|
||||||
|
export interface QueryResult<TData> {
|
||||||
|
data: TData | undefined;
|
||||||
|
loading: boolean;
|
||||||
|
error?: ApolloError;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDepthUpdate({ marketId }: marketDepthVariables, wait = 0) {
|
||||||
|
const queryResultRef = useRef<QueryResult<marketDepth>>({
|
||||||
|
data: undefined,
|
||||||
|
loading: true,
|
||||||
|
error: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [queryResult, setQueryResult] = useState<QueryResult<marketDepth>>({
|
||||||
|
data: undefined,
|
||||||
|
loading: true,
|
||||||
|
error: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sequenceNumber = useRef<null | number>(null);
|
||||||
|
const [stallCount, setStallCount] = useState(0);
|
||||||
|
|
||||||
|
const client = useApolloClient();
|
||||||
|
|
||||||
|
const handleUpdate = useMemo(
|
||||||
|
() => throttle(setQueryResult, wait, { leading: true }),
|
||||||
|
[wait]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
const { data, loading, error } = await client.query<
|
||||||
|
marketDepth,
|
||||||
|
marketDepthVariables
|
||||||
|
>({
|
||||||
|
query: MARKET_DEPTH_QUERY,
|
||||||
|
variables: { marketId },
|
||||||
|
fetchPolicy: 'no-cache',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.market?.depth.sequenceNumber) {
|
||||||
|
sequenceNumber.current = Number.parseInt(
|
||||||
|
data.market?.depth.sequenceNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
queryResultRef.current = { data, loading, error };
|
||||||
|
handleUpdate({ data, loading, error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
}, [client, handleUpdate, marketId, stallCount]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!marketId) return;
|
||||||
|
|
||||||
|
const result = client.subscribe<
|
||||||
|
marketDepthUpdateSubscribe,
|
||||||
|
marketDepthUpdateSubscribeVariables
|
||||||
|
>({
|
||||||
|
query: MARKET_DEPTH_UPDATE_SUB,
|
||||||
|
variables: { marketId },
|
||||||
|
fetchPolicy: 'no-cache',
|
||||||
|
errorPolicy: 'none',
|
||||||
|
});
|
||||||
|
|
||||||
|
const subscription = result.subscribe((result) => {
|
||||||
|
const prev = queryResultRef.current.data;
|
||||||
|
const subscriptionData = result;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!prev ||
|
||||||
|
!subscriptionData.data ||
|
||||||
|
subscriptionData.data?.marketDepthUpdate?.market?.id !== prev.market?.id
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextSequenceNumber = Number.parseInt(
|
||||||
|
subscriptionData.data.marketDepthUpdate.sequenceNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
prev.market &&
|
||||||
|
subscriptionData.data?.marketDepthUpdate &&
|
||||||
|
sequenceNumber.current !== null &&
|
||||||
|
nextSequenceNumber !== sequenceNumber.current + 1
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`Refetching: Expected ${
|
||||||
|
sequenceNumber.current + 1
|
||||||
|
} but got ${nextSequenceNumber}`
|
||||||
|
);
|
||||||
|
|
||||||
|
sequenceNumber.current = null;
|
||||||
|
|
||||||
|
// Trigger refetch
|
||||||
|
setStallCount((count) => count + 1);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sequenceNumber.current = nextSequenceNumber;
|
||||||
|
|
||||||
|
const depth = updateDepthUpdate(prev, { data: subscriptionData.data });
|
||||||
|
|
||||||
|
queryResultRef.current.data = depth;
|
||||||
|
handleUpdate({ data: depth, loading: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subscription && subscription.unsubscribe();
|
||||||
|
};
|
||||||
|
}, [client, handleUpdate, marketId]);
|
||||||
|
|
||||||
|
return queryResult;
|
||||||
|
}
|
53
libs/depth-chart/src/lib/queries/market-depth.ts
Normal file
53
libs/depth-chart/src/lib/queries/market-depth.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const MARKET_DEPTH_QUERY = gql`
|
||||||
|
query MarketDepth($marketId: ID!) {
|
||||||
|
market(id: $marketId) {
|
||||||
|
id
|
||||||
|
decimalPlaces
|
||||||
|
data {
|
||||||
|
midPrice
|
||||||
|
}
|
||||||
|
depth {
|
||||||
|
lastTrade {
|
||||||
|
price
|
||||||
|
}
|
||||||
|
sell {
|
||||||
|
price
|
||||||
|
volume
|
||||||
|
numberOfOrders
|
||||||
|
}
|
||||||
|
buy {
|
||||||
|
price
|
||||||
|
volume
|
||||||
|
numberOfOrders
|
||||||
|
}
|
||||||
|
sequenceNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const MARKET_DEPTH_UPDATE_SUB = gql`
|
||||||
|
subscription MarketDepthUpdateSubscribe($marketId: ID!) {
|
||||||
|
marketDepthUpdate(marketId: $marketId) {
|
||||||
|
market {
|
||||||
|
id
|
||||||
|
data {
|
||||||
|
midPrice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sell {
|
||||||
|
price
|
||||||
|
volume
|
||||||
|
numberOfOrders
|
||||||
|
}
|
||||||
|
buy {
|
||||||
|
price
|
||||||
|
volume
|
||||||
|
numberOfOrders
|
||||||
|
}
|
||||||
|
sequenceNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
25
libs/depth-chart/tsconfig.json
Normal file
25
libs/depth-chart/tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.lib.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
19
libs/depth-chart/tsconfig.lib.json
Normal file
19
libs/depth-chart/tsconfig.lib.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"declaration": true,
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.spec.tsx",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"**/*.spec.js",
|
||||||
|
"**/*.test.js",
|
||||||
|
"**/*.spec.jsx",
|
||||||
|
"**/*.test.jsx"
|
||||||
|
],
|
||||||
|
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||||
|
}
|
19
libs/depth-chart/tsconfig.spec.json
Normal file
19
libs/depth-chart/tsconfig.spec.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"module": "commonjs",
|
||||||
|
"types": ["jest", "node"]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"**/*.spec.tsx",
|
||||||
|
"**/*.test.js",
|
||||||
|
"**/*.spec.js",
|
||||||
|
"**/*.test.jsx",
|
||||||
|
"**/*.spec.jsx",
|
||||||
|
"**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
12
libs/types/src/__generated__/globalTypes.ts
generated
12
libs/types/src/__generated__/globalTypes.ts
generated
@ -76,6 +76,18 @@ export enum DepositStatus {
|
|||||||
Open = "Open",
|
Open = "Open",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interval for trade candles when subscribing via VEGA graphql, default is I15M
|
||||||
|
*/
|
||||||
|
export enum Interval {
|
||||||
|
I15M = "I15M",
|
||||||
|
I1D = "I1D",
|
||||||
|
I1H = "I1H",
|
||||||
|
I1M = "I1M",
|
||||||
|
I5M = "I5M",
|
||||||
|
I6H = "I6H",
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current state of a market
|
* The current state of a market
|
||||||
*/
|
*/
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"next": "12.0.7",
|
"next": "12.0.7",
|
||||||
"nx": "^13.8.3",
|
"nx": "^13.8.3",
|
||||||
|
"pennant": "0.4.5",
|
||||||
"postcss": "^8.4.6",
|
"postcss": "^8.4.6",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-copy-to-clipboard": "^5.0.4",
|
"react-copy-to-clipboard": "^5.0.4",
|
||||||
@ -112,6 +113,7 @@
|
|||||||
"eslint-plugin-unicorn": "^41.0.0",
|
"eslint-plugin-unicorn": "^41.0.0",
|
||||||
"husky": "^7.0.4",
|
"husky": "^7.0.4",
|
||||||
"jest": "27.2.3",
|
"jest": "27.2.3",
|
||||||
|
"jest-canvas-mock": "^2.3.1",
|
||||||
"lint-staged": "^12.3.3",
|
"lint-staged": "^12.3.3",
|
||||||
"nx": "^13.8.3",
|
"nx": "^13.8.3",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
|
@ -16,9 +16,11 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@vegaprotocol/accounts": ["libs/accounts/src/index.ts"],
|
"@vegaprotocol/accounts": ["libs/accounts/src/index.ts"],
|
||||||
|
"@vegaprotocol/chart": ["libs/chart/src/index.ts"],
|
||||||
"@vegaprotocol/cypress": ["libs/cypress/src/index.ts"],
|
"@vegaprotocol/cypress": ["libs/cypress/src/index.ts"],
|
||||||
"@vegaprotocol/deal-ticket": ["libs/deal-ticket/src/index.ts"],
|
"@vegaprotocol/deal-ticket": ["libs/deal-ticket/src/index.ts"],
|
||||||
"@vegaprotocol/deposits": ["libs/deposits/src/index.ts"],
|
"@vegaprotocol/deposits": ["libs/deposits/src/index.ts"],
|
||||||
|
"@vegaprotocol/depth-chart": ["libs/depth-chart/src/index.ts"],
|
||||||
"@vegaprotocol/market-list": ["libs/market-list/src/index.ts"],
|
"@vegaprotocol/market-list": ["libs/market-list/src/index.ts"],
|
||||||
"@vegaprotocol/network-stats": ["libs/network-stats/src/index.ts"],
|
"@vegaprotocol/network-stats": ["libs/network-stats/src/index.ts"],
|
||||||
"@vegaprotocol/order-list": ["libs/order-list/src/index.ts"],
|
"@vegaprotocol/order-list": ["libs/order-list/src/index.ts"],
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
"version": 2,
|
"version": 2,
|
||||||
"projects": {
|
"projects": {
|
||||||
"accounts": "libs/accounts",
|
"accounts": "libs/accounts",
|
||||||
|
"chart": "libs/chart",
|
||||||
"cypress": "libs/cypress",
|
"cypress": "libs/cypress",
|
||||||
"deal-ticket": "libs/deal-ticket",
|
"deal-ticket": "libs/deal-ticket",
|
||||||
"deposits": "libs/deposits",
|
"deposits": "libs/deposits",
|
||||||
|
"depth-chart": "libs/depth-chart",
|
||||||
"explorer": "apps/explorer",
|
"explorer": "apps/explorer",
|
||||||
"explorer-e2e": "apps/explorer-e2e",
|
"explorer-e2e": "apps/explorer-e2e",
|
||||||
"market-list": "libs/market-list",
|
"market-list": "libs/market-list",
|
||||||
|
Loading…
Reference in New Issue
Block a user