From dabe029fd57c04cd35500c839cc40ab773b8818c Mon Sep 17 00:00:00 2001 From: Chef5 Date: Sat, 12 Oct 2024 14:19:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9C=9F=E8=B4=A7=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 63 ++++++++++++++++++++------------- package.json | 11 +++--- src/extension.ts | 6 ++-- src/futures.ts | 40 ++++++++++++--------- src/render.ts | 83 ++++++++++++++++++++++---------------------- src/stock.ts | 11 +++++- types/stock-bar.d.ts | 2 ++ 7 files changed, 124 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 645b974..d680c11 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,44 @@ # Stock Bar -VScode 插件 | A 股 | 港股 | 实时股票数据 | 状态栏实时更新 +VScode 插件 | A 股 | 港股 | 期货 | 实时股票数据 | 状态栏实时更新 `Stock Bar`会在开盘期间自动刷新股票数据,并在 VScode 底部状态栏显示股票基本数据,让你在使用 VScode 期间能随时关注到你的股票。 -为了隐秘性,`Stock Bar`默认只会显示股价、百分点这样的纯数字,当你将鼠标移上去就可以查看详情。当然为了区分,也可以自定义显示股票的名称(建议使用英文字母,别问我为什么) +为了隐秘性,`Stock Bar`默认只会显示股价、百分点这样的纯数字,当你将鼠标移上去就可以查看详情。当然为了区分,也可以自定义显示股票的名称 ![image](https://raw.githubusercontent.com/Chef5/stock-bar/main/stock-bar-plugin.png) 插件已开源,开源地址:[Github](https://github.com/Chef5/stock-bar),欢迎点星星 ⭐️、提 issue 或者 pr -## 启动 +## 命令 -(ctrl + shift + P) 后运行命令: +`Ctrl/Cmd + Shift + P` 打开命令输入面板: -- stock watch start 开始显示 -- stock watch stop 关闭显示 +- Show Stock Bar 显示Stock Bar +- Hide Stock Bar 关闭Stock Bar(后台不再发起更新数据请求) ## 插件配置 修改用户配置,添加你所需要监控的股票代码 ```js -// 股票:这是一个数组,你可以直接添加股票代码字符串,也可以添加对象,对象格式如下: -// { -// code: string, // 股票代码:需要添加股市前缀,前缀参考文档下方:前缀说明 -// alias: string, // 别名:默认为空,建议使用字母 -// } -// 使用字符串时,别名默认为空:["sz000001"] 等价于 [{"code": "sz000001", "alias": ""}] +// 股票配置:数组 "stock-bar.stocks": [ - "sh000001", + "sh000001", // 可以直接添加股票代码字符串 { - "code": "sz000001", - "alias": "平安" + "code": "sz000001", // 也可以添加详细配置 + "alias": "平安", + "hold_price": 0, + "hold_number": 0 } ], -// 期货: 数组,可以添加期货代码 -// { -// code: string, //期货代码, 比如 sa9999, sa2409 -// alias: string, // 期货别名,这个可以不填,会自动根据代码获取 -// hold_price: number, //持仓价格,即多空单的价格 -// hold_number: number //持仓, 正数代表持有多单,负数代表持有空单,当配置了持仓价格和持仓后, -// 会显示持仓盈亏,否则显示当日价格涨跌幅 -// } +// 期货配置:数组 "stock-bar.futures": [ { - "code": "sa9999", - "hold_price": 2500, + "code": "cu2409", + "alias": "铜", + "hold_price": 0, "hold_number": -1 } ], @@ -62,6 +53,25 @@ VScode 插件 | A 股 | 港股 | 实时股票数据 | 状态栏实时更新 "stock-bar.fallColor": "" ``` +## 股票配置说明 + +股票配置可以股票代码字符串,也可以进行一下配置: + +- `**code**: string`: 股票代码,需要添加股市前缀,前缀参考文档下方:前缀说明 +- `**alias**: string`: 别名,默认为空 +- `**hold_price**: number`: 持仓价格,非必填,默认为0 +- `**hold_number**: number`: 持仓数量,非必填,默认为0 + +股票代码字符串时,如:`["sz000001"]` 等价于 `[{"code": "sz000001", "alias": "", "hold_price": 0, "hold_number": 0}]` + +## 股票配置说明 + +- `**code**: string`: 期货代码, 如:`cu2409`、`sa2409` +- `**alias**: string`: 期货别名 +- `**hold_price**: number`: 持仓价格,即多空单的价格 +- `**hold_number**: number`: 持仓,正数代表持有多单,负数代表持有空单 + + ## 前缀说明 - sh:沪市,不加前缀的情况下,6 开头的代码默认加上 sh(上证指数:sh000001) @@ -95,6 +105,9 @@ VScode 插件 | A 股 | 港股 | 实时股票数据 | 状态栏实时更新 + + +

## 版本许可 diff --git a/package.json b/package.json index dfb239f..78cf56f 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,9 @@ "sh000001", { "code": "sz000001", - "alias": "平安" + "alias": "平安", + "hold_price": 0, + "hold_number": 0 } ], "description": "股票代码数组,配置需要监控的股票代码,具体配置规则请查看插件详情页" @@ -39,7 +41,8 @@ "default": [ { "code": "sa9999", - "hold_price": 2300, + "alias": "铜", + "hold_price": 0, "hold_number": -1 } ], @@ -65,11 +68,11 @@ "commands": [ { "command": "stockbar.start", - "title": "stock watch start" + "title": "Show Stock Bar" }, { "command": "stockbar.stop", - "title": "stock watch stop" + "title": "Hide Stock Bar" } ] }, diff --git a/src/extension.ts b/src/extension.ts index fbc9641..1fc8d6e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -13,7 +13,7 @@ function loadChoiceStocks() { return new Stock(v); } if (typeof v === 'object') { - return new Stock(v.code, v.alias); + return new Stock(v.code, v.alias, v.hold_price, v.hold_number); } throw new Error( '配置格式错误, 查看 https://github.com/Chef5/stock-bar#配置', @@ -25,7 +25,6 @@ let timer = null; let stocks: Stock[]; function restart() { - //console.log('restart'); const interval = Configuration.getUpdateInterval(); if (timer) { clearInterval(timer); @@ -66,7 +65,6 @@ async function ticker() { } function stop() { - //console.log('stop'); if (timer) { clearInterval(timer); timer = null; @@ -75,7 +73,6 @@ function stop() { } export function activate(context: vscode.ExtensionContext) { - //console.log('activivate'); stocks = loadChoiceStocks(); const startCmd = vscode.commands.registerCommand('stockbar.start', restart); @@ -90,6 +87,7 @@ export function activate(context: vscode.ExtensionContext) { ); context.subscriptions.push(startCmd); context.subscriptions.push(stopCmd); + restart(); // 初始默认打开 } export function deactivate() { diff --git a/src/futures.ts b/src/futures.ts index 6592c65..32c81c3 100644 --- a/src/futures.ts +++ b/src/futures.ts @@ -4,6 +4,7 @@ import axios, { AxiosInstance } from 'axios'; import { FutureOption } from 'stock-bar'; import logger from './logger'; +import { keepDecimal } from './utils'; class Provider { instance: AxiosInstance; @@ -17,9 +18,13 @@ class Provider { try { const url = `https://fupage.10jqka.com.cn/futgwapi/api/f10/contract/v1/info?code=${code}`; const results = await Promise.all([ - this.instance.get(`https://fupage.10jqka.com.cn/futgwapi/api/f10/contract/v1/info?code=${code.toUpperCase()}`), - this.instance.get(`https://fupage.10jqka.com.cn/futgwapi/api/f10/contract/v1/info?code=${code.toLowerCase()}`) - ]) + this.instance.get( + `https://fupage.10jqka.com.cn/futgwapi/api/f10/contract/v1/info?code=${code.toUpperCase()}`, + ), + this.instance.get( + `https://fupage.10jqka.com.cn/futgwapi/api/f10/contract/v1/info?code=${code.toLowerCase()}`, + ), + ]); const data = results[0].data.data || results[1].data.data || {}; const name = data.name || ''; const tradeUnit = (data.trade_unit || '').trim(); @@ -90,22 +95,31 @@ class ProviderSina extends Provider { 'Content-Type': 'application/json', }; const url = `https://hq.sinajs.cn?list=nf_${code}`; - let ret = (await this.instance.get(url, { headers, responseType:'arraybuffer'})).data; + let ret = ( + await this.instance.get(url, { headers, responseType: 'arraybuffer' }) + ).data; ret = new TextDecoder('GBK').decode(ret); const match = ret.match(/\"(.*)\"/); if (!match) { return null; } const arr = match[1].split(','); - if (arr.length <= 5) { return null; } + if (arr.length <= 5) { + return null; + } const name = arr[0]; const open = parseFloat(arr[2]); const high = parseFloat(arr[3]); const low = parseFloat(arr[4]); const current_price = parseFloat(arr[8]); const pre_price = parseFloat(arr[10]); + const updown = keepDecimal((current_price ?? 0) - (pre_price ?? 0), 2); return { name, + open, + high, + low, + updown, pre_price, current_price, }; @@ -128,6 +142,10 @@ export class FutureData { inited = false; init_times = 0; price: { + open: number; + high: number; + low: number; + updown: string; pre_price: number; current_price: number; } = null; @@ -199,15 +217,3 @@ export default class FutureHandler { await Promise.all(this.futures.map((item) => item.updatePrice())); } } - -async function testGetBasicInfo() { - const data = await provider.getBasicInfo('cu2409'); - console.log(data); -} - -async function testGetPrice() { - const data = await provider.getLatestPrice('CU2409'); - console.log(data); -} - -testGetBasicInfo(); \ No newline at end of file diff --git a/src/render.ts b/src/render.ts index cb27d63..5135f1c 100644 --- a/src/render.ts +++ b/src/render.ts @@ -4,7 +4,6 @@ import Configuration from './configuration'; import Stock from './stock'; import { calcFixedNumber, keepDecimal } from './utils'; import { FutureData } from './futures'; -import logger from './logger'; const stockHub = new Map(); @@ -24,12 +23,21 @@ function getItemText(item: Stock) { } function getTooltipText(item: Stock) { - return ( - `【${item.name}】今日行情\n` + - `涨跌:${item.updown} 百分:${keepDecimal(item.percent * 100, 2)}%\n` + - `最高:${item.high} 最低:${item.low}\n` + - `今开:${item.open} 昨收:${item.yestclose}` - ); + const hasHold = item.hold_price && item.hold_number; + const tooltips = [ + `【${item.name}】今日行情`, + `涨跌:${item.updown} 百分:${keepDecimal(item.percent * 100, 2)}%`, + `最高:${item.high} 最低:${item.low}`, + `今开:${item.open} 昨收:${item.yestclose}`, + ]; + if (hasHold) { + const balance = Math.round( + (item.price - item.hold_price) * item.hold_number, + ); + const balanceStr = balance > 0 ? `+${balance}` : `${balance}`; + tooltips.push(`盈亏:${balanceStr}`); + } + return tooltips.join('\n'); } /** @@ -104,52 +112,45 @@ function syncFutureBarItem(futures: FutureData[]) { } function formatFuture(item: FutureData) { - const itemName = item.alias || item.name || ''; - let text = `${item.code} ---`; - const tooltip = new vscode.MarkdownString(`**${itemName}** ${item.code}\n\n`); - - try { - if (!item.price) { - return { - text, - tooltip, - }; - } - + let percentStr = '-'; + let balanceStr = '-'; + const hasHold = item.hold_price && item.hold_number && item.ratio; + if (item.price) { const percent = item.price.pre_price ? (item.price.current_price - item.price.pre_price) / item.price.pre_price : 0; - - const percentStr = `${keepDecimal(percent * 100, 2)}%`; - const balance = Math.round( - (item.price.current_price - item.hold_price) * - item.hold_number * - item.ratio, - ); - tooltip.appendMarkdown( - `价格: **${item.price.current_price}** 涨跌: **${percentStr}**\n\n`, - ); - - const balanceStr = balance > 0 ? `+${balance}` : `${balance}`; - console.log(item); - if (item.hold_price && item.hold_number && item.ratio) { - text = `${item.code} ${item.price.current_price} ${balanceStr}`; - tooltip.appendMarkdown(`盈亏: **${balanceStr}**`); - } else { - text = `${item.code} ${item.price.current_price} ${percentStr}`; + percentStr = `${keepDecimal(percent * 100, 2)}`; + if (hasHold) { + const balance = Math.round( + (item.price.current_price - item.hold_price) * + item.hold_number * + item.ratio, + ); + balanceStr = balance > 0 ? `+${balance}` : `${balance}`; } - } catch (err) { - logger.error('%O', err); + } + + const text = `${item.alias ?? item.name ?? item.code} ${ + item.price?.current_price ?? '-' + } ${percentStr}%`; + + const tooltips = [ + `【${item.name}】今日行情`, + `涨跌:${item.price?.updown} 百分:${percentStr}%`, + `最高:${item.price?.high} 最低:${item.price?.low}`, + `今开:${item.price?.open} 昨收:${item.price?.pre_price}`, + ]; + if (hasHold) { + tooltips.push(`盈亏:${balanceStr}`); } return { text, - tooltip, + tooltip: tooltips.join('\n'), }; } export function renderFutures(futures: FutureData[]) { - //logger.debug('renderFutures', futures); syncFutureBarItem(futures); for (const [index, barItem] of futureBars.entries()) { const { text, tooltip } = formatFuture(futures[index]); diff --git a/src/stock.ts b/src/stock.ts index a19c3d1..6f13216 100644 --- a/src/stock.ts +++ b/src/stock.ts @@ -3,6 +3,8 @@ export default class Stock { symbol: string; name: string | null; alias: string; + hold_price = 0; + hold_number = 0; price = 0; updown = 0; percent = 0; @@ -11,11 +13,18 @@ export default class Stock { open = 0; yestclose = 0; - constructor(code: string, alias?: string | undefined) { + constructor( + code: string, + alias?: string | undefined, + hold_price?: number | undefined, + hold_number?: number | undefined, + ) { this.code = code; this.symbol = code; this.name = null; this.alias = alias ?? ''; + this.hold_price = hold_price ?? 0; + this.hold_number = hold_number ?? 0; } update(origin: Stock) { this.name = origin.name; diff --git a/types/stock-bar.d.ts b/types/stock-bar.d.ts index e6d0a21..f4518ae 100644 --- a/types/stock-bar.d.ts +++ b/types/stock-bar.d.ts @@ -2,6 +2,8 @@ declare module 'stock-bar' { export interface StockOption { code: string; alias: string; + hold_price?: number; + hold_number?: number; } type color = string;