koishi-plugin-monetary-bourse 1.1.2-Alpha.5 → 1.1.2-Alpha.6

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 +17 -34
  2. package/package.json +1 -1
  3. package/readme.md +14 -12
package/lib/index.js CHANGED
@@ -417,7 +417,7 @@ function apply(ctx, config) {
417
417
  const basePrice = state.startPrice;
418
418
  const totalDuration = state.endTime.getTime() - state.lastCycleStart.getTime();
419
419
  const elapsed = Math.min(totalDuration, now.getTime() - state.lastCycleStart.getTime());
420
- const cycleProgress = elapsed / totalDuration;
420
+ const cycleProgress = Math.max(0, Math.min(1, elapsed / totalDuration));
421
421
  const trendPrice = basePrice + (state.targetPrice - basePrice) * cycleProgress;
422
422
  const dayStart = new Date(now);
423
423
  dayStart.setHours(config.openHour, 0, 0, 0);
@@ -426,14 +426,13 @@ function apply(ctx, config) {
426
426
  const dayDuration = dayEnd.getTime() - dayStart.getTime();
427
427
  const dayElapsed = now.getTime() - dayStart.getTime();
428
428
  const dayProgress = Math.max(0, Math.min(1, dayElapsed / dayDuration));
429
- const dailyAmplitude = basePrice * 0.07;
430
429
  const patternFn = kLinePatterns[currentDayPattern];
431
- const patternOffset = patternFn(dayProgress) * dailyAmplitude;
430
+ const dailyFactor = patternFn(dayProgress) * 0.03;
432
431
  const waveFrequency = macroWaveCount;
433
- const weeklyAmplitude = basePrice * macroWeeklyAmplitudeRatio;
434
- const waveOffset = weeklyAmplitude * Math.sin(2 * Math.PI * waveFrequency * cycleProgress);
435
- const noise = basePrice * 4e-3 * (Math.random() * 2 - 1);
436
- let newPrice = trendPrice + patternOffset + waveOffset + noise;
432
+ const weeklyAmplitudeRatio = macroWeeklyAmplitudeRatio;
433
+ const waveFactor = Math.sin(2 * Math.PI * waveFrequency * cycleProgress) * weeklyAmplitudeRatio;
434
+ const noiseFactor = (Math.random() * 2 - 1) * 1e-3;
435
+ let newPrice = trendPrice * (1 + dailyFactor + waveFactor + noiseFactor);
437
436
  const dayBase = dailyOpenPrice ?? basePrice;
438
437
  const weekUpper = basePrice * 1.5;
439
438
  const weekLower = basePrice * 0.5;
@@ -687,24 +686,26 @@ function apply(ctx, config) {
687
686
  const duration = hours || 24;
688
687
  const now = /* @__PURE__ */ new Date();
689
688
  const endTime = new Date(now.getTime() + duration * 3600 * 1e3);
690
- const minutes = duration * 60;
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);
689
+ const existing = (await ctx.database.get("bourse_state", { key: "macro_state" }))[0];
690
+ const keepBasePrice = existing?.startPrice ?? currentPrice;
691
+ const dayBase = dailyOpenPrice ?? keepBasePrice;
692
+ const upper = Math.min(keepBasePrice * 1.5, dayBase * 1.5);
693
+ const lower = Math.max(keepBasePrice * 0.5, dayBase * 0.5);
695
694
  const targetPriceClamped = Math.max(lower, Math.min(upper, price));
695
+ const minutes = duration * 60;
696
696
  const trendFactor = (targetPriceClamped - currentPrice) / minutes;
697
697
  const newState = {
698
698
  key: "macro_state",
699
- lastCycleStart: now,
700
- startPrice: currentPrice,
699
+ lastCycleStart: existing?.lastCycleStart ?? now,
700
+ // 保持原周期起点
701
+ startPrice: keepBasePrice,
702
+ // 保持原基准价,不重置
701
703
  targetPrice: targetPriceClamped,
702
704
  trendFactor,
703
705
  mode: "manual",
704
706
  endTime
705
707
  };
706
- const existing = await ctx.database.get("bourse_state", { key: "macro_state" });
707
- if (existing.length === 0) {
708
+ if (!existing) {
708
709
  await ctx.database.create("bourse_state", newState);
709
710
  } else {
710
711
  const { key, ...updateFields } = newState;
@@ -749,24 +750,6 @@ function apply(ctx, config) {
749
750
  switchKLinePattern("管理员手动");
750
751
  return "已切换K线模型。";
751
752
  });
752
- ctx.command("bourse.test.price [ticks:number]", "开发测试:推进价格更新若干次并返回当前价格", { authority: 3 }).action(async ({ session }, ticks) => {
753
- const n = typeof ticks === "number" && ticks > 0 ? Math.min(ticks, 500) : 1;
754
- for (let i = 0; i < n; i++) {
755
- await updatePrice();
756
- }
757
- return `测试完成:推进${n}次;当前价格:${Number(currentPrice.toFixed(2))}`;
758
- });
759
- ctx.command("bourse.test.clamp <percent:number>", "开发测试:尝试目标涨跌幅并查看限幅结果", { authority: 3 }).action(async ({ session }, percent) => {
760
- if (typeof percent !== "number") return "请输入百分比(例如 60 或 -40)";
761
- const baseStart = (await ctx.database.get("bourse_state", { key: "macro_state" }))[0]?.startPrice ?? currentPrice;
762
- const dayBase = dailyOpenPrice ?? baseStart;
763
- const upper = Math.min(baseStart * 1.5, dayBase * 1.5);
764
- const lower = Math.max(baseStart * 0.5, dayBase * 0.5);
765
- const expected = currentPrice * (1 + percent / 100);
766
- const clamped = Math.max(lower, Math.min(upper, expected));
767
- const hint = clamped !== expected ? `(已按±50%限幅从${Number(expected.toFixed(2))}调整为${Number(clamped.toFixed(2))})` : "";
768
- return `测试限幅:当前价=${Number(currentPrice.toFixed(2))};期望=${Number(expected.toFixed(2))};结果=${Number(clamped.toFixed(2))}${hint}`;
769
- });
770
753
  async function renderHoldingImage(ctx2, username, holding, pending, currency) {
771
754
  const hasCostData = holding && holding.totalCost !== null;
772
755
  const isProfit = hasCostData ? holding.profit >= 0 : true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-monetary-bourse",
3
- "version": "1.1.2-Alpha.5",
3
+ "version": "1.1.2-Alpha.6",
4
4
  "main": "lib/index.js",
5
5
  "typings": "lib/index.d.ts",
6
6
  "files": [
package/readme.md CHANGED
@@ -19,6 +19,12 @@
19
19
 
20
20
  ## 更新记录
21
21
 
22
+ - **Alpha.6**:
23
+ - **重大重构**:改用百分比波动模式,所有波动(K线、周波浪、噪音)以百分比形式叠加在趋势价格上,彻底解决振幅失控问题。
24
+ - 修复:手动宏观调控不再重置周期基准价和起点,保持现有波动机制持续生效。
25
+ - 优化:K线波动幅度调整为±3%,周波浪和噪音也改为百分比计算,波动更符合真实股票特性。
26
+ - 性能:新的百分比合成模式更稳定,长期运行不会出现价格失控或波动失效。
27
+
22
28
  - **Alpha.5**:
23
29
  - **重写股票走势引擎**:将增量叠加模式改为绝对价格计算模式,彻底解决长期运行后的高频抖动问题。
24
30
  - 修复:走势计算采用绝对值合成(基准价+趋势进度+日内波动+周波浪+噪音),避免累积误差导致的失控。
@@ -150,21 +156,17 @@ A: 本插件设计了基于交易金额的动态冻结机制。交易额越大
150
156
  ---
151
157
 
152
158
  **Q: 股价是如何波动的?**
153
- A: 股价采用**绝对价格计算模式**,由以下部分合成:
159
+ A: 股价采用**百分比波动模式**(Alpha.6),由以下部分合成:
154
160
 
155
- 1. **宏观趋势**:周期内从起始价线性向目标价移动的进度价格。
156
- 2. **日内K线形态**:12种剧本叠加在趋势上的波动偏移(约±4%)。
157
- 3. **周内波浪**:正弦曲线叠加的平滑周期波动。
158
- 4. **微小噪音**:随机扰动避免价格过于规律。
161
+ 1. **宏观趋势**:周期内从起始价线性向目标价移动的基准价格。
162
+ 2. **日内K线形态**:12种剧本产生的波动因子,以±3%的百分比形式叠加在趋势上。
163
+ 3. **周内波浪**:正弦曲线的波动因子,以周波浪幅度百分比(6%–12%)形式叠加。
164
+ 4. **微小噪音**:±0.1%的随机扰动避免价格过于规律。
159
165
 
160
- 新引擎完全避免增量累积误差,长期运行稳定可靠。
166
+ **最终价格 = 趋势价格 × (1 + K线因子 + 波浪因子 + 噪音因子)**
161
167
 
162
- 在Alpha.5版本以前,股价由四部分叠加而成:
163
- 1. **宏观趋势**:根据自动或手动的目标价格计算的线性趋势。
164
- 2. **周级波浪**:7天为一个大周期的正弦复合波浪。
165
- 3. **日内形态**:每天随机从 12 种剧本中选择多种(如 V 型、倒 V 型、阶梯上涨等)。
166
- 4. **随机噪音**:微小的随机波动。
168
+ 这种百分比合成方式更符合真实股票特性,所有波动相对于当前价格保持合理比例,避免绝对值叠加导致的振幅失控。
167
169
 
168
- 新旧引擎的效果相同,仅仅是在算法上进行优化与修复。
170
+ 在Alpha.5版本以前,股价仍由四部分叠加而成,新旧引擎的效果相同,仅仅是在算法上进行优化与修复。
169
171
 
170
172
  此外,为保证市场稳定性,应用了**涨跌幅硬性限制**:相对于周周期起始价与当日开盘价的双重基准,股价的涨跌幅不得超过 ±50%。当随机或手动设置的目标超出限幅时,将自动截断至限幅边界。