Candlestick chart: price axis, last-price tag, volume max and day labels in a pixel font
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e1390a6921
commit
96e91a4884
2 files changed
+70
-5
No files matched your search
@@ -4,6 +4,7 @@
|
||||
|
||||
### 行情图
|
||||
|
||||
- K 线图标数字:图里用一套 3×5 像素数字字体(放大 2 倍后约 6×10),和像素风格一致。右侧刻度栏(宽 22 像素)标三条网格线的价格;最新收盘价是黄底价签,网格刻度离它太近时让开;预测阈值线的价格用蓝色标在刻度栏;成交量区右上标这一屏最大的成交额(≥ 1 万写成 "36k");底部每逢天数是 5 的倍数的蜡烛下面标天数并画一个刻度点。价格位数随大小变:≥ 100 不带小数,≥ 10 一位,≥ 1 两位,小于 1 三位(".995")。图宽 206 像素(显示 412)。游戏内验证:PSC 刻度 113 / 81.3、价签 91.8、量柱 36k、日期 −10 … 15(测试存档在第 16 天,调试入口往前补了 30 天,所以出现负数;真实历史从第 1 天开始)。
|
||||
- 所有可交易资产都有日 K 和成交量:PSC、SPARK、CRT、CRA、CRC、Ponero。开门时每个币开一根(开盘接昨天收盘),夜间结算在价格更新前收盘;稳定币和 SPARK 的价格在夜里变,变动算进第二天那根。成交量:市场的量 = 主池深度 × 各币比例(PSC 12%、CRC 10%、CRT 6%、CRA 4%、SPARK 3%、Ponero 1%)×(1 + 8 × 当天的涨跌与振幅)× 0.6–1.4 的随机,波动越大成交越多;你的量包括 Poinbase / DEX 兑换(两边的币都算)、SPARK 铸销(SPARK 和 CRA)、Ponero 兑换、永续开平仓(按名义价值,计入 PSC 或 SPARK)。
|
||||
- 图上方加一排币种按钮(Poinbase 现货页、Web3 交易页各自记住选的币,Ponero 要黑市解锁后才出现);永续页显示当前选中的品种;预测页仍是 PSC。均线改为先画,蜡烛盖在上面,贴着锚的稳定币不会被均线遮住。
|
||||
- 游戏内验证:Web3 交易页切换 PSC / SPARK / CRT 都正常,SPARK 有完整的蜡烛和放量,CRT 在锚上是一条线(30 天里没脱锚)。没有在游戏里验证:脱锚时稳定币的图、Ponero、玩家各类成交计入量柱(只看过 PSC 现货的青色柱)。
|
||||
|
||||
@@ -56,14 +56,52 @@ namespace Decentralized
|
||||
|
||||
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 int Days = 30, Step = 6, Body = 5, PriceH = 70, Gap = 3, VolH = 16, Pad = 2, AxisW = 22, DateH = 7;
|
||||
public const int W = Days * Step + Pad * 2 + AxisW, H = PriceH + Gap + VolH + Pad * 2 + DateH;
|
||||
public const float Scale = 2f;
|
||||
|
||||
private static readonly Color32 Bg = new Color32(30, 31, 34, 255), Grid = new Color32(48, 50, 54, 255);
|
||||
private static readonly Color32 Up = new Color32(108, 190, 112, 255), Down = new Color32(214, 94, 88, 255);
|
||||
private static readonly Color32 Ma = new Color32(226, 196, 92, 255), Thr = new Color32(150, 170, 230, 255);
|
||||
private static readonly Color32 VolNpc = new Color32(92, 96, 104, 255), VolMe = new Color32(96, 200, 210, 255);
|
||||
private static readonly Color32 Txt = new Color32(150, 154, 160, 255), TagBg = new Color32(226, 196, 92, 255), TagTxt = new Color32(30, 31, 34, 255);
|
||||
|
||||
/// <summary>3×5 像素数字字体(每行 3 位,自上而下)。</summary>
|
||||
private static readonly Dictionary<char, string[]> Glyph = new Dictionary<char, string[]>
|
||||
{
|
||||
['0'] = new[] { "111", "101", "101", "101", "111" }, ['1'] = new[] { "010", "110", "010", "010", "111" },
|
||||
['2'] = new[] { "111", "001", "111", "100", "111" }, ['3'] = new[] { "111", "001", "011", "001", "111" },
|
||||
['4'] = new[] { "101", "101", "111", "001", "001" }, ['5'] = new[] { "111", "100", "111", "001", "111" },
|
||||
['6'] = new[] { "111", "100", "111", "101", "111" }, ['7'] = new[] { "111", "001", "010", "010", "010" },
|
||||
['8'] = new[] { "111", "101", "111", "101", "111" }, ['9'] = new[] { "111", "101", "111", "001", "111" },
|
||||
['.'] = new[] { "0", "0", "0", "0", "1" }, ['-'] = new[] { "000", "000", "111", "000", "000" },
|
||||
['k'] = new[] { "100", "101", "110", "101", "101" },
|
||||
};
|
||||
|
||||
private static int TextW(string s) => s.Sum(ch => Glyph.TryGetValue(ch, out var g) ? g[0].Length + 1 : 0) - 1;
|
||||
|
||||
/// <summary>在 (x, y) 处写字,y 是字的底边。</summary>
|
||||
private static void Text(Color32[] px, int x, int y, string s, Color32 c)
|
||||
{
|
||||
foreach (char ch in s)
|
||||
{
|
||||
if (!Glyph.TryGetValue(ch, out var g)) continue;
|
||||
for (int r = 0; r < 5; r++)
|
||||
for (int k = 0; k < g[r].Length; k++)
|
||||
if (g[r][k] == '1') Set(px, x + k, y + 4 - r, c);
|
||||
x += g[0].Length + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>刻度上的价格:大于 100 不带小数,越小小数越多,最多 5 个字符左右。</summary>
|
||||
private static string Num(double p)
|
||||
{
|
||||
if (p >= 10000) return (p / 1000).ToString("0", CultureInfo.InvariantCulture) + "k";
|
||||
if (p >= 100) return p.ToString("0", CultureInfo.InvariantCulture);
|
||||
if (p >= 10) return p.ToString("0.0", CultureInfo.InvariantCulture);
|
||||
if (p >= 1) return p.ToString("0.00", CultureInfo.InvariantCulture);
|
||||
return p.ToString("0.000", CultureInfo.InvariantCulture).Substring(1);
|
||||
}
|
||||
|
||||
private sealed class Slot { public Texture2D Tex; public Sprite Sprite; public Color32[] Px; }
|
||||
private static readonly Dictionary<string, Slot> Slots = new Dictionary<string, Slot>();
|
||||
@@ -79,9 +117,10 @@ namespace Decentralized
|
||||
var px = s.Px;
|
||||
for (int i = 0; i < px.Length; i++) px[i] = Bg;
|
||||
var c = all.Skip(Math.Max(0, all.Count - Days)).ToList();
|
||||
int volY0 = Pad, priceY0 = Pad + VolH + Gap;
|
||||
int volY0 = Pad + DateH, priceY0 = volY0 + VolH + Gap, plotR = Pad + Days * Step, axisX = plotR + 2;
|
||||
// 网格:价格区四等分
|
||||
for (int g = 1; g < 4; g++) HLine(px, Pad, W - Pad - 1, priceY0 + PriceH * g / 4, Grid);
|
||||
for (int g = 1; g < 4; g++) HLine(px, Pad, plotR - 1, priceY0 + PriceH * g / 4, Grid);
|
||||
VLine(px, plotR, volY0, priceY0 + PriceH - 1, Grid);
|
||||
if (c.Count > 0)
|
||||
{
|
||||
var ma = CandleBook.Ma(all, 7).Skip(Math.Max(0, all.Count - Days)).ToList();
|
||||
@@ -114,7 +153,32 @@ namespace Decentralized
|
||||
if (hm > 0) VLine(px, xx, volY0 + hn, volY0 + hn + hm - 1, VolMe);
|
||||
}
|
||||
}
|
||||
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); }
|
||||
if (threshold > 0) { int ty = y(threshold); for (int xx = Pad; xx < plotR; xx++) if ((xx / 3) % 2 == 0) Set(px, xx, ty, Thr); }
|
||||
// 右侧刻度:三条网格线的价格;最新收盘价标成黄底的价签,和网格刻度挤在一起时让开
|
||||
int lastY = y(c[c.Count - 1].C);
|
||||
for (int g = 1; g < 4; g++)
|
||||
{
|
||||
int gy = priceY0 + PriceH * g / 4;
|
||||
if (Math.Abs(gy - lastY) < 7) continue;
|
||||
double gp = lo + (gy - priceY0) / (double)(PriceH - 1) * span;
|
||||
Text(px, axisX, gy - 2, Num(gp), Txt);
|
||||
}
|
||||
if (threshold > 0) { int ty = y(threshold); if (Math.Abs(ty - lastY) >= 7) Text(px, axisX, ty - 2, Num(threshold), Thr); }
|
||||
string tag = Num(c[c.Count - 1].C);
|
||||
int ty0 = Math.Max(priceY0, Math.Min(priceY0 + PriceH - 7, lastY - 3));
|
||||
for (int yy = ty0; yy < ty0 + 7; yy++) HLine(px, axisX - 1, Math.Min(W - 1, axisX + TextW(tag)), yy, TagBg);
|
||||
Text(px, axisX, ty0 + 1, tag, TagTxt);
|
||||
// 底部日期:天数是 5 的倍数的蜡烛下面写天数
|
||||
for (int i = 0; i < c.Count; i++)
|
||||
{
|
||||
if (c[i].Day % 5 != 0) continue;
|
||||
string ds = c[i].Day.ToString(CultureInfo.InvariantCulture);
|
||||
int cx = x0 + i * Step + Body / 2;
|
||||
VLine(px, cx, volY0 - 1, volY0 - 1, Txt);
|
||||
Text(px, Math.Max(0, Math.Min(plotR - TextW(ds), cx - TextW(ds) / 2)), Pad - 1, ds, Txt);
|
||||
}
|
||||
// 成交量区右上:这一屏最大的量
|
||||
Text(px, axisX, volY0 + VolH - 5, Num(vmax), VolNpc);
|
||||
}
|
||||
s.Tex.SetPixels32(px);
|
||||
s.Tex.Apply(false);
|
||||
|
||||
Reference in new issue
Block a user