diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3227ebf --- /dev/null +++ b/.gitignore @@ -0,0 +1,284 @@ + +# Created by https://www.gitignore.io/api/go,vim,linux,macos,emacs,nanoc,sublimetext,jetbrains+all,visualstudiocode +# Edit at https://www.gitignore.io/?templates=go,vim,linux,macos,emacs,nanoc,sublimetext,jetbrains+all,visualstudiocode + +### Emacs ### +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +# network security +/network-security.data + + +### Go ### +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +### Go Patch ### +/vendor/ +/Godeps/ + +### JetBrains+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### JetBrains+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Linux ### + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Nanoc ### +# For projects using Nanoc (http://nanoc.ws/) + +# Default location for output (needs to match output_dir's value found in nanoc.yaml) +output/ + +# Temporary file directory +tmp/nanoc/ + +# Crash Log +crash.log + +### SublimeText ### +# Cache files for Sublime Text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# Workspace files are user-specific +*.sublime-workspace + +# Project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using Sublime Text +# *.sublime-project + +# SFTP configuration file +sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +Package Control.merged-ca-bundle +Package Control.user-ca-bundle +oscrypto-ca-bundle.crt +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist + +# Auto-generated tag files +tags + +# Persistent undo +[._]*.un~ + +# Coc configuration directory +.vim + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history + +# End of https://www.gitignore.io/api/go,vim,linux,macos,emacs,nanoc,sublimetext,jetbrains+all,visualstudiocode \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d436c62 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +B3 History \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cb726e0 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/Bezunca/B3History + +go 1.14 + +require github.com/Bezunca/ZipInMemory v0.0.5 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..06ecd1d --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/Bezunca/ZipInMemory v0.0.5 h1:Ph9pDqjJjR50X8AENMGfRlaQCTxvrKMH7wnbgVpJ3ZA= +github.com/Bezunca/ZipInMemory v0.0.5/go.mod h1:BLCWq2W2roVK/Z4l3fMz6tkauOzPOHN56Zlpwo9dzxY= diff --git a/pkg/b3/get_history.go b/pkg/b3/get_history.go new file mode 100644 index 0000000..a0b6c26 --- /dev/null +++ b/pkg/b3/get_history.go @@ -0,0 +1,25 @@ +package b3 + +import ( + "github.com/Bezunca/B3History/pkg/http" + "github.com/Bezunca/B3History/pkg/internal/models" + "github.com/Bezunca/B3History/pkg/internal/parser" + "github.com/Bezunca/ZipInMemory/pkg/zip" +) + +func GetHistory(year uint) ([]models.AssetInfo, error){ + responseData, err := http.DownloadB3HistoryZip(year) + if err != nil { + return nil, err + } + encodedContent, err := zip.ExtractInMemory(responseData) + if err != nil { + return nil, err + } + data, err := parser.ParseHistoricDataFromBytes(encodedContent) + if err != nil { + return nil, err + } + + return data, nil +} \ No newline at end of file diff --git a/pkg/b3_history.go b/pkg/b3_history.go new file mode 100644 index 0000000..c1caffe --- /dev/null +++ b/pkg/b3_history.go @@ -0,0 +1 @@ +package pkg diff --git a/pkg/http/download_zip.go b/pkg/http/download_zip.go new file mode 100644 index 0000000..3473e81 --- /dev/null +++ b/pkg/http/download_zip.go @@ -0,0 +1,30 @@ +package http + +import ( + "fmt" + "io/ioutil" + "net/http" +) + +func Download(url string) ([]byte, error){ + response, err := http.Get(url) + if err != nil { + return nil, err + } + + responseData, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + + return responseData, nil +} + +func DownloadB3HistoryZip(year uint) ([]byte, error){ + zip, err := Download(fmt.Sprintf("http://bvmf.bmfbovespa.com.br/InstDados/SerHist/COTAHIST_A%v.ZIP", year)) + if err != nil { + return nil, err + } + + return zip, err +} \ No newline at end of file diff --git a/pkg/internal/models/asset.go b/pkg/internal/models/asset.go new file mode 100644 index 0000000..78ef5bf --- /dev/null +++ b/pkg/internal/models/asset.go @@ -0,0 +1,34 @@ +package models + +import "time" + +type FixedPoint int // Represents the actual value multiplied by 100 + +type AssetInfo struct { + TipReg int + DataCollectionDate time.Time + BDICode int + Ticker string + MarketType int + CompanyName string + SecurityType string + FutureMarketExpiration string // Need to check this against an example that actually has a value + Currency string + PriceOpen FixedPoint + PriceMax FixedPoint + PriceMin FixedPoint + PriceMean FixedPoint + PriceClose FixedPoint + PriceBid FixedPoint + PriceAsk FixedPoint + TotalTrades int + TotalQuantity int + TotalVolume FixedPoint + PreExe FixedPoint // Needs further investigation + IndOpc int // Needs further investigation + ExpirationDate time.Time + FatCot int // Needs further investigation + PtoExe int // Needs further investigation + ISINCode string + DistributionNumber int +} diff --git a/pkg/internal/parser/history_parser.go b/pkg/internal/parser/history_parser.go new file mode 100644 index 0000000..c593f90 --- /dev/null +++ b/pkg/internal/parser/history_parser.go @@ -0,0 +1,141 @@ +package parser + +import ( + "strconv" + "strings" + "time" + + "github.com/Bezunca/B3History/pkg/internal/models" +) + +func parseContentLine(rawLine string) (*models.AssetInfo, error) { + tipReg, err := strconv.ParseInt(rawLine[:2], 10, 64) + if err != nil { + return nil, err + } + date, err := time.Parse("20060102", rawLine[2:2+8]) + if err != nil { + return nil, err + } + bdiCode, err := strconv.ParseInt(rawLine[10:10+2], 10, 64) + if err != nil { + return nil, err + } + marketType, err := strconv.ParseInt(rawLine[24:24+3], 10, 64) + if err != nil { + return nil, err + } + priceOpen, err := strconv.ParseInt(rawLine[56:56+13], 10, 64) + if err != nil { + return nil, err + } + priceMax, err := strconv.ParseInt(rawLine[69:69+13], 10, 64) + if err != nil { + return nil, err + } + priceMin, err := strconv.ParseInt(rawLine[82:82+13], 10, 64) + if err != nil { + return nil, err + } + priceMean, err := strconv.ParseInt(rawLine[95:95+13], 10, 64) + if err != nil { + return nil, err + } + priceClose, err := strconv.ParseInt(rawLine[108:108+13], 10, 64) + if err != nil { + return nil, err + } + priceBid, err := strconv.ParseInt(rawLine[121:121+13], 10, 64) + if err != nil { + return nil, err + } + priceAsk, err := strconv.ParseInt(rawLine[134:134+13], 10, 64) + if err != nil { + return nil, err + } + totalTrades, err := strconv.ParseInt(rawLine[147:147+5], 10, 64) + if err != nil { + return nil, err + } + totalQuantity, err := strconv.ParseInt(rawLine[152:152+18], 10, 64) + if err != nil { + return nil, err + } + totalVolume, err := strconv.ParseInt(rawLine[170:170+18], 10, 64) + if err != nil { + return nil, err + } + preExe, err := strconv.ParseInt(rawLine[188:188+13], 10, 64) + if err != nil { + return nil, err + } + indOpc, err := strconv.ParseInt(rawLine[201:201+1], 10, 64) + if err != nil { + return nil, err + } + expirationDate, err := time.Parse("20060102", rawLine[202:202+8]) + if err != nil { + return nil, err + } + fatCot, err := strconv.ParseInt(rawLine[210:210+7], 10, 64) + if err != nil { + return nil, err + } + ptoExe, err := strconv.ParseInt(rawLine[210:210+7], 10, 64) + if err != nil { + return nil, err + } + distributionNumber, err := strconv.ParseInt(rawLine[242:242+3], 10, 64) + if err != nil { + return nil, err + } + return &models.AssetInfo{ + TipReg: int(tipReg), + DataCollectionDate: date, + BDICode: int(bdiCode), + Ticker: strings.TrimSpace(rawLine[12 : 12+12]), + MarketType: int(marketType), + CompanyName: strings.TrimSpace(rawLine[27 : 27+12]), + SecurityType: strings.TrimSpace(rawLine[39 : 39+10]), + FutureMarketExpiration: strings.TrimSpace(rawLine[49 : 49+3]), + Currency: strings.TrimSpace(rawLine[52 : 52+4]), + PriceOpen: models.FixedPoint(priceOpen), + PriceMax: models.FixedPoint(priceMax), + PriceMin: models.FixedPoint(priceMin), + PriceMean: models.FixedPoint(priceMean), + PriceClose: models.FixedPoint(priceClose), + PriceBid: models.FixedPoint(priceBid), + PriceAsk: models.FixedPoint(priceAsk), + TotalTrades: int(totalTrades), + TotalQuantity: int(totalQuantity), + TotalVolume: models.FixedPoint(totalVolume), + PreExe: models.FixedPoint(preExe), + IndOpc: int(indOpc), + ExpirationDate: expirationDate, + FatCot: int(fatCot), + PtoExe: int(ptoExe), + ISINCode: strings.TrimSpace(rawLine[230 : 230+12]), + DistributionNumber: int(distributionNumber), + }, nil +} + +func ParseHistoricData(rawData []string) ([]models.AssetInfo, error) { + contentList := make([]models.AssetInfo, len(rawData)-3) + for i, rawLine := range rawData[1:len(rawData)-2] { + slice, err := parseContentLine(rawLine) + if err != nil{ + return nil, err + } + contentList[i] = *slice + } + return contentList, nil +} + +func ParseHistoricDataFromBytes(data []byte) ([]models.AssetInfo, error){ + segmentedLines := strings.Split(string(data), "\n") + histData, err := ParseHistoricData(segmentedLines) + if err != nil{ + return nil, err + } + return histData, nil +} diff --git a/test/base.go b/test/base.go new file mode 100644 index 0000000..97dd862 --- /dev/null +++ b/test/base.go @@ -0,0 +1,10 @@ +package test + +import ( + "fmt" + "testing" +) + +func TestBase(t *testing.T) { + fmt.Print("Hello world") +}