Skip to content

Commit

Permalink
Shown non adjusted quotation and p/e ratio
Browse files Browse the repository at this point in the history
  • Loading branch information
dude333 committed May 18, 2021
1 parent c6bf2bf commit 28bbcaa
Show file tree
Hide file tree
Showing 13 changed files with 230 additions and 49 deletions.
7 changes: 6 additions & 1 deletion common.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,14 @@ func MonthsFromToday(n int) []string {
}

// LastBusinessDayOfYear returns the last business day of the 'year' (the business
// day before Dec 30).
// day before Dec 30). If current year, returns last business day before today.
// Returns date as YYYY-MM-DD.
func LastBusinessDayOfYear(year int) string {
today := time.Now()
if year == today.Year() {
return LastBusinessDay(1)
}

date := time.Date(year, time.December, 29, 12, 0, 0, 0, time.UTC)

if date.Weekday() == time.Saturday {
Expand Down
4 changes: 2 additions & 2 deletions fetch/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,11 @@ func processFREReport(db *sql.DB, dataDir string, year int) error {
}

//
// fetchFiles on CVM server
// fetchFiles from web.
//
func fetchFiles(url, dataDir string, zipfile string) ([]string, error) {

// Download file from CVM server
// Download file from web
err := downloadFile(url, zipfile)
fmt.Println()
if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions fetch/fetch_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,9 @@ func (h HTTPFetch) JSON(url string, target interface{}) error {
}
defer r.Body.Close()

// for _, c := range r.Cookies() {
// fmt.Printf("COOKIE: %+v\n", c)
// }

return json.NewDecoder(r.Body).Decode(target)
}
94 changes: 80 additions & 14 deletions fetch/fetch_stockquote.go → fetch/fetch_stock.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ const (
APIyahoo
)

// StockFetch implements a fetcher stock quotes.
type StockFetch struct {
// Stock implements a fetcher for stock info.
type Stock struct {
apiKey string // API key for Alpha Vantage API server
store rapina.StockParser
cache map[string]int // Cache to avoid duplicated fetch on Alpha Vantage server
Expand All @@ -32,10 +32,10 @@ type StockFetch struct {
}

//
// NewStock returns a new instance of *StockServer
// NewStock returns a new instance of *Stock
//
func NewStock(store rapina.StockParser, log rapina.Logger, apiKey, dataDir string) *StockFetch {
return &StockFetch{
func NewStock(store rapina.StockParser, log rapina.Logger, apiKey, dataDir string) *Stock {
return &Stock{
apiKey: apiKey,
store: store,
cache: make(map[string]int),
Expand All @@ -46,7 +46,7 @@ func NewStock(store rapina.StockParser, log rapina.Logger, apiKey, dataDir strin

// Quote returns the quote for 'code' on 'date'.
// Date format: YYYY-MM-DD.
func (s StockFetch) Quote(code, date string) (float64, error) {
func (s *Stock) Quote(code, date string) (float64, error) {
if !rapina.IsDate(date) {
return 0, fmt.Errorf("data inválida: %s", date)
}
Expand Down Expand Up @@ -75,9 +75,7 @@ func (s StockFetch) Quote(code, date string) (float64, error) {
// stockQuoteFromB3 downloads the quotes for all companies for the given date,
// where 'date' format is YYYY-MM-DD.
//
func (s StockFetch) stockQuoteFromB3(date string) error {
dataDir := ".data"

func (s *Stock) stockQuoteFromB3(date string) error {
// Convert date string from YYYY-MM-DD to DDMMYYYY
if len(date) != len("2021-05-03") {
return fmt.Errorf("data com formato inválido: %s", date)
Expand All @@ -86,8 +84,8 @@ func (s StockFetch) stockQuoteFromB3(date string) error {
url := fmt.Sprintf(`http://bvmf.bmfbovespa.com.br/InstDados/SerHist/COTAHIST_D%s.ZIP`,
conv)
// Download ZIP file and unzips its files
zip := fmt.Sprintf("%s/COTAHIST_D%s.ZIP", dataDir, conv)
files, err := fetchFiles(url, dataDir, zip)
zip := fmt.Sprintf("%s/COTAHIST_D%s.ZIP", s.dataDir, conv)
files, err := fetchFiles(url, s.dataDir, zip)
if err != nil {
return err
}
Expand All @@ -105,8 +103,7 @@ func (s StockFetch) stockQuoteFromB3(date string) error {

dec := transform.NewReader(fh, charmap.ISO8859_1.NewDecoder())

_, err = s.store.Save(dec, "")
if err != nil {
if _, err := s.store.Save(dec, ""); err != nil {
return err
}
}
Expand All @@ -119,7 +116,7 @@ func (s StockFetch) stockQuoteFromB3(date string) error {
// daily low, daily close, daily volume) of the global equity specified,
// covering 20+ years of historical data.
//
func (s StockFetch) stockQuoteFromAPIServer(code, date string, apiProvider int) error {
func (s *Stock) stockQuoteFromAPIServer(code, date string, apiProvider int) error {
if v := s.cache[code]; v == APIalphavantage && apiProvider == APIalphavantage {
// return fmt.Errorf("cotação histórica para '%s' já foi feita", code)
return nil // silent return if this fetch has been run already
Expand Down Expand Up @@ -171,6 +168,75 @@ func (s StockFetch) stockQuoteFromAPIServer(code, date string, apiProvider int)
return err
}

func (s *Stock) Code(companyName, stockType string) (string, error) {
if val, err := s.store.Code(companyName, stockType); err == nil {
return val, nil // returning data found on db
}

if err := s.stockCodeFromB3(companyName); err != nil {
return "", err
}

return s.store.Code(companyName, stockType)
}

type b3CodesFile struct {
RedirectURL string `json:"redirectUrl"`
Token string `json:"token"`
File struct {
Name string `json:"name"`
Extension string `json:"extension"`
} `json:"file"`
}

func (s *Stock) stockCodeFromB3(companyName string) error {
s.log.Debug("stockCodeFromB3 -> %s", companyName)

// Get file url
var f b3CodesFile
url := `https://arquivos.b3.com.br/api/download/requestname?fileName=InstrumentsConsolidated&date=`
url += rapina.LastBusinessDay(2)
h := NewHTTP()
err := h.JSON(url, &f)
if err != nil {
return err
}

// Download file
fp := fmt.Sprintf("%s/codes.csv", s.dataDir)
tries := 3
for {
url = fmt.Sprintf(`https://arquivos.b3.com.br/api/download/?token=%s`, f.Token)
s.log.Debug("%s", url)
err = downloadFile(url, fp)
if err != nil {
s.log.Error("download do arquivo de códigos: %v", err)
tries--
if tries <= 0 {
return err
}
time.Sleep(2 * time.Second)
continue
}
// Delete files on return
defer filesCleanup([]string{fp})
break
}

// Parse and store files content
fh, err := os.Open(fp)
if err != nil {
return errors.Wrapf(err, "abrindo arquivo %s", fp)
}
defer fh.Close()

_, err = s.store.Save(fh, "")

return err
}

/* --- UTILS --- */

func apiURL(provider int, apiKey, code, date string) string {
v := url.Values{}
switch provider {
Expand Down
6 changes: 5 additions & 1 deletion fetch/fetch_stockquote_test.go → fetch/fetch_stock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ func (m MockStockFetch) Quote(code, date string) (float64, error) {
return 123.45, nil
}

func (m MockStockFetch) Code(companyName, typ string) (string, error) {
return "WXYZ3", nil
}

func TestStockFetch_Quote(t *testing.T) {
type fields struct {
apiKey string
Expand Down Expand Up @@ -70,7 +74,7 @@ func TestStockFetch_Quote(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := StockFetch{
s := Stock{
apiKey: tt.fields.apiKey,
store: tt.fields.store,
}
Expand Down
1 change: 1 addition & 0 deletions interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type StockParser interface {
Save(stream io.Reader, code string) (int, error)
SaveB3Quotes(filename string) error
Quote(code, date string) (float64, error)
Code(companyName, stockType string) (string, error)
}

// Logger interface contains the methods needed to poperly display log messages.
Expand Down
3 changes: 3 additions & 0 deletions parsers/codeaccounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ const (

// Financial scale (unit, thousand)
Escala

// Stock quote from last day of year
Quote
)

// account code, description and bookkeeping code
Expand Down
23 changes: 22 additions & 1 deletion parsers/stock.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ type stockCode struct {
type StockParser struct {
db *sql.DB
log rapina.Logger
mu sync.Mutex // ensures atomic writes to db
}

//
Expand Down Expand Up @@ -78,6 +77,25 @@ func (s *StockParser) Quote(code, date string) (float64, error) {
return close, nil
}

//
// Quote returns the company ON stock code, where stockType is:
// ON, PN, UNT, CI [CI = FII]
//
func (s *StockParser) Code(companyName, stockType string) (string, error) {
query := `SELECT trading_code FROM stock_codes WHERE company_name LIKE ? AND SpcfctnCd LIKE ?;`
st := strings.ToUpper(stockType + "%")
var code string
err := s.db.QueryRow(query, "%"+companyName+"%", st).Scan(&code)
if err == sql.ErrNoRows {
return "", errors.New("não encontrado no bd")
}
if err != nil {
return "", errors.Wrapf(err, "lendo código de %s do bd", companyName)
}

return code, nil
}

func (s *StockParser) SaveB3Quotes(filename string) error {
isNew, err := isNewFile(s.db, filename)
if !isNew && err == nil { // if error, process file
Expand Down Expand Up @@ -309,6 +327,9 @@ func provider(header string) int {
if strings.HasPrefix(header, "00COTAHIST.") {
return b3Quotes
}
if strings.HasPrefix(header, "RptDt;TckrSymb;Asst;AsstDesc;SgmtNm;MktNm;SctyCtgyNm;XprtnDt;") {
return b3Codes
}
return none
}

Expand Down
7 changes: 4 additions & 3 deletions parsers/tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package parsers
import (
"database/sql"
"fmt"
"strings"

"github.com/pkg/errors"
)

const currentDbVersion = 210305
const currentDbVersion = 210514
const currentFIIDbVersion = 210426
const currentStockCodesVersion = 210305
const currentStockQuotesVersion = 210305
Expand Down Expand Up @@ -71,7 +72,7 @@ var createTableMap = map[string]string{
"NAME" varchar(100)
);`,

"stock_codes": `CREATE TABLE IF NOT EXISTS companies
"stock_codes": `CREATE TABLE IF NOT EXISTS stock_codes
(
"trading_code" VARCHAR NOT NULL PRIMARY KEY,
"company_name" VARCHAR,
Expand Down Expand Up @@ -182,7 +183,7 @@ func createTable(db *sql.DB, dataType string) (err error) {
return errors.Wrap(err, "erro ao criar índice para table "+table)
}

if dataType == "STATUS" {
if strings.ToUpper(dataType) == "STATUS" {
return nil
}

Expand Down
56 changes: 49 additions & 7 deletions reports/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strconv"
"strings"

"github.com/dude333/rapina"
"github.com/dude333/rapina/parsers"
"github.com/pkg/errors"
)
Expand Down Expand Up @@ -93,6 +94,21 @@ func (r report) accountsValues(cid, year int) (map[uint32]float32, error) {
values[parsers.EquityAvg] = avg(values[parsers.Equity], v)
}

// Stock code
code, err := r.fetchStock.Code(r.company, "ON")
if err != nil {
fmt.Printf("[x] erro obtendo código negociação: %v\n", err)
}

// Stock quote
date := rapina.LastBusinessDayOfYear(year)
q, err := r.fetchStock.Quote(code, date)
if err != nil {
fmt.Printf("[x] Cotação %s (%d): %v\n", code, year, err)
} else {
values[parsers.Quote] = float32(q)
}

return values, err
}

Expand Down Expand Up @@ -479,7 +495,7 @@ func (r report) accountsAverage(company string, year int) (map[uint32]float32, e

cids := make([]string, len(companies))
for i, co := range companies {
if id, err := cid(r.db, co); err == nil {
if id, err := r.getCid(co); err == nil {
cids[i] = strconv.Itoa(id)
}
}
Expand Down Expand Up @@ -617,16 +633,42 @@ func companies(db *sql.DB) ([]CompanyInfo, error) {
}

//
// cid returns the company ID
// setCompany sets the company ID, CNPJ and stock code based on it's name.
//
func cid(db *sql.DB, company string) (int, error) {
selectID := fmt.Sprintf(`SELECT DISTINCT ID FROM companies WHERE NAME LIKE "%s%%"`, company)
func (r *report) setCompany(company string) error {
if company == "" {
return errors.New("company name not set")
}
if r.fetchStock == nil {
return errors.New("fetchStock not set")
}

// Reset company data
r.cid = 0
r.cnpj = ""
// r.code = ""

query := `SELECT DISTINCT ID, NAME, CNPJ FROM companies WHERE NAME LIKE ?`
var cid int
err := db.QueryRow(selectID).Scan(&cid)
var name, cnpj string
err := r.db.QueryRow(query, "%"+company+"%").Scan(&cid, &name, &cnpj)
if err != nil {
return 0, err
return err
}
return cid, nil
r.cid = cid
r.company = name // reset company name to match the name stored on db
r.cnpj = cnpj
// r.code, _ = r.fetchStock.Code(name, "ON")

return nil
}

func (r *report) getCid(companyName string) (int, error) {
selectID := `SELECT DISTINCT ID FROM companies WHERE NAME LIKE ?`
var cid int
err := r.db.QueryRow(selectID, "%"+companyName+"%").Scan(&cid)

return cid, err
}

//
Expand Down
Loading

0 comments on commit 28bbcaa

Please sign in to comment.