Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanlott committed Jun 28, 2023
1 parent fb709dd commit 4591d04
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 24 deletions.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
test:
go test -race -count 1 -v ./pkg/...

test-benchmark:
go test -benchtime=60s ./pkg/orderbook/...

test-profile:
go test -cpuprofile=prof.out ./pkg/orderbook/...

build-docker:
docker build -t golem .

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/dylanlott/orderbook
go 1.18

require (
github.com/brianvoe/gofakeit/v6 v6.21.0
github.com/labstack/echo/v4 v4.10.2
github.com/matryer/is v1.4.0
github.com/sasha-s/go-deadlock v0.3.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/brianvoe/gofakeit/v6 v6.21.0 h1:tNkm9yxEbpuPK8Bx39tT4sSc5i9SUGiciLdNix+VDQY=
github.com/brianvoe/gofakeit/v6 v6.21.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
Expand Down
16 changes: 13 additions & 3 deletions pkg/accounts/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type AccountManager interface {
Transaction

Get(id string) (Account, error)
Create(id string, acct Account) (Account, error)
Create(id string, balance float64) (Account, error)
Delete(id string) error
}

Expand Down Expand Up @@ -52,6 +52,16 @@ type InMemoryManager struct {
Accounts map[string]*UserAccount
}

func NewAccountManager(path string) AccountManager {
if path == "" {
return &InMemoryManager{
Accounts: make(map[string]*UserAccount),
}
} else {
panic("todo: not impl")
}
}

// Get returns an account
func (i *InMemoryManager) Get(id string) (Account, error) {
i.Lock()
Expand All @@ -63,10 +73,10 @@ func (i *InMemoryManager) Get(id string) (Account, error) {
}

// Create makes a new account
func (i *InMemoryManager) Create(email string, account Account) (Account, error) {
func (i *InMemoryManager) Create(email string, balance float64) (Account, error) {
a := &UserAccount{
Email: email,
CurrentBalance: 0.0,
CurrentBalance: balance,
}
i.Lock()
defer i.Unlock()
Expand Down
2 changes: 1 addition & 1 deletion pkg/orderbook/attempt_fill.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ func greedy(
var numOps = 10_000
var bufferSize = 1000

func TestRun(t *testing.T) {
func TestAttemptFillRun(t *testing.T) {
ctx := context.Background()
wg := &sync.WaitGroup{}

Expand Down
30 changes: 22 additions & 8 deletions pkg/orderbook/orderbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package orderbook

import (
"context"
"log"
"sort"
"time"

Expand Down Expand Up @@ -45,7 +44,9 @@ type Orderbook interface {
Match(buy []Order, sell []Order) []Match
}

// Run starts looping the MatchOrders function.
// Run starts looping the MatchOrders function. It is a blocking function
// and it is meant to completely own the buy and sell lists to prevent
// external modification.
func Run(
ctx context.Context,
accounts accounts.AccountManager,
Expand All @@ -55,6 +56,7 @@ func Run(
) {
// NB: buy and sell are not accessible anywhere but here for safety.
var buy, sell []Order

handleMatches(ctx, accounts, buy, sell, in, out, status)
}

Expand All @@ -69,16 +71,24 @@ func handleMatches(
status chan []Order,
) {
for {
// feed off the orders that accumulated since the last loop
for o := range in {
if o.Side == "buy" {
buy = append(buy, o)
} else {
sell = append(sell, o)
}
}
// create the orderlist for state updates
orderlist := []Order{}
orderlist = append(orderlist, buy...)
orderlist = append(orderlist, sell...)
status <- orderlist

// generate a list of matches and output them
matches := MatchOrders(accts, buy, sell)
for _, match := range matches {
log.Printf("%+v", match)
time.Sleep(delay)
// feed to ouptut
out <- &match
}
}
}
Expand Down Expand Up @@ -107,10 +117,13 @@ func MatchOrders(accts accounts.AccountManager, buyOrders []Order, sellOrders []
for sellIndex < len(sellOrders) {
// Check if the current Buy order matches the current Sell order
if buyOrders[buyIndex].Price >= sellOrders[sellIndex].Price {
// Create a Match of the Buy and Sell side
// Create a match and add it to the matches
sell := &sellOrders[sellIndex]
buy := &buyOrders[buyIndex]
m := Match{
Buy: &buyOrders[buyIndex],
Sell: &sellOrders[sellIndex],
Buy: buy,
Sell: sell,
Price: sell.Price,
}
matches = append(matches, m)
// Increment the Sell order index
Expand All @@ -119,6 +132,7 @@ func MatchOrders(accts accounts.AccountManager, buyOrders []Order, sellOrders []
// Move on to the next Buy order
buyIndex++
}

// Check if there are no more Buy orders left
if buyIndex == len(buyOrders) {
break
Expand Down
75 changes: 66 additions & 9 deletions pkg/orderbook/orderbook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"time"

"github.com/dylanlott/orderbook/pkg/accounts"

"github.com/brianvoe/gofakeit/v6"
)

func TestMatchOrders(t *testing.T) {
Expand Down Expand Up @@ -49,6 +51,28 @@ func BenchmarkAttemptFill(b *testing.B) {
wg.Wait()
}

// newTestAccountManager returns a new account manager and the set of
// ids that it randomly generated.
func newTestAccountManager(t *testing.T, num int) (accounts.AccountManager, []string) {
acct := accounts.NewAccountManager("")
ids := []string{}

for i := 0; i < num; i++ {
email := gofakeit.Email()
balance := gofakeit.Float64()
_, err := acct.Create(email, balance)
ids = append(ids, email)
if err != nil {
t.Error(err)
}
}
return acct, ids
}

// newTestOrders creates a set of buy and sell orders with a random
// price between minPrice and maxPrice, an open quantity between minOpen
// and maxOpen, an equal chance to be owned by foo or bar,
// and with an even chance of being a buy or sell order.
func newTestOrders(count int) (buyOrders []Order, sellOrders []Order) {
log.Printf("count %d", count)
rand.Seed(time.Now().UnixNano())
Expand All @@ -58,17 +82,16 @@ func newTestOrders(count int) (buyOrders []Order, sellOrders []Order) {

for i := 0; i < count; i++ {
o := Order{
ID: fmt.Sprintf("%d", i),
AccountID: "", // TODO: add a random account owner
Kind: "market",
Price: uint64(rand.Intn(maxPrice-minPrice) + minPrice),
Open: uint64(rand.Intn(maxOpen-minOpen) + minOpen),
Filled: 0,
History: []Match{}, // history should be nil
ID: fmt.Sprintf("%d", i),
Kind: "market",
Price: uint64(rand.Intn(maxPrice-minPrice) + minPrice),
Open: uint64(rand.Intn(maxOpen-minOpen) + minOpen),
Filled: 0,
History: []Match{},
}

// half buy, half sell orders
if i%2 == 0 {
randBuy := uint64(rand.Intn(maxPrice-minPrice) + minPrice)
if randBuy%2 == 0 {
o.Side = "buy"
buyOrders = append(buyOrders, o)
} else {
Expand Down Expand Up @@ -105,3 +128,37 @@ func newRandOrder(id, account string) Order {

return o
}

var numTestOrders = 10_000_000
var numTestAccounts = 1_000_000

func TestRunLoad(t *testing.T) {
in := make(chan Order, 1)
out := make(chan *Match, 1)
status := make(chan []Order, 1)

// Generate default random accounts for testing
accts, ids := newTestAccountManager(t, numTestAccounts)

// Start the server
go Run(context.Background(), accts, in, out, status)

// Consume the status updates
go func() {
for e := range status {
t.Logf("\ncurrent orders: %v\n", e)
}
}()

// Generate test orders
buy, sell := newTestOrders(numTestOrders)

for _, o := range buy {
o.AccountID = gofakeit.RandomString(ids)
in <- o
}
for _, o := range sell {
o.AccountID = gofakeit.RandomString(ids) // assign to a random account last of all
in <- o
}
}
17 changes: 14 additions & 3 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,25 @@ func NewServer(

e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
return c.JSON(http.StatusOK, map[string]interface{}{
"name": "orderbook",
"version": "0.1",
})
})
e.GET("/orders", func(c echo.Context) error {
return c.String(http.StatusInternalServerError, "not impl!")
return c.JSON(http.StatusOK, engine.state)
})

e.POST("/orders", func(c echo.Context) error {
return c.String(http.StatusInternalServerError, "not impl!")
o := new(orderbook.Order)
if err := c.Bind(o); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
e.Logger.Infof("order received: %+v", o)
engine.in <- *o
return nil
})

e.DELETE("/orders", func(c echo.Context) error {
return c.String(http.StatusInternalServerError, "not impl!")
})
Expand Down

0 comments on commit 4591d04

Please sign in to comment.