An amateur research of stock trading + Learning GO Lang

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

Leave a Reply

Your email address will not be published. Required fields are marked *