koishi-plugin-monetary-bourse 2.0.2 → 2.0.3-Alpha.12

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 +192 -121
  2. package/package.json +1 -1
  3. package/readme.md +3 -61
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 });
@@ -746,7 +755,7 @@ function apply(ctx, config) {
746
755
  }));
747
756
  }
748
757
  __name(getPriceHistory, "getPriceHistory");
749
- ctx.command("stock [interval:string]", "查看股市行情").action(async ({ session }, interval) => {
758
+ ctx.command("stock [interval:string]", "查看股市行情").userFields(["id"]).action(async ({ session }, interval) => {
750
759
  if (["buy", "sell", "my"].includes(interval)) {
751
760
  const parts = session.content.trim().split(/\s+/).slice(2);
752
761
  const rest = parts.join(" ");
@@ -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,29 @@ 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;
804
818
  const visibleUserId = session.userId;
805
- if (!uid || typeof uid !== "number") {
819
+ if (!session.user || session.user.id == null) {
820
+ logger.error(`stock.buy: session.user 不存在或 id 为空 user=${session.userId}`);
806
821
  return "无法获取用户ID,请稍后重试。";
807
822
  }
823
+ let uid = session.user.id;
824
+ if (typeof uid !== "number") {
825
+ const parsedUid = Number(uid);
826
+ if (!parsedUid || isNaN(parsedUid)) {
827
+ logger.error(`stock.buy: 无法获取数字UID user=${session.userId}, rawId=${uid}`);
828
+ return "无法获取用户ID,请稍后重试。";
829
+ }
830
+ uid = parsedUid;
831
+ }
808
832
  const cost = Number((currentPrice * amount).toFixed(2));
809
833
  const payResult = await pay(uid, cost, config.currency);
810
834
  if (!payResult.success) {
835
+ logger.warn(`stock.buy: 支付失败 user=${session.userId}, amount=${amount}, cost=${cost}, reason=${payResult.msg}`);
811
836
  return payResult.msg;
812
837
  }
813
838
  let freezeMinutes = 0;
@@ -875,16 +900,30 @@ function apply(ctx, config) {
875
900
  );
876
901
  });
877
902
  ctx.command("stock.sell <amount:number>", "卖出股票").userFields(["id"]).action(async ({ session }, amount) => {
878
- if (!amount || amount <= 0 || !Number.isInteger(amount)) return "请输入有效的卖出股数。";
903
+ if (!amount || amount <= 0 || !Number.isInteger(amount)) {
904
+ logger.warn(`stock.sell: 非法卖出数量 user=${session.userId}, amount=${amount}`);
905
+ return "请输入有效的卖出股数。";
906
+ }
879
907
  if (!await isMarketOpen()) return "休市中,无法交易。";
880
- const uid = session.user?.id;
881
908
  const visibleUserId = session.userId;
882
- if (!uid || typeof uid !== "number") {
909
+ if (!session.user || session.user.id == null) {
910
+ logger.error(`stock.sell: session.user 不存在或 id 为空 user=${session.userId}`);
883
911
  return "无法获取用户ID,请稍后重试。";
884
912
  }
913
+ let uid = session.user.id;
914
+ if (typeof uid !== "number") {
915
+ const parsedUid = Number(uid);
916
+ if (!parsedUid || isNaN(parsedUid)) {
917
+ logger.error(`stock.sell: 无法获取数字UID user=${session.userId}, rawId=${uid}`);
918
+ return "无法获取用户ID,请稍后重试。";
919
+ }
920
+ uid = parsedUid;
921
+ }
885
922
  const holding = await ctx.database.get("bourse_holding", { userId: visibleUserId, stockId });
886
923
  if (holding.length === 0 || holding[0].amount < amount) {
887
- return `持仓不足!当前持有: ${holding.length ? holding[0].amount : 0} 股。`;
924
+ const currentAmount = holding.length ? holding[0].amount : 0;
925
+ logger.warn(`stock.sell: 持仓不足 user=${session.userId}, amount=${amount}, current=${currentAmount}`);
926
+ return `持仓不足!当前持有: ${currentAmount} 股。`;
888
927
  }
889
928
  const currentHolding = holding[0];
890
929
  let existingTotalCost = currentHolding.totalCost;
@@ -1021,7 +1060,10 @@ function apply(ctx, config) {
1021
1060
  return img;
1022
1061
  });
1023
1062
  ctx.command("stock.control <price:number> [hours:number]", "管理员:设置宏观调控目标", { authority: 3 }).action(async ({ session }, price, hours) => {
1024
- if (!price || price <= 0) return "请输入有效的目标价格。";
1063
+ if (!price || price <= 0) {
1064
+ logger.warn(`stock.control: 非法目标价格 user=${session.userId}, price=${price}`);
1065
+ return "请输入有效的目标价格。";
1066
+ }
1025
1067
  const duration = hours || 24;
1026
1068
  const now = /* @__PURE__ */ new Date();
1027
1069
  const endTime = new Date(now.getTime() + duration * 3600 * 1e3);
@@ -1058,7 +1100,10 @@ function apply(ctx, config) {
1058
1100
  到期后将自动切回随机调控。`;
1059
1101
  });
1060
1102
  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";
1103
+ if (!["open", "close", "auto"].includes(status)) {
1104
+ logger.warn(`bourse.admin.market: 非法状态 user=${session.userId}, status=${status}`);
1105
+ return "无效状态,请使用 open, close, 或 auto";
1106
+ }
1062
1107
  const wasOpen = await isMarketOpen();
1063
1108
  const key = "macro_state";
1064
1109
  const existing = await ctx.database.get("bourse_state", { key });
@@ -1162,118 +1207,144 @@ function apply(ctx, config) {
1162
1207
  是否继续波动:${moved ? "是" : "否(需检查)"}`;
1163
1208
  });
1164
1209
  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");
1210
+ try {
1211
+ const templatePath = (0, import_path.resolve)(__dirname, "templates", "holding-card.html");
1212
+ let template = await import_fs.promises.readFile(templatePath, "utf-8");
1213
+ const data = {
1214
+ username,
1215
+ holding,
1216
+ pending,
1217
+ currency,
1218
+ updateTime: (/* @__PURE__ */ new Date()).toLocaleString("zh-CN")
1219
+ };
1220
+ template = template.replace("{{DATA}}", JSON.stringify(data));
1221
+ const page = await ctx2.puppeteer.page();
1222
+ try {
1223
+ await page.setContent(template);
1224
+ const element = await page.$(".card");
1225
+ if (!element) throw new Error("找不到 .card 元素");
1226
+ const imgBuf = await element.screenshot({ encoding: "binary" });
1227
+ return import_koishi.h.image(imgBuf, "image/png");
1228
+ } finally {
1229
+ await page.close();
1230
+ }
1231
+ } catch (err) {
1232
+ logger.error("renderHoldingImage 失败:", err);
1233
+ return `[错误] 生成图片失败: ${err.message}`;
1234
+ }
1183
1235
  }
1184
1236
  __name(renderHoldingImage, "renderHoldingImage");
1185
1237
  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");
1238
+ try {
1239
+ const templatePath = (0, import_path.resolve)(__dirname, "templates", "trade-result.html");
1240
+ let template = await import_fs.promises.readFile(templatePath, "utf-8");
1241
+ const tradeIndex = priceHistory.length - 1;
1242
+ const status = tradeMeta?.status ?? "settled";
1243
+ const pendingMinutes = tradeMeta?.pendingMinutes ?? 0;
1244
+ const pendingEndTime = tradeMeta?.pendingEndTime ?? null;
1245
+ const data = {
1246
+ tradeType,
1247
+ stockName,
1248
+ amount,
1249
+ tradePrice,
1250
+ totalCost,
1251
+ currency,
1252
+ tradeTime: (/* @__PURE__ */ new Date()).toLocaleString("zh-CN"),
1253
+ prices: priceHistory.map((d) => d.price),
1254
+ timestamps: priceHistory.map((d) => d.timestamp),
1255
+ tradeIndex,
1256
+ // 卖出额外信息
1257
+ avgBuyPrice: sellInfo?.avgBuyPrice ?? null,
1258
+ buyCost: sellInfo?.buyCost ?? null,
1259
+ profit: sellInfo?.profit ?? null,
1260
+ profitPercent: sellInfo?.profitPercent ?? null,
1261
+ // 买入后持仓
1262
+ newHolding: newHolding ?? amount,
1263
+ status,
1264
+ pendingMinutes,
1265
+ pendingEndTime
1266
+ };
1267
+ template = template.replace("{{DATA}}", JSON.stringify(data));
1268
+ const page = await ctx2.puppeteer.page();
1269
+ try {
1270
+ await page.setContent(template);
1271
+ const element = await page.$(".card");
1272
+ if (!element) throw new Error("找不到 .card 元素");
1273
+ const imgBuf = await element.screenshot({ encoding: "binary" });
1274
+ return import_koishi.h.image(imgBuf, "image/png");
1275
+ } finally {
1276
+ await page.close();
1277
+ }
1278
+ } catch (err) {
1279
+ logger.error("renderTradeResultImage 失败:", err);
1280
+ return `[错误] 生成交易确认单失败: ${err.message}`;
1281
+ }
1223
1282
  }
1224
1283
  __name(renderTradeResultImage, "renderTradeResultImage");
1225
1284
  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);
1285
+ if (data.length < 2) {
1286
+ logger.warn(`renderStockImage: 数据点不足(${data.length}),无法绘制图表`);
1287
+ return "数据不足,无法绘制走势图。";
1288
+ }
1289
+ try {
1290
+ const startPrice = data[0].price;
1291
+ const change = current - startPrice;
1292
+ const changePercent = change / startPrice * 100;
1293
+ const isUp = change >= 0;
1294
+ const templatePath = (0, import_path.resolve)(__dirname, "templates", "stock-chart.html");
1295
+ let html = await import_fs.promises.readFile(templatePath, "utf-8");
1296
+ const colorScheme = {
1297
+ mainColor: isUp ? "#f23645" : "#089981",
1298
+ gradientStart: isUp ? "rgba(242, 54, 69, 0.25)" : "rgba(8, 153, 129, 0.25)",
1299
+ gradientEnd: "rgba(255, 255, 255, 0)",
1300
+ glowColor: isUp ? "rgba(242, 54, 69, 0.4)" : "rgba(8, 153, 129, 0.4)",
1301
+ iconGradientStart: isUp ? "#f23645" : "#089981",
1302
+ iconGradientEnd: isUp ? "#ff7e87" : "#40c2aa",
1303
+ iconShadow: isUp ? "rgba(242, 54, 69, 0.3)" : "rgba(8, 153, 129, 0.3)",
1304
+ changeBadgeBg: isUp ? "rgba(242, 54, 69, 0.12)" : "rgba(8, 153, 129, 0.12)"
1305
+ };
1306
+ const replacements = {
1307
+ "{{MAIN_COLOR}}": colorScheme.mainColor,
1308
+ "{{GRADIENT_START}}": colorScheme.gradientStart,
1309
+ "{{GRADIENT_END}}": colorScheme.gradientEnd,
1310
+ "{{GLOW_COLOR}}": colorScheme.glowColor,
1311
+ "{{ICON_GRADIENT_START}}": colorScheme.iconGradientStart,
1312
+ "{{ICON_GRADIENT_END}}": colorScheme.iconGradientEnd,
1313
+ "{{ICON_SHADOW}}": colorScheme.iconShadow,
1314
+ "{{CHANGE_BADGE_BG}}": colorScheme.changeBadgeBg,
1315
+ "{{STOCK_NAME}}": name2,
1316
+ "{{VIEW_LABEL}}": viewLabel,
1317
+ "{{CURRENT_TIME}}": (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" }),
1318
+ "{{CURRENT_PRICE}}": current.toFixed(2),
1319
+ "{{CHANGE_VALUE}}": `${change >= 0 ? "+" : ""}${change.toFixed(2)}`,
1320
+ "{{CHANGE_ICON}}": change >= 0 ? "↑" : "↓",
1321
+ "{{CHANGE_PERCENT}}": Math.abs(changePercent).toFixed(2),
1322
+ "{{HIGH_PRICE}}": high.toFixed(2),
1323
+ "{{LOW_PRICE}}": low.toFixed(2),
1324
+ "{{AMPLITUDE}}": ((high - low) / startPrice * 100).toFixed(2),
1325
+ "{{START_PRICE}}": startPrice.toFixed(2),
1326
+ "{{UPDATE_TIME}}": (/* @__PURE__ */ new Date()).toLocaleString("zh-CN"),
1327
+ "{{PRICES}}": JSON.stringify(data.map((d) => d.price)),
1328
+ "{{TIMES}}": JSON.stringify(data.map((d) => d.time)),
1329
+ "{{TIMESTAMPS}}": JSON.stringify(data.map((d) => d.timestamp))
1330
+ };
1331
+ for (const [key, value] of Object.entries(replacements)) {
1332
+ html = html.replace(new RegExp(key, "g"), value);
1333
+ }
1334
+ const page = await ctx2.puppeteer.page();
1335
+ try {
1336
+ await page.setContent(html);
1337
+ const element = await page.$(".card");
1338
+ if (!element) throw new Error("找不到 .card 元素");
1339
+ const imgBuf = await element.screenshot({ encoding: "binary" });
1340
+ return import_koishi.h.image(imgBuf, "image/png");
1341
+ } finally {
1342
+ await page.close();
1343
+ }
1344
+ } catch (err) {
1345
+ logger.error("renderStockImage 失败:", err);
1346
+ return `[错误] 生成行情图失败: ${err.message}`;
1270
1347
  }
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
1348
  }
1278
1349
  __name(renderStockImage, "renderStockImage");
1279
1350
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-monetary-bourse",
3
- "version": "2.0.2",
3
+ "version": "2.0.3-Alpha.12",
4
4
  "main": "lib/index.js",
5
5
  "typings": "lib/index.d.ts",
6
6
  "files": [
package/readme.md CHANGED
@@ -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
 
@@ -137,62 +137,4 @@ A: 股价采用 **"智能期望模型"** 驱动,更贴近真实博弈:
137
137
  - **enableDebug**: 是否启用调试模式(默认 `false`)。开启后可使用 `bourse.test.*` 系列调试指令。
138
138
 
139
139
  ### 宏观调控
140
- - 已移除配置项中的手动宏观调控字段;请使用管理员指令进行宏观调控。
141
-
142
- ## 更新记录
143
-
144
- ### 2.0.2
145
-
146
- - **doc**: 更新同步更新Gitee和Github链接至`README`
147
-
148
- ### 2.0.1
149
- - **fix(build)**: 将静态 HTML 打包进 `lib`,修复构建未更新静态页面的问题
150
-
151
- ### 2.0.0
152
- - **perf/alg**: 提升随机噪声强度,实时曲线更具呼吸感,减少单调走势。
153
- - **ui/layout**: 重构交易详情页布局,分离股票信息与价格展示区域,底部改为 Grid 布局,并将页面外间距增至 64px 以提升视觉呼吸感。
154
- - **feat(ui/display)**: 优化状态徽标逻辑,支持「交易方向」与「挂单中」状态同时并列显示,并为 Pending 状态增加呼吸动画。
155
- - **ui/typography**: 显著增大详情页关键数值与标签字号,回滚并强制使用 Roboto Mono 等宽字体,确保金融数据对齐与专业感。
156
- - **fix(ui/trade)**: 交易结果回单在存在冻结时间时也会即时弹出;修正时间戳位置,将其强制固定于页面右下角。
157
-
158
- ### Alpha.10
159
- - **feat(core)**: 引入 **25 种** 全新 K 线形态库;新增 `selectPatternByExpectation` 智能调度算法,按价格偏离度动态调整形态概率以实现“估值修复”。
160
- - **feat(ui)**: 新增交易交割单(成交结算图),并将 HTML 模板模板化重构,支持高分辨率渲染与更友好的样式复用。
161
- - **perf**: 优化持仓成本计算、挂单排队体验与兼容旧数据的处理逻辑。
162
-
163
- ### Alpha.9
164
- - **fix(build)**: 将静态 HTML 打包进 `lib`,修复构建遗漏导致的插件无法加载问题。
165
-
166
- ### Alpha.8
167
- - **feat(ui)**: 提取并模板化走势图(`src/templates/stock-chart.html`),全面视觉优化(深色玻璃拟态、坐标轴与字体改进、增强可读性)。
168
- - **feat(queue)**: 将挂单改为用户串行排队,防止拆单规避冻结时长(T+0 防绕过)。
169
- - **perf/alg**: 提升基础波动率与形态影响力,改进周波浪与均值回归;新增 `enableDebug` 调试开关以便开发验证。
170
-
171
- ### Alpha.7
172
- - **feat(model)**: 引入几何布朗运动 + 均值回归,添加正态分布噪声与软着陆限幅,改善日内波动特性以提高拟真度。
173
-
174
- ### Alpha.6
175
- - **refactor**: 采用百分比波动合成模型,解决长期振幅失控并提升稳定性。
176
- - **fix**: 修正手动宏观调控在部分场景下重置基准价的问题。
177
-
178
- ### Alpha.5
179
- - **refactor**: 重写走势引擎为绝对价格合成(基准价 + 趋势 + 日内波动 + 周波浪 + 噪声),修复长期运行抖动并强化 ±50% 涨跌幅限幅。
180
-
181
- ### 1.1.1 (Alpha.4)
182
- - **feat**: 引入硬性 ±50% 涨跌幅上限并随机刷新宏观目标以增强稳定性和随机性。
183
- - **fix**: 避免在更新负载中修改主键;移除过时的手动控制配置;新增调试命令以便验证逻辑。
184
-
185
- ### 1.1.0 (Alpha.3)
186
- - **feat**: 新增持仓盈亏与成本追踪(`totalCost`)及持仓卡片渲染(Puppeteer),提升展示与交互体验。
187
- - **fix**: 统一金额与价格为两位小数以保证精度与兼容性;当 `maxFreezeTime = 0` 时即时完成交易处理。
188
-
189
- ### 1.0.0
190
- - **fix**: 修复因更新负载包含主键而导致的 `TypeError: cannot modify primary key`,提高数据库兼容性。
191
-
192
- ### Alpha.2
193
- - **feat**: 扩展日内走势剧本并叠加周级波浪,改进 Puppeteer 渲染以减少横轴标签重叠与视觉冲突。
194
- - **note**: 历史版本曾存在主键相关问题,后续版本已逐步修复。
195
-
196
- ### Alpha.1
197
- - **feat**: 初始发布:实现买卖、挂单、资金冻结、历史行情存储及 `stock` 系列基础指令,支持与 `monetary` / `monetary-bank` 的基础联动。
198
-
140
+ - 已移除配置项中的手动宏观调控字段;请使用管理员指令进行宏观调控。