223 lines
4.2 KiB
Go
223 lines
4.2 KiB
Go
|
package main
|
||
|
|
||
|
import "encoding/csv"
|
||
|
import "fmt"
|
||
|
import "io"
|
||
|
import "log"
|
||
|
import "os"
|
||
|
import "strconv"
|
||
|
|
||
|
var COLUMNS []string
|
||
|
|
||
|
type FinancialData struct {
|
||
|
Date string
|
||
|
Open, High, Low, Close, Adj_Close, Volume float64
|
||
|
}
|
||
|
|
||
|
func printHTML(data []FinancialData, out io.Writer) {
|
||
|
io.WriteString(out, `<!DOCTYPE html>
|
||
|
<html>
|
||
|
<head><title>Financial</title></head>
|
||
|
<body>
|
||
|
<table border="1">
|
||
|
<tr>
|
||
|
`)
|
||
|
for _, col := range COLUMNS {
|
||
|
io.WriteString(out, " <th>")
|
||
|
io.WriteString(out, col)
|
||
|
io.WriteString(out, "</th>\n")
|
||
|
}
|
||
|
io.WriteString(out, " </tr>\n")
|
||
|
|
||
|
// data
|
||
|
for _, row := range data {
|
||
|
row.printHTMLRow(out)
|
||
|
}
|
||
|
|
||
|
io.WriteString(out, ` </table>
|
||
|
<div id="container"></div>
|
||
|
<style>
|
||
|
#container {
|
||
|
min-width: 310px;
|
||
|
max-width: 800px;
|
||
|
height: 400px;
|
||
|
margin: 0 auto
|
||
|
}
|
||
|
</style>
|
||
|
<script
|
||
|
src="https://code.jquery.com/jquery-3.2.1.min.js"
|
||
|
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
|
||
|
crossorigin="anonymous"></script>
|
||
|
<script src="https://code.highcharts.com/highcharts.src.js"></script>
|
||
|
<script>
|
||
|
Highcharts.chart('container', {
|
||
|
title: {
|
||
|
text: 'Financial data'
|
||
|
},
|
||
|
|
||
|
subtitle: {
|
||
|
text: 'Source: yahoo.com'
|
||
|
},
|
||
|
|
||
|
yAxis: {
|
||
|
title: {
|
||
|
text: 'Value'
|
||
|
}
|
||
|
},
|
||
|
legend: {
|
||
|
layout: 'vertical',
|
||
|
align: 'right',
|
||
|
verticalAlign: 'middle'
|
||
|
},
|
||
|
|
||
|
series: [{
|
||
|
name: 'Open',
|
||
|
data: [`+getDataOpen(data)+`]
|
||
|
}, {
|
||
|
name: 'Close',
|
||
|
data: [`+getDataClose(data)+`]
|
||
|
}]
|
||
|
});
|
||
|
</script>
|
||
|
</body>
|
||
|
</html>`)
|
||
|
}
|
||
|
|
||
|
func getDataOpen(data []FinancialData) string {
|
||
|
str := ""
|
||
|
for i, v := range data {
|
||
|
str += fmt.Sprintf("%.4f", v.Open)
|
||
|
if i < len(data)-1 {
|
||
|
str += ","
|
||
|
}
|
||
|
}
|
||
|
return str
|
||
|
}
|
||
|
func getDataClose(data []FinancialData) string {
|
||
|
str := ""
|
||
|
for i, v := range data {
|
||
|
str += fmt.Sprintf("%.4f", v.Close)
|
||
|
if i < len(data)-1 {
|
||
|
str += ","
|
||
|
}
|
||
|
}
|
||
|
return str
|
||
|
}
|
||
|
|
||
|
func (data FinancialData) printHTMLRow(out io.Writer) {
|
||
|
io.WriteString(out, " <tr>")
|
||
|
printCellString(data.Date, out)
|
||
|
printCellFloat(data.Open, out)
|
||
|
printCellFloat(data.High, out)
|
||
|
printCellFloat(data.Low, out)
|
||
|
printCellFloat(data.Close, out)
|
||
|
printCellFloat(data.Adj_Close, out)
|
||
|
printCellFloatFmt(data.Volume, "%f", out)
|
||
|
io.WriteString(out, "</tr>\n")
|
||
|
}
|
||
|
|
||
|
func printCellString(val string, out io.Writer) {
|
||
|
io.WriteString(out, "<td>"+val+"</td>")
|
||
|
}
|
||
|
|
||
|
func printCellFloatFmt(val float64, format string, out io.Writer) {
|
||
|
io.WriteString(out, fmt.Sprintf("<td>"+format+"</td>", val))
|
||
|
}
|
||
|
func printCellFloat(val float64, out io.Writer) {
|
||
|
printCellFloatFmt(val, "%.2f", out)
|
||
|
}
|
||
|
|
||
|
func printFooter() {
|
||
|
fmt.Println(` </table>
|
||
|
</body>
|
||
|
</html> `)
|
||
|
}
|
||
|
|
||
|
func readCsvLine(line []string) *FinancialData {
|
||
|
data := new(FinancialData)
|
||
|
for i, col := range line {
|
||
|
switch COLUMNS[i] {
|
||
|
case "Date":
|
||
|
data.Date = col
|
||
|
case "Open":
|
||
|
data.Open = toFloat(col)
|
||
|
case "High":
|
||
|
data.High = toFloat(col)
|
||
|
case "Low":
|
||
|
data.Low = toFloat(col)
|
||
|
case "Close":
|
||
|
data.Close = toFloat(col)
|
||
|
case "Adj Close":
|
||
|
data.Adj_Close = toFloat(col)
|
||
|
case "Volume":
|
||
|
data.Volume = toFloat(col)
|
||
|
}
|
||
|
}
|
||
|
return data
|
||
|
}
|
||
|
|
||
|
func toFloat(v string) float64 {
|
||
|
f, err := strconv.ParseFloat(v, 64)
|
||
|
if err != nil {
|
||
|
log.Fatalln(err)
|
||
|
}
|
||
|
return f
|
||
|
}
|
||
|
|
||
|
func readCsvHeader(line []string) {
|
||
|
for _, col := range line {
|
||
|
COLUMNS = append(COLUMNS, col)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func readCsv(in io.Reader) []FinancialData {
|
||
|
data := []FinancialData{}
|
||
|
csvReader := csv.NewReader(in)
|
||
|
for {
|
||
|
line, err := csvReader.Read()
|
||
|
if err == io.EOF {
|
||
|
break
|
||
|
} else if err != nil {
|
||
|
log.Fatal(err)
|
||
|
}
|
||
|
if len(COLUMNS) == 0 {
|
||
|
readCsvHeader(line)
|
||
|
} else {
|
||
|
data = append(data, *readCsvLine(line))
|
||
|
}
|
||
|
}
|
||
|
return data
|
||
|
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
if len(os.Args) < 2 {
|
||
|
log.Fatalln("Usage go-financial <file>")
|
||
|
}
|
||
|
|
||
|
// read input
|
||
|
csv, err := os.Open(os.Args[1])
|
||
|
if err != nil {
|
||
|
log.Fatalln("Error reading file", err)
|
||
|
}
|
||
|
defer csv.Close()
|
||
|
|
||
|
// output
|
||
|
var out io.Writer
|
||
|
if len(os.Args) > 2 {
|
||
|
fout, err := os.Create(os.Args[2])
|
||
|
if err != nil {
|
||
|
log.Fatalln("Error creating output file", err)
|
||
|
}
|
||
|
defer fout.Close()
|
||
|
out = fout
|
||
|
} else {
|
||
|
out = os.Stdout
|
||
|
}
|
||
|
|
||
|
// parse data
|
||
|
data := readCsv(csv)
|
||
|
|
||
|
printHTML(data, out)
|
||
|
}
|