Terminal: buy-average price per coin in Poinbase and Web3; drop the price line on the Poinbase card
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9441d4d1bd
commit
a2dc1dd2f6
5 files changed
+48
-3
No files matched your search
@@ -19,11 +19,30 @@ namespace Decentralized.Domain
|
||||
/// <summary>被执法标记。</summary>
|
||||
public bool Flagged;
|
||||
|
||||
/// <summary>买入记录:每种币买入的数量与花费(信用点等值),用来显示买入均价。卖出或转出时按比例减少。</summary>
|
||||
public Dictionary<string, double> BuyQty = new Dictionary<string, double>(), BuyCost = new Dictionary<string, double>();
|
||||
|
||||
public void RecordBuy(string asset, double qty, double costCredits)
|
||||
{
|
||||
if (qty <= 0) return;
|
||||
BuyQty[asset] = (BuyQty.TryGetValue(asset, out var q) ? q : 0) + qty;
|
||||
BuyCost[asset] = (BuyCost.TryGetValue(asset, out var c) ? c : 0) + Math.Max(0, costCredits);
|
||||
}
|
||||
|
||||
/// <summary>买入均价(信用点);没有买入记录(例如全是挖出来的)返回 null。</summary>
|
||||
public double? AvgBuy(string asset) => BuyQty.TryGetValue(asset, out var q) && q > 1e-9 ? BuyCost[asset] / q : (double?)null;
|
||||
|
||||
public double Get(string asset) => Balances.TryGetValue(asset, out var v) ? v : 0;
|
||||
|
||||
public void Add(string asset, double amount)
|
||||
{
|
||||
if (amount == 0) return;
|
||||
if (amount < 0 && BuyQty.TryGetValue(asset, out var bq))
|
||||
{
|
||||
double before = Get(asset), keep = before > 1e-12 ? Math.Max(0, 1 + amount / before) : 0;
|
||||
BuyQty[asset] = bq * keep; BuyCost[asset] *= keep;
|
||||
if (BuyQty[asset] < 1e-9) { BuyQty.Remove(asset); BuyCost.Remove(asset); }
|
||||
}
|
||||
double v = Get(asset) + amount;
|
||||
if (v < -1e-9) throw new InvalidOperationException($"{Address}: {asset} 余额不足");
|
||||
if (Math.Abs(v) < 1e-12) Balances.Remove(asset);
|
||||
|
||||
@@ -148,3 +148,6 @@ bank.rent,"Rent: {0} credits, due in {1} days"
|
||||
bank.crc,"Poinbase account (CRC): {0}"
|
||||
bank.hint,"Deposit turns credits into CRC; withdrawing turns CRC back into credits and passes compliance review."
|
||||
poin.account,"Poinbase account: CRC {0}, PSC {1}"
|
||||
avg.line,"{0} average buy price: {1} credits"
|
||||
avg.short,"avg. buy {0}"
|
||||
avg.none,"—"
|
||||
|
@@ -147,3 +147,6 @@ bank.rent,"房租:{0} 信用点,{1} 天后支付"
|
||||
bank.crc,"Poinbase 账户(CRC):{0}"
|
||||
bank.hint,"存入把信用点换成 CRC,提取把 CRC 换回信用点,提取要过合规审查。"
|
||||
poin.account,"Poinbase 账户:CRC {0},PSC {1}"
|
||||
avg.line,"{0} 买入均价:{1} 信用点"
|
||||
avg.short,"买入均价 {0}"
|
||||
avg.none,"—"
|
||||
|
@@ -56,7 +56,7 @@ namespace Decentralized
|
||||
b.AddImage(ModInfo.Id + ":" + icon, 96f, 96f);
|
||||
b.AddHeader(name);
|
||||
b.AddLabel(line1);
|
||||
b.AddLabel(line2);
|
||||
if (line2 != null) b.AddLabel(line2);
|
||||
b.AddFlexibleSpace();
|
||||
b.AddButton(L.T("home.enter"), () => Go(target));
|
||||
b.End();
|
||||
@@ -70,7 +70,7 @@ namespace Decentralized
|
||||
b.AddSpace(6f);
|
||||
b.BeginRow(8f, TextAnchor.UpperCenter);
|
||||
Entry(b, "ui_bank", L.T("entry.bank"), L.T("entry.bank.l1"), L.T("entry.bank.l2", PlayerStore.Instance.playerCash), Page.Bank);
|
||||
Entry(b, "ui_poinbase", L.T("entry.poinbase"), L.T("entry.poinbase.l1"), L.T("entry.poinbase.l2", Fmt.Num(d.Market.Price)), Page.Poinbase);
|
||||
Entry(b, "ui_poinbase", L.T("entry.poinbase"), L.T("entry.poinbase.l1"), null, Page.Poinbase);
|
||||
Entry(b, "ui_web3", L.T("entry.web3"), L.T("entry.web3.l1"), L.T("entry.web3.l2", Fmt.Credits(chain)), Page.Web3);
|
||||
b.End();
|
||||
b.AddSpace(8f);
|
||||
@@ -125,6 +125,7 @@ namespace Decentralized
|
||||
var w = Session.Data.Hot;
|
||||
b.AddHeader(L.T("entry.poinbase"));
|
||||
b.AddLabel(L.T("poin.account", Fmt.Num(w.Get(Assets.CRC)), Fmt.Num(w.Get(Assets.PSC))));
|
||||
b.AddLabel(L.T("avg.line", Assets.PSC, AvgText(w, Assets.PSC)));
|
||||
b.AddLabel(L.T("trade.hint"));
|
||||
AmountRow(b);
|
||||
b.AddSeparator();
|
||||
@@ -138,7 +139,7 @@ namespace Decentralized
|
||||
b.AddHeader(L.T("entry.web3"));
|
||||
if (w.Balances.Count == 0) b.AddLabel(L.T("wallet.empty"));
|
||||
foreach (var kv in w.Balances.OrderBy(k => k.Key))
|
||||
b.AddLabel(L.T("wallet.line", kv.Key, Fmt.Num(kv.Value), Fmt.Credits(kv.Value * Session.Price(kv.Key))));
|
||||
b.AddLabel(L.T("wallet.line", kv.Key, Fmt.Num(kv.Value), Fmt.Credits(kv.Value * Session.Price(kv.Key))) + " " + L.T("avg.short", AvgText(w, kv.Key)));
|
||||
b.AddLabel(L.T("wallet.total", Fmt.Credits(w.Value(Session.Price))));
|
||||
if (w.Taint > 0.5) b.AddLabel(L.T("wallet.taint", Fmt.Credits(w.Taint)));
|
||||
b.AddSeparator();
|
||||
@@ -148,6 +149,8 @@ namespace Decentralized
|
||||
TradeRows(b, Venue.Dex);
|
||||
}
|
||||
|
||||
private static string AvgText(Wallet w, string asset) => w.AvgBuy(asset) is double a ? Fmt.Num(a) : L.T("avg.none");
|
||||
|
||||
private static void ReportPage(CustomUIBuilder b)
|
||||
{
|
||||
var r = Session.Data.LastNight;
|
||||
@@ -181,6 +184,7 @@ namespace Decentralized
|
||||
ModHelper.AddCredit(-credits, false);
|
||||
var w = Session.Data.Hot;
|
||||
w.Add(Assets.CRC, credits / Session.Data.Stable.Crc);
|
||||
w.RecordBuy(Assets.CRC, credits / Session.Data.Stable.Crc, credits);
|
||||
w.Basis += credits;
|
||||
_message = L.T("msg.deposit", credits);
|
||||
Session.Save();
|
||||
@@ -213,8 +217,10 @@ namespace Decentralized
|
||||
string pay = buy ? p.Quote : p.Base, get = buy ? p.Base : p.Quote;
|
||||
if (w.Get(pay) + 1e-9 < v) { _message = L.T("msg.balance"); Open(); return; }
|
||||
double got = buy ? p.BuyBase(v) : p.SellBase(v);
|
||||
double spent = v * Session.Price(pay);
|
||||
w.Add(pay, -v);
|
||||
w.Add(get, got);
|
||||
w.RecordBuy(get, got, spent);
|
||||
_message = L.T("msg.trade", Fmt.Num(v), pay, Fmt.Num(got), get);
|
||||
Session.Save();
|
||||
Open();
|
||||
|
||||
@@ -248,6 +248,20 @@ internal static class Program
|
||||
Check(hi.Price <= 300 + 1e-9, $"上限 3 倍:{hi.Price:0.0}");
|
||||
});
|
||||
|
||||
Test("钱包:买入均价按买入记录算,卖出和转出按比例减少,挖出的币不计入", () =>
|
||||
{
|
||||
var w = new Wallet { Address = "x" };
|
||||
w.Add("PSC", 10); // 挖出的 10 个,无买入记录
|
||||
Check(w.AvgBuy("PSC") == null, "没有买入记录时没有均价");
|
||||
w.Add("PSC", 5); w.RecordBuy("PSC", 5, 400);
|
||||
w.Add("PSC", 5); w.RecordBuy("PSC", 5, 600);
|
||||
Check(Near(w.AvgBuy("PSC").Value, 100, 1e-9), "买 5 个花 400 + 买 5 个花 600:均价 100");
|
||||
w.Add("PSC", -10); // 卖掉一半(20 个里的 10 个)
|
||||
Check(Near(w.AvgBuy("PSC").Value, 100, 1e-9) && Near(w.BuyQty["PSC"], 5, 1e-9), "卖出后均价不变,买入记录按比例减少");
|
||||
w.Add("PSC", -10);
|
||||
Check(w.AvgBuy("PSC") == null, "清仓后记录清除");
|
||||
});
|
||||
|
||||
Test("市场:减半抬高价格锚(逐日靠拢),减半前一周有预期行情", () =>
|
||||
{
|
||||
Check(Near(Market.SupplyTarget(10, Tuning.BlockReward), 1.0, 1e-9), "减半前很远:目标因子 1");
|
||||
|
||||
Reference in new issue
Block a user