Skip to content

laustindasauce/tasty-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

58 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tasty-go

Go Reference GitHub go.mod Go version Go Report Card codecov

This library provides unofficial Go clients for tastytrade API.

You will need to opt into tastytrade's API here

tastytrade

tastytrade pioneered options trading technology for retail traders.

Create your account if you don't already have one to begin trading with tastytrade.

Dependencies

There are very few direct dependencies for this lightweight API wrapper.

Untested endpoints

  • Order reconfirm
    • tastytrade API support has informed me that this endpoint is for Equity Offering orders only.

Installation

go get github.com/austinbspencer.com/tasty-go

Example Usage

Simple usage to get you started.

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient = http.Client{Timeout: time.Duration(30) * time.Second}
	client  *tasty.Client
)

var certCreds = tasty.LoginInfo{Login: os.Getenv("certUsername"), Password: os.Getenv("certPassword")}

func main() {
	client, _ = tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	accounts, err := client.GetMyAccounts()
	if err != nil {
		log.Fatal(err)
	}

	balances, err := client.GetAccountBalances(accounts[0].AccountNumber)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(balances.CashBalance)
}

Basic API Usage

Check out tastytrade's documentation

Auth Patterns (Token, session lifetime)

docs

  • Create / validate / create from remember token
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	_, err = client.ValidateSession()
	if err != nil {
		_, err = client.
			CreateSession(tasty.LoginInfo{
				Login:    client.Session.User.Email,
				Password: *client.Session.RememberToken,
			}, nil)
		if err != nil {
			log.Fatal(err)
		}
	}

	fmt.Println("Session is valid")

	// Destroy the session
	err = client.DestroySession()
	if err != nil {
		log.Fatal(err)
	}
}
User Management

docs

Password Reset

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	err = client.RequestPasswordResetEmail(client.Session.User.Email)
	if err != nil {
		log.Fatal(err)
	}

	// You will get an email with a reset link after the above request
	// This link will have a token in the query
	// https://developer.tastytrade.com/password/reset/?token=this-is-your-token

	// Attach the token along with new password in change request
	// Password change will invalidate all current sessions
	err = client.ChangePassword(tasty.PasswordReset{
		Password:             "newPassword",
		PasswordConfirmation: "newPassword",
		ResetPasswordToken:   "this-is-your-token",
	})
	if err != nil {
		log.Fatal(err)
	}
}
Customer Account Information

docs

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	accounts, err := client.GetMyAccounts()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("I have access to %d accounts!", len(accounts))
}
Account Positions

View all current account positions

docs

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	positions, err := client.GetAccountPositions(accountNumber, tasty.AccountPositionQuery{})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("You have %d positions on your account!", len(positions))
}
Account Balances

docs

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	balances, err := client.GetAccountBalances(accountNumber)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Your account %s has a cash balance of %f.", balances.AccountNumber, balances.CashBalance)
}
Watchlists

docs

Public Watchlists

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	countsOnly := false

	watchlists, err := client.GetPublicWatchlists(countsOnly)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("There are %d public watchlists!", len(watchlists))
}
Instruments

docs and Open API Spec

Equity Options

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	eoSymbol := tasty.EquityOptionsSymbology{
		Symbol:     "AMD",
		OptionType: tasty.Call,
		Strike:     180,
		Expiration: time.Date(2023, 06, 23, 0, 0, 0, 0, time.UTC),
	}

	equityOptions, err := client.GetEquityOptions(tasty.EquityOptionsQuery{Symbols: []string{eoSymbol.Build()}})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Your equity option with underlying symbol: %s", equityOptions[0].UnderlyingSymbol)
}

Future Options

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	future := tasty.FutureSymbology{ProductCode: "ES", MonthCode: tasty.December, YearDigit: 9}

	expiry := time.Date(2019, 9, 27, 0, 0, 0, 0, time.Local)
	fcc := tasty.FutureOptionsSymbology{
		OptionContractCode: "EW4U9",
		FutureContractCode: future.Build(),
		OptionType:         tasty.Put,
		Strike:             2975,
		Expiration:         expiry,
	}

	query := tasty.FutureOptionsQuery{
		Symbols: []string{fcc.Build()},
	}

	futureOptions, err := client.GetFutureOptions(query)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Your future option with underlying symbol: %s", futureOptions[0].UnderlyingSymbol)
}
Transaction History All transactions impacting an accounts balances or positions are available at this endpoint.

docs

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	transactions, _, err := client.GetAccountTransactions(accountNumber, tasty.TransactionsQuery{PerPage: 2})
	if err != nil {
		log.Fatal(err)
	}

	latest := transactions[0]

	fmt.Printf("Your latest transaction was a %s of %s!", latest.TransactionType, latest.UnderlyingSymbol)
}

With Pagination Handling

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	query := tasty.TransactionsQuery{PerPage: 25}

	transactions, pagination, err := client.GetAccountTransactions(accountNumber, query)
	if err != nil {
		log.Fatal(err)
	}

	for pagination.PageOffset < (pagination.TotalPages - 1) {
		query.PageOffset += 1
		moreTransactions, newPagination, err := client.GetAccountTransactions(accountNumber, query)
		if err != nil {
			log.Fatal(err)
		}

		transactions = append(transactions, moreTransactions...)
		pagination = newPagination
	}

	latest := transactions[0]

	fmt.Printf("Your latest transaction was a %s of %s!", latest.TransactionType, latest.UnderlyingSymbol)
}

Order Management

Check out tastytrade's documentation

Search Orders

docs

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	// Query for narrowing search of orders
	query := tasty.OrdersQuery{Status: []tasty.OrderStatus{tasty.Filled}}

	orders, _, err := client.GetAccountOrders(accountNumber, query)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Your account has %d live orders!", len(orders))
}
Search Orders

docs

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	liveOrders, err := client.GetAccountLiveOrders(accountNumber)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Your account has %d live orders!", len(liveOrders))
}
Order Dry Run

docs

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	symbol := "AMD"
	quantity := 1
	action := tasty.BTO

	order := tasty.NewOrder{
		TimeInForce: tasty.Day,
		OrderType:   tasty.Market,
		Legs: []tasty.NewOrderLeg{
			{
				InstrumentType: tasty.EquityIT,
				Symbol:         symbol,
				Quantity:       quantity,
				Action:         action,
			},
		},
	}

	resp, orderErr, err := client.SubmitOrderDryRun(accountNumber, order)
	if err != nil {
		log.Fatal(err)
	} else if orderErr != nil {
		log.Fatal(orderErr)
	}

	fmt.Printf("Your dry run order status is %s!", resp.Order.Status)
}
Submit Order

docs

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	symbol := "RIVN"
	quantity := 1
	action1 := tasty.BTC

	symbol1 := tasty.EquityOptionsSymbology{
		Symbol:     symbol,
		OptionType: tasty.Call,
		Strike:     15,
		Expiration: time.Date(2023, 6, 23, 0, 0, 0, 0, time.Local),
	}

	order := tasty.NewOrder{
		TimeInForce: tasty.GTC,
		OrderType:   tasty.Limit,
		PriceEffect: tasty.Debit,
		Price:       0.04,
		Legs: []tasty.NewOrderLeg{
			{
				InstrumentType: tasty.EquityOptionIT,
				Symbol:         symbol1.Build(),
				Quantity:       quantity,
				Action:         action1,
			},
		},
		Rules: tasty.NewOrderRules{Conditions: []tasty.NewOrderCondition{
			{
				Action:         tasty.Route,
				Symbol:         symbol,
				InstrumentType: "Equity",
				Indicator:      tasty.Last,
				Comparator:     tasty.LTE,
				Threshold:      0.01,
			},
		}},
	}

	resp, orderErr, err := client.SubmitOrder(accountNumber, order)
	if err != nil {
		log.Fatal(err)
	} else if orderErr != nil {
		log.Fatal(orderErr)
	}

	fmt.Printf("Your order with id: %d has a status of %s!", resp.Order.ID, resp.Order.Status)
}
Cancel Order

docs

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"
const orderID = 123456

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	if _, err := client.CancelOrder(accountNumber, orderID); err != nil {
		log.Fatal(err)
	}

	fmt.Println("Order has been cancelled!")
}
Cancel Replace

docs

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	orderID := 68678

	orderECR := tasty.NewOrderECR{
		TimeInForce: tasty.Day,
		Price:       185.45,
		OrderType:   tasty.Limit,
		PriceEffect: tasty.Debit,
		ValueEffect: tasty.Debit,
	}

	newOrder, err := client.ReplaceOrder(accountNumber, orderID, orderECR)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Your order was replaced with order with id: %d has a status of %s!", newOrder.ID, newOrder.Status)
}
Examples

docs

Market Order

order := tasty.NewOrder{
	TimeInForce: tasty.Day,
	OrderType:   tasty.Market,
	Legs: []tasty.NewOrderLeg{
		{
			InstrumentType: tasty.EquityIT,
			Symbol: "AMD",
			Quantity: 1,
			Action: tasty.BTO,
		},
	},
}

GTC Closing Order

order := tasty.NewOrder{
	TimeInForce: tasty.GTC,
	Price: 150.25,
	PriceEffect: tasty.Credit,
	OrderType:   tasty.Limit,
	Legs: []tasty.NewOrderLeg{
		{
			InstrumentType: tasty.EquityIT,
			Symbol: "AMD",
			Quantity: 1,
			Action: tasty.STC,
		},
	},
}

Short Futures Limit Order

order := tasty.NewOrder{
	TimeInForce: tasty.Day,
	Price: 90.03,
	PriceEffect: tasty.Credit,
	OrderType:   tasty.Limit,
	Legs: []tasty.NewOrderLeg{
		{
			InstrumentType: tasty.FutureIT,
			Symbol: "/CLZ2",
			Quantity: 1,
			Action: tasty.STO,
		},
	},
}

Bear Call Spread

eoSymbolShort := tasty.EquityOptionsSymbology{
	Symbol:     "AMD",
	OptionType: tasty.Call,
	Strike:     185,
	Expiration: time.Date(2023, 06, 23, 0, 0, 0, 0, time.UTC),
}

eoSymbolLong := tasty.EquityOptionsSymbology{
	Symbol:     "AMD",
	OptionType: tasty.Call,
	Strike:     187.5,
	Expiration: time.Date(2023, 06, 23, 0, 0, 0, 0, time.UTC),
}

order := tasty.NewOrder{
	TimeInForce: tasty.Day,
	Price:       0.85,
	PriceEffect: tasty.Credit,
	OrderType:   tasty.Limit,
	Legs: []tasty.NewOrderLeg{
		{
			InstrumentType: tasty.EquityOptionIT,
			Symbol:         eoSymbolShort.Build(),
			Quantity:       1,
			Action:         tasty.STO,
		},
		{
			InstrumentType: tasty.EquityOptionIT,
			Symbol:         eoSymbolLong.Build(),
			Quantity:       1,
			Action:         tasty.BTO,
		},
	},
}

GTD Order

order := tasty.NewOrder{
	TimeInForce: tasty.GTD,
	GtcDate:     "2023-06-23",
	Price:       0.85,
	PriceEffect: tasty.Credit,
	OrderType:   tasty.Limit,
	Legs: []tasty.NewOrderLeg{
		{
			InstrumentType: tasty.EquityIT,
			Symbol:         "AMD",
			Quantity:       1,
			Action:         tasty.BTO,
		},
	},
}

Stop Limit Order

order := tasty.NewOrder{
	TimeInForce: tasty.Day,
	Price:       180.0,
	PriceEffect: tasty.Debit,
	OrderType:   tasty.Limit,
	StopTrigger: 180.0,
	Legs: []tasty.NewOrderLeg{
		{
			InstrumentType: tasty.EquityIT,
			Symbol:         "AMD",
			Quantity:       1,
			Action:         tasty.BTO,
		},
	},
}

Notional Cryptocurrency Order

order := tasty.NewOrder{
	TimeInForce: tasty.GTC,
	OrderType:   tasty.NotionalMarket,
	Value:       10.0,
	ValueEffect: tasty.Debit,
	Legs: []tasty.NewOrderLeg{
		{
			InstrumentType: tasty.Crypto,
			Symbol:         string(tasty.Bitcoin),
			Action:         tasty.BTO,
		},
	},
}
Example Order Requests

Tastytrade only supports fractional trading of certain equity products.

  • To determine if an equity can be fractionally traded, fetch the equity instrument and check the is-fractional-quantity-eligible field

Check out tastytrade's documentation

Fractional Quantity Order

// Fractional orders must have a minimum monetary value of $5.
// Buy orders for 0.5 shares of a $1 stock will be rejected.
order := tasty.NewOrder{
	TimeInForce: tasty.Day,
	OrderType:   tasty.Market,
	Legs: []tasty.NewOrderLeg{
		{
			InstrumentType: tasty.EquityIT,
			Symbol:         "AMD",
			Quantity:       0.5,
			Action:         tasty.BTO,
		},
	},
}

Notional Amount Order

// To buy $10 of AMD stock, submit a Notional Market order with a value
// instead of a price. Omit the quantity field from the legs:
order := tasty.NewOrder{
	TimeInForce: tasty.Day,
	OrderType:   tasty.NotionalMarket,
	Value: 10.0,
	ValueEffect: tasty.Debit,
	Legs: []tasty.NewOrderLeg{
		{
			InstrumentType: tasty.EquityIT,
			Symbol:         "AMD",
			Action:         tasty.BTO,
		},
	},
}

Streaming Market Data

Check out tastytrade's documentation

Get a Streamer Token

This requires using the DXFeed Streamer which isn't supported by tastytrade or this unofficial tastytrade API wrapper.

Check out tastytrade's documentation

package main

import (
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client, _ := tasty.NewCertClient(&hClient)
	_, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	dxFeedData, err := client.GetQuoteStreamerTokens()
	if err != nil {
		log.Fatal(err)
	}

	// Do something with the streamer data
}

Streaming Account Data

Check out tastytrade's documentation

Simple Websocket Account Streamer

This is an oversimplified websocket connection example for streaming account data

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/austinbspencer/tasty-go"
	"golang.org/x/net/websocket"
)

var (
	hClient   = http.Client{Timeout: time.Duration(30) * time.Second}
	certCreds = tasty.LoginInfo{
		Login:      os.Getenv("certUsername"),
		Password:   os.Getenv("certPassword"),
		RememberMe: true,
	}
)

const accountNumber = "5WV48989"

func main() {
	client := tasty.NewCertClient(&hClient)
	_, _, err := client.CreateSession(certCreds, nil)
	if err != nil {
		log.Fatal(err)
	}

	protocol := ""
	origin := "http://localhost:8080"

	// Open Websocket connection
	ws, err := websocket.Dial(client.GetWebsocketURL(), protocol, origin)
	if err != nil {
		log.Fatal(err)
	}

	incomingMessages := make(chan string)
	go readClientMessages(ws, incomingMessages)

	// Send connect message
	response := new(tasty.WebsocketMessage)
	response.Action = "connect"
	response.Value = []string{accountNumber}
	response.AuthToken = *client.Session.SessionToken
	err = websocket.JSON.Send(ws, response)
	if err != nil {
		fmt.Printf("Send failed: %s\n", err.Error())
		os.Exit(1)
	}

	// Subscribe to notifications
	// Add notification subscription message here
	// All available -> https://developer.tastytrade.com/streaming-account-data/#available-actions

	// Await responses and send heartbeats
	i := 0
	for {
		select {
		case <-time.After(time.Duration(time.Second * 15)):
			// Send heartbeat every 15 seconds to keep connection alive
			fmt.Println("sending heartbeat")
			i++
			response := new(tasty.WebsocketMessage)
			response.Action = "heartbeat"
			response.AuthToken = *client.Session.SessionToken
			err = websocket.JSON.Send(ws, response)
			if err != nil {
				fmt.Printf("Send failed: %s\n", err.Error())
				os.Exit(1)
			}
		case message := <-incomingMessages:
			fmt.Println(`Message Received:`, message)
		}
	}
}

func readClientMessages(ws *websocket.Conn, incomingMessages chan string) {
	for {
		var message string
		err := websocket.Message.Receive(ws, &message)
		if err != nil {
			fmt.Printf("Error::: %s\n", err.Error())
			return
		}
		incomingMessages <- message
	}
}

Testing

Nearly 100% code coverage testing.

Run all tests

go test .

Run all tests with code coverage information

go test -race -covermode=atomic -coverprofile=coverage.out -v .

Contributing

Please consider opening an issue if you notice any bugs or areas of possible improvement. You can also fork this repo and open a pull request with your own changes. Be sure that all changes have adequate testing in a similar fashion to the rest of the repository.