From 0560c342311d40a10200544c689a54bfacba64e2 Mon Sep 17 00:00:00 2001 From: ZHU MINCHAO <48154958+MinchaoZhu@users.noreply.github.com> Date: Tue, 3 Nov 2026 15:13:57 +0800 Subject: [PATCH] Halving now raises the price anchor (pass-through 0.6, ramp the week before) with a pre-halving news item Co-Authored-By: Claude Sonnet 5.5 --- src/Decentralized.Domain/Market.cs | 15 ++++++++++++++- src/Decentralized.Domain/Tuning.cs | 7 +++++++ src/Decentralized/ModFiles/Localization/en.csv | 2 ++ src/Decentralized/ModFiles/Localization/zh.csv | 2 ++ src/Decentralized/Night.cs | 2 ++ tests/Decentralized.Domain.Tests/Program.cs | 15 +++++++++++++++ 6 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/Decentralized.Domain/Market.cs b/src/Decentralized.Domain/Market.cs index 78481d3..8714412 100644 --- a/src/Decentralized.Domain/Market.cs +++ b/src/Decentralized.Domain/Market.cs @@ -16,6 +16,8 @@ namespace Decentralized.Domain public sealed class Market { public double Sentiment = 1.0; + /// 供给因子:出块奖励减半抬高挖矿成本,价格锚随之上移;减半前一周有预期行情。每晚向目标靠拢。 + public double Supply = 1.0; public List Pools = new List(); /// 开门时的参考价,夜间用它和收盘价算当日均价(合约的标记价)。 public double OpenPrice = Tuning.PscStartPrice; @@ -42,9 +44,20 @@ namespace Decentralized.Domain /// PSC 的参考价(信用点)。 public double Price => Main.Price; + /// 供给因子的目标:奖励比起始奖励每少一半,锚上移 2^传导指数;减半前 7 天内再逐日加预期。 + public static double SupplyTarget(int day, double reward) + { + double t = Math.Pow(Tuning.BlockReward / Math.Max(1e-9, reward), Tuning.HalvingPassThrough); + int toHalving = Tuning.HalvingDays - day % Tuning.HalvingDays; // 减半当晚 day % HalvingDays == 0,那天之后又是一个完整周期 + if (toHalving <= Tuning.HalvingRampDays) t *= 1 + Tuning.HalvingRamp * (Tuning.HalvingRampDays - toHalving + 1) / Tuning.HalvingRampDays; + return t; + } + + public void UpdateSupply(int day, double reward) => Supply += (SupplyTarget(day, reward) - Supply) * Tuning.SupplyAdjust; + public double Anchor(ICollection active) { - double a = Tuning.PscStartPrice * Sentiment; + double a = Tuning.PscStartPrice * Sentiment * Supply; if (active.Contains(MarketEvent.Blackout)) a *= 1.10; if (active.Contains(MarketEvent.Crackdown)) a *= 0.92; return a; diff --git a/src/Decentralized.Domain/Tuning.cs b/src/Decentralized.Domain/Tuning.cs index 63d169d..095839c 100644 --- a/src/Decentralized.Domain/Tuning.cs +++ b/src/Decentralized.Domain/Tuning.cs @@ -9,6 +9,13 @@ namespace Decentralized.Domain public const double PscStartPrice = 100; public const double BlockReward = 90; public const int HalvingDays = 90; + /// 减半对币价的传导:奖励减半时,价格锚上移 2^传导指数 倍(0.6 即约 +52%),市场已部分预期到,所以不是翻倍。 + public const double HalvingPassThrough = 0.6; + /// 减半前一周的预期行情:锚逐日抬高,到减半当天为 +8%。 + public const int HalvingRampDays = 7; + public const double HalvingRamp = 0.08; + /// 价格锚向目标的逐晚靠拢比例(约 7 天完成大半)。 + public const double SupplyAdjust = 0.15; public const double NpcHash0 = 360; public const double NpcKjPerH = 2.4; public const double ElecPrice = 1.5; diff --git a/src/Decentralized/ModFiles/Localization/en.csv b/src/Decentralized/ModFiles/Localization/en.csv index a09334f..8551bc3 100644 --- a/src/Decentralized/ModFiles/Localization/en.csv +++ b/src/Decentralized/ModFiles/Localization/en.csv @@ -127,3 +127,5 @@ lbl.ev.serial.1,"Serial: bad factory code or length" lbl.ev.serial.2,"Serial: tier code does not match" lbl.ev.print.1,"Print shows another model" lbl.ev.print.2,"Print was redone" +news.prehalving.title,"PSC Halving Ahead" +news.prehalving.desc,"Miners will receive half as many coins per night in three days. Holders expect the price to rise." diff --git a/src/Decentralized/ModFiles/Localization/zh.csv b/src/Decentralized/ModFiles/Localization/zh.csv index 7edc231..82822ba 100644 --- a/src/Decentralized/ModFiles/Localization/zh.csv +++ b/src/Decentralized/ModFiles/Localization/zh.csv @@ -126,3 +126,5 @@ lbl.ev.serial.1,"序列号工厂码或位数不合规" lbl.ev.serial.2,"序列号档次码与型号不符" lbl.ev.print.1,"丝印印着另一个型号" lbl.ev.print.2,"丝印重印过" +news.prehalving.title,"PSC 即将减半" +news.prehalving.desc,"三天后矿工每晚获得的币减半。持币者预期币价上行。" diff --git a/src/Decentralized/Night.cs b/src/Decentralized/Night.cs index 2fd8a0c..17f5e6a 100644 --- a/src/Decentralized/Night.cs +++ b/src/Decentralized/Night.cs @@ -108,6 +108,8 @@ namespace Decentralized if (d.Network.Reward < rewardBefore) News.Show("pd_news_halving_" + day, L.T("news.halving.title"), L.T("news.halving.desc"), 2); double crcFactor = d.Stable.Night(day, d.Rng, 0); if (crcFactor < 1) d.Hot.Balances[Assets.CRC] = d.Hot.Get(Assets.CRC) * crcFactor; + d.Market.UpdateSupply(day, d.Network.Reward); + if (Tuning.HalvingDays - day % Tuning.HalvingDays == 3) News.Show("pd_news_prehalving_" + day, L.T("news.prehalving.title"), L.T("news.prehalving.desc"), 3); d.Market.Night(events, d.Rng, day, Session.Price); report.Price = d.Market.Price; d.LastNight = report; diff --git a/tests/Decentralized.Domain.Tests/Program.cs b/tests/Decentralized.Domain.Tests/Program.cs index 63a0d7b..78e66ce 100644 --- a/tests/Decentralized.Domain.Tests/Program.cs +++ b/tests/Decentralized.Domain.Tests/Program.cs @@ -248,6 +248,21 @@ internal static class Program Check(hi.Price <= 300 + 1e-9, $"上限 3 倍:{hi.Price:0.0}"); }); + Test("市场:减半抬高价格锚(逐日靠拢),减半前一周有预期行情", () => + { + Check(Near(Market.SupplyTarget(10, Tuning.BlockReward), 1.0, 1e-9), "减半前很远:目标因子 1"); + double before = Market.SupplyTarget(89, Tuning.BlockReward), start = Market.SupplyTarget(83, Tuning.BlockReward); + Check(start > 1.0 && before > start && before <= 1 + Tuning.HalvingRamp + 1e-9, $"减半前 7 天逐日抬高,最多 +8%:{start:0.000} → {before:0.000}"); + double after = Market.SupplyTarget(91, Tuning.BlockReward / 2); + Check(Near(after, Math.Pow(2, Tuning.HalvingPassThrough), 1e-9), $"减半后目标因子 2^0.6 = {after:0.000}"); + var m = new Market(); + for (int i = 0; i < 7; i++) m.UpdateSupply(91 + i, Tuning.BlockReward / 2); + Check(m.Supply > 1.0 && m.Supply < after, "逐晚靠拢目标,不一次跳到位"); + for (int i = 0; i < 40; i++) m.UpdateSupply(100 + i % 5, Tuning.BlockReward / 2); + Check(Near(m.Supply, after, 0.02), "几十晚后基本到位"); + Check(m.Anchor(new MarketEvent[0]) > Tuning.PscStartPrice * 1.4, "锚随之上移"); + }); + Test("市场:其他交易对每晚被套利拉向参考价,三角价差收敛", () => { var m = Market.CreateDefault(1);