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

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 +41 -17
  2. package/package.json +1 -1
  3. package/readme.md +30 -14
package/lib/index.js CHANGED
@@ -377,21 +377,18 @@ function apply(ctx, config) {
377
377
  }
378
378
  const createAutoState = /* @__PURE__ */ __name(async () => {
379
379
  const durationHours = 7 * 24;
380
- const fluctuation = 0.3;
380
+ const fluctuation = 0.25;
381
381
  const targetRatio = 1 + (Math.random() * 2 - 1) * fluctuation;
382
- let targetPrice = currentPrice * targetRatio;
383
- const upperTarget = currentPrice * 1.5;
384
- const lowerTarget = currentPrice * 0.5;
385
- targetPrice = Math.max(lowerTarget, Math.min(upperTarget, targetPrice));
382
+ let targetPrice2 = currentPrice * targetRatio;
383
+ targetPrice2 = Math.max(currentPrice * 0.5, Math.min(currentPrice * 1.5, targetPrice2));
386
384
  const endTime = new Date(now.getTime() + durationHours * 3600 * 1e3);
387
- const minutes = durationHours * 60;
388
- const trendFactor = (targetPrice - currentPrice) / minutes;
389
385
  const newState = {
390
386
  key: "macro_state",
391
387
  lastCycleStart: now,
392
388
  startPrice: currentPrice,
393
- targetPrice,
394
- trendFactor,
389
+ targetPrice: targetPrice2,
390
+ trendFactor: 0,
391
+ // 不再使用线性趋势因子
395
392
  mode: "auto",
396
393
  endTime
397
394
  };
@@ -415,10 +412,10 @@ function apply(ctx, config) {
415
412
  switchKLinePattern("随机时间");
416
413
  }
417
414
  const basePrice = state.startPrice;
415
+ const targetPrice = state.targetPrice;
418
416
  const totalDuration = state.endTime.getTime() - state.lastCycleStart.getTime();
419
- const elapsed = Math.min(totalDuration, now.getTime() - state.lastCycleStart.getTime());
417
+ const elapsed = now.getTime() - state.lastCycleStart.getTime();
420
418
  const cycleProgress = Math.max(0, Math.min(1, elapsed / totalDuration));
421
- const trendPrice = basePrice + (state.targetPrice - basePrice) * cycleProgress;
422
419
  const dayStart = new Date(now);
423
420
  dayStart.setHours(config.openHour, 0, 0, 0);
424
421
  const dayEnd = new Date(now);
@@ -426,13 +423,32 @@ function apply(ctx, config) {
426
423
  const dayDuration = dayEnd.getTime() - dayStart.getTime();
427
424
  const dayElapsed = now.getTime() - dayStart.getTime();
428
425
  const dayProgress = Math.max(0, Math.min(1, dayElapsed / dayDuration));
426
+ const expectedPrice = basePrice + (targetPrice - basePrice) * cycleProgress;
427
+ const deviation = (expectedPrice - currentPrice) / currentPrice;
428
+ const meanReversionStrength = 0.02;
429
+ const driftReturn = deviation * meanReversionStrength;
430
+ const getVolatility = /* @__PURE__ */ __name((progress) => {
431
+ const morningVol = Math.exp(-8 * progress);
432
+ const afternoonVol = Math.exp(-8 * (1 - progress));
433
+ const baseVol = 0.3;
434
+ return baseVol + morningVol * 0.5 + afternoonVol * 0.4;
435
+ }, "getVolatility");
436
+ const volatility = getVolatility(dayProgress);
437
+ const u1 = Math.random();
438
+ const u2 = Math.random();
439
+ const normalRandom = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
440
+ const baseVolatilityPerTick = 15e-4;
441
+ const randomReturn = normalRandom * baseVolatilityPerTick * volatility;
429
442
  const patternFn = kLinePatterns[currentDayPattern];
430
- const dailyFactor = patternFn(dayProgress) * 0.03;
431
- const waveFrequency = macroWaveCount;
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);
443
+ const patternValue = patternFn(dayProgress);
444
+ const prevPatternValue = patternFn(Math.max(0, dayProgress - 0.01));
445
+ const patternTrend = (patternValue - prevPatternValue) * 0.5;
446
+ const patternBias = patternTrend * 3e-3;
447
+ const wavePhase = 2 * Math.PI * macroWaveCount * cycleProgress;
448
+ const prevWavePhase = 2 * Math.PI * macroWaveCount * Math.max(0, cycleProgress - 1e-3);
449
+ const waveTrend = (Math.sin(wavePhase) - Math.sin(prevWavePhase)) * macroWeeklyAmplitudeRatio;
450
+ const totalReturn = driftReturn + randomReturn + patternBias + waveTrend;
451
+ let newPrice = currentPrice * (1 + totalReturn);
436
452
  const dayBase = dailyOpenPrice ?? basePrice;
437
453
  const weekUpper = basePrice * 1.5;
438
454
  const weekLower = basePrice * 0.5;
@@ -440,6 +456,14 @@ function apply(ctx, config) {
440
456
  const dayLower = dayBase * 0.5;
441
457
  const upperLimit = Math.min(weekUpper, dayUpper);
442
458
  const lowerLimit = Math.max(weekLower, dayLower);
459
+ if (newPrice > upperLimit * 0.95) {
460
+ const overshoot = (newPrice - upperLimit * 0.95) / (upperLimit * 0.05);
461
+ newPrice = upperLimit * 0.95 + upperLimit * 0.05 * Math.tanh(overshoot);
462
+ }
463
+ if (newPrice < lowerLimit * 1.05) {
464
+ const undershoot = (lowerLimit * 1.05 - newPrice) / (lowerLimit * 0.05);
465
+ newPrice = lowerLimit * 1.05 - lowerLimit * 0.05 * Math.tanh(undershoot);
466
+ }
443
467
  newPrice = Math.max(lowerLimit, Math.min(upperLimit, newPrice));
444
468
  if (newPrice < 1) newPrice = 1;
445
469
  newPrice = Number(newPrice.toFixed(2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-monetary-bourse",
3
- "version": "1.1.2-Alpha.6",
3
+ "version": "1.1.2-Alpha.7",
4
4
  "main": "lib/index.js",
5
5
  "typings": "lib/index.d.ts",
6
6
  "files": [
package/readme.md CHANGED
@@ -19,8 +19,17 @@
19
19
 
20
20
  ## 更新记录
21
21
 
22
+ - **Alpha.7**:
23
+ - **采用真实股票模型**:使用几何布朗运动 + 均值回归模型,更贴近真实股票走势。
24
+ - 新增:均值回归机制——价格会自然向"预期价格"回归,而非硬性跳变。
25
+ - 新增:动态波动率——开盘和收盘波动大,午盘相对平静(U型波动率曲线)。
26
+ - 新增:正态分布随机项——使用Box-Muller变换生成更真实的随机波动。
27
+ - 新增:软着陆限幅——接近涨跌幅限制时逐渐减缓,避免硬切导致的不自然走势。
28
+ - 优化:K线形态改为提供方向性偏置,而非直接决定价格位置。
29
+ - 优化:周期波浪改为增量式叠加,更平滑自然。
30
+
22
31
  - **Alpha.6**:
23
- - **重大重构**:改用百分比波动模式,所有波动(K线、周波浪、噪音)以百分比形式叠加在趋势价格上,彻底解决振幅失控问题。
32
+ - **再次重构**:改用百分比波动模式,所有波动(K线、周波浪、噪音)以百分比形式叠加在趋势价格上,彻底解决振幅失控问题。
24
33
  - 修复:手动宏观调控不再重置周期基准价和起点,保持现有波动机制持续生效。
25
34
  - 优化:K线波动幅度调整为±3%,周波浪和噪音也改为百分比计算,波动更符合真实股票特性。
26
35
  - 性能:新的百分比合成模式更稳定,长期运行不会出现价格失控或波动失效。
@@ -137,10 +146,6 @@
137
146
  - 开发测试:推进价格更新若干次并返回当前价格。
138
147
  - 参数 `ticks`: 推进次数,默认 1,最大 500。
139
148
 
140
- - 【默认不开启】**`bourse.test.clamp <percent>`**
141
- - 开发测试:尝试目标涨跌幅并查看限幅结果。
142
- - 参数 `percent`: 期望涨跌百分比,例如 `60` 或 `-40`。
143
-
144
149
  ## 💡 常见问题
145
150
 
146
151
  **Q:Alpha版本有什么区别?**
@@ -156,17 +161,28 @@ A: 本插件设计了基于交易金额的动态冻结机制。交易额越大
156
161
  ---
157
162
 
158
163
  **Q: 股价是如何波动的?**
159
- A: 股价采用**百分比波动模式**(Alpha.6),由以下部分合成:
164
+ A: 股价采用**几何布朗运动 + 均值回归**模型(Alpha.7),更贴近真实股票:
165
+
166
+ **核心公式:** `新价格 = 当前价格 × (1 + 总收益率)`
167
+
168
+ **总收益率由以下部分组成:**
169
+
170
+ 1. **均值回归项(Drift)**:价格会自然向"预期价格"回归
171
+ - 预期价格 = 周期起始价 → 目标价的线性插值
172
+ - 偏离越大,回归力越强(每次回归2%的偏差)
160
173
 
161
- 1. **宏观趋势**:周期内从起始价线性向目标价移动的基准价格。
162
- 2. **日内K线形态**:12种剧本产生的波动因子,以±3%的百分比形式叠加在趋势上。
163
- 3. **周内波浪**:正弦曲线的波动因子,以周波浪幅度百分比(6%–12%)形式叠加。
164
- 4. **微小噪音**:±0.1%的随机扰动避免价格过于规律。
174
+ 2. **随机波动项(Random Walk)**:模拟市场的随机性
175
+ - 使用正态分布随机数(Box-Muller变换)
176
+ - 基础波动率约0.15%/tick
165
177
 
166
- **最终价格 = 趋势价格 × (1 + K线因子 + 波浪因子 + 噪音因子)**
178
+ 3. **动态波动率**:模拟日内波动率变化
179
+ - 开盘:波动率高(活跃交易)
180
+ - 午盘:波动率低(相对平静)
181
+ - 尾盘:波动率再次升高
167
182
 
168
- 这种百分比合成方式更符合真实股票特性,所有波动相对于当前价格保持合理比例,避免绝对值叠加导致的振幅失控。
183
+ 4. **K线形态偏置**:12种日内形态提供微小的方向性偏好
184
+ - 不再直接决定价格位置,而是影响涨跌概率
169
185
 
170
- 在Alpha.5版本以前,股价仍由四部分叠加而成,新旧引擎的效果相同,仅仅是在算法上进行优化与修复。
186
+ 5. **周期波浪**:中期波动,模拟市场情绪周期
171
187
 
172
- 此外,为保证市场稳定性,应用了**涨跌幅硬性限制**:相对于周周期起始价与当日开盘价的双重基准,股价的涨跌幅不得超过 ±50%。当随机或手动设置的目标超出限幅时,将自动截断至限幅边界。
188
+ **限幅机制:** 相对于周期起始价和日开盘价的±50%,采用软着陆方式避免硬切。