koishi-plugin-monetary-bourse 1.1.0-alpha.3 → 1.1.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/lib/index.d.ts CHANGED
@@ -71,9 +71,6 @@ export interface Config {
71
71
  freezeCostPerMinute: number;
72
72
  minFreezeTime: number;
73
73
  maxFreezeTime: number;
74
- enableManualControl: boolean;
75
- manualTargetPrice: number;
76
- manualDuration: number;
77
74
  marketStatus: 'open' | 'close' | 'auto';
78
75
  }
79
76
  export declare const Config: Schema<Config>;
package/lib/index.js CHANGED
@@ -51,12 +51,7 @@ var Config = import_koishi.Schema.intersect([
51
51
  freezeCostPerMinute: import_koishi.Schema.number().min(1).default(100).description("每多少货币计为1分钟冻结时间"),
52
52
  minFreezeTime: import_koishi.Schema.number().min(0).default(10).description("最小冻结时间(分钟)"),
53
53
  maxFreezeTime: import_koishi.Schema.number().min(0).default(1440).description("最大交易冻结时间(分钟)")
54
- }).description("冻结机制"),
55
- import_koishi.Schema.object({
56
- enableManualControl: import_koishi.Schema.boolean().default(false).description("开启手动宏观调控(覆盖自动)"),
57
- manualTargetPrice: import_koishi.Schema.number().min(0.01).default(1e3).description("手动目标价格"),
58
- manualDuration: import_koishi.Schema.number().min(1).default(24).description("手动调控周期(小时)")
59
- }).description("手动宏观调控")
54
+ }).description("冻结机制")
60
55
  ]);
61
56
  function apply(ctx, config) {
62
57
  ctx.model.extend("bourse_holding", {
@@ -106,10 +101,17 @@ function apply(ctx, config) {
106
101
  }
107
102
  });
108
103
  let wasMarketOpen = false;
104
+ let dailyOpenPrice = null;
105
+ let macroWaveCount = 7;
106
+ let macroWeeklyAmplitudeRatio = 0.08;
107
+ let nextMacroSwitchTime = null;
109
108
  ctx.setInterval(async () => {
110
109
  const isOpen = await isMarketOpen();
111
110
  if (isOpen && !wasMarketOpen) {
112
111
  switchKLinePattern("自动开市");
112
+ dailyOpenPrice = currentPrice;
113
+ const hours = 6 + Math.floor(Math.random() * 19);
114
+ nextMacroSwitchTime = new Date(Date.now() + hours * 3600 * 1e3);
113
115
  }
114
116
  wasMarketOpen = isOpen;
115
117
  if (!isOpen) return;
@@ -366,65 +368,45 @@ function apply(ctx, config) {
366
368
  if (!state.endTime) state.endTime = new Date(state.lastCycleStart.getTime() + 7 * 24 * 3600 * 1e3);
367
369
  if (!(state.endTime instanceof Date)) state.endTime = new Date(state.endTime);
368
370
  }
369
- if (config.enableManualControl) {
370
- if (!state || state.mode !== "manual" || Math.abs(state.targetPrice - config.manualTargetPrice) > 0.01) {
371
- const durationHours = config.manualDuration;
372
- const targetPrice = config.manualTargetPrice;
373
- const endTime = new Date(now.getTime() + durationHours * 3600 * 1e3);
374
- const minutes = durationHours * 60;
375
- const trendFactor = (targetPrice - currentPrice) / minutes;
376
- const newState = {
377
- key: "macro_state",
378
- lastCycleStart: now,
379
- startPrice: currentPrice,
380
- targetPrice,
381
- trendFactor,
382
- mode: "manual",
383
- endTime
384
- };
385
- if (!state) await ctx.database.create("bourse_state", newState);
386
- else {
387
- const { key, ...updateFields } = newState;
388
- await ctx.database.set("bourse_state", { key: "macro_state" }, updateFields);
389
- }
390
- state = newState;
391
- }
392
- }
393
371
  let needNewState = false;
394
- if (!config.enableManualControl) {
395
- if (!state) {
396
- needNewState = true;
397
- } else {
398
- const endTime = state.endTime || new Date(state.lastCycleStart.getTime() + 7 * 24 * 3600 * 1e3);
399
- if (now > endTime) {
400
- needNewState = true;
401
- }
402
- }
403
- if (needNewState) {
404
- const durationHours = 7 * 24;
405
- const fluctuation = 0.3;
406
- const targetRatio = 1 + (Math.random() * 2 - 1) * fluctuation;
407
- const targetPrice = currentPrice * targetRatio;
408
- const endTime = new Date(now.getTime() + durationHours * 3600 * 1e3);
409
- const minutes = durationHours * 60;
410
- const trendFactor = (targetPrice - currentPrice) / minutes;
411
- const newState = {
412
- key: "macro_state",
413
- lastCycleStart: now,
414
- startPrice: currentPrice,
415
- targetPrice,
416
- trendFactor,
417
- mode: "auto",
418
- endTime
419
- };
420
- if (!state) {
421
- await ctx.database.create("bourse_state", newState);
422
- } else {
423
- const { key, ...updateFields } = newState;
424
- await ctx.database.set("bourse_state", { key: "macro_state" }, updateFields);
425
- }
426
- state = newState;
372
+ if (!state) {
373
+ needNewState = true;
374
+ } else {
375
+ const endTime = state.endTime || new Date(state.lastCycleStart.getTime() + 7 * 24 * 3600 * 1e3);
376
+ if (state.mode !== "manual" && now > endTime) needNewState = true;
377
+ }
378
+ const createAutoState = /* @__PURE__ */ __name(async () => {
379
+ const durationHours = 7 * 24;
380
+ const fluctuation = 0.3;
381
+ const targetRatio = 1 + (Math.random() * 2 - 1) * fluctuation;
382
+ const targetPrice = currentPrice * targetRatio;
383
+ const endTime = new Date(now.getTime() + durationHours * 3600 * 1e3);
384
+ const minutes = durationHours * 60;
385
+ const trendFactor = (targetPrice - currentPrice) / minutes;
386
+ macroWaveCount = 5 + Math.floor(Math.random() * 6);
387
+ macroWeeklyAmplitudeRatio = 0.06 + Math.random() * 0.06;
388
+ const hours = 6 + Math.floor(Math.random() * 19);
389
+ nextMacroSwitchTime = new Date(now.getTime() + hours * 3600 * 1e3);
390
+ const newState = {
391
+ key: "macro_state",
392
+ lastCycleStart: now,
393
+ startPrice: currentPrice,
394
+ targetPrice,
395
+ trendFactor,
396
+ mode: "auto",
397
+ endTime
398
+ };
399
+ if (!state) await ctx.database.create("bourse_state", newState);
400
+ else {
401
+ const { key, ...updateFields } = newState;
402
+ await ctx.database.set("bourse_state", { key: "macro_state" }, updateFields);
427
403
  }
404
+ state = newState;
405
+ }, "createAutoState");
406
+ if (needNewState) {
407
+ await createAutoState();
408
+ } else if (state.mode === "auto" && nextMacroSwitchTime && now >= nextMacroSwitchTime) {
409
+ await createAutoState();
428
410
  }
429
411
  const timeSinceLastSwitch = now.getTime() - lastPatternSwitchTime.getTime();
430
412
  const forceSwitchDuration = 30 * 3600 * 1e3;
@@ -447,8 +429,8 @@ function apply(ctx, config) {
447
429
  const totalDuration = state.endTime.getTime() - state.lastCycleStart.getTime();
448
430
  const elapsed = now.getTime() - state.lastCycleStart.getTime();
449
431
  const prevElapsed = elapsed - 2 * 60 * 1e3;
450
- const waveCount = 7;
451
- const weeklyAmplitude = state.startPrice * 0.08;
432
+ const waveCount = macroWaveCount;
433
+ const weeklyAmplitude = state.startPrice * macroWeeklyAmplitudeRatio;
452
434
  const getWaveValue = /* @__PURE__ */ __name((t) => {
453
435
  const progress = t / totalDuration;
454
436
  return weeklyAmplitude * (Math.sin(2 * Math.PI * waveCount * progress) * 0.7 + Math.sin(2 * Math.PI * waveCount * 2.5 * progress) * 0.3);
@@ -456,6 +438,11 @@ function apply(ctx, config) {
456
438
  const waveDelta = getWaveValue(elapsed) - getWaveValue(prevElapsed);
457
439
  let newPrice = currentPrice + trend + volatility + patternDelta + waveDelta;
458
440
  if (newPrice < 1) newPrice = 1;
441
+ const dayBase = dailyOpenPrice ?? state.startPrice;
442
+ const upperLimit = Math.min(state.startPrice * 1.5, dayBase * 1.5);
443
+ const lowerLimit = Math.max(state.startPrice * 0.5, dayBase * 0.5);
444
+ if (newPrice > upperLimit) newPrice = upperLimit;
445
+ if (newPrice < lowerLimit) newPrice = lowerLimit;
459
446
  newPrice = Number(newPrice.toFixed(2));
460
447
  currentPrice = newPrice;
461
448
  await ctx.database.create("bourse_history", { stockId, price: newPrice, time: /* @__PURE__ */ new Date() });
@@ -701,12 +688,17 @@ function apply(ctx, config) {
701
688
  const now = /* @__PURE__ */ new Date();
702
689
  const endTime = new Date(now.getTime() + duration * 3600 * 1e3);
703
690
  const minutes = duration * 60;
704
- const trendFactor = (price - currentPrice) / minutes;
691
+ const baseStart = (await ctx.database.get("bourse_state", { key: "macro_state" }))[0]?.startPrice ?? currentPrice;
692
+ const dayBase = dailyOpenPrice ?? baseStart;
693
+ const upper = Math.min(baseStart * 1.5, dayBase * 1.5);
694
+ const lower = Math.max(baseStart * 0.5, dayBase * 0.5);
695
+ const targetPriceClamped = Math.max(lower, Math.min(upper, price));
696
+ const trendFactor = (targetPriceClamped - currentPrice) / minutes;
705
697
  const newState = {
706
698
  key: "macro_state",
707
699
  lastCycleStart: now,
708
700
  startPrice: currentPrice,
709
- targetPrice: price,
701
+ targetPrice: targetPriceClamped,
710
702
  trendFactor,
711
703
  mode: "manual",
712
704
  endTime
@@ -715,10 +707,12 @@ function apply(ctx, config) {
715
707
  if (existing.length === 0) {
716
708
  await ctx.database.create("bourse_state", newState);
717
709
  } else {
718
- await ctx.database.set("bourse_state", "macro_state", newState);
710
+ const { key, ...updateFields } = newState;
711
+ await ctx.database.set("bourse_state", { key: "macro_state" }, updateFields);
719
712
  }
713
+ const hint = targetPriceClamped !== price ? `(已按±50%限幅从${price}调整为${Number(targetPriceClamped.toFixed(2))})` : "";
720
714
  return `宏观调控已设置:
721
- 目标价格:${price}
715
+ 目标价格:${Number(targetPriceClamped.toFixed(2))}${hint}
722
716
  期限:${duration}小时
723
717
  模式:手动干预
724
718
  到期后将自动切回随机调控。`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-monetary-bourse",
3
- "version": "1.1.0-alpha.3",
3
+ "version": "1.1.1",
4
4
  "main": "lib/index.js",
5
5
  "typings": "lib/index.d.ts",
6
6
  "files": [
package/readme.md CHANGED
@@ -12,13 +12,23 @@
12
12
  - 内置 **12 种日内走势形态**(如:早盘冲高回落、V型反转、尾盘拉升、M顶/W底等)。
13
13
  - 走势不再是单纯的随机波动,每天自动或随机切换不同的操盘剧本,大幅提升观察乐趣。
14
14
  - 结合 **周级波浪** 与 **宏观趋势**,模拟真实市场的多周期叠加效应。
15
- - **📊 精美 K 线渲染**:使用 Puppeteer 渲染实时/日/周线图,Alpha 2 版本优化了坐标轴标签算法,防止文字重叠,观感更佳。
16
- - **❄️ 资金冻结机制**:交易采用 T+0 但资金/股票非即时到账模式(根据金额计算冻结时间),增加博弈深度。
15
+ - **📊 精美 K 线与持仓渲染**:使用 Puppeteer 渲染实时/日/周线图和个人持仓信息,Alpha 2 版本优化了坐标轴标签算法,防止文字重叠,观感更佳。
16
+ - **❄️ 资金冻结机制**:交易采用 T+0 但资金/股票非即时到账模式,根据金额计算冻结时间,增加博弈深度。
17
17
  - **🏦 银行联动**:支持与 `koishi-plugin-monetary-bank` 联动,现金不足时自动扣除银行活期存款。
18
18
  - **🕹️ 宏观调控**:管理员可手动干预股价目标,或强制切换当天的 K 线剧本。
19
19
 
20
20
  ## 更新记录
21
21
 
22
+ - **1.1.1(Alpha.4)**:
23
+ - 新增:硬性涨跌幅上限规则(±50%),同时约束日内与周内视角。随机与手动调控的结果均会在应用前进行限幅。
24
+ - 新增:更随机的自动宏观调控——随机刷新宏观目标(约每 6–24 小时),并随机调整周波浪频率与幅度(波浪段数约 5–10、周幅度约 6%–12%)。
25
+ - 修复:手动宏观调控命令更新状态时,严格排除主键字段,避免 `TypeError: cannot modify primary key`。
26
+ - 变更:移除配置项中的手动宏观调控相关字段(`enableManualControl`、`manualTargetPrice`、`manualDuration`),改为仅通过管理员指令进行宏观调控。
27
+ - 新增:开发测试命令方便快速验证逻辑(`bourse.test.price`、`bourse.test.clamp`),默认注释不启用,若需要请手动修改代码打开调试。
28
+
29
+ - **1.1.0**:
30
+ - Alpha.3版本转为正式版。
31
+
22
32
  - **Alpha.3**:
23
33
  - 新增:持仓盈亏计算与持仓成本追踪(`totalCost` 字段),在 `stock.my` 中展示成本价、持仓成本、当前市值、盈亏金额与盈亏百分比。
24
34
  - 新增:持仓信息 HTML 渲染(`renderHoldingImage`),使用 Puppeteer 输出美观的持仓卡片图像,包含进行中的挂单列表和盈亏高亮显示。
@@ -69,9 +79,7 @@
69
79
  - **maxFreezeTime**: 最大冻结时间(分钟,默认 `1440` 即24小时)。
70
80
 
71
81
  ### 宏观调控
72
- - **enableManualControl**: 是否开启手动强力调控模式(覆盖自动波动)。
73
- - **manualTargetPrice**: 手动模式下的目标价格。
74
- - **manualDuration**: 手动调控周期(小时)。
82
+ - 已移除配置项中的手动宏观调控字段;请使用管理员指令进行宏观调控。
75
83
 
76
84
  ## 🎮 指令说明
77
85
 
@@ -100,7 +108,7 @@
100
108
 
101
109
  - **`stock.control <price> [hours]`**
102
110
  - 设置宏观调控目标。
103
- - 说明:强行引导股价在指定时间内向目标价格移动。
111
+ - 说明:强行引导股价在指定时间内向目标价格移动。若目标涨跌超出±50%限幅,会自动调整至限幅边界后再应用。
104
112
  - 示例:`stock.control 5000 12` (在12小时内让股价涨/跌到5000)。
105
113
 
106
114
  - **`stock.pattern`** *(Alpha 2 新增)*
@@ -112,6 +120,14 @@
112
120
  - 参数 `status`: `open` (开启), `close` (关闭), `auto` (自动)。
113
121
  - 说明:手动开市时会自动重置并切换一个新的日内 K 线形态。
114
122
 
123
+ - **`bourse.test.price [ticks]`**
124
+ - 开发测试:推进价格更新若干次并返回当前价格。
125
+ - 参数 `ticks`: 推进次数,默认 1,最大 500。
126
+
127
+ - **`bourse.test.clamp <percent>`**
128
+ - 开发测试:尝试目标涨跌幅并查看限幅结果。
129
+ - 参数 `percent`: 期望涨跌百分比,例如 `60` 或 `-40`。
130
+
115
131
  ## 💡 常见问题
116
132
 
117
133
  **Q: 为什么买了股票没有立刻到账?**
@@ -123,3 +139,5 @@ A: 股价由四部分叠加而成:
123
139
  2. **周级波浪**:7天为一个大周期的正弦复合波浪。
124
140
  3. **日内形态**:每天随机从 12 种剧本中选择多种(如 V 型、倒 V 型、阶梯上涨等)。
125
141
  4. **随机噪音**:微小的随机波动。
142
+
143
+ 此外,为保证市场稳定性,应用了**涨跌幅硬性限制**:相对于周周期起始价与当日开盘价的双重基准,股价的涨跌幅不得超过 ±50%。当随机或手动设置的目标超出限幅时,将自动截断至限幅边界。