koishi-plugin-monetary-bourse 2.0.1 → 2.0.3-Alpha.11

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.
Files changed (3) hide show
  1. package/lib/index.js +183 -120
  2. package/package.json +1 -1
  3. package/readme.md +55 -44
package/lib/index.js CHANGED
@@ -242,18 +242,24 @@ function apply(ctx, config) {
242
242
  const bankDemand = await getBankDemandBalance(uid, currency);
243
243
  logger.info(`pay: 现金=${cash}, 活期=${bankDemand}, 需要=${cost}`);
244
244
  if (cash + bankDemand < cost) {
245
- return { success: false, msg: `资金不足!需要 ${cost.toFixed(2)},当前现金 ${cash.toFixed(2)} + 活期 ${bankDemand.toFixed(2)}` };
245
+ const msg = `资金不足!需要 ${cost.toFixed(2)},当前现金 ${cash.toFixed(2)} + 活期 ${bankDemand.toFixed(2)}`;
246
+ logger.warn(`pay 失败: ${msg}, uid=${uid}`);
247
+ return { success: false, msg };
246
248
  }
247
249
  let remainingCost = Number(cost.toFixed(2));
248
250
  const cashDeduct = Number(Math.min(cash, remainingCost).toFixed(2));
249
251
  if (cashDeduct > 0) {
250
252
  const success = await changeCashBalance(uid, currency, -cashDeduct);
251
- if (!success) return { success: false, msg: "扣除现金失败,请重试" };
253
+ if (!success) {
254
+ logger.error(`pay 失败: 扣除现金失败 uid=${uid}, cost=${cashDeduct}`);
255
+ return { success: false, msg: "扣除现金失败,请重试" };
256
+ }
252
257
  remainingCost = Number((remainingCost - cashDeduct).toFixed(2));
253
258
  }
254
259
  if (remainingCost > 0) {
255
260
  const success = await deductBankDemand(uid, currency, remainingCost);
256
261
  if (!success) {
262
+ logger.error(`pay 失败: 银行活期扣款失败 uid=${uid}, cost=${remainingCost}`);
257
263
  if (cashDeduct > 0) await changeCashBalance(uid, currency, cashDeduct);
258
264
  return { success: false, msg: "银行活期扣款失败" };
259
265
  }
@@ -670,7 +676,7 @@ function apply(ctx, config) {
670
676
  const morningVol = Math.exp(-8 * dayProgress);
671
677
  const afternoonVol = Math.exp(-8 * (1 - dayProgress));
672
678
  const volatility = 0.3 + morningVol * 0.5 + afternoonVol * 0.4;
673
- const randomReturn = normalRandom * 35e-4 * volatility;
679
+ const randomReturn = normalRandom * 65e-4 * volatility;
674
680
  const totalReturn = patternReturn + reversionReturn + randomReturn;
675
681
  let newPrice = currentPrice * (1 + totalReturn);
676
682
  const dayBase = dailyOpenPrice ?? basePrice;
@@ -723,9 +729,12 @@ function apply(ctx, config) {
723
729
  } else if (txn.type === "sell") {
724
730
  if (txn.uid && typeof txn.uid === "number") {
725
731
  const amount = Number(txn.cost.toFixed(2));
726
- await changeCashBalance(txn.uid, config.currency, amount);
732
+ const success = await changeCashBalance(txn.uid, config.currency, amount);
733
+ if (!success) {
734
+ logger.error(`processPendingTransactions 失败: 卖出结算充值失败 txn.id=${txn.id}, uid=${txn.uid}, amount=${amount}`);
735
+ }
727
736
  } else {
728
- logger.warn(`processPendingTransactions: 卖出订单缺少有效uid, txn.id=${txn.id}`);
737
+ logger.warn(`processPendingTransactions 警告: 卖出订单缺少有效uid, txn.id=${txn.id}`);
729
738
  }
730
739
  }
731
740
  await ctx.database.remove("bourse_pending", { id: txn.id });
@@ -774,7 +783,10 @@ function apply(ctx, config) {
774
783
  });
775
784
  history = history.reverse();
776
785
  }
777
- if (history.length === 0) return "暂无行情数据。";
786
+ if (history.length === 0) {
787
+ logger.warn(`stock: 数据库中未找到 ${stockId} 的行情数据`);
788
+ return "暂无行情数据。";
789
+ }
778
790
  if (history.length > 300) {
779
791
  const step = Math.ceil(history.length / 300);
780
792
  history = history.filter((_, index) => index % step === 0);
@@ -798,16 +810,25 @@ function apply(ctx, config) {
798
810
  return img;
799
811
  });
800
812
  ctx.command("stock.buy <amount:number>", "买入股票").userFields(["id"]).action(async ({ session }, amount) => {
801
- if (!amount || amount <= 0 || !Number.isInteger(amount)) return "请输入有效的购买股数(整数)。";
813
+ if (!amount || amount <= 0 || !Number.isInteger(amount)) {
814
+ logger.warn(`stock.buy: 非法买入数量 user=${session.userId}, amount=${amount}`);
815
+ return "请输入有效的购买股数(整数)。";
816
+ }
802
817
  if (!await isMarketOpen()) return "休市中,无法交易。";
803
- const uid = session.user?.id;
818
+ let uid = session.user?.id;
804
819
  const visibleUserId = session.userId;
805
820
  if (!uid || typeof uid !== "number") {
806
- return "无法获取用户ID,请稍后重试。";
821
+ const parsedUid = Number(uid);
822
+ if (isNaN(parsedUid)) {
823
+ logger.error(`stock.buy: 无法获取数字UID user=${session.userId}`);
824
+ return "无法获取用户ID,请稍后重试。";
825
+ }
826
+ uid = parsedUid;
807
827
  }
808
828
  const cost = Number((currentPrice * amount).toFixed(2));
809
829
  const payResult = await pay(uid, cost, config.currency);
810
830
  if (!payResult.success) {
831
+ logger.warn(`stock.buy: 支付失败 user=${session.userId}, amount=${amount}, cost=${cost}, reason=${payResult.msg}`);
811
832
  return payResult.msg;
812
833
  }
813
834
  let freezeMinutes = 0;
@@ -875,16 +896,26 @@ function apply(ctx, config) {
875
896
  );
876
897
  });
877
898
  ctx.command("stock.sell <amount:number>", "卖出股票").userFields(["id"]).action(async ({ session }, amount) => {
878
- if (!amount || amount <= 0 || !Number.isInteger(amount)) return "请输入有效的卖出股数。";
899
+ if (!amount || amount <= 0 || !Number.isInteger(amount)) {
900
+ logger.warn(`stock.sell: 非法卖出数量 user=${session.userId}, amount=${amount}`);
901
+ return "请输入有效的卖出股数。";
902
+ }
879
903
  if (!await isMarketOpen()) return "休市中,无法交易。";
880
- const uid = session.user?.id;
904
+ let uid = session.user?.id;
881
905
  const visibleUserId = session.userId;
882
906
  if (!uid || typeof uid !== "number") {
883
- return "无法获取用户ID,请稍后重试。";
907
+ const parsedUid = Number(uid);
908
+ if (isNaN(parsedUid)) {
909
+ logger.error(`stock.sell: 无法获取数字UID user=${session.userId}`);
910
+ return "无法获取用户ID,请稍后重试。";
911
+ }
912
+ uid = parsedUid;
884
913
  }
885
914
  const holding = await ctx.database.get("bourse_holding", { userId: visibleUserId, stockId });
886
915
  if (holding.length === 0 || holding[0].amount < amount) {
887
- return `持仓不足!当前持有: ${holding.length ? holding[0].amount : 0} 股。`;
916
+ const currentAmount = holding.length ? holding[0].amount : 0;
917
+ logger.warn(`stock.sell: 持仓不足 user=${session.userId}, amount=${amount}, current=${currentAmount}`);
918
+ return `持仓不足!当前持有: ${currentAmount} 股。`;
888
919
  }
889
920
  const currentHolding = holding[0];
890
921
  let existingTotalCost = currentHolding.totalCost;
@@ -1021,7 +1052,10 @@ function apply(ctx, config) {
1021
1052
  return img;
1022
1053
  });
1023
1054
  ctx.command("stock.control <price:number> [hours:number]", "管理员:设置宏观调控目标", { authority: 3 }).action(async ({ session }, price, hours) => {
1024
- if (!price || price <= 0) return "请输入有效的目标价格。";
1055
+ if (!price || price <= 0) {
1056
+ logger.warn(`stock.control: 非法目标价格 user=${session.userId}, price=${price}`);
1057
+ return "请输入有效的目标价格。";
1058
+ }
1025
1059
  const duration = hours || 24;
1026
1060
  const now = /* @__PURE__ */ new Date();
1027
1061
  const endTime = new Date(now.getTime() + duration * 3600 * 1e3);
@@ -1058,7 +1092,10 @@ function apply(ctx, config) {
1058
1092
  到期后将自动切回随机调控。`;
1059
1093
  });
1060
1094
  ctx.command("bourse.admin.market <status>", "设置股市开关状态 (open/close/auto)", { authority: 3 }).action(async ({ session }, status) => {
1061
- if (!["open", "close", "auto"].includes(status)) return "无效状态,请使用 open, close, 或 auto";
1095
+ if (!["open", "close", "auto"].includes(status)) {
1096
+ logger.warn(`bourse.admin.market: 非法状态 user=${session.userId}, status=${status}`);
1097
+ return "无效状态,请使用 open, close, 或 auto";
1098
+ }
1062
1099
  const wasOpen = await isMarketOpen();
1063
1100
  const key = "macro_state";
1064
1101
  const existing = await ctx.database.get("bourse_state", { key });
@@ -1162,118 +1199,144 @@ function apply(ctx, config) {
1162
1199
  是否继续波动:${moved ? "是" : "否(需检查)"}`;
1163
1200
  });
1164
1201
  async function renderHoldingImage(ctx2, username, holding, pending, currency) {
1165
- const fs2 = require("fs");
1166
- const path = require("path");
1167
- const templatePath = path.join(__dirname, "templates", "holding-card.html");
1168
- let template = fs2.readFileSync(templatePath, "utf-8");
1169
- const data = {
1170
- username,
1171
- holding,
1172
- pending,
1173
- currency,
1174
- updateTime: (/* @__PURE__ */ new Date()).toLocaleString("zh-CN")
1175
- };
1176
- template = template.replace("{{DATA}}", JSON.stringify(data));
1177
- const page = await ctx2.puppeteer.page();
1178
- await page.setContent(template);
1179
- const element = await page.$(".card");
1180
- const imgBuf = await element?.screenshot({ encoding: "binary" });
1181
- await page.close();
1182
- return import_koishi.h.image(imgBuf, "image/png");
1202
+ try {
1203
+ const templatePath = (0, import_path.resolve)(__dirname, "templates", "holding-card.html");
1204
+ let template = await import_fs.promises.readFile(templatePath, "utf-8");
1205
+ const data = {
1206
+ username,
1207
+ holding,
1208
+ pending,
1209
+ currency,
1210
+ updateTime: (/* @__PURE__ */ new Date()).toLocaleString("zh-CN")
1211
+ };
1212
+ template = template.replace("{{DATA}}", JSON.stringify(data));
1213
+ const page = await ctx2.puppeteer.page();
1214
+ try {
1215
+ await page.setContent(template);
1216
+ const element = await page.$(".card");
1217
+ if (!element) throw new Error("找不到 .card 元素");
1218
+ const imgBuf = await element.screenshot({ encoding: "binary" });
1219
+ return import_koishi.h.image(imgBuf, "image/png");
1220
+ } finally {
1221
+ await page.close();
1222
+ }
1223
+ } catch (err) {
1224
+ logger.error("renderHoldingImage 失败:", err);
1225
+ return `[错误] 生成图片失败: ${err.message}`;
1226
+ }
1183
1227
  }
1184
1228
  __name(renderHoldingImage, "renderHoldingImage");
1185
1229
  async function renderTradeResultImage(ctx2, tradeType, stockName, amount, tradePrice, totalCost, currency, priceHistory, sellInfo, newHolding, tradeMeta) {
1186
- const fs2 = require("fs");
1187
- const path = require("path");
1188
- const templatePath = path.join(__dirname, "templates", "trade-result.html");
1189
- let template = fs2.readFileSync(templatePath, "utf-8");
1190
- const tradeIndex = priceHistory.length - 1;
1191
- const status = tradeMeta?.status ?? "settled";
1192
- const pendingMinutes = tradeMeta?.pendingMinutes ?? 0;
1193
- const pendingEndTime = tradeMeta?.pendingEndTime ?? null;
1194
- const data = {
1195
- tradeType,
1196
- stockName,
1197
- amount,
1198
- tradePrice,
1199
- totalCost,
1200
- currency,
1201
- tradeTime: (/* @__PURE__ */ new Date()).toLocaleString("zh-CN"),
1202
- prices: priceHistory.map((d) => d.price),
1203
- timestamps: priceHistory.map((d) => d.timestamp),
1204
- tradeIndex,
1205
- // 卖出额外信息
1206
- avgBuyPrice: sellInfo?.avgBuyPrice ?? null,
1207
- buyCost: sellInfo?.buyCost ?? null,
1208
- profit: sellInfo?.profit ?? null,
1209
- profitPercent: sellInfo?.profitPercent ?? null,
1210
- // 买入后持仓
1211
- newHolding: newHolding ?? amount,
1212
- status,
1213
- pendingMinutes,
1214
- pendingEndTime
1215
- };
1216
- template = template.replace("{{DATA}}", JSON.stringify(data));
1217
- const page = await ctx2.puppeteer.page();
1218
- await page.setContent(template);
1219
- const element = await page.$(".card");
1220
- const imgBuf = await element?.screenshot({ encoding: "binary" });
1221
- await page.close();
1222
- return import_koishi.h.image(imgBuf, "image/png");
1230
+ try {
1231
+ const templatePath = (0, import_path.resolve)(__dirname, "templates", "trade-result.html");
1232
+ let template = await import_fs.promises.readFile(templatePath, "utf-8");
1233
+ const tradeIndex = priceHistory.length - 1;
1234
+ const status = tradeMeta?.status ?? "settled";
1235
+ const pendingMinutes = tradeMeta?.pendingMinutes ?? 0;
1236
+ const pendingEndTime = tradeMeta?.pendingEndTime ?? null;
1237
+ const data = {
1238
+ tradeType,
1239
+ stockName,
1240
+ amount,
1241
+ tradePrice,
1242
+ totalCost,
1243
+ currency,
1244
+ tradeTime: (/* @__PURE__ */ new Date()).toLocaleString("zh-CN"),
1245
+ prices: priceHistory.map((d) => d.price),
1246
+ timestamps: priceHistory.map((d) => d.timestamp),
1247
+ tradeIndex,
1248
+ // 卖出额外信息
1249
+ avgBuyPrice: sellInfo?.avgBuyPrice ?? null,
1250
+ buyCost: sellInfo?.buyCost ?? null,
1251
+ profit: sellInfo?.profit ?? null,
1252
+ profitPercent: sellInfo?.profitPercent ?? null,
1253
+ // 买入后持仓
1254
+ newHolding: newHolding ?? amount,
1255
+ status,
1256
+ pendingMinutes,
1257
+ pendingEndTime
1258
+ };
1259
+ template = template.replace("{{DATA}}", JSON.stringify(data));
1260
+ const page = await ctx2.puppeteer.page();
1261
+ try {
1262
+ await page.setContent(template);
1263
+ const element = await page.$(".card");
1264
+ if (!element) throw new Error("找不到 .card 元素");
1265
+ const imgBuf = await element.screenshot({ encoding: "binary" });
1266
+ return import_koishi.h.image(imgBuf, "image/png");
1267
+ } finally {
1268
+ await page.close();
1269
+ }
1270
+ } catch (err) {
1271
+ logger.error("renderTradeResultImage 失败:", err);
1272
+ return `[错误] 生成交易确认单失败: ${err.message}`;
1273
+ }
1223
1274
  }
1224
1275
  __name(renderTradeResultImage, "renderTradeResultImage");
1225
1276
  async function renderStockImage(ctx2, data, name2, viewLabel, current, high, low) {
1226
- if (data.length < 2) return "数据不足,无法绘制走势图。";
1227
- const startPrice = data[0].price;
1228
- const change = current - startPrice;
1229
- const changePercent = change / startPrice * 100;
1230
- const isUp = change >= 0;
1231
- const templatePath = (0, import_path.resolve)(__dirname, "templates", "stock-chart.html");
1232
- let html = await import_fs.promises.readFile(templatePath, "utf-8");
1233
- const colorScheme = {
1234
- mainColor: isUp ? "#f23645" : "#089981",
1235
- gradientStart: isUp ? "rgba(242, 54, 69, 0.25)" : "rgba(8, 153, 129, 0.25)",
1236
- gradientEnd: "rgba(255, 255, 255, 0)",
1237
- glowColor: isUp ? "rgba(242, 54, 69, 0.4)" : "rgba(8, 153, 129, 0.4)",
1238
- iconGradientStart: isUp ? "#f23645" : "#089981",
1239
- iconGradientEnd: isUp ? "#ff7e87" : "#40c2aa",
1240
- iconShadow: isUp ? "rgba(242, 54, 69, 0.3)" : "rgba(8, 153, 129, 0.3)",
1241
- changeBadgeBg: isUp ? "rgba(242, 54, 69, 0.12)" : "rgba(8, 153, 129, 0.12)"
1242
- };
1243
- const replacements = {
1244
- "{{MAIN_COLOR}}": colorScheme.mainColor,
1245
- "{{GRADIENT_START}}": colorScheme.gradientStart,
1246
- "{{GRADIENT_END}}": colorScheme.gradientEnd,
1247
- "{{GLOW_COLOR}}": colorScheme.glowColor,
1248
- "{{ICON_GRADIENT_START}}": colorScheme.iconGradientStart,
1249
- "{{ICON_GRADIENT_END}}": colorScheme.iconGradientEnd,
1250
- "{{ICON_SHADOW}}": colorScheme.iconShadow,
1251
- "{{CHANGE_BADGE_BG}}": colorScheme.changeBadgeBg,
1252
- "{{STOCK_NAME}}": name2,
1253
- "{{VIEW_LABEL}}": viewLabel,
1254
- "{{CURRENT_TIME}}": (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }),
1255
- "{{CURRENT_PRICE}}": current.toFixed(2),
1256
- "{{CHANGE_VALUE}}": `${change >= 0 ? "+" : ""}${change.toFixed(2)}`,
1257
- "{{CHANGE_ICON}}": change >= 0 ? "↑" : "↓",
1258
- "{{CHANGE_PERCENT}}": Math.abs(changePercent).toFixed(2),
1259
- "{{HIGH_PRICE}}": high.toFixed(2),
1260
- "{{LOW_PRICE}}": low.toFixed(2),
1261
- "{{AMPLITUDE}}": ((high - low) / startPrice * 100).toFixed(2),
1262
- "{{START_PRICE}}": startPrice.toFixed(2),
1263
- "{{UPDATE_TIME}}": (/* @__PURE__ */ new Date()).toLocaleString("zh-CN"),
1264
- "{{PRICES}}": JSON.stringify(data.map((d) => d.price)),
1265
- "{{TIMES}}": JSON.stringify(data.map((d) => d.time)),
1266
- "{{TIMESTAMPS}}": JSON.stringify(data.map((d) => d.timestamp))
1267
- };
1268
- for (const [key, value] of Object.entries(replacements)) {
1269
- html = html.replace(new RegExp(key, "g"), value);
1277
+ if (data.length < 2) {
1278
+ logger.warn(`renderStockImage: 数据点不足(${data.length}),无法绘制图表`);
1279
+ return "数据不足,无法绘制走势图。";
1280
+ }
1281
+ try {
1282
+ const startPrice = data[0].price;
1283
+ const change = current - startPrice;
1284
+ const changePercent = change / startPrice * 100;
1285
+ const isUp = change >= 0;
1286
+ const templatePath = (0, import_path.resolve)(__dirname, "templates", "stock-chart.html");
1287
+ let html = await import_fs.promises.readFile(templatePath, "utf-8");
1288
+ const colorScheme = {
1289
+ mainColor: isUp ? "#f23645" : "#089981",
1290
+ gradientStart: isUp ? "rgba(242, 54, 69, 0.25)" : "rgba(8, 153, 129, 0.25)",
1291
+ gradientEnd: "rgba(255, 255, 255, 0)",
1292
+ glowColor: isUp ? "rgba(242, 54, 69, 0.4)" : "rgba(8, 153, 129, 0.4)",
1293
+ iconGradientStart: isUp ? "#f23645" : "#089981",
1294
+ iconGradientEnd: isUp ? "#ff7e87" : "#40c2aa",
1295
+ iconShadow: isUp ? "rgba(242, 54, 69, 0.3)" : "rgba(8, 153, 129, 0.3)",
1296
+ changeBadgeBg: isUp ? "rgba(242, 54, 69, 0.12)" : "rgba(8, 153, 129, 0.12)"
1297
+ };
1298
+ const replacements = {
1299
+ "{{MAIN_COLOR}}": colorScheme.mainColor,
1300
+ "{{GRADIENT_START}}": colorScheme.gradientStart,
1301
+ "{{GRADIENT_END}}": colorScheme.gradientEnd,
1302
+ "{{GLOW_COLOR}}": colorScheme.glowColor,
1303
+ "{{ICON_GRADIENT_START}}": colorScheme.iconGradientStart,
1304
+ "{{ICON_GRADIENT_END}}": colorScheme.iconGradientEnd,
1305
+ "{{ICON_SHADOW}}": colorScheme.iconShadow,
1306
+ "{{CHANGE_BADGE_BG}}": colorScheme.changeBadgeBg,
1307
+ "{{STOCK_NAME}}": name2,
1308
+ "{{VIEW_LABEL}}": viewLabel,
1309
+ "{{CURRENT_TIME}}": (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }),
1310
+ "{{CURRENT_PRICE}}": current.toFixed(2),
1311
+ "{{CHANGE_VALUE}}": `${change >= 0 ? "+" : ""}${change.toFixed(2)}`,
1312
+ "{{CHANGE_ICON}}": change >= 0 ? "↑" : "↓",
1313
+ "{{CHANGE_PERCENT}}": Math.abs(changePercent).toFixed(2),
1314
+ "{{HIGH_PRICE}}": high.toFixed(2),
1315
+ "{{LOW_PRICE}}": low.toFixed(2),
1316
+ "{{AMPLITUDE}}": ((high - low) / startPrice * 100).toFixed(2),
1317
+ "{{START_PRICE}}": startPrice.toFixed(2),
1318
+ "{{UPDATE_TIME}}": (/* @__PURE__ */ new Date()).toLocaleString("zh-CN"),
1319
+ "{{PRICES}}": JSON.stringify(data.map((d) => d.price)),
1320
+ "{{TIMES}}": JSON.stringify(data.map((d) => d.time)),
1321
+ "{{TIMESTAMPS}}": JSON.stringify(data.map((d) => d.timestamp))
1322
+ };
1323
+ for (const [key, value] of Object.entries(replacements)) {
1324
+ html = html.replace(new RegExp(key, "g"), value);
1325
+ }
1326
+ const page = await ctx2.puppeteer.page();
1327
+ try {
1328
+ await page.setContent(html);
1329
+ const element = await page.$(".card");
1330
+ if (!element) throw new Error("找不到 .card 元素");
1331
+ const imgBuf = await element.screenshot({ encoding: "binary" });
1332
+ return import_koishi.h.image(imgBuf, "image/png");
1333
+ } finally {
1334
+ await page.close();
1335
+ }
1336
+ } catch (err) {
1337
+ logger.error("renderStockImage 失败:", err);
1338
+ return `[错误] 生成行情图失败: ${err.message}`;
1270
1339
  }
1271
- const page = await ctx2.puppeteer.page();
1272
- await page.setContent(html);
1273
- const element = await page.$(".card");
1274
- const imgBuf = await element?.screenshot({ encoding: "binary" });
1275
- await page.close();
1276
- return import_koishi.h.image(imgBuf, "image/png");
1277
1340
  }
1278
1341
  __name(renderStockImage, "renderStockImage");
1279
1342
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-monetary-bourse",
3
- "version": "2.0.1",
3
+ "version": "2.0.3-Alpha.11",
4
4
  "main": "lib/index.js",
5
5
  "typings": "lib/index.d.ts",
6
6
  "files": [
package/readme.md CHANGED
@@ -1,12 +1,12 @@
1
- # koishi-plugin-monetary-bourse · v2.0.0
1
+ # koishi-plugin-monetary-bourse
2
2
 
3
- [![npm](https://img.shields.io/npm/v/koishi-plugin-monetary-bourse?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-monetary-bourse)
3
+ [![npm](https://img.shields.io/npm/v/koishi-plugin-monetary-bourse?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-monetary-bourse) [![GitHub stars](https://img.shields.io/github/stars/BYWled/koishi-plugin-monetary-bourse?style=flat-square&logo=github)](https://github.com/BYWled/koishi-plugin-monetary-bourse) [![Gitee](https://img.shields.io/badge/Gitee-Project-c71d23?style=flat-square&logo=gitee&logoColor=white)](https://gitee.com/BYWled/koishi-plugin-monetary-bourse)
4
4
 
5
5
  为 Koishi 提供基于 `monetary` 通用货币系统的股票交易所功能。
6
6
 
7
7
  本插件模拟了一个具备自动宏观调控、25种经典K线形态、智能概率博弈和可视化交割单的深度拟真股票市场。用户可以使用机器人通用的货币(如信用点)进行股票买卖、炒股理财。
8
8
 
9
- > 版本:**2.0.1**
9
+ > 版本:**2.0.2**
10
10
 
11
11
  ## ✨ 特性
12
12
 
@@ -89,9 +89,9 @@
89
89
 
90
90
  **Q:Alpha版本有什么区别?**
91
91
 
92
- A:本插件在发布一个稳定版之前会进行测试,但是股票的走向和时间有关,即使通过开发调试,也难以测试出稳定的结果。因此,在新版本开发出来后(尤其是算法上的更新),我不确定是否存在非致命但影响体验的漏洞。
92
+ A:本插件在发布一个稳定版之前会进行测试,但是股票的走向和时间有关,即使通过开发调试,也难以测试出稳定的结果。因此,在新版本开发出来后(尤其是*算法*上的更新),我不确定是否存在非致命但影响体验的漏洞。
93
93
 
94
- Alpha版本就是在新版本稳定之前的**过渡版**,它们具备了一些没有验证的新功能、更新,但与之一同的是未知的bug。如需使用Alpha版本,请备份数据库和配置文件,防止以外发生。
94
+ Alpha版本就是在新版本稳定之前的**过渡版**,它们具备了一些没有验证的新功能等更新,但与之一同的是未知的bug。如需使用Alpha版本,请备份数据库和配置文件,防止以外发生。
95
95
 
96
96
  ---
97
97
 
@@ -141,53 +141,64 @@ A: 股价采用 **"智能期望模型"** 驱动,更贴近真实博弈:
141
141
 
142
142
  ## 更新记录
143
143
 
144
- - **2.0.1**
145
- - **fix(build)**: 将静态 HTML 打包进 `lib`,修复构建未更新静态页面的问题
144
+ ### Alpha.11
146
145
 
147
- - **2.0.0**
148
- - **perf/alg**: 提升随机噪声强度,实时曲线更具呼吸感,减少单调走势。
149
- - **ui/layout**: 重构交易详情页布局,分离股票信息与价格展示区域,底部改为 Grid 布局,并将页面外间距增至 64px 以提升视觉呼吸感。
150
- - **feat(ui/display)**: 优化状态徽标逻辑,支持「交易方向」与「挂单中」状态同时并列显示,并为 Pending 状态增加呼吸动画。
151
- - **ui/typography**: 显著增大详情页关键数值与标签字号,回滚并强制使用 Roboto Mono 等宽字体,确保金融数据对齐与专业感。
152
- - **fix(ui/trade)**: 交易结果回单在存在冻结时间时也会即时弹出;修正时间戳位置,将其强制固定于页面右下角。
153
-
154
- - **Alpha.10**
155
- - **feat(core)**: 引入 **25 种** 全新 K 线形态库;新增 `selectPatternByExpectation` 智能调度算法,按价格偏离度动态调整形态概率以实现“估值修复”。
156
- - **feat(ui)**: 新增交易交割单(成交结算图),并将 HTML 模板模板化重构,支持高分辨率渲染与更友好的样式复用。
157
- - **perf**: 优化持仓成本计算、挂单排队体验与兼容旧数据的处理逻辑。
146
+ - **perf/alg**: 提升随机噪声强度。
147
+ - **fix**: 修复末尾渲染部分语法错误。
148
+ - **feat(logging)**: 在支付逻辑、交易处理、指令校验和图片渲染函数中添加详细的日志记录,包括警告和错误级别输出。
149
+
150
+ ### 2.0.2
151
+
152
+ - **doc**: 更新同步更新Gitee和Github链接至`README`
153
+
154
+ ### 2.0.1
155
+ - **fix(build)**: 将静态 HTML 打包进 `lib`,修复构建未更新静态页面的问题
156
+
157
+ ### 2.0.0
158
+ - **perf/alg**: 提升随机噪声强度,实时曲线更具呼吸感,减少单调走势。
159
+ - **ui/layout**: 重构交易详情页布局,分离股票信息与价格展示区域,底部改为 Grid 布局,并将页面外间距增至 64px 以提升视觉呼吸感。
160
+ - **feat(ui/display)**: 优化状态徽标逻辑,支持「交易方向」与「挂单中」状态同时并列显示,并为 Pending 状态增加呼吸动画。
161
+ - **ui/typography**: 显著增大详情页关键数值与标签字号,回滚并强制使用 Roboto Mono 等宽字体,确保金融数据对齐与专业感。
162
+ - **fix(ui/trade)**: 交易结果回单在存在冻结时间时也会即时弹出;修正时间戳位置,将其强制固定于页面右下角。
163
+
164
+ ### Alpha.10
165
+ - **feat(core)**: 引入 **25 种** 全新 K 线形态库;新增 `selectPatternByExpectation` 智能调度算法,按价格偏离度动态调整形态概率以实现“估值修复”。
166
+ - **feat(ui)**: 新增交易交割单(成交结算图),并将 HTML 模板模板化重构,支持高分辨率渲染与更友好的样式复用。
167
+ - **perf**: 优化持仓成本计算、挂单排队体验与兼容旧数据的处理逻辑。
168
+
169
+ ### Alpha.9
170
+ - **fix(build)**: 将静态 HTML 打包进 `lib`,修复构建遗漏导致的插件无法加载问题。
158
171
 
159
- - **Alpha.9**
160
- - **fix(build)**: 将静态 HTML 打包进 `lib`,修复构建遗漏导致的插件无法加载问题。
172
+ ### Alpha.8
173
+ - **feat(ui)**: 提取并模板化走势图(`src/templates/stock-chart.html`),全面视觉优化(深色玻璃拟态、坐标轴与字体改进、增强可读性)。
174
+ - **feat(queue)**: 将挂单改为用户串行排队,防止拆单规避冻结时长(T+0 防绕过)。
175
+ - **perf/alg**: 提升基础波动率与形态影响力,改进周波浪与均值回归;新增 `enableDebug` 调试开关以便开发验证。
161
176
 
162
- - **Alpha.8**
163
- - **feat(ui)**: 提取并模板化走势图(`src/templates/stock-chart.html`),全面视觉优化(深色玻璃拟态、坐标轴与字体改进、增强可读性)。
164
- - **feat(queue)**: 将挂单改为用户串行排队,防止拆单规避冻结时长(T+0 防绕过)。
165
- - **perf/alg**: 提升基础波动率与形态影响力,改进周波浪与均值回归;新增 `enableDebug` 调试开关以便开发验证。
177
+ ### Alpha.7
178
+ - **feat(model)**: 引入几何布朗运动 + 均值回归,添加正态分布噪声与软着陆限幅,改善日内波动特性以提高拟真度。
166
179
 
167
- - **Alpha.7**
168
- - **feat(model)**: 引入几何布朗运动 + 均值回归,添加正态分布噪声与软着陆限幅,改善日内波动特性以提高拟真度。
180
+ ### Alpha.6
181
+ - **refactor**: 采用百分比波动合成模型,解决长期振幅失控并提升稳定性。
182
+ - **fix**: 修正手动宏观调控在部分场景下重置基准价的问题。
169
183
 
170
- - **Alpha.6**
171
- - **refactor**: 采用百分比波动合成模型,解决长期振幅失控并提升稳定性。
172
- - **fix**: 修正手动宏观调控在部分场景下重置基准价的问题。
184
+ ### Alpha.5
185
+ - **refactor**: 重写走势引擎为绝对价格合成(基准价 + 趋势 + 日内波动 + 周波浪 + 噪声),修复长期运行抖动并强化 ±50% 涨跌幅限幅。
173
186
 
174
- - **Alpha.5**
175
- - **refactor**: 重写走势引擎为绝对价格合成(基准价 + 趋势 + 日内波动 + 周波浪 + 噪声),修复长期运行抖动并强化 ±50% 涨跌幅限幅。
187
+ ### 1.1.1 (Alpha.4)
188
+ - **feat**: 引入硬性 ±50% 涨跌幅上限并随机刷新宏观目标以增强稳定性和随机性。
189
+ - **fix**: 避免在更新负载中修改主键;移除过时的手动控制配置;新增调试命令以便验证逻辑。
176
190
 
177
- - **1.1.1 (Alpha.4)**
178
- - **feat**: 引入硬性 ±50% 涨跌幅上限并随机刷新宏观目标以增强稳定性和随机性。
179
- - **fix**: 避免在更新负载中修改主键;移除过时的手动控制配置;新增调试命令以便验证逻辑。
191
+ ### 1.1.0 (Alpha.3)
192
+ - **feat**: 新增持仓盈亏与成本追踪(`totalCost`)及持仓卡片渲染(Puppeteer),提升展示与交互体验。
193
+ - **fix**: 统一金额与价格为两位小数以保证精度与兼容性;当 `maxFreezeTime = 0` 时即时完成交易处理。
180
194
 
181
- - **1.1.0 (Alpha.3)**
182
- - **feat**: 新增持仓盈亏与成本追踪(`totalCost`)及持仓卡片渲染(Puppeteer),提升展示与交互体验。
183
- - **fix**: 统一金额与价格为两位小数以保证精度与兼容性;当 `maxFreezeTime = 0` 时即时完成交易处理。
195
+ ### 1.0.0
196
+ - **fix**: 修复因更新负载包含主键而导致的 `TypeError: cannot modify primary key`,提高数据库兼容性。
184
197
 
185
- - **1.0.0**
186
- - **fix**: 修复因更新负载包含主键而导致的 `TypeError: cannot modify primary key`,提高数据库兼容性。
198
+ ### Alpha.2
199
+ - **feat**: 扩展日内走势剧本并叠加周级波浪,改进 Puppeteer 渲染以减少横轴标签重叠与视觉冲突。
200
+ - **note**: 历史版本曾存在主键相关问题,后续版本已逐步修复。
187
201
 
188
- - **Alpha.2**
189
- - **feat**: 扩展日内走势剧本并叠加周级波浪,改进 Puppeteer 渲染以减少横轴标签重叠与视觉冲突。
190
- - **note**: 历史版本曾存在主键相关问题,后续版本已逐步修复。
202
+ ### Alpha.1
203
+ - **feat**: 初始发布:实现买卖、挂单、资金冻结、历史行情存储及 `stock` 系列基础指令,支持与 `monetary` / `monetary-bank` 的基础联动。
191
204
 
192
- - **Alpha.1**
193
- - **feat**: 初始发布:实现买卖、挂单、资金冻结、历史行情存储及 `stock` 系列基础指令,支持与 `monetary` / `monetary-bank` 的基础联动。