Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vendor-deepcoin): add vendor deepcoin for tick #445

Merged
merged 2 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
411 changes: 411 additions & 0 deletions apps/vendor-deepcoin/api-extractor.json

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions apps/vendor-deepcoin/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# syntax = docker/dockerfile:1.4
FROM node:18.17.0-bullseye-slim

LABEL maintainer="Siyuan Wang <[email protected]>"

# https://github.com/nodejs/docker-node/blob/main/docs/BestPractices.md#handling-kernel-signals
ARG TINI_VERSION=v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod a+x /tini
ENTRYPOINT ["/tini", "--"]

ENV NODE_ENV=production

USER node

WORKDIR /app

COPY --chown=node:node ./out/app-vendor-deepcoin-out /app

RUN node create-links.js create

WORKDIR /app/apps/vendor-deepcoin

# USER nobody
CMD ["node", "./lib/index.js"]
3 changes: 3 additions & 0 deletions apps/vendor-deepcoin/config/jest.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "@rushstack/heft-node-rig/profiles/default/config/jest.config.json"
}
18 changes: 18 additions & 0 deletions apps/vendor-deepcoin/config/rig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",

/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@rushstack/heft-node-rig"

/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}
87 changes: 87 additions & 0 deletions apps/vendor-deepcoin/config/typescript.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Configures the TypeScript plugin for Heft. This plugin also manages linting.
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/heft/typescript.schema.json",

/**
* Optionally specifies another JSON config file that this file extends from. This provides a way for standard
* settings to be shared across multiple projects.
*/
// "extends": "base-project/config/typescript.json",

/**
* Can be set to "copy" or "hardlink". If set to "copy", copy files from cache.
* If set to "hardlink", files will be hardlinked to the cache location.
* This option is useful when producing a tarball of build output as TAR files don't
* handle these hardlinks correctly. "hardlink" is the default behavior.
*/
// "copyFromCacheMode": "copy",

/**
* If provided, emit these module kinds in addition to the modules specified in the tsconfig.
* Note that this option only applies to the main tsconfig.json configuration.
*/
"additionalModuleKindsToEmit": [
{
"moduleKind": "esnext",
"outFolderName": "dist"
}
// {
// /**
// * (Required) Must be one of "commonjs", "amd", "umd", "system", "es2015", "esnext"
// */
// "moduleKind": "amd",
//
// /**
// * (Required) The name of the folder where the output will be written.
// */
// "outFolderName": "lib-amd"
// }
],

/**
* Specifies the intermediary folder that tests will use. Because Jest uses the
* Node.js runtime to execute tests, the module format must be CommonJS.
*
* The default value is "lib".
*/
// "emitFolderNameForTests": "lib-commonjs",

/**
* If set to "true", the TSlint task will not be invoked.
*/
// "disableTslint": true,

/**
* Set this to change the maximum number of file handles that will be opened concurrently for writing.
* The default is 50.
*/
// "maxWriteParallelism": 50,

/**
* Configures additional file types that should be copied into the TypeScript compiler's emit folders, for example
* so that these files can be resolved by import statements.
*/
"staticAssetsToCopy": {
/**
* File extensions that should be copied from the src folder to the destination folder(s).
*/
// "fileExtensions": [
// ".json", ".css"
// ],
/**
* Glob patterns that should be explicitly included.
*/
// "includeGlobs": [
// "some/path/*.js"
// ],
/**
* Glob patterns that should be explicitly excluded. This takes precedence over globs listed
* in "includeGlobs" and files that match the file extensions provided in "fileExtensions".
*/
// "excludeGlobs": [
// "some/path/*.css"
// ]
}
}
9 changes: 9 additions & 0 deletions apps/vendor-deepcoin/etc/vendor-deepcoin.api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## API Report File for "@yuants/vendor-deepcoin"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

// (No @packageDocumentation comment for this package)

```
44 changes: 44 additions & 0 deletions apps/vendor-deepcoin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@yuants/vendor-deepcoin",
"version": "0.0.0",
"homepage": "https://github.com/No-Trade-No-Life/Yuan/tree/main/apps/vendor-deepcoin",
"main": "lib/index.js",
"module": "dist/index.js",
"files": [
"dist",
"lib",
"temp"
],
"scripts": {
"build": "heft test --clean && api-extractor run --local && yuan-toolkit post-build"
},
"dependencies": {
"@types/json-schema": "~7.0.11",
"@yuants/data-model": "workspace:*",
"@yuants/kernel": "workspace:*",
"@yuants/protocol": "workspace:*",
"@yuants/utils": "workspace:*",
"ajv": "~8.12.0",
"ajv-formats": "~2.1.1",
"rxjs": "~7.5.6",
"ws": "~8.16.0"
},
"devDependencies": {
"@microsoft/api-extractor": "~7.30.0",
"@rushstack/heft": "~0.47.5",
"@rushstack/heft-jest-plugin": "~0.3.30",
"@rushstack/heft-node-rig": "~1.10.7",
"@types/heft-jest": "1.0.3",
"@types/json-schema": "~7.0.11",
"@types/node": "18",
"@yuants/extension": "workspace:*",
"@yuants/tool-kit": "workspace:*",
"json-schema": "~0.4.0",
"typescript": "~4.7.4",
"@types/ws": "~8.5.10"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
}
}
99 changes: 99 additions & 0 deletions apps/vendor-deepcoin/src/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { IExtensionContext, makeDockerEnvs, makeK8sEnvs } from '@yuants/extension';
export default (context: IExtensionContext) => {
context.registerDeployProvider({
make_json_schema: () => ({
type: 'object',
properties: {
env: {
type: 'object',
required: ['HOST_URL'],
properties: {
HOST_URL: {
type: 'string',
title: '主机地址',
},
},
},
},
}),
make_docker_compose_file: async (ctx, envCtx) => {
return {
[`crysto-api`.replace(/\s/g, '')]: {
image: `registry.ap-southeast-1.aliyuncs.com/ntnl-y/vendor-deepcoin-api:${
ctx.version ?? envCtx.version
}`,
restart: 'always',
environment: makeDockerEnvs(ctx.env),
},
};
},
make_k8s_resource_objects: async (ctx, envCtx) => {
const entry = ctx.env?.ENTRY!;
const manifest_key = ctx.key;
return {
deployment: {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
labels: {
'y.ntnl.io/version': ctx.version ?? envCtx.version,
'y.ntnl.io/component': 'crysto-api',
'y.ntnl.io/manifest-key': manifest_key,
},
name: `crysto-api-${manifest_key}`.replace(/\s/g, '').replace(/\./g, '').toLocaleLowerCase(),
namespace: 'yuan',
},
spec: {
replicas: 1,
selector: {
matchLabels: {
'y.ntnl.io/component': 'crysto-api',
'y.ntnl.io/manifest-key': manifest_key,
},
},
template: {
metadata: {
labels: {
'y.ntnl.io/version': ctx.version ?? envCtx.version,
'y.ntnl.io/component': 'crysto-api',
'y.ntnl.io/manifest-key': manifest_key,
},
},
spec: {
containers: [
{
command: ['./node_modules/.bin/ts-node'],
args: [`src/${entry}`],
env: makeK8sEnvs(ctx.env),
image: `registry.ap-southeast-1.aliyuncs.com/ntnl-y/vendor-deepcoin-api:${
ctx.version ?? envCtx.version
}`,
// TODO: remove this after CI is ready
imagePullPolicy: 'Always',
name: 'crysto-api',
resources: {
limits: {
cpu: ctx.cpu?.max ?? '500m',
memory: ctx.memory?.max ?? '256Mi',
},
requests: {
cpu: ctx.cpu?.min ?? '100m',
memory: ctx.memory?.min ?? '128Mi',
},
},
},
],
hostname: 'crysto-api',
imagePullSecrets: [
{
name: 'pull-secret',
},
],
},
},
},
},
};
},
});
};
100 changes: 100 additions & 0 deletions apps/vendor-deepcoin/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { ITick, UUID, formatTime } from '@yuants/data-model';
import { Terminal } from '@yuants/protocol';
import { Subject, filter, fromEvent, interval, map, mergeMap, tap } from 'rxjs';
import { MessageEvent, WebSocket } from 'ws';

// API Doc: https://www.deepcoin.com/zh/docs#WebSocket-public-address
const ws = new WebSocket('wss://stream.deepcoin.com/public/ws');

const datasource_id = 'DeepCoin';
const terminal = new Terminal(process.env.HOST_URL!, {
terminal_id: `DeepCoinAPI/${UUID()}`,
name: 'DeepCoin API',
});

const tick$ = new Subject<ITick>();

// TODO
// const products$ = defer(() =>
// fetch(
// `https://api.deepcoin.com/deepcoin/market/instruments?instType=SWAP`
// ).then((v) => v.json())
// ).pipe(
// //
// mergeAll(),
// map(
// (v: any): IProduct => ({
// datasource_id,
// product_id: `${v.baseCcy}${v.quoteCcy}`,
// name: v.InstId,
// base_currency: v.baseCcy,
// quote_currency: v.quoteCcy,
// price_step: v.tickSz,
// // volume_step: v.ctVal,
// // value_scale:
// })
// ),
// shareReplay(1)
// );

interval(5000).subscribe(() => {
ws.send('ping');
});

fromEvent<MessageEvent>(ws, 'message')
.pipe(
map((e) => e.data.toString()),
// tap((x) => {
// console.info(formatTime(Date.now()), "RX", x);
// }),
filter((x) => x !== 'pong'),
map((x) => JSON.parse(x)),
filter((v) => v.action === 'PushMarketDataOverView' && !!v.result),
// map deepcoin tick to ITick
mergeMap((v) => v.result),
map((v: any): ITick => {
return {
datasource_id,
product_id: v.data.InstrumentID,
//NOTE: rawTick.UpdateMilliSecond should be the millisecond part of rawTick.UpdateTime
// yet it's not documented and always be 132 in my test.
updated_at: v.data.UpdateTime * 1000,
price: v.data.LastPrice,
// volume: v.data.Volume,
ask: v.data.AskPrice1,
bid: v.data.BidPrice1,
interest_rate_for_long: -v.data.PositionFeeRate,
interest_rate_for_short: v.data.PositionFeeRate,
};
}),
)
.subscribe(tick$);

let LocalNo = 0;

fromEvent(ws, 'close').subscribe(() => {
console.info(formatTime(Date.now()), 'WS Closed, Exiting...');
process.exit(0);
});

terminal.provideTicks(datasource_id, (product_id) => {
console.info(formatTime(Date.now()), 'Subscribing', product_id);
ws.send(
JSON.stringify({
SendTopicAction: {
Action: '1',
FilterValue: `DeepCoin_${product_id}`,
LocalNo: LocalNo++,
ResumeNo: -1,
TopicID: '7',
},
}),
);
return tick$.pipe(
//
filter((v) => v.product_id === product_id),
tap((x) => {
console.info(formatTime(Date.now()), 'Tick', JSON.stringify(x));
}),
);
});
Loading
Loading