Skip to content

Commit

Permalink
feat(vendor-deepcoin): add vendor deepcoin for tick
Browse files Browse the repository at this point in the history
  • Loading branch information
Thrimbda committed Feb 20, 2024
1 parent 985be6b commit 9f38abd
Show file tree
Hide file tree
Showing 14 changed files with 882 additions and 6 deletions.
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

0 comments on commit 9f38abd

Please sign in to comment.