I’ve been looking into a possibility to get familiar with GO language for some time, but recently I had a reason: a weekend project where I could test a theory about stock trading.
I’ve entered a discussion with my colleagues about why it does not work to buy stock right before the ex-div date to receive the dividends and then to sell them right away. In my experience, it has a simple explanation: after the ex-dividend date, the price drops accordingly to the amount of dividends paid. But let’s have a deeper look and see if it’s really like that.
For that, I will need to get a list of stock symbols, then get the list of ex-div dates for some years and compare historical prices before and after dividend payment to check if it would have been financially viable to buy stock right before ex-div date and sell them afterwards (in my experiment – 5 days prior to ex-div to buy and 5 days prior to ex-div to sell).
Let’s start with stockparser package, the core concepts, file Stockparser.go. Set up imports and structure to store stock record results after simulated trading:
package stockparser // set up imports import ( "fmt" "net/http" "encoding/csv" "io" "strconv" "strings" "time" "math" "os" ) // string formats to access yahoo finance api and get csv-formatted data // list of historical dividend payments and their dates: const DIVIDEND_LIST_FORMAT = "http://real-chart.finance.yahoo.com/table.csv?s=%s&a=00&b=1&c=%d&d=12&e=31&f=%d&g=v&ignore=.csv"; // list of prices on days prior/after the ex-div dates: const CLOSE_PRICE_LIST_FORMAT = "http://real-chart.finance.yahoo.com/table.csv?s=%s&a=%d&b=%d&c=%d&d=%d&e=%d&f=%d&g=d&ignore=.csv"; // struct to store Stock Parsing result for sorting/comparison purposes later type StockRecord struct { Symbol string Earnings float64 }
To get a stock record data, we will need the following information. First, fetch the ex-dividend dates and the amount paid, then go through each date and get the prices before and after, then calculate the profits. The dividend list fetching is going to be reviewed first.
Function getYearsDividend
takes the http link and fetches the csv data from it. It is pretty straightforward: try to get the data, if something is returned go line-by-line:
func getYearsDividend(yearLink string) [][]string { var result [][]string response, err := http.Get(yearLink); if err != nil { fmt.Println(err) } else { defer response.Body.Close() csvr:= csv.NewReader(response.Body) rec, err := csvr.Read() var first bool = true for err == nil { if (first) { // ignore first line as it contains the names of the columns // but not the data first = false; } else { result = append(result, rec) } rec, err = csvr.Read() } if err != io.EOF { fmt.Println(err) } } return result }
ReadDividendData
(Important: capital leter first – it is going to be exported for use in other files) function gets the list of dividend payments for symbol
in the years between(including) yearStart
and yearEnd
. It takes additional parameters: baseMoney
is how much money (in USD) is going to be invested in stock purchase, daysBuyBefore
and daysSellAfter
indicate how many days do we wait before buying and selling (and takes the historical prices for these days accordingly – if ex-div is fifth of February 2015, and daysBuyBefore equals 2, the system is going to try to fetch historical closing price of February third) and finally, commission
value shows how much money to be substracted from baseMoney as payment for the deal (my bank takes a fixed amount).
const DATE_INDEX int = 0 const DIVIDEND_INDEX int = 1 func ReadDividendData(symbol string, yearStart int, yearEnd int, baseMoney int, daysBuyBefore int, daysSellAfter int, commission float64) StockRecord { // this will store the sum of all possible trades for that symbol // that were made during the given time frame var sum float64 = 0; var link_str string // form the link - insert the value link_str = fmt.Sprintf(DIVIDEND_LIST_FORMAT, symbol, yearStart, yearEnd) // get the array of dividends - dates/amounts paid // yahoo finance csv format requested by format link // just includes the date and dividend in one string var date_dividend [][]string = getYearsDividend(link_str) // create a log file to store extended data f, err:= os.Create("res/" + symbol + ".txt") if err == nil { defer f.Close() // go through every date-dividend record and calculate the total sum // from every one of them for line := range date_dividend { var divPrice float64 = 0 // convert the dividend price from string to float divPrice, err := strconv.ParseFloat(date_dividend[line][DIVIDEND_INDEX], 64) if (err == nil) { if err == nil { sum += calculateDividendSum(symbol, date_dividend[line][DATE_INDEX], divPrice, daysBuyBefore, daysSellAfter, baseMoney, commission, f) } } else { f.WriteString(err.Error()) } } } f.WriteString("\n=========\n") f.WriteString("Final Sum: " + strconv.FormatFloat(sum, 'f', 3, 64)) return StockRecord{symbol, sum}; }
The next intermediate function is calculateDividendSum
which fetches historical prices:
const DATE_YEAR_INDEX = 0; const DATE_MONTH_INDEX = 1; const DATE_DAY_INDEX = 2; const HISTORICAL_HIGH_INDEX = 2; func calculateDividendSum(symbol string, dateUnprocessed string, dividendBonus float64, daysBuyBefore, daysSellAfter, moneyForPurchase int, commission float64, f *os.File) (result float64) { result = 0 // date is passed as yyyy-mm-dd // first explode it into logical chunks, removing "-" and placing // year, month and day into separate indexes of a string array var dateSeparated []string = strings.Split(dateUnprocessed, "-") // then convert them to numbers var year int64 year, _ = strconv.ParseInt(dateSeparated[DATE_YEAR_INDEX], 10, 64) var month int64 month, _ = strconv.ParseInt(dateSeparated[DATE_MONTH_INDEX], 10, 64) var day int64 day, _ = strconv.ParseInt(dateSeparated[DATE_DAY_INDEX], 10, 64) // calculate the dividend date var divDate time.Time = time.Date(int(year), time.Month(month), int(day), 0, 0, 0, 0, time.UTC) // then substract given amount of days to get the date when we purchase the stock var buyDate time.Time = divDate.AddDate(0, 0, -daysBuyBefore) // then add given amount of days to get the date when we sell the stock var sellDate time.Time = divDate.AddDate(0, 0, daysSellAfter) var datalink string openYear, openMonth, openDay := buyDate.Date() closeYear, closeMonth, closeDay := sellDate.Date() // form the proper link to request a historical dates between buy and sell date datalink = fmt.Sprintf(CLOSE_PRICE_LIST_FORMAT, symbol, int(openMonth) - 1, openDay, openYear, int(closeMonth - 1), closeDay, closeYear) response, err := http.Get(datalink); fmt.Println("========"); fmt.Println("Getting Data from: " + datalink + " , dividend date: " + divDate.String()); if err != nil { fmt.Println(err) } else { defer response.Body.Close() // the csv format: // Date,Open,High,Low,Close,Volume,Adj Close csvr:= csv.NewReader(response.Body) rec, err := csvr.ReadAll() if (err == nil) { f.WriteString("===========\n"); f.WriteString("Getting Data from: " + datalink + " , dividend date: " + divDate.String()) f.WriteString("\n") var buyCost float64 = 0 var sellCost float64 = 0 // buy price is at the bottom of the list (earliest date) // respectuflly, sell price is on top (we sell for that price) buyCost, errBuy := strconv.ParseFloat(rec[len(rec) - 1][HISTORICAL_HIGH_INDEX], 64); sellCost, errSell := strconv.ParseFloat(rec[1][HISTORICAL_HIGH_INDEX], 64) if errBuy == nil && errSell == nil && err == nil { result = summarizeData(buyCost, sellCost, dividendBonus, float64(moneyForPurchase), commission, f) } } } return result }
The final function is summarizeData
which simply adds values together:
func summarizeData(buyPrice float64, sellPrice float64, dividendBonus float64, budget float64, commission float64, f *os.File) float64 { // how many stock can we buy with money we have? // substract commission from our budget and divide by price of one stock var buyAmount int = int(math.Floor((float64(budget) - commission) / buyPrice)); // how much money we actually spent? // they can be leftover money if we had budget if we have // for example 40 USD in remaining budget and one stock price is 30 USD var buyMoneySpent float64 = float64(buyAmount) * buyPrice + commission; // how much money do we get from selling by sellPrice // after we get the dividend? substract commission // because it is charged on sell operations too var sellMoneyGained float64 = float64(buyAmount) * sellPrice - commission; // how much money did we actually get from dividend? // multiply stock amount with dividend payout for one stock var dividendMoneyGained float64 = dividendBonus * float64(buyAmount); // calculate final balance var balance float64 = dividendMoneyGained + sellMoneyGained - buyMoneySpent; // log into file // Proper way would be to make one more function // But since this is a weekend project // I try to forgive myself var logData = fmt.Sprintf(`BUYPRICE: %f, MONEY SPENT TO BUY: %f, AMOUNT BOUGHT: %d, SELLPRICE: %f, DIVIDEND PRICE: %f, AMOUNT GAINED FROM DIVIDENDS: %f, AMOUNT GAINED FROM SELL: %f, TOTAL PROFIT IN THE END OF THE TRANSACTION: %f`, buyPrice, buyMoneySpent, buyAmount, sellPrice, dividendBonus, dividendMoneyGained, sellMoneyGained, balance) f.WriteString(logData); f.WriteString(""); return balance }
The main File stock.go
is simple: read csv file with list of symbols on my computer (it should be customized according to your list):
package main import ( "fmt" "stockparser" "os" "time" "encoding/csv" "io" "sort" "strconv" ) // you need those functions in order to sort the stock records: type byEarnings []stockparser.StockRecord func (arr byEarnings) Len() int { return len(arr) } func (arr byEarnings) Swap(i, j int) { arr[i], arr[j] = arr[j], arr[i] } func (arr byEarnings) Less(i, j int) bool { return arr[i].Earnings < arr[j].Earnings } func main() { csvfile, err:= os.Open("companylist.csv") if err != nil { fmt.Println(err) return } defer csvfile.Close() var records byEarnings reader := csv.NewReader(csvfile) rec, err := reader.Read() var first bool = true var counter int = 0 fmt.Println("Reading data...") for err == nil { if (first) { first = false; } else { fmt.Println("Reading line " + strconv.Itoa(counter)) records = append(records, stockparser.ReadDividendData(rec[0], 2012, 2015, 4000, 5, 5, 18)); // set the delay in order not to flood the yahoo server with http request time.Sleep(5000 * time.Millisecond) } rec, err = reader.Read() counter++ } if err != io.EOF { print(err) } fmt.Println("Sorting Data") sort.Sort(records) fmt.Println("Done") for _,element := range records { fmt.Println("S: " + element.Symbol + " Earnings: " + strconv.FormatFloat(element.Earnings, 'f', 3, 64)) } }
To sum things up: in the end we have a GO program that fetches the data from yahoo finance and summarizes it in order to evaluate stock purchases right before ex-div payouts. I might make a separate article if I find results interesting enough (and if I got the large enough list of stock symbols to evaluate). You can get the full source code at my github:
https://github.com/vladimirslav/go-dividend-checker