Skip to main content
Glama

Controtto

by contre95
tradingHandlers.go7.51 kB
package rest import ( "controtto/src/app/managing" "controtto/src/app/querying" "controtto/src/app/trading" "encoding/csv" "io" "strconv" "fmt" "log/slog" "slices" "time" "github.com/gofiber/fiber/v2" ) func deleteTrade(tpm trading.TradeRecorder) func(*fiber.Ctx) error { return func(c *fiber.Ctx) error { req := trading.DeleteTradeReq{ ID: c.Params("id"), } slog.Info("Trade delete requested.", "id", req.ID) resp, err := tpm.DeleteTrade(req) if err != nil { return c.Render("toastErr", fiber.Map{ "Title": "Error", "Msg": err, }) } slog.Info("Trade deleted", "trasnaction", resp.ID) c.Append("HX-Trigger", "newTrade") return c.Render("toastOk", fiber.Map{ "Title": "Deleted", "Msg": "Trade deleted", }) } } func newTradeImport(tpm trading.TradeRecorder) func(*fiber.Ctx) error { return func(c *fiber.Ctx) error { slog.Info("Importing trades") file, err := c.FormFile("trancsv") if err != nil { slog.Error("Couldnot import trades", "error", err.Error()) return c.Render("toastErr", fiber.Map{ "Title": "Error", "Msg": fmt.Sprintln("Error reading CSV req:", err), }) } uploadedFile, err := file.Open() if err != nil { slog.Error("Couldnot import trades", "error", err.Error()) return c.Render("toastErr", fiber.Map{ "Title": "Error", "Msg": fmt.Sprintln("Error reading CSV req:", err), }) } defer uploadedFile.Close() csvReader := csv.NewReader(uploadedFile) // Iterate over the CSV records csvReader.Read() // Skip column tCount := 0 reqs := []trading.RecordTradeReq{} for { line, err := csvReader.Read() if err == io.EOF { break } if err != nil { return c.Render("toastErr", fiber.Map{ "Title": "Error", "Msg": fmt.Sprintln("Error reading CSV req:", err), }) } req := trading.RecordTradeReq{} req.PairID = c.Params("id") req.Type = line[0] req.Timestamp, err = time.Parse("2006-01-02 15:04", line[1]) if err != nil { slog.Error("Error parsing imported trade.", "error", err) return c.Render("toastErr", fiber.Map{"Title": "Created", "Msg": fmt.Sprintf("Error on row %d col %d", tCount, 0)}) } req.BaseAmount, err = strconv.ParseFloat(line[2], 64) if err != nil { slog.Error("Error parsing imported trade.", "error", err) return c.Render("toastErr", fiber.Map{"Title": "Created", "Msg": fmt.Sprintf("Error on row %d col %d", tCount, 1)}) } req.QuoteAmount, err = strconv.ParseFloat(line[3], 64) if err != nil { slog.Error("Error parsing imported trade.", "error", err) return c.Render("toastErr", fiber.Map{"Title": "Created", "Msg": fmt.Sprintf("Error on row %d col %d", tCount, 2)}) } req.FeeInBase, err = strconv.ParseFloat(line[4], 64) if err != nil { slog.Error("Error parsing imported trade.", "error", err) return c.Render("toastErr", fiber.Map{"Title": "Created", "Msg": fmt.Sprintf("Error on row %d col %d", tCount, 3)}) } req.FeeInQuote, err = strconv.ParseFloat(line[5], 64) if err != nil { slog.Error("Error parsing imported trade.", "error", err) return c.Render("toastErr", fiber.Map{"Title": "Created", "Msg": fmt.Sprintf("Error on row %d col %d", tCount, 4)}) } reqs = append(reqs, req) tCount++ } failedTrades := []string{} for i, r := range reqs { _, err := tpm.RecordTrade(r) if err != nil { slog.Error("Attempt to import trade failed.", "error", err, "row", i) failedTrades = append(failedTrades, fmt.Sprint(i)) } } tok := int(tCount - len(failedTrades)) c.Append("HX-Trigger", "newTrade") return c.Render("toastOk", fiber.Map{ "Title": "Created", "Extra": "", "Msg": fmt.Sprintf("%d/%d imported.\n%d/%d failed.", tok, tCount, len(failedTrades), tCount), }) } } func newTradeForm(tpq querying.PairsQuerier, pq *managing.PriceProviderManager) func(*fiber.Ctx) error { return func(c *fiber.Ctx) error { slog.Info("Create Trade UI requested") id := c.Params("id") req := querying.GetPairReq{ TPID: id, WithTrades: false, WithCalculations: false, } resp, err := tpq.GetPair(req) if err != nil { return c.Render("toastErr", fiber.Map{ "Title": "Error", "Msg": err, }) } qRes, err := pq.QueryPrice(managing.QueryPriceReq{ AssetSymbolA: resp.Pair.BaseAsset.Symbol, AssetSymbolB: resp.Pair.QuoteAsset.Symbol, }) if err != nil { return c.Render("tradingForm", fiber.Map{ "Error": err, }) } return c.Render("tradingForm", fiber.Map{ "Pair": resp.Pair, "Price": qRes.Price, "Today": time.Now().Format("2006-01-02"), }) } } func newTrade(tpm trading.TradeRecorder) func(*fiber.Ctx) error { return func(c *fiber.Ctx) error { slog.Info("Recording new trade") payload := struct { Base float64 `form:"base"` Quote float64 `form:"quote"` BFee float64 `form:"bfee"` QFee float64 `form:"qfee"` TType string `form:"ttype"` TTDate string `form:"tdate"` }{} if err := c.BodyParser(&payload); err != nil { fmt.Println(err) return err } tdate, err := time.Parse("2006-01-02", payload.TTDate) if err != nil { return c.Render("toastErr", fiber.Map{ "Title": "Error", "Msg": err, }) } req := trading.RecordTradeReq{ PairID: c.Params("id"), Timestamp: tdate, BaseAmount: payload.Base, QuoteAmount: payload.Quote, FeeInBase: payload.BFee, FeeInQuote: payload.QFee, Type: payload.TType, } resp, err := tpm.RecordTrade(req) if err != nil { return c.Render("toastErr", fiber.Map{ "Title": "Error", "Msg": err, }) } c.Append("HX-Trigger", "newTrade") return c.Render("toastOk", fiber.Map{ "Title": "Created", "Msg": resp.Msg, "Extra": resp.RecordTime.Format("15h 04m 05s"), }) } } func tradingTable(tpq querying.PairsQuerier) func(*fiber.Ctx) error { return func(c *fiber.Ctx) error { id := c.Params("id") req := querying.GetPairReq{ TPID: id, WithTrades: true, WithCalculations: false, } resp, err := tpq.GetPair(req) if err != nil { return c.Render("toastErr", fiber.Map{ "Title": "Error", "Msg": err, }) } // TODO: Should this happen here? or in the app layer ? slices.Reverse(resp.Pair.Trades) slog.Info("Pair Section requested", "Pair", resp.Pair.ID) c.Append("HX-Trigger", "refreshTrade") return c.Render("tradingTable", fiber.Map{ "Today": time.Now().Format(time.UnixDate), "TodayShort": time.Now().Format("02/01/2006"), "Pair": resp.Pair, }) } } func tradingExport(tpq querying.PairsQuerier) func(*fiber.Ctx) error { return func(c *fiber.Ctx) error { id := c.Params("id") req := querying.GetPairReq{ TPID: id, WithTrades: true, WithCalculations: false, } resp, err := tpq.GetPair(req) if err != nil { return c.SendString(fmt.Sprintf("Error exporting trades. %s", err)) } file := "TradeType,Timestamp,BaseAmount,QuoteAmount,FeeInBase,FeeInQuote\n" for _, t := range resp.Pair.Trades { file += fmt.Sprintf("%s,%s,%f,%f,%f,%f\n", t.TradeType, t.Timestamp.Format("2006-01-02 15:04"), t.BaseAmount, t.QuoteAmount, t.FeeInBase, t.FeeInQuote) } c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=export_%s_%s.csv", resp.Pair.BaseAsset.Symbol, resp.Pair.QuoteAsset.Symbol)) c.Set("Content-Type", "application/octet-stream") // Send the string as a response return c.SendString(file) } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/contre95/controtto'

If you have feedback or need assistance with the MCP directory API, please join our Discord server