@watsonserve/stock-base 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dao.js +103 -0
- package/index.js +3 -0
- package/log.js +42 -0
- package/package.json +18 -0
- package/stock.js +69 -0
package/dao.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { InfluxDB, Point } from '@influxdata/influxdb-client';
|
|
2
|
+
import { request } from 'node:http';
|
|
3
|
+
import { EnCurrency, EnMarket } from './stock.js';
|
|
4
|
+
function getStartTime() {
|
|
5
|
+
switch (new Date().getUTCDay()) {
|
|
6
|
+
case 0: // Sunday
|
|
7
|
+
return '-2d';
|
|
8
|
+
case 1:
|
|
9
|
+
return '-3d';
|
|
10
|
+
default:
|
|
11
|
+
}
|
|
12
|
+
return '-1d';
|
|
13
|
+
}
|
|
14
|
+
export function toFxPoint(sgd, hkd, cny, timestamp) {
|
|
15
|
+
return new Point(EnMarket.FX)
|
|
16
|
+
.floatField('SGD', sgd)
|
|
17
|
+
.floatField('HKD', hkd)
|
|
18
|
+
.floatField('CNY', cny)
|
|
19
|
+
.timestamp(timestamp);
|
|
20
|
+
}
|
|
21
|
+
export function toStockPoint(market, st, timestamp) {
|
|
22
|
+
return new Point(market)
|
|
23
|
+
.tag('nc', st.nc)
|
|
24
|
+
.floatField('o', st.o || 0)
|
|
25
|
+
.floatField('h', st.h || 0)
|
|
26
|
+
.floatField('l', st.l || 0)
|
|
27
|
+
.floatField('c', st.c || 0)
|
|
28
|
+
.intField('v', st.v || 0)
|
|
29
|
+
.floatField('boll_u', st.boll_u || 0)
|
|
30
|
+
.floatField('boll_l', st.boll_l || 0)
|
|
31
|
+
.floatField('ma_5', st.ma_5 || 0)
|
|
32
|
+
.floatField('ma_20', st.ma_20 || 0)
|
|
33
|
+
.floatField('ma_60', st.ma_60 || 0)
|
|
34
|
+
.floatField('ma_250', st.ma_250 || 0)
|
|
35
|
+
.timestamp(timestamp);
|
|
36
|
+
}
|
|
37
|
+
export class InfluxDAO {
|
|
38
|
+
origin = '';
|
|
39
|
+
org = '';
|
|
40
|
+
bucket = '';
|
|
41
|
+
token = '';
|
|
42
|
+
constructor(dbConf) {
|
|
43
|
+
const { origin, org, bucket, token } = dbConf;
|
|
44
|
+
this.origin = origin;
|
|
45
|
+
this.org = org;
|
|
46
|
+
this.bucket = bucket;
|
|
47
|
+
this.token = token;
|
|
48
|
+
}
|
|
49
|
+
async rm(measurement, start, stop) {
|
|
50
|
+
const conf = {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
hostname: `${this.origin}/api/v2/delete?org=${this.org}&bucket=${this.bucket}`,
|
|
53
|
+
headers: {
|
|
54
|
+
'Authorization': `Token ${this.token}`,
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const body = JSON.stringify({ start, stop, predicate: `_measurement="${measurement}"` });
|
|
59
|
+
return new Promise((s, j) => request(conf, res => res.on('error', j).on('end', s)).end(body).on('error', j));
|
|
60
|
+
}
|
|
61
|
+
async writeToInfluxDB(points) {
|
|
62
|
+
const { origin: url, token, org, bucket } = this;
|
|
63
|
+
const client = new InfluxDB({ url, token });
|
|
64
|
+
const writeClient = client.getWriteApi(org, bucket, 's');
|
|
65
|
+
writeClient.writePoints(points);
|
|
66
|
+
await writeClient.flush();
|
|
67
|
+
await writeClient.close();
|
|
68
|
+
}
|
|
69
|
+
async __query(fileds, start, markets, ncs) {
|
|
70
|
+
const { origin: url, token, org, bucket } = this;
|
|
71
|
+
const client = new InfluxDB({ url, token });
|
|
72
|
+
const queryClient = client.getQueryApi(org);
|
|
73
|
+
const mkQuery = markets.map(mk => `r["_measurement"] == "${mk}"`).join(' or ');
|
|
74
|
+
const ncQuery = ncs.map(nc => `r["nc"] == "${nc}"`).join(' or ');
|
|
75
|
+
const fdQuery = fileds.map(fd => `r["_field"] == "${fd}"`).join(' or ');
|
|
76
|
+
const filters = [mkQuery, ncQuery, fdQuery].filter(Boolean).map(str => `|> filter(fn: (r) => ${str})`).join('\n');
|
|
77
|
+
const fluxQuery = `from(bucket: "${bucket}")
|
|
78
|
+
|> range(start: ${start})
|
|
79
|
+
${filters}
|
|
80
|
+
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")`;
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
const list = [];
|
|
83
|
+
queryClient.queryRows(fluxQuery, {
|
|
84
|
+
next: (row, tableMeta) => {
|
|
85
|
+
const item = tableMeta.toObject(row);
|
|
86
|
+
const { result, table, _start, _stop, _measurement, _time, nc, ...content } = item;
|
|
87
|
+
list.push({ time: new Date(_time), market: _measurement, nc, ...content });
|
|
88
|
+
},
|
|
89
|
+
error: reject,
|
|
90
|
+
complete: () => resolve(list),
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
;
|
|
95
|
+
async readFxLatest() {
|
|
96
|
+
const start = getStartTime();
|
|
97
|
+
return this.__query([EnCurrency.SGD, EnCurrency.HKD, EnCurrency.CNY], start, [EnMarket.FX], []);
|
|
98
|
+
}
|
|
99
|
+
async readStockLatest(ncs) {
|
|
100
|
+
const start = getStartTime();
|
|
101
|
+
return this.__query(['c', 'v'], start, [EnMarket.SGX, EnMarket.USA, EnMarket.HKEX], ncs);
|
|
102
|
+
}
|
|
103
|
+
}
|
package/index.js
ADDED
package/log.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { appendFile } from 'fs/promises';
|
|
2
|
+
export var EnLogLevel;
|
|
3
|
+
(function (EnLogLevel) {
|
|
4
|
+
EnLogLevel[EnLogLevel["DEBUG"] = 7] = "DEBUG";
|
|
5
|
+
EnLogLevel[EnLogLevel["INFO"] = 6] = "INFO";
|
|
6
|
+
EnLogLevel[EnLogLevel["WARN"] = 4] = "WARN";
|
|
7
|
+
EnLogLevel[EnLogLevel["ERROR"] = 3] = "ERROR";
|
|
8
|
+
})(EnLogLevel || (EnLogLevel = {}));
|
|
9
|
+
function findAppName() {
|
|
10
|
+
const __dirPath = import.meta.dirname.split('/').reverse();
|
|
11
|
+
if (['release', 'src', 'dist'].includes(__dirPath[0])) {
|
|
12
|
+
__dirPath.shift();
|
|
13
|
+
}
|
|
14
|
+
return __dirPath[0];
|
|
15
|
+
}
|
|
16
|
+
export class Log {
|
|
17
|
+
_logDir = `/var/log/${process.env.APP_NAME || findAppName()}`;
|
|
18
|
+
level = EnLogLevel.INFO;
|
|
19
|
+
constructor(logDir = '') {
|
|
20
|
+
if (!logDir)
|
|
21
|
+
return;
|
|
22
|
+
this._logDir = logDir;
|
|
23
|
+
}
|
|
24
|
+
log(lev, msg) {
|
|
25
|
+
if (lev > this.level)
|
|
26
|
+
return Promise.resolve();
|
|
27
|
+
return appendFile(`${this._logDir}/${EnLogLevel[lev].toLowerCase()}.log`, `[${new Date().toISOString()}] ${msg}\n`);
|
|
28
|
+
}
|
|
29
|
+
debug(msg) {
|
|
30
|
+
return this.log(EnLogLevel.DEBUG, msg);
|
|
31
|
+
}
|
|
32
|
+
info(msg) {
|
|
33
|
+
return this.log(EnLogLevel.INFO, msg);
|
|
34
|
+
}
|
|
35
|
+
warn(msg) {
|
|
36
|
+
return this.log(EnLogLevel.WARN, msg);
|
|
37
|
+
}
|
|
38
|
+
error(msg) {
|
|
39
|
+
return this.log(EnLogLevel.ERROR, msg.toString());
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export const log = new Log();
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@watsonserve/stock-base",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [],
|
|
7
|
+
"author": "",
|
|
8
|
+
"license": "ISC",
|
|
9
|
+
"packageManager": "pnpm@10.28.2",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@influxdata/influxdb-client": "^1.35.0",
|
|
12
|
+
"influx": "^5.12.0"
|
|
13
|
+
},
|
|
14
|
+
"main": "index.js",
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/stock.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export var EnMarket;
|
|
2
|
+
(function (EnMarket) {
|
|
3
|
+
EnMarket["FX"] = "FX";
|
|
4
|
+
EnMarket["JPX"] = "JPX";
|
|
5
|
+
EnMarket["CHN"] = "CHN";
|
|
6
|
+
EnMarket["HKEX"] = "HKEX";
|
|
7
|
+
EnMarket["SGX"] = "SGX";
|
|
8
|
+
EnMarket["LSE"] = "LSE";
|
|
9
|
+
EnMarket["USA"] = "USA";
|
|
10
|
+
})(EnMarket || (EnMarket = {}));
|
|
11
|
+
export var EnCurrency;
|
|
12
|
+
(function (EnCurrency) {
|
|
13
|
+
EnCurrency["SGD"] = "SGD";
|
|
14
|
+
EnCurrency["USD"] = "USD";
|
|
15
|
+
EnCurrency["HKD"] = "HKD";
|
|
16
|
+
EnCurrency["CNY"] = "CNY";
|
|
17
|
+
})(EnCurrency || (EnCurrency = {}));
|
|
18
|
+
export class Stock {
|
|
19
|
+
nc;
|
|
20
|
+
name;
|
|
21
|
+
currency;
|
|
22
|
+
o = 0;
|
|
23
|
+
c = 0;
|
|
24
|
+
h = 0;
|
|
25
|
+
l = 0;
|
|
26
|
+
v = 0;
|
|
27
|
+
constructor(code, name = '', currency = EnCurrency.USD) {
|
|
28
|
+
this.nc = code;
|
|
29
|
+
this.name = name;
|
|
30
|
+
this.currency = currency;
|
|
31
|
+
}
|
|
32
|
+
setInfo(name, close, open = 0, height = 0, low = 0, vol = 0) {
|
|
33
|
+
this.name = name;
|
|
34
|
+
this.o = open;
|
|
35
|
+
this.c = close;
|
|
36
|
+
this.h = height;
|
|
37
|
+
this.l = low;
|
|
38
|
+
this.v = vol;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export class HandleStock extends Stock {
|
|
42
|
+
count = 0;
|
|
43
|
+
cost = 0;
|
|
44
|
+
category = '';
|
|
45
|
+
usd_cost = 0;
|
|
46
|
+
proportion = 0;
|
|
47
|
+
_usdValue = 0;
|
|
48
|
+
constructor(nc, count = 0, cost = 0.0, currency = EnCurrency.USD, category = '') {
|
|
49
|
+
super(nc, '', currency);
|
|
50
|
+
this.count = count;
|
|
51
|
+
this.cost = cost;
|
|
52
|
+
this.category = category;
|
|
53
|
+
this.usd_cost = cost;
|
|
54
|
+
}
|
|
55
|
+
setFx(fx) {
|
|
56
|
+
this._usdValue = this.c * this.count / fx;
|
|
57
|
+
this.usd_cost = this.cost / fx;
|
|
58
|
+
}
|
|
59
|
+
set asset(asset) {
|
|
60
|
+
this.proportion = this.usdValue / asset * 100.0;
|
|
61
|
+
this.proportion;
|
|
62
|
+
}
|
|
63
|
+
get usdValue() {
|
|
64
|
+
return this._usdValue || (this.c * this.count);
|
|
65
|
+
}
|
|
66
|
+
toJSON() {
|
|
67
|
+
return `{"category": "${this.category}", "title": "${this.name}", "amount": ${this.usdValue.toFixed(2)}, "proportion": "${this.proportion.toFixed(2)}%"}`;
|
|
68
|
+
}
|
|
69
|
+
}
|