aboutsummaryrefslogtreecommitdiff
path: root/internal/cryptomarket/chart.go
blob: c1fef2942a39a2fecea9b7ece30c0b92292fd9ea (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package cryptomarket

import (
	"fmt"
	"math"
	"strings"
	"time"
)

// Chart uses one column per completed daily candle, auto-scales for BTC and
// tiny tokens alike, and delegates color to the player's configured palette.
func Chart(candles []Candle, width int, unicode bool, paint func(string, bool) string) []string {
	width = max(12, min(80, width))
	label := 12
	count := width - label - 2
	if count < 2 || len(candles) < 2 {
		return []string{"Not enough completed daily candles."}
	}
	if len(candles) > count {
		candles = candles[len(candles)-count:]
	}
	type bar struct{ o, h, l, c float64 }
	bars := []bar{}
	low, high := math.Inf(1), math.Inf(-1)
	for _, c := range candles {
		if c.Validate() != nil {
			return []string{"Invalid candle history."}
		}
		o, _ := rat(c.Open).Float64()
		h, _ := rat(c.High).Float64()
		l, _ := rat(c.Low).Float64()
		v, _ := rat(c.Close).Float64()
		bars = append(bars, bar{o, h, l, v})
		low = math.Min(low, l)
		high = math.Max(high, h)
	}
	if high == low {
		padding := math.Max(high*0.01, 1e-18)
		low -= padding
		high += padding
	}
	const height = 10
	row := func(p float64) int { return max(0, min(height-1, int(math.Round((high-p)/(high-low)*(height-1))))) }
	grid := make([][]string, height)
	for y := range grid {
		grid[y] = make([]string, len(bars))
		for x := range grid[y] {
			grid[y][x] = " "
		}
	}
	wick, body, doji, axis := "|", "#", "-", "|"
	if unicode {
		wick, body, doji, axis = "│", "█", "━", "│"
	}
	for x, b := range bars {
		top, bottom := min(row(b.o), row(b.c)), max(row(b.o), row(b.c))
		for y := row(b.h); y <= row(b.l); y++ {
			ch := wick
			if y >= top && y <= bottom {
				ch = body
				if top == bottom {
					ch = doji
				}
			}
			if paint != nil {
				ch = paint(ch, b.c >= b.o)
			}
			grid[y][x] = ch
		}
	}
	out := []string{"USD / credits; daily candles (UTC). Up/down colored; body=open-close, wick=range."}
	for y := range grid {
		v := high - float64(y)*(high-low)/(height-1)
		out = append(out, fmt.Sprintf("%11.5g %s%s", v, axis, strings.Join(grid[y], "")))
	}
	out = append(out, fmt.Sprintf("%s to %s", time.Unix(candles[0].Time, 0).UTC().Format("2006-01-02"), time.Unix(candles[len(candles)-1].Time, 0).UTC().Format("2006-01-02")))
	return out
}