update of posotion by trade his
This commit is contained in:
BIN
Portifolio
BIN
Portifolio
Binary file not shown.
@@ -8,6 +8,22 @@ import (
|
|||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GetCompanyBySymbol(db *sql.DB, symbol string) (*model.Company, error) {
|
||||||
|
var c model.Company
|
||||||
|
err := db.QueryRow(
|
||||||
|
`SELECT id, symbol, shares_outstanding, price, currency_id FROM companies WHERE symbol = ?`,
|
||||||
|
symbol,
|
||||||
|
).Scan(&c.ID, &c.Symbol, &c.SharesOutstanding, &c.Price, &c.CurrencyID)
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("query company: %w", err)
|
||||||
|
}
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetCompanyByID(db *sql.DB, id int) (*model.Company, error) {
|
func GetCompanyByID(db *sql.DB, id int) (*model.Company, error) {
|
||||||
var c model.Company
|
var c model.Company
|
||||||
err := db.QueryRow(
|
err := db.QueryRow(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func GetTrades(db *sql.DB) ([]model.Trade, error) {
|
func GetTrades(db *sql.DB) ([]model.Trade, error) {
|
||||||
rows, err := db.Query("SELECT company_id, symbol, currency_id, currency_code, shares, product, type, price, traded_at FROM trades")
|
rows, err := db.Query("SELECT symbol, currency_code, shares, product, type, price, traded_at FROM trades")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@ func GetTrades(db *sql.DB) ([]model.Trade, error) {
|
|||||||
var typeInt int
|
var typeInt int
|
||||||
var t model.Trade
|
var t model.Trade
|
||||||
|
|
||||||
err := rows.Scan(&t.CompanyID, &t.Symbol, &t.CompanyID, &t.CurrencyCode, &t.Shares, &t.Product, &typeInt, &t.Price, &t.Date)
|
err := rows.Scan(&t.Symbol, &t.CurrencyCode, &t.Shares, &t.Product, &typeInt, &t.Price, &t.Date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -69,10 +69,8 @@ func GetPositions(db *sql.DB) ([]model.Position, error) {
|
|||||||
|
|
||||||
func InsertTrade(db *sql.DB, trade model.Trade) error {
|
func InsertTrade(db *sql.DB, trade model.Trade) error {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"INSERT INTO trades (company_id, symbol, currency_id, currency_code, shares, product, type, price, traded_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
"INSERT INTO trades (symbol, currency_code, shares, product, type, price, traded_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
trade.CompanyID,
|
|
||||||
trade.Symbol,
|
trade.Symbol,
|
||||||
trade.CurrencyID,
|
|
||||||
trade.CurrencyCode,
|
trade.CurrencyCode,
|
||||||
trade.Shares,
|
trade.Shares,
|
||||||
trade.Product,
|
trade.Product,
|
||||||
@@ -82,3 +80,56 @@ func InsertTrade(db *sql.DB, trade model.Trade) error {
|
|||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdatePositions(db *sql.DB, positions []model.Position) error {
|
||||||
|
// Complete overwrite of the db positions
|
||||||
|
_, err := db.Exec("DELETE FROM position")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to clear positions: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range positions {
|
||||||
|
// Resolve company_id if missing
|
||||||
|
if p.CompanyID == 0 {
|
||||||
|
company, err := GetCompanyBySymbol(db, p.Symbol)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not find company %s: %s", p.Symbol, err)
|
||||||
|
}
|
||||||
|
p.CompanyID = company.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve currency_id if missing
|
||||||
|
if p.CurrencyID == 0 {
|
||||||
|
currency, err := GetCurrencyByCode(db, p.CurrencyCode)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not find currency %s: %s", p.CurrencyCode, err)
|
||||||
|
}
|
||||||
|
p.CurrencyID = currency.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := db.Exec(`
|
||||||
|
INSERT INTO position (company_id, symbol, currency_id, currency_code, shares, weight, cost_basis)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
|
ON CONFLICT(company_id) DO UPDATE SET
|
||||||
|
symbol = excluded.symbol,
|
||||||
|
currency_id = excluded.currency_id,
|
||||||
|
currency_code = excluded.currency_code,
|
||||||
|
shares = excluded.shares,
|
||||||
|
weight = excluded.weight,
|
||||||
|
cost_basis = excluded.cost_basis
|
||||||
|
`,
|
||||||
|
p.CompanyID,
|
||||||
|
p.Symbol,
|
||||||
|
p.CurrencyID,
|
||||||
|
p.CurrencyCode,
|
||||||
|
p.Shares,
|
||||||
|
p.Weight,
|
||||||
|
p.CostBasis,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to upsert position %s: %s", p.Symbol, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"Portifolio/internal/database"
|
"Portifolio/internal/database"
|
||||||
"Portifolio/internal/model"
|
"Portifolio/internal/model"
|
||||||
|
"Portifolio/internal/service"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -32,15 +33,8 @@ func AddTradeHandler(db *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if company is in the db.
|
|
||||||
company, err := database.GetCompanyByID(db, req.TickerId)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, fmt.Sprintf("failed to find currency: %s", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
trade := model.Trade{
|
trade := model.Trade{
|
||||||
Symbol: company.Symbol,
|
Symbol: req.Symbol,
|
||||||
Shares: req.Shares,
|
Shares: req.Shares,
|
||||||
Product: model.TradeProduct(req.Product),
|
Product: model.TradeProduct(req.Product),
|
||||||
Type: model.TradeType(req.Type),
|
Type: model.TradeType(req.Type),
|
||||||
@@ -49,7 +43,23 @@ func AddTradeHandler(db *sql.DB) http.HandlerFunc {
|
|||||||
Date: req.Date,
|
Date: req.Date,
|
||||||
}
|
}
|
||||||
|
|
||||||
database.InsertTrade(db, trade)
|
err = database.InsertTrade(db, trade)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("failed to insert trade into db: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = service.UpdatePositionByTradeList(db)
|
||||||
|
update := true
|
||||||
|
if err != nil {
|
||||||
|
update = false
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(map[string]any{"success": true, "position update": update}); err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("failed to encode trades: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,20 +32,8 @@ const (
|
|||||||
Sell TradeType = false
|
Sell TradeType = false
|
||||||
)
|
)
|
||||||
|
|
||||||
type Trade struct {
|
|
||||||
CompanyID int
|
|
||||||
Symbol string
|
|
||||||
CurrencyID int
|
|
||||||
CurrencyCode string
|
|
||||||
Shares int
|
|
||||||
Product TradeProduct
|
|
||||||
Type TradeType
|
|
||||||
Price float64
|
|
||||||
Date time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type AddTradeRequest struct {
|
type AddTradeRequest struct {
|
||||||
TickerId int `json:"ticker_id"`
|
Symbol string `json:"symbol"`
|
||||||
Shares int `json:"shares"`
|
Shares int `json:"shares"`
|
||||||
Product int `json:"product"`
|
Product int `json:"product"`
|
||||||
Type bool `json:"type"`
|
Type bool `json:"type"`
|
||||||
@@ -54,9 +42,19 @@ type AddTradeRequest struct {
|
|||||||
Date time.Time `json:"date"`
|
Date time.Time `json:"date"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Trade struct {
|
||||||
|
Symbol string
|
||||||
|
CurrencyCode string
|
||||||
|
Shares int
|
||||||
|
Product TradeProduct
|
||||||
|
Type TradeType
|
||||||
|
Price float64
|
||||||
|
Date time.Time
|
||||||
|
}
|
||||||
|
|
||||||
func (r *AddTradeRequest) Validate() error {
|
func (r *AddTradeRequest) Validate() error {
|
||||||
if r.TickerId <= 0 {
|
if r.Symbol == "" {
|
||||||
return errors.New("ticker id must be a positive integer")
|
return errors.New("empty SYmbol string")
|
||||||
}
|
}
|
||||||
if r.Shares <= 0 {
|
if r.Shares <= 0 {
|
||||||
return errors.New("shares must be a positive integer")
|
return errors.New("shares must be a positive integer")
|
||||||
@@ -67,7 +65,7 @@ func (r *AddTradeRequest) Validate() error {
|
|||||||
if r.Price <= 0 {
|
if r.Price <= 0 {
|
||||||
return errors.New("price must be a positive number")
|
return errors.New("price must be a positive number")
|
||||||
}
|
}
|
||||||
if r.CurrencyCode != "" {
|
if r.CurrencyCode == "" {
|
||||||
return errors.New("currency is required")
|
return errors.New("currency is required")
|
||||||
}
|
}
|
||||||
if r.Date.IsZero() {
|
if r.Date.IsZero() {
|
||||||
|
|||||||
51
internal/service/portfolio.go
Normal file
51
internal/service/portfolio.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"Portifolio/internal/database"
|
||||||
|
"Portifolio/internal/model"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UpdatePositionByTradeList(db *sql.DB) error {
|
||||||
|
|
||||||
|
trades, err := database.GetTrades(db)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to get the trades from db: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var TradeSum map[string]model.Position
|
||||||
|
|
||||||
|
for _, trade := range trades {
|
||||||
|
if trade.Type == model.Buy {
|
||||||
|
TradeSum[trade.Symbol] = model.Position{
|
||||||
|
Symbol: trade.Symbol,
|
||||||
|
CurrencyCode: trade.CurrencyCode,
|
||||||
|
CostBasis: TradeSum[trade.Symbol].CostBasis + trade.Price,
|
||||||
|
Shares: TradeSum[trade.Symbol].Shares + trade.Shares,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TradeSum[trade.Symbol] = model.Position{
|
||||||
|
Symbol: trade.Symbol,
|
||||||
|
CurrencyCode: trade.CurrencyCode,
|
||||||
|
CostBasis: TradeSum[trade.Symbol].CostBasis - trade.Price,
|
||||||
|
Shares: TradeSum[trade.Symbol].Shares - trade.Shares,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var NewPositinos []model.Position
|
||||||
|
for _, pos := range TradeSum {
|
||||||
|
NewPositinos = append(NewPositinos, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = database.UpdatePositions(db, NewPositinos)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to insert the new postions number into db: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user