Skip to content

Commit

Permalink
etrade copying skeleton
Browse files Browse the repository at this point in the history
  • Loading branch information
capture120 committed Apr 18, 2024
1 parent 5e874b0 commit 5c38d68
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 1 deletion.
4 changes: 3 additions & 1 deletion backend/src/models/etrade.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package models

import "backend/src/types"
import (
"backend/src/types"
)

type OAuthTokens struct {
types.Model
Expand Down
72 changes: 72 additions & 0 deletions backend/src/services/etrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"time"

"github.com/gomodule/oauth1/oauth"
Expand Down Expand Up @@ -337,6 +338,77 @@ func (s *ETradeService) GetUserPortfolio(userID string) (models.UserPortfolio, e
return portfolio, nil
}

// PlaceOrder places an order on the E*Trade API
func (s *ETradeService) PlaceOrder(userID string, order types.Order) error {
oauthTokens, err := s.getLastOAuthTokens(userID)
if err != nil {
return fmt.Errorf("error getting oauth token: %s", err)
}

client := newJSONClient()

// Create the required order details structure
orderDetails := types.PreviewOrderRequest{
ClientOrderID: fmt.Sprintf("%d", time.Now().UnixMilli()), // Use unique client order ID
Order: []types.OrderEntry{
{
Instrument: []types.Instrument{
{
Product: types.Product{
SecurityType: order.SecurityType, // "EQ" for stocks, "OPTION" for options, etc.
Symbol: order.Symbol,
},
OrderAction: order.Action, // "BUY", "SELL", etc.
QuantityType: types.QuantityType(order.QuantityType), // "QUANTITY", "DOLLARS", etc.
Quantity: order.Quantity,
},
},
OrderTerm: order.OrderTerm, // "GOOD_FOR_DAY", "IMMEDIATE_OR_CANCEL", etc.
MarketSession: order.MarketSession, // "REGULAR", "EXTENDED_HOURS"
PriceType: order.PriceType, // "MARKET", "LIMIT", etc.
StopPrice: order.StopPrice, // Used for stop orders
LimitPrice: order.LimitPrice, // Used for limit orders
AllOrNone: order.AllOrNone, // Boolean
},
},
}

// Serialize orderDetails into JSON
orderData, err := json.Marshal(orderDetails)
if err != nil {
return fmt.Errorf("error serializing order details: %s", err)
}

// Calculate the URL based on order preview vs. actual placement
baseURL := fmt.Sprintf("https://%s.etrade.com/v1/accounts/%s/orders/place", APIEnv, order.AccountID)

// Make the API call using the OAuth credentials
orderValues, err := url.ParseQuery(string(orderData))
if err != nil {
return fmt.Errorf("error parsing order data: %s", err)
}

resp, err := oauthClient.Post(client, &oauth.Credentials{
Token: oauthTokens.AccessToken,
Secret: oauthTokens.AccessSecret,
}, baseURL, orderValues)
if err != nil {
return fmt.Errorf("error sending order to E*Trade: %s", err)
}
defer resp.Body.Close()

// Handle potential API errors
if resp.StatusCode >= 400 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("E*Trade API error: %s", body)
}

// On success, you can extract the order ID from the response for confirmation
// or further tracking.

return nil
}

// newJSONClient is a helper function that creates an HTTP client to interact with the E*Trade API
// not using this causes the E*Trade API to return XML instead of JSON
func newJSONClient() *http.Client {
Expand Down
64 changes: 64 additions & 0 deletions backend/src/types/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,67 @@ type ClerkWebhookEvent struct {
Object string `json:"object"`
Type string `json:"type"`
}

// Represents a trade order
type Order struct {
AccountID string `json:"accountId"`
OrderType string `json:"orderType"`
Symbol string `json:"Product>symbol"`
SecurityType string `json:"Product>securityType"` // "EQ" for equity, "OPTION"
Action string `json:"orderAction"` // "BUY", "SELL"
QuantityType QuantityType `json:"quantityType"` // "QUANTITY", "DOLLARS"
Quantity int `json:"quantity"`
OrderTerm string `json:"orderTerm"` // "GOOD_FOR_DAY", "GOOD_UNTIL_CANCEL"
MarketSession string `json:"marketSession"` // "REGULAR", "EXTENDED_HOURS"
PriceType string `json:"priceType"` // "MARKET", "LIMIT"
LimitPrice float64 `json:"limitPrice"`
StopPrice float64 `json:"stopPrice"`
AllOrNone bool `json:"allOrNone"`
ClientOrderID string `json:"clientOrderId"`
IsPreview bool `json:"isPreview"`
OrderID string `json:"orderId,omitempty"` // Provided by the API after order placement
Status string `json:"status,omitempty"` // "OPEN", "FILLED", "CANCELLED"
PlacedTime time.Time `json:"placedTime,omitempty"`
}

// QuantityType represents the type of quantity used in an order
type QuantityType string

const (
QuantityTypeQuantity QuantityType = "QUANTITY"
QuantityTypeDollars QuantityType = "DOLLARS"
)

// PreviewOrderRequest represents the structure for previewing an order on E*Trade
type PreviewOrderRequest struct {
ClientOrderID string `json:"clientOrderId"`
Order []OrderEntry `json:"Order"`
}

// OrderEntry represents a single order entry with the order details
type OrderEntry struct {
Instrument []Instrument `json:"Instrument"`
OrderAction string `json:"orderAction"`
QuantityType QuantityType `json:"quantityType"`
Quantity int `json:"quantity"`
OrderTerm string `json:"orderTerm"`
MarketSession string `json:"marketSession"`
PriceType string `json:"priceType"`
LimitPrice float64 `json:"limitPrice,omitempty"`
StopPrice float64 `json:"stopPrice,omitempty"`
AllOrNone bool `json:"allOrNone"`
}

// Instrument represents a single financial instrument in an order
type Instrument struct {
Product Product `json:"Product"`
OrderAction string `json:"orderAction"` // Redundant, but sometimes expected by E*Trade APIs
QuantityType QuantityType `json:"quantityType"` // Also possibly redundant
Quantity int `json:"quantity"`
}

// Product represents product details like security type and symbol
type Product struct {
SecurityType string `json:"securityType"`
Symbol string `json:"symbol"`
}
16 changes: 16 additions & 0 deletions frontend/services/etrade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,19 @@ export const getPortoflio = async (id: string): Promise<UserPortfolio> => {
);
return response.data;
}

// export const copyPortfolio = async (id: string, portfolio: UserPortfolio): Promise<HttpStatusCode> => {
// const response: AxiosResponse = await axios.post<UserPortfolio>(
// `http://${API_LINK}/etrade/portfolio/${id}`,
// portfolio,
// );
// return response.status;
// }

export const makeOrder = async (id: string, ticker: string, quantity: number, type: string): Promise<HttpStatusCode> => {
const response: AxiosResponse = await axios.post<UserPortfolio>(
`http://${API_LINK}/etrade/order/${id}`,
{ ticker: ticker, quantity: quantity, type: type },
);
return response.status;
}

0 comments on commit 5c38d68

Please sign in to comment.