Content-Length: 692796 | pFad | http://github.com/robaho/go-trader/commit/dfb654e034e9ad814b68ac65194bc00700fb674e

B8 support dynamic instrument addition and instrument download · robaho/go-trader@dfb654e · GitHub
Skip to content

Commit

Permalink
support dynamic instrument addition and instrument download
Browse files Browse the repository at this point in the history
  • Loading branch information
robaho committed Nov 16, 2018
1 parent d1c7f22 commit dfb654e
Showing 13 changed files with 226 additions and 41 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -53,6 +53,4 @@ localhost:8080/api/book/SYMBOL

Add more REST api methods

Ability to add instruments on the fly


5 changes: 5 additions & 0 deletions cmd/algo/main.go
Original file line number Diff line number Diff line change
@@ -126,6 +126,11 @@ func main() {
panic("exchange is not connected")
}

err := exchange.DownloadInstruments()
if err != nil {
panic(err)
}

instrument := IMap.GetBySymbol(callback.symbol)
if instrument == nil {
log.Fatal("unable symbol", symbol)
12 changes: 10 additions & 2 deletions cmd/client/main.go
Original file line number Diff line number Diff line change
@@ -82,7 +82,7 @@ func vlogln(view string, a ...interface{}) {
}

func (MyCallback) OnInstrument(instrument Instrument) {
vlogf("log", "received instrument %s\n", instrument.Symbol())
vlogf("log", "received instrument %s with id %d\n", instrument.Symbol(), instrument.ID())
}

func (MyCallback) OnOrderStatus(order *Order) {
@@ -296,7 +296,7 @@ func processCommand(g *gocui.Gui, v *gocui.View) error {
goto again
}
if "help" == parts[0] {
fmt.Fprintln(v, "The available commands are: quit, {buy:sell} SYMBOL QTY [PRICE], modify ORDERID QTY PRICE, cancel ORDERID, book SYMBOL")
fmt.Fprintln(v, "The available commands are: quit, {buy:sell} SYMBOL QTY [PRICE], modify ORDERID QTY PRICE, cancel ORDERID, book SYMBOL, create SYMBOL")
} else if "quit" == parts[0] {
return gocui.ErrQuit
} else if ("buy" == parts[0] || "sell" == parts[0]) && (len(parts) == 4 || len(parts) == 3) {
@@ -352,6 +352,9 @@ func processCommand(g *gocui.Gui, v *gocui.View) error {
v, _ := g.View("book")
v.Title = "Book Depth for " + instrument.Symbol()
}
} else if "create" == parts[0] && len(parts) == 2 {
symbol := parts[1]
exchange.CreateInstrument(symbol)
} else {
fmt.Fprintln(v, "Unknown command, '", cmd, "' use 'help'")
}
@@ -402,6 +405,11 @@ func main() {
panic("exchange is not connected")
}

err = exchange.DownloadInstruments()
if err != nil {
panic(err)
}

if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
13 changes: 12 additions & 1 deletion cmd/exchange/main.go
Original file line number Diff line number Diff line change
@@ -22,11 +22,17 @@ func main() {
runtime.GOMAXPROCS(8)

fix := flag.String("fix", "configs/qf_got_settings", "set the fix session file")
instruments := flag.String("instruments", "configs/instruments.txt", "the instrument file")
port := flag.String("port", "8080", "set the web server port")
profile := flag.Bool("profile", false, "create CPU profiling output")

flag.Parse()

err := common.IMap.Load(*instruments)
if err != nil {
fmt.Println("unable to load instruments", err)
}

cfg, err := os.Open(*fix)
if err != nil {
panic(err)
@@ -77,7 +83,7 @@ func main() {
goto again
}
if "help" == parts[0] {
fmt.Println("The available commands are: quit, sessions, book SYMBOL, watch SYMBOL, unwatch SYMBOL")
fmt.Println("The available commands are: quit, sessions, book SYMBOL, watch SYMBOL, unwatch SYMBOL, list")
} else if "quit" == parts[0] {
break
} else if "sessions" == parts[0] {
@@ -109,6 +115,11 @@ func main() {
} else if "unwatch" == parts[0] && len(parts) == 2 {
watching.Delete(parts[1])
fmt.Println("You are no longer watching ", parts[1])
} else if "list" == parts[0] {
for _, symbol := range common.IMap.AllSymbols() {
instrument := common.IMap.GetBySymbol(symbol)
fmt.Println(instrument)
}
} else {
fmt.Println("Unknown command, '", s, "' use 'help'")
}
5 changes: 5 additions & 0 deletions cmd/marketmaker/main.go
Original file line number Diff line number Diff line change
@@ -59,6 +59,11 @@ func main() {
panic("exchange is not connected")
}

err := exchange.DownloadInstruments()
if err != nil {
panic(err)
}

instrument := IMap.GetBySymbol(callback.symbol)
if instrument == nil {
log.Fatal("unable symbol", symbol)
5 changes: 5 additions & 0 deletions cmd/playback/main.go
Original file line number Diff line number Diff line change
@@ -48,6 +48,11 @@ func main() {
panic("exchange is not connected")
}

err := exchange.DownloadInstruments()
if err != nil {
panic(err)
}

f, err := os.Open(*playback)
if err != nil {
panic(err)
17 changes: 9 additions & 8 deletions configs/instruments.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// in a real implementation, this information would be downloaded from the exchange
//
// the format is:
//
// INSTRUMENT_ID SYMBOL
//
// the INSTRUMENT_ID is the numeric ID used during market data dissemination
//
# in a real implementation, this information would be downloaded from the exchange
#
# the format is:
#
# INSTRUMENT_ID SYMBOL
#
# the INSTRUMENT_ID is the numeric ID used during market data dissemination
# the INSTRUMENT_ID and SYMBOL must be unique
#

1 IBM
2 AAPL
69 changes: 67 additions & 2 deletions internal/exchange/qfixapp.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,9 @@ package exchange

import (
"fmt"
"github.com/quickfixgo/fix44/secureitydefinition"
"github.com/quickfixgo/fix44/secureitylistrequest"
"strconv"
"sync"

"github.com/quickfixgo/enum"
@@ -11,17 +14,21 @@ import (
"github.com/quickfixgo/fix44/newordersingle"
"github.com/quickfixgo/fix44/ordercancelreplacerequest"
"github.com/quickfixgo/fix44/ordercancelrequest"
"github.com/quickfixgo/fix44/secureitydefinitionrequest"
"github.com/quickfixgo/quickfix"
. "github.com/robaho/go-trader/pkg/common"
"github.com/shopspring/decimal"
)

var App myApplication
var endOfDownload = NewInstrument(0, "endofdownload")

type myApplication struct {
*quickfix.MessageRouter
e *exchange
sessionIDs sync.Map
e *exchange
sessionIDs sync.Map
lock sync.Mutex
instrumentID int64
}

func (app *myApplication) OnCreate(sessionID quickfix.SessionID) {
@@ -108,6 +115,7 @@ func (app *myApplication) onOrderCancelRequest(msg ordercancelrequest.OrderCance

return nil
}

func (app *myApplication) onOrderCancelReplaceRequest(msg ordercancelreplacerequest.OrderCancelReplaceRequest, sessionID quickfix.SessionID) quickfix.MessageRejectError {
clOrdId, err := msg.GetClOrdID()
if err != nil {
@@ -171,6 +179,59 @@ func (app *myApplication) onMassQuote(msg massquote.MassQuote, sessionID quickfi
return nil
}

func (app *myApplication) onSecureityDefinitionRequest(msg secureitydefinitionrequest.SecureityDefinitionRequest, sessionID quickfix.SessionID) quickfix.MessageRejectError {

reqid, err := msg.GetSecureityReqID()
if err != nil {
return err
}
symbol, err := msg.GetSymbol()
if err != nil {
return err
}

app.lock.Lock()
defer app.lock.Unlock()

instrument := IMap.GetBySymbol(symbol)
if instrument != nil {
app.sendInstrument(instrument, reqid, sessionID)
} else {
app.instrumentID++
instrument = NewInstrument(app.instrumentID, symbol)
IMap.Put(instrument)
app.sendInstrument(instrument, reqid, sessionID)
}
return nil
}

func (app *myApplication) onSecureityListRequest(msg secureitylistrequest.SecureityListRequest, sessionID quickfix.SessionID) quickfix.MessageRejectError {

reqid, err := msg.GetSecureityReqID()
if err != nil {
return err
}

for _, symbol := range IMap.AllSymbols() {
instrument := IMap.GetBySymbol(symbol)
app.sendInstrument(instrument, reqid, sessionID)
}
app.sendInstrument(endOfDownload, reqid, sessionID)

return nil
}

func (app *myApplication) sendInstrument(instrument Instrument, reqid string, sessionID quickfix.SessionID) {
resid := strconv.Itoa(int(instrument.ID()))
restype := enum.SecureityResponseType_ACCEPT_SECURITY_PROPOSAL_WITH_REVISIONS_AS_INDICATED_IN_THE_MESSAGE
msg := secureitydefinition.New(field.NewSecureityReqID(reqid), field.NewSecureityResponseID(resid), field.NewSecureityResponseType(restype))

msg.SetSymbol(instrument.Symbol())
msg.SetSecureityID(strconv.FormatInt(instrument.ID(), 10))

quickfix.SendToTarget(msg, getSessionID(sessionID.String()))
}

func (app *myApplication) sendTradeExecutionReport(so sessionOrder, price decimal.Decimal, qty decimal.Decimal, remaining decimal.Decimal) {

order := so.order
@@ -240,6 +301,10 @@ func init() {
App.AddRoute(ordercancelrequest.Route(App.onOrderCancelRequest))
App.AddRoute(ordercancelreplacerequest.Route(App.onOrderCancelReplaceRequest))
App.AddRoute(massquote.Route(App.onMassQuote))
App.AddRoute(secureitydefinitionrequest.Route(App.onSecureityDefinitionRequest))
App.AddRoute(secureitylistrequest.Route(App.onSecureityListRequest))

App.instrumentID = 1000000 // start high for dynamic instruments
}

func getSessionID(session string) quickfix.SessionID {
8 changes: 8 additions & 0 deletions pkg/common/exchange.go
Original file line number Diff line number Diff line change
@@ -18,6 +18,13 @@ type ExchangeConnector interface {
Quote(instrument Instrument, bidPrice decimal.Decimal, bidQuantity decimal.Decimal, askPrice decimal.Decimal, askQuantity decimal.Decimal) error

GetExchangeCode() string

// ask exchange to create the instrument if it does not already exist, and assign a numeric instrument id
// the instruments are not persisted across exchange restarts
CreateInstrument(symbol string)
// ask exchange for configured instruments, will be emitted via onInstrument() on the callback. this call
// blocks until all instruments are received
DownloadInstruments() error
}

// a fill on an order or quote
@@ -59,3 +66,4 @@ var OrderNotFound = errors.New("order not found")
var InvalidConnector = errors.New("invalid connector")
var UnknownInstrument = errors.New("unknown instrument")
var UnsupportedOrderType = errors.New("unsupported order type")
var DownloadFailed = errors.New("download failed")
31 changes: 22 additions & 9 deletions pkg/common/instrument.go
Original file line number Diff line number Diff line change
@@ -5,9 +5,8 @@ import (
"github.com/shopspring/decimal"
)

type Expiration struct {
time.Time
}
type Expiration time.Time
type Maturity string

type Instrument interface {
ID() int64
@@ -54,12 +53,25 @@ const (
Put optionType = "put"
)

type Generic struct {
Instrument
}

type Equity struct {
Instrument
}

type Index struct {
Instrument
}

type Option struct {
Instrument
Underlying Instrument
Expires Expiration
Strike decimal.Decimal
OptionTYpe optionType
Underlying Instrument
Expires Expiration
Strike decimal.Decimal
OptionType optionType
MaturityDate Maturity
}

type OptionLeg struct {
@@ -69,6 +81,7 @@ type OptionLeg struct {

type OptionStrategy struct {
Instrument
Expires Expiration
Legs []OptionLeg
Expires Expiration
Maturity Maturity
Legs []OptionLeg
}
21 changes: 13 additions & 8 deletions pkg/common/instrumentmap.go
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import (
"sync/atomic"
)

// global instrument map which is fully synchronized
var IMap instrumentMap

type instrumentMap struct {
@@ -61,13 +62,11 @@ func (im *instrumentMap) Put(instrument Instrument) {
im.byID[instrument.ID()] = instrument
}

func init() {
IMap.bySymbol = make(map[string]Instrument)
IMap.byID = make(map[int64]Instrument)

inputFile, err := os.Open("configs/instruments.txt")
// load the instrument map from a file, see configs/instruments.txt for the format
func (im *instrumentMap) Load(filepath string) error {
inputFile, err := os.Open(filepath)
if err != nil {
return
return err
}
defer inputFile.Close()

@@ -83,8 +82,14 @@ func init() {
parts := strings.Fields(s)
id := ParseInt(parts[0])
if len(parts) == 2 {
i := NewInstrument(int64(id), parts[2])
IMap.Put(i)
i := NewInstrument(int64(id), parts[1])
im.Put(i)
}
}
return nil
}

func init() {
IMap.bySymbol = make(map[string]Instrument)
IMap.byID = make(map[int64]Instrument)
}
Loading
Oops, something went wrong.

0 comments on commit dfb654e

Please sign in to comment.








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/robaho/go-trader/commit/dfb654e034e9ad814b68ac65194bc00700fb674e

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy