2017-07-08 07:43:10 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import "encoding/csv"
|
|
|
|
import "fmt"
|
|
|
|
import "io"
|
|
|
|
import "log"
|
|
|
|
import "os"
|
|
|
|
import "strconv"
|
2017-07-08 08:16:32 +00:00
|
|
|
import "strings"
|
2017-07-08 07:43:10 +00:00
|
|
|
|
|
|
|
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'
|
|
|
|
},
|
|
|
|
|
2017-07-08 08:16:32 +00:00
|
|
|
xAxis: {
|
|
|
|
type: 'datetime'
|
|
|
|
},
|
2017-07-08 07:43:10 +00:00
|
|
|
yAxis: {
|
|
|
|
title: {
|
|
|
|
text: 'Value'
|
|
|
|
}
|
|
|
|
},
|
2017-07-08 08:16:32 +00:00
|
|
|
plotOptions: {
|
|
|
|
area: {
|
|
|
|
fillColor: {
|
|
|
|
linearGradient: {
|
|
|
|
x1: 0,
|
|
|
|
y1: 0,
|
|
|
|
x2: 0,
|
|
|
|
y2: 1
|
|
|
|
},
|
|
|
|
stops: [
|
|
|
|
[0, Highcharts.getOptions().colors[0]],
|
|
|
|
[1, Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get('rgba')]
|
|
|
|
]
|
|
|
|
},
|
|
|
|
marker: {
|
|
|
|
radius: 2
|
|
|
|
},
|
|
|
|
lineWidth: 1,
|
|
|
|
states: {
|
|
|
|
hover: {
|
|
|
|
lineWidth: 1
|
|
|
|
}
|
|
|
|
},
|
|
|
|
threshold: null
|
|
|
|
}
|
|
|
|
},
|
2017-07-08 07:43:10 +00:00
|
|
|
legend: {
|
|
|
|
layout: 'vertical',
|
|
|
|
align: 'right',
|
|
|
|
verticalAlign: 'middle'
|
|
|
|
},
|
|
|
|
|
2017-07-08 08:16:32 +00:00
|
|
|
series: [{
|
|
|
|
type: 'area',
|
|
|
|
name: 'Open',
|
|
|
|
data: `+getChartDataOpen(data)+`
|
|
|
|
},{
|
|
|
|
type: 'area',
|
|
|
|
name: 'Close',
|
|
|
|
data: `+getChartDataClose(data)+`
|
|
|
|
}]
|
2017-07-08 07:43:10 +00:00
|
|
|
});
|
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>`)
|
|
|
|
}
|
|
|
|
|
2017-07-08 08:16:32 +00:00
|
|
|
func toJSDate(date string) string {
|
|
|
|
str := "Date.UTC("
|
|
|
|
parts := strings.Split(date, "-")
|
|
|
|
for i, part := range parts {
|
|
|
|
str += strings.TrimLeft(part, "0")
|
|
|
|
if i < len(parts)-1 {
|
|
|
|
str += ","
|
|
|
|
}
|
|
|
|
}
|
|
|
|
str += ")"
|
|
|
|
return str
|
|
|
|
}
|
|
|
|
|
|
|
|
func getChartDataOpen(data []FinancialData) string {
|
|
|
|
str := "["
|
2017-07-08 07:43:10 +00:00
|
|
|
for i, v := range data {
|
2017-07-08 08:16:32 +00:00
|
|
|
str += "["
|
|
|
|
str += toJSDate(v.Date)
|
|
|
|
str += fmt.Sprintf(", %.4f", v.Open)
|
|
|
|
str += "]"
|
2017-07-08 07:43:10 +00:00
|
|
|
if i < len(data)-1 {
|
|
|
|
str += ","
|
|
|
|
}
|
|
|
|
}
|
2017-07-08 08:16:32 +00:00
|
|
|
str += "]"
|
2017-07-08 07:43:10 +00:00
|
|
|
return str
|
|
|
|
}
|
2017-07-08 08:16:32 +00:00
|
|
|
func getChartDataClose(data []FinancialData) string {
|
|
|
|
str := "["
|
2017-07-08 07:43:10 +00:00
|
|
|
for i, v := range data {
|
2017-07-08 08:16:32 +00:00
|
|
|
str += "["
|
|
|
|
str += toJSDate(v.Date)
|
|
|
|
str += fmt.Sprintf(", %.4f", v.Close)
|
|
|
|
str += "]"
|
2017-07-08 07:43:10 +00:00
|
|
|
if i < len(data)-1 {
|
|
|
|
str += ","
|
|
|
|
}
|
|
|
|
}
|
2017-07-08 08:16:32 +00:00
|
|
|
str += "]"
|
2017-07-08 07:43:10 +00:00
|
|
|
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)
|
|
|
|
}
|