aboutsummaryrefslogtreecommitdiff
path: root/backend/internal/database/stocks.go
diff options
context:
space:
mode:
authorGravatar Anshul Gupta <ansg191@anshulg.com> 2024-08-05 18:55:10 -0700
committerGravatar Anshul Gupta <ansg191@anshulg.com> 2024-08-05 18:55:19 -0700
commitb96fcd1a54a46a95f98467b49a051564bc21c23c (patch)
tree93caeeb05f8d6310e241095608ea2428c749b18c /backend/internal/database/stocks.go
downloadibd-trader-b96fcd1a54a46a95f98467b49a051564bc21c23c.tar.gz
ibd-trader-b96fcd1a54a46a95f98467b49a051564bc21c23c.tar.zst
ibd-trader-b96fcd1a54a46a95f98467b49a051564bc21c23c.zip
Initial Commit
Diffstat (limited to 'backend/internal/database/stocks.go')
-rw-r--r--backend/internal/database/stocks.go264
1 files changed, 264 insertions, 0 deletions
diff --git a/backend/internal/database/stocks.go b/backend/internal/database/stocks.go
new file mode 100644
index 0000000..701201e
--- /dev/null
+++ b/backend/internal/database/stocks.go
@@ -0,0 +1,264 @@
+package database
+
+import (
+ "context"
+ "database/sql"
+ "errors"
+ "fmt"
+
+ "ibd-trader/internal/analyzer"
+ "ibd-trader/internal/utils"
+
+ "github.com/Rhymond/go-money"
+)
+
+var ErrStockNotFound = errors.New("stock not found")
+
+type StockStore interface {
+ GetStock(ctx context.Context, symbol string) (Stock, error)
+ AddStock(ctx context.Context, stock Stock) error
+ AddRanking(ctx context.Context, symbol string, ibd50, cap20 int) error
+ AddStockInfo(ctx context.Context, info *StockInfo) (string, error)
+ GetStockInfo(ctx context.Context, id string) (*StockInfo, error)
+ AddAnalysis(ctx context.Context, ratingId string, analysis *analyzer.Analysis) error
+}
+
+func (d *database) GetStock(ctx context.Context, symbol string) (Stock, error) {
+ row, err := d.queryRow(ctx, d.db, "stocks/get_stock", symbol)
+ if err != nil {
+ return Stock{}, err
+ }
+
+ var stock Stock
+ if err = row.Scan(&stock.Symbol, &stock.Name, &stock.IBDUrl); err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return Stock{}, ErrStockNotFound
+ }
+ return Stock{}, err
+ }
+
+ return stock, nil
+}
+
+func (d *database) AddStock(ctx context.Context, stock Stock) error {
+ _, err := d.exec(ctx, d.db, "stocks/add_stock", stock.Symbol, stock.Name, stock.IBDUrl)
+ return err
+}
+
+func (d *database) AddRanking(ctx context.Context, symbol string, ibd50, cap20 int) error {
+ if ibd50 > 0 {
+ _, err := d.exec(ctx, d.db, "stocks/add_rank", symbol, "ibd50", ibd50)
+ if err != nil {
+ return err
+ }
+ }
+ if cap20 > 0 {
+ _, err := d.exec(ctx, d.db, "stocks/add_rank", symbol, "cap20", cap20)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (d *database) AddStockInfo(ctx context.Context, info *StockInfo) (string, error) {
+ tx, err := d.db.BeginTx(ctx, nil)
+ if err != nil {
+ return "", err
+ }
+ defer func(tx *sql.Tx) {
+ _ = tx.Rollback()
+ }(tx)
+
+ // Add raw chart analysis
+ row, err := d.queryRow(ctx, tx, "stocks/add_raw_chart_analysis", info.ChartAnalysis)
+ if err != nil {
+ return "", err
+ }
+
+ var chartAnalysisID string
+ if err = row.Scan(&chartAnalysisID); err != nil {
+ return "", err
+ }
+
+ // Add stock info
+ row, err = d.queryRow(ctx, tx,
+ "stocks/add_rating",
+ info.Symbol,
+ info.Ratings.Composite,
+ info.Ratings.EPS,
+ info.Ratings.RelStr,
+ info.Ratings.GroupRelStr,
+ info.Ratings.SMR,
+ info.Ratings.AccDis,
+ chartAnalysisID,
+ info.Price.Display(),
+ )
+ if err != nil {
+ return "", err
+ }
+
+ var ratingsID string
+ if err = row.Scan(&ratingsID); err != nil {
+ return "", err
+ }
+
+ return ratingsID, tx.Commit()
+}
+
+func (d *database) GetStockInfo(ctx context.Context, id string) (*StockInfo, error) {
+ row, err := d.queryRow(ctx, d.db, "stocks/get_stock_info", id)
+ if err != nil {
+ return nil, err
+ }
+
+ var info StockInfo
+ var priceStr string
+ err = row.Scan(
+ &info.Symbol,
+ &info.Name,
+ &info.ChartAnalysis,
+ &info.Ratings.Composite,
+ &info.Ratings.EPS,
+ &info.Ratings.RelStr,
+ &info.Ratings.GroupRelStr,
+ &info.Ratings.SMR,
+ &info.Ratings.AccDis,
+ &priceStr,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ info.Price, err = utils.ParseMoney(priceStr)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
+
+func (d *database) AddAnalysis(ctx context.Context, ratingId string, analysis *analyzer.Analysis) error {
+ _, err := d.exec(ctx, d.db, "stocks/add_analysis",
+ ratingId,
+ analysis.Action,
+ analysis.Price.Display(),
+ analysis.Reason,
+ analysis.Confidence,
+ )
+ return err
+}
+
+type Stock struct {
+ Symbol string
+ Name string
+ IBDUrl string
+}
+
+type StockInfo struct {
+ Symbol string
+ Name string
+ ChartAnalysis string
+ Ratings Ratings
+ Price *money.Money
+}
+
+type Ratings struct {
+ Composite uint8
+ EPS uint8
+ RelStr uint8
+ GroupRelStr LetterRating
+ SMR LetterRating
+ AccDis LetterRating
+}
+
+type LetterRating uint8
+
+const (
+ LetterRatingE LetterRating = iota
+ LetterRatingEPlus
+ LetterRatingDMinus
+ LetterRatingD
+ LetterRatingDPlus
+ LetterRatingCMinus
+ LetterRatingC
+ LetterRatingCPlus
+ LetterRatingBMinus
+ LetterRatingB
+ LetterRatingBPlus
+ LetterRatingAMinus
+ LetterRatingA
+ LetterRatingAPlus
+)
+
+func (r LetterRating) String() string {
+ switch r {
+ case LetterRatingE:
+ return "E"
+ case LetterRatingEPlus:
+ return "E+"
+ case LetterRatingDMinus:
+ return "D-"
+ case LetterRatingD:
+ return "D"
+ case LetterRatingDPlus:
+ return "D+"
+ case LetterRatingCMinus:
+ return "C-"
+ case LetterRatingC:
+ return "C"
+ case LetterRatingCPlus:
+ return "C+"
+ case LetterRatingBMinus:
+ return "B-"
+ case LetterRatingB:
+ return "B"
+ case LetterRatingBPlus:
+ return "B+"
+ case LetterRatingAMinus:
+ return "A-"
+ case LetterRatingA:
+ return "A"
+ case LetterRatingAPlus:
+ return "A+"
+ default:
+ return "Unknown"
+ }
+}
+
+func LetterRatingFromString(str string) (LetterRating, error) {
+ switch str {
+ case "N/A":
+ fallthrough
+ case "E":
+ return LetterRatingE, nil
+ case "E+":
+ return LetterRatingEPlus, nil
+ case "D-":
+ return LetterRatingDMinus, nil
+ case "D":
+ return LetterRatingD, nil
+ case "D+":
+ return LetterRatingDPlus, nil
+ case "C-":
+ return LetterRatingCMinus, nil
+ case "C":
+ return LetterRatingC, nil
+ case "C+":
+ return LetterRatingCPlus, nil
+ case "B-":
+ return LetterRatingBMinus, nil
+ case "B":
+ return LetterRatingB, nil
+ case "B+":
+ return LetterRatingBPlus, nil
+ case "A-":
+ return LetterRatingAMinus, nil
+ case "A":
+ return LetterRatingA, nil
+ case "A+":
+ return LetterRatingAPlus, nil
+ default:
+ return 0, fmt.Errorf("unknown rating: %s", str)
+ }
+}