koishi-plugin-monetary-bourse 1.1.0 → 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 +0 -3
- package/lib/index.js +63 -69
- package/package.json +1 -1
- package/readme.md +20 -5
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 (!
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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 =
|
|
451
|
-
const weeklyAmplitude = state.startPrice *
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
目标价格:${
|
|
715
|
+
目标价格:${Number(targetPriceClamped.toFixed(2))}${hint}
|
|
722
716
|
期限:${duration}小时
|
|
723
717
|
模式:手动干预
|
|
724
718
|
到期后将自动切回随机调控。`;
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -19,7 +19,14 @@
|
|
|
19
19
|
|
|
20
20
|
## 更新记录
|
|
21
21
|
|
|
22
|
-
- 1.1.
|
|
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**:
|
|
23
30
|
- Alpha.3版本转为正式版。
|
|
24
31
|
|
|
25
32
|
- **Alpha.3**:
|
|
@@ -72,9 +79,7 @@
|
|
|
72
79
|
- **maxFreezeTime**: 最大冻结时间(分钟,默认 `1440` 即24小时)。
|
|
73
80
|
|
|
74
81
|
### 宏观调控
|
|
75
|
-
-
|
|
76
|
-
- **manualTargetPrice**: 手动模式下的目标价格。
|
|
77
|
-
- **manualDuration**: 手动调控周期(小时)。
|
|
82
|
+
- 已移除配置项中的手动宏观调控字段;请使用管理员指令进行宏观调控。
|
|
78
83
|
|
|
79
84
|
## 🎮 指令说明
|
|
80
85
|
|
|
@@ -103,7 +108,7 @@
|
|
|
103
108
|
|
|
104
109
|
- **`stock.control <price> [hours]`**
|
|
105
110
|
- 设置宏观调控目标。
|
|
106
|
-
-
|
|
111
|
+
- 说明:强行引导股价在指定时间内向目标价格移动。若目标涨跌超出±50%限幅,会自动调整至限幅边界后再应用。
|
|
107
112
|
- 示例:`stock.control 5000 12` (在12小时内让股价涨/跌到5000)。
|
|
108
113
|
|
|
109
114
|
- **`stock.pattern`** *(Alpha 2 新增)*
|
|
@@ -115,6 +120,14 @@
|
|
|
115
120
|
- 参数 `status`: `open` (开启), `close` (关闭), `auto` (自动)。
|
|
116
121
|
- 说明:手动开市时会自动重置并切换一个新的日内 K 线形态。
|
|
117
122
|
|
|
123
|
+
- **`bourse.test.price [ticks]`**
|
|
124
|
+
- 开发测试:推进价格更新若干次并返回当前价格。
|
|
125
|
+
- 参数 `ticks`: 推进次数,默认 1,最大 500。
|
|
126
|
+
|
|
127
|
+
- **`bourse.test.clamp <percent>`**
|
|
128
|
+
- 开发测试:尝试目标涨跌幅并查看限幅结果。
|
|
129
|
+
- 参数 `percent`: 期望涨跌百分比,例如 `60` 或 `-40`。
|
|
130
|
+
|
|
118
131
|
## 💡 常见问题
|
|
119
132
|
|
|
120
133
|
**Q: 为什么买了股票没有立刻到账?**
|
|
@@ -126,3 +139,5 @@ A: 股价由四部分叠加而成:
|
|
|
126
139
|
2. **周级波浪**:7天为一个大周期的正弦复合波浪。
|
|
127
140
|
3. **日内形态**:每天随机从 12 种剧本中选择多种(如 V 型、倒 V 型、阶梯上涨等)。
|
|
128
141
|
4. **随机噪音**:微小的随机波动。
|
|
142
|
+
|
|
143
|
+
此外,为保证市场稳定性,应用了**涨跌幅硬性限制**:相对于周周期起始价与当日开盘价的双重基准,股价的涨跌幅不得超过 ±50%。当随机或手动设置的目标超出限幅时,将自动截断至限幅边界。
|