Candles and volume for every traded asset, chart asset switcher
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b8308e1010
commit
e1390a6921
9 files changed
+105
-27
No files matched your search
@@ -13,6 +13,49 @@ namespace Decentralized
|
||||
/// </summary>
|
||||
public static class CandleChart
|
||||
{
|
||||
/// <summary>画图的币种:全部可交易资产。</summary>
|
||||
public static readonly string[] ChartAssets = { Assets.PSC, Assets.SPARK, Assets.CRT, Assets.CRA, Assets.CRC, Assets.Ponero };
|
||||
|
||||
private static double VolShare(string a) =>
|
||||
a == Assets.PSC ? Tuning.ChartVolPsc : a == Assets.CRC ? Tuning.ChartVolCrc : a == Assets.CRT ? Tuning.ChartVolCrt :
|
||||
a == Assets.CRA ? Tuning.ChartVolCra : a == Assets.SPARK ? Tuning.ChartVolSpark : Tuning.ChartVolPonero;
|
||||
|
||||
/// <summary>开门:每个币开一根蜡烛(开盘接昨天收盘,夜里的价格变化算进来)。</summary>
|
||||
public static void Morning(SaveData d, int day)
|
||||
{
|
||||
foreach (var a in ChartAssets) d.Candles.Open(a, day, Session.Price(a));
|
||||
}
|
||||
|
||||
/// <summary>白天价格变了(不计成交)。</summary>
|
||||
public static void Touch(SaveData d, int day, params string[] assets)
|
||||
{
|
||||
foreach (var a in assets) d.Candles.Tick(a, day, Session.Price(a));
|
||||
}
|
||||
|
||||
/// <summary>玩家的一笔成交:按信用点计进这个币当天的成交量。</summary>
|
||||
public static void Trade(string asset, double credits)
|
||||
{
|
||||
var d = Session.Data;
|
||||
if (d == null || Array.IndexOf(ChartAssets, asset) < 0 || credits <= 0) return;
|
||||
d.Candles.Tick(asset, StoreStation.GetDayCounter(), Session.Price(asset), credits, true);
|
||||
}
|
||||
|
||||
/// <summary>夜间结算:补上当天 NPC 的成交量,收盘。在价格的夜间更新之前调用。</summary>
|
||||
public static void Close(SaveData d, int day)
|
||||
{
|
||||
double depth = Tuning.PoolDepth(day);
|
||||
foreach (var a in ChartAssets)
|
||||
{
|
||||
double p = Session.Price(a);
|
||||
var c = d.Candles.Today(a, day, p);
|
||||
double move = c.O > 0 ? Math.Abs(p / c.O - 1) + (c.H - c.L) / c.O : 0;
|
||||
d.Candles.Tick(a, day, p, CandleBook.NpcVolume(depth, VolShare(a), move, d.Rng));
|
||||
d.Candles.Close(a, day, p);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, string> Picked = new Dictionary<string, string>();
|
||||
|
||||
public const int Days = 30, Step = 6, Body = 5, PriceH = 70, Gap = 3, VolH = 16, Pad = 2;
|
||||
public const int W = Days * Step + Pad * 2, H = PriceH + Gap + VolH + Pad * 2;
|
||||
public const float Scale = 2f;
|
||||
@@ -49,6 +92,8 @@ namespace Decentralized
|
||||
Func<double, int> y = p => priceY0 + (int)Math.Round((p - lo) / span * (PriceH - 1));
|
||||
double vmax = Math.Max(1, c.Max(x => x.VolPlayer + x.VolNpc));
|
||||
int x0 = Pad + (Days - c.Count) * Step; // 不足 30 根时靠右
|
||||
// 均线先画,蜡烛盖在上面(贴着锚的稳定币不会被均线遮住)
|
||||
for (int i = 1; i < ma.Count; i++) Line(px, x0 + (i - 1) * Step + Body / 2, y(ma[i - 1]), x0 + i * Step + Body / 2, y(ma[i]), Ma);
|
||||
for (int i = 0; i < c.Count; i++)
|
||||
{
|
||||
var k = c[i];
|
||||
@@ -69,7 +114,6 @@ namespace Decentralized
|
||||
if (hm > 0) VLine(px, xx, volY0 + hn, volY0 + hn + hm - 1, VolMe);
|
||||
}
|
||||
}
|
||||
for (int i = 1; i < ma.Count; i++) Line(px, x0 + (i - 1) * Step + Body / 2, y(ma[i - 1]), x0 + i * Step + Body / 2, y(ma[i]), Ma);
|
||||
if (threshold > 0) { int ty = y(threshold); for (int xx = Pad; xx < W - Pad; xx++) if ((xx / 3) % 2 == 0) Set(px, xx, ty, Thr); }
|
||||
}
|
||||
s.Tex.SetPixels32(px);
|
||||
@@ -78,13 +122,13 @@ namespace Decentralized
|
||||
}
|
||||
|
||||
/// <summary>图上方的一行说明:最高、最低、最新收盘,涨跌。</summary>
|
||||
public static string Caption(IList<Candle> all)
|
||||
public static string Caption(string asset, IList<Candle> all)
|
||||
{
|
||||
var c = all.Skip(Math.Max(0, all.Count - Days)).ToList();
|
||||
if (c.Count == 0) return L.T("chart.empty");
|
||||
if (c.Count == 0) return L.T("chart.empty", asset);
|
||||
var last = c[c.Count - 1];
|
||||
double prev = c.Count > 1 ? c[c.Count - 2].C : last.O, chg = prev > 0 ? last.C / prev - 1 : 0;
|
||||
return L.T("chart.caption", c.Count, Fmt.Num(c.Max(x => x.H)), Fmt.Num(c.Min(x => x.L)), Fmt.Num(last.C), (chg * 100).ToString("+0.0;-0.0;0.0", CultureInfo.InvariantCulture));
|
||||
return L.T("chart.caption", asset, c.Count, Fmt.Num(c.Max(x => x.H)), Fmt.Num(c.Min(x => x.L)), Fmt.Num(last.C), (chg * 100).ToString("+0.0;-0.0;0.0", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
private static void Set(Color32[] px, int x, int y, Color32 c) { if (x >= 0 && x < W && y >= 0 && y < H) px[y * W + x] = c; }
|
||||
@@ -104,12 +148,28 @@ namespace Decentralized
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>把图放进界面:说明一行、图、图例一行。</summary>
|
||||
public static void Add(CustomUIBuilder b, string key, IList<Candle> all, double threshold = 0)
|
||||
/// <summary>把一个币的图放进界面:说明一行、图、图例一行。</summary>
|
||||
public static void Add(CustomUIBuilder b, string key, string asset, double threshold = 0)
|
||||
{
|
||||
b.AddLabel(Caption(all));
|
||||
var all = Session.Data.Candles.Of(asset);
|
||||
b.AddLabel(Caption(asset, all));
|
||||
b.AddImage(Render(key, all, threshold), W * Scale, H * Scale);
|
||||
b.AddLabel(L.T(threshold > 0 ? "chart.legend.thr" : "chart.legend"));
|
||||
}
|
||||
|
||||
/// <summary>带币种切换的图:上面一排币种按钮(选中的加方括号),切换后重画。</summary>
|
||||
public static void AddPicker(CustomUIBuilder b, string key, string fallback, Action refresh)
|
||||
{
|
||||
var list = ChartAssets.Where(a => a != Assets.Ponero || Unlock.BlackUnlocked).ToList();
|
||||
if (!Picked.TryGetValue(key, out var cur) || !list.Contains(cur)) cur = fallback;
|
||||
b.BeginRow(4f);
|
||||
foreach (var a in list)
|
||||
{
|
||||
string asset = a;
|
||||
b.AddButton(asset == cur ? "[" + asset + "]" : asset, () => { Picked[key] = asset; refresh(); });
|
||||
}
|
||||
b.End();
|
||||
Add(b, key, cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user