Skip to main content
Glama

Controtto

by contre95
trading212.go10.8 kB
package markets import ( "controtto/src/domain/pnl" "encoding/json" "fmt" "io" "net/http" "time" ) // Trading212API is a client for the Trading 212 API type Trading212API struct { baseURL string apiKey string httpClient *http.Client } // NewTrading212API creates a new Trading212API client func NewTrading212API(apiKey string) pnl.MarketAPI { baseURL := "https://live.trading212.com" // if isDemo { // baseURL = "https://demo.trading212.com" // } return &Trading212API{ baseURL: baseURL, apiKey: apiKey, httpClient: &http.Client{ Timeout: time.Second * 10, }, } } func (t *Trading212API) HealthCheck() bool { // return true _, err := t.AccountDetails() return err == nil } // Buy places a buy market order (Trading 212 API doesn't seem to have a direct buy/sell with amount in quote currency) func (t *Trading212API) Buy(options pnl.TradeOptions) (*pnl.Trade, error) { // if options.Amount <= 0 { // return nil, errors.New("amount must be greater than zero") // } // // marketRequest := MarketRequest{ // Quantity: options.Amount, // Ticker: string(options.Pair.BaseAsset.Symbol + "_" + options.Pair.QuoteAsset.Symbol), // Assuming pnl.Pair can be directly used as Ticker // } // // order, err := t.placeMarketOrder(marketRequest) // if err != nil { // return nil, err // } // // // Adapt Trading212 Order to pnl.Trade (some fields might not directly map) // return &pnl.Trade{ // ID: strconv.FormatInt(order.ID, 10), // Timestamp: order.CreationTime, // BaseAmount: order.FilledQuantity, // Assuming filled quantity is the base amount // QuoteAmount: order.FilledValue, // Assuming filled value is the quote amount // FeeInBase: 0, // Fees might not be directly available in this call // FeeInQuote: 0, // TradeType: "Buy", // Price: 0, // Price is not directly returned in the place market order, might need to fetch fills // }, nil panic("not implemented") } // Sell places a sell market order (Trading 212 API doesn't seem to have a direct buy/sell with amount in quote currency) func (t *Trading212API) Sell(options pnl.TradeOptions) (*pnl.Trade, error) { // if options.Amount <= 0 { // return nil, errors.New("amount must be greater than zero") // } // // marketRequest := MarketRequest{ // Quantity: options.Amount, // Ticker: string(options.Pair.BaseAsset.Symbol + "_" + options.Pair.QuoteAsset.Symbol), // Assuming pnl.Pair can be directly used as Ticker // } // // order, err := t.placeMarketOrder(marketRequest) // if err != nil { // return nil, err // } // // // Adapt Trading212 Order to pnl.Trade (some fields might not directly map) // return &pnl.Trade{ // ID: strconv.FormatInt(order.ID, 10), // Timestamp: order.CreationTime, // BaseAmount: order.FilledQuantity, // Assuming filled quantity is the base amount // QuoteAmount: order.FilledValue, // Assuming filled value is the quote amount // FeeInBase: 0, // Fees might not be directly available in this call // FeeInQuote: 0, // TradeType: "Sell", // Price: 0, // Price is not directly returned in the place market order, might need to fetch fills // }, nil panic("not implemented") } // ImportTrades fetches historical order data from Trading 212 API func (t *Trading212API) ImportTrades(tradingPair pnl.Pair, since time.Time) ([]pnl.Trade, error) { // endpoint := fmt.Sprintf("%s/api/v0/equity/history/orders", t.baseURL) // req, err := http.NewRequest("GET", endpoint, nil) // if err != nil { // return nil, err // } // req.Header.Set("Authorization", t.apiKey) // // // Add query parameters for ticker (tradingPair) if provided // if tradingPair.ID != "" { // q := req.URL.Query() // q.Add("ticker", string(tradingPair.BaseAsset.Symbol+"_"+tradingPair.QuoteAsset.Symbol)) // req.URL.RawQuery = q.Encode() // } // // resp, err := t.httpClient.Do(req) // if err != nil { // return nil, err // } // defer resp.Body.Close() // // if resp.StatusCode != http.StatusOK { // bodyBytes, _ := io.ReadAll(resp.Body) // return nil, fmt.Errorf("failed to fetch historical orders, status: %d, body: %s", resp.StatusCode, string(bodyBytes)) // } // // var paginatedResponse PaginatedResponseHistoricalOrder // err = json.NewDecoder(resp.Body).Decode(&paginatedResponse) // if err != nil { // return nil, err // } // // var trades []pnl.Trade // for _, order := range paginatedResponse.Items { // // Basic mapping, more details might be available in other endpoints or require further processing // tradeType := pnl.Buy // Default to buy, need logic to determine from order details // if order.OrderedQuantity < 0 || (order.Type == "MARKET" && order.FilledQuantity < 0) { // tradeType = pnl.Sell // } // // trades = append(trades, pnl.Trade{ // ID: strconv.FormatInt(order.FillId, 10), // Using FillId as a potential unique trade ID // Timestamp: order.DateExecuted, // BaseAmount: abs(order.FilledQuantity), // QuoteAmount: abs(order.FillCost), // FeeInBase: 0, // Fees are in a separate Taxes field // FeeInQuote: 0, // TradeType: tradeType, // Price: order.FillPrice, // }) // } // // return trades, nil panic("not implemented") } func abs(f float64) float64 { if f < 0 { return -f } return f } // AccountDetails fetches account metadata (specifically looking for an email-like identifier) func (t *Trading212API) AccountDetails() (string, error) { endpoint := fmt.Sprintf("%s/api/v0/equity/account/info", t.baseURL) req, err := http.NewRequest("GET", endpoint, nil) if err != nil { return "", err } req.Header.Set("Authorization", t.apiKey) resp, err := t.httpClient.Do(req) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(resp.Body) return "", fmt.Errorf("failed to fetch account info, status: %d, body: %s", resp.StatusCode, string(bodyBytes)) } var account Account err = json.NewDecoder(resp.Body).Decode(&account) if err != nil { return "", err } fmt.Println(fmt.Sprintf("Account: %d", account.ID), nil) return fmt.Sprintf("Account: %d", account.ID), nil } // FetchAssetAmount is not directly supported by the Trading 212 API in a simple way. // It would likely require fetching the portfolio and filtering by symbol. func (t *Trading212API) FetchAssetAmount(symbol string) (float64, error) { endpoint := fmt.Sprintf("%s/api/v0/equity/portfolio", t.baseURL) req, err := http.NewRequest("GET", endpoint, nil) if err != nil { return 0, err } req.Header.Set("Authorization", t.apiKey) resp, err := t.httpClient.Do(req) if err != nil { return 0, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(resp.Body) return 0, fmt.Errorf("failed to fetch portfolio, status: %d, body: %s", resp.StatusCode, string(bodyBytes)) } var positions []Position err = json.NewDecoder(resp.Body).Decode(&positions) if err != nil { return 0, err } for _, pos := range positions { if pos.Ticker == symbol { return pos.Quantity, nil } } return 0, nil // pnl.Asset not found in portfolio } // placeMarketOrder makes the API call to place a market order func (t *Trading212API) placeMarketOrder(request MarketRequest) (*Order, error) { panic("not implemented") } // --- Data Structures based on the OpenAPI specification --- // Account schema type Account struct { CurrencyCode string `json:"currencyCode"` ID int64 `json:"id"` } // Cash schema type Cash struct { Blocked float64 `json:"blocked"` Free float64 `json:"free"` Invested float64 `json:"invested"` PieCash float64 `json:"pieCash"` PPL float64 `json:"ppl"` Result float64 `json:"result"` Total float64 `json:"total"` } // HistoricalOrder schema type HistoricalOrder struct { DateCreated time.Time `json:"dateCreated"` DateExecuted time.Time `json:"dateExecuted"` DateModified time.Time `json:"dateModified"` Executor string `json:"executor"` FillCost float64 `json:"fillCost"` FillId int64 `json:"fillId"` FillPrice float64 `json:"fillPrice"` FillResult float64 `json:"fillResult"` FillType string `json:"fillType"` FilledQuantity float64 `json:"filledQuantity"` FilledValue float64 `json:"filledValue"` ID int64 `json:"id"` LimitPrice float64 `json:"limitPrice"` OrderedQuantity float64 `json:"orderedQuantity"` OrderedValue float64 `json:"orderedValue"` ParentOrder int64 `json:"parentOrder"` Status string `json:"status"` StopPrice float64 `json:"stopPrice"` Taxes []Tax `json:"taxes"` Ticker string `json:"ticker"` TimeValidity string `json:"timeValidity"` Type string `json:"type"` } // PaginatedResponseHistoricalOrder schema type PaginatedResponseHistoricalOrder struct { Items []HistoricalOrder `json:"items"` NextPagePath string `json:"nextPagePath"` } // Order schema type Order struct { CreationTime time.Time `json:"creationTime"` FilledQuantity float64 `json:"filledQuantity"` FilledValue float64 `json:"filledValue"` ID int64 `json:"id"` LimitPrice float64 `json:"limitPrice"` Quantity float64 `json:"quantity"` Status string `json:"status"` StopPrice float64 `json:"stopPrice"` Strategy string `json:"strategy"` Ticker string `json:"ticker"` Type string `json:"type"` Value float64 `json:"value"` } // MarketRequest schema type MarketRequest struct { Quantity float64 `json:"quantity"` Ticker string `json:"ticker"` } // Position schema type Position struct { AveragePrice float64 `json:"averagePrice"` CurrentPrice float64 `json:"currentPrice"` Frontend string `json:"frontend"` FxPpl float64 `json:"fxPpl"` InitialFillDate time.Time `json:"initialFillDate"` MaxBuy float64 `json:"maxBuy"` MaxSell float64 `json:"maxSell"` PieQuantity float64 `json:"pieQuantity"` Ppl float64 `json:"ppl"` Quantity float64 `json:"quantity"` Ticker string `json:"ticker"` } // Tax schema type Tax struct { FillId string `json:"fillId"` Name string `json:"name"` Quantity float64 `json:"quantity"` TimeCharged time.Time `json:"timeCharged"` } // --- Other Schemas (only including those potentially used) --- // PlaceOrderError schema type PlaceOrderError struct { Clarification string `json:"clarification"` Code string `json:"code"` }

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