Skip to content

Commit

Permalink
Adds API documentation (#20)
Browse files Browse the repository at this point in the history
* Adds API documentation

* Updates readme.md
  • Loading branch information
sneilan authored Nov 22, 2023
1 parent 2474c84 commit eabe946
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 22 deletions.
156 changes: 139 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 💻 Personal C++ Low Latency Stock Exchange

![CI](https://github.com/sneilan/stock-exchange/actions/workflows/tests.yml/badge.svg)

# 💻 Personal C++ Low Latency Stock Exchange

This is a stock exchange that you can run on your laptop or desktop that will process 10's of thousands of trades per second.

I built this as a fun nerdy project to show off my skills. Check out my [linkedin](https://linkedin.com/in/seanneilan)
Expand All @@ -11,14 +11,15 @@ I built this as a fun nerdy project to show off my skills. Check out my [linkedi
* Plug in stock or crypto data and test against that
* Test against slippage and network failures
* Allow trading robots to develop new market patterns and write software to detect them
# Easy install

It uses the same techniques and algorithms as [NASDAQ](https://martinfowler.com/articles/lmax.html) but unoptimized.

Compare to [LMAX exchange](https://lmax-exchange.github.io/disruptor/).

## What is an Exchange?

For reference, a stock exchange is a server that takes buy/sell orders from traders and matches them up. When you open up Robinhood on your phone,
A stock exchange is a server that takes buy/sell orders from traders and matches them up. When you open up Robinhood on your phone,
robinhood takes your order to buy Gamestop and sends it to an exchange called NASDAQ. NASDAQ finds a trader willing to sell you Gamestop and then
Robinhood sends you a notification once that sale is complete. This works vice-versa for sales. If you want to sell that share of Gamestop, Robinhood
sends your request to sell Gamestop to NASDAQ. NASDAQ finds someone willing to buy your share of Gamestop and once someone buys your share, tells Robinhood
Expand All @@ -28,7 +29,7 @@ to tell you!
```
git clone git@github.com:sneilan/stock-exchange.git stock-exchange
cd stock-exchange
docker-compose up
docker compose up
```

The exchange will start up on the default port 8888.
Expand All @@ -53,16 +54,139 @@ Honestly there's a lot of work to do but I hope this becomes the premier stock e

## Protocol

<< TODO >>
The exchange is basic for now. You connect, place trades and recieve notifications about your trades.

### Connect

Open a connection to the server with a socket. Use this in python

```python
import socket

host = '0.0.0.0'
port = 8888
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
response = sock.recv(1024)
print("Connected!")
```

### Authentication

_or lack thereof_

There is no authentication currently.

You will be assigned a User ID however on connection that is not told
to the client except on trade notifications. Your "user id" is the socket number.

The exchange supports up to 30 concurrent clients.
First user to connect will have user id 0, second 1 and so one. If User 1 disconnects and a new user reconnects,
the new user will have user id 1 also. It's not great but it works for a demo. You do not need to authenticate

### What is traded

Currently the exchange trades one unnamed symbol. The name of the symbol
is whatever you want. To trade multiple symbols, start up multiple exchanges.

### Balances

Trade balances are infinite. Wallets and balances will come later.

## Current features
### Risk Controls

No risk controls at the moment. Place as many trades as you like.

### Sending a trade

After connecting, send a trade by submitting 9 bytes.
1. Byte 0: Char - buy or sell with 'b' for buy and 's' for sell.
2. Bytes 1-4 (inclusive) - Price as a positive unsigned integer in pennies.
3. Bytes 5-9 (inclusive) - Quantity as a positive unsigned integer.

Here's an example of sending a buy order in Python for 500 shares at
$1.23 / share. Assuming sock is an open socket to the exchange.

```python
price = 123 # $1.23 in 1 hundred 23 pennies.
quantity = 500
side = 'b' # 's' for sell
message = pack(
'cii',
bytes(side, 'ascii'),
price,
quantity,
)
sock.sendall(message)
```
[x] Connect from internet
[x] Place trades
[x] Get trade fill notifications
[x] Easy install

Server will immediately send a trade notification with the id
of the trade.

### Trade Notifications

As soon as you send a trade, the server will tell you
the trade is recieved with the ID of the trade. Each trade
notification is 21 bytes.
1. Byte 0: Char - Notification type.
'r' is 'recieved'
'u' is 'updated'
'f' is 'filled'
2. Bytes 1-8 (inclusive): Unsigned long long - trade id
3. Bytes 9-12 (inclusive): Unsigned integer - quantity
4. Bytes 13-16 (inclusive): Unsigned integer - filled quantity
5. Bytes 17-20 (inclusive): Unsigned integer - client id
Client id is not important but will tell you what integer 0-30 your "user id" is.

You will always get a recieved notification. The other two notifications to a trade are either updated
or filled.

Here's an example of recieving trade notifications in Python. It assumes that sock is a connected
socket to the server.

```python
from struct import unpack

msg_type_to_msg = {}
msg_type_to_msg['u'] = 'updated'
msg_type_to_msg['f'] = 'filled'
msg_type_to_msg['r'] = 'recieved'
while True:
data = sock.recv(21)
if data:
# c is char
# Q is unsigned long long
# i is 4 byte integer
# I originally tried to use 'cQiii' but unpack would not
# parse the bytes correctly. This works for now.
format_string = 'Qiii'
unpacked_data = unpack(format_string, data[1:])
msg_type = chr(data[0])
message = msg_type_to_msg[msg_type]
id = unpacked_data[0]
quantity = unpacked_data[1]
filled_quantity = unpacked_data[2]
client_id = unpacked_data[3]

print('id', id, 'message', message, 'quantity', quantity, 'filled_quantity', filled_quantity, 'client_id', client_id)
```

Check out scripts/loadTest.py for an example trading client.

You can paste these protocols into Chat GPT and produce trading frontends in your preferred language.

### Market Data

This is not implemented yet. This is a high priority on the roadmap.

### Getting Current Bid / Ask

Not implemented. Very high priority!

### Backups

Not implemented.

## Test
```
docker compose run -it core /app/test
Expand All @@ -73,11 +197,9 @@ Will run all tests automatically.
## TODO
There's a lot (to do)[https://github.com/sneilan/stock-exchange/issues] in creating a low-latency stock exchange from the ground up.

```
[ ] Market data
[ ] Cancel trades
[ ] Authentication / Security
[ ] Journaling (save trades to database)
[ ] Simple trading client / GUI
```
Check out the issue list.

## Contributing

Check out any of the tickets on the issues list or file a new one with a proposal!

7 changes: 2 additions & 5 deletions scripts/loadTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@
side = 'b'

def listener(sock):
while True:
while True:
data = sock.recv(21)
if data:
# c is char
# Q is unsigned long long
# i is 4 byte integer
format_string = 'Qiii'
# import ipdb
# ipdb.set_trace()
unpacked_data = unpack(format_string, data[1:])
msg_type = chr(data[0])
message = ''
Expand Down Expand Up @@ -64,11 +62,10 @@ def listener(sock):
price = random.randrange(100, 1000)
quantity = random.randrange(100, 200, 10)
message = pack(
'ciic',
'cii',
bytes(side, 'ascii'),
price,
quantity,
bytes(str(random.randrange(0, 9)), 'ascii')
)
side_msg = 'buy' if side == 'b' else 'sell'
print(f"Placing {side_msg} for quantity {quantity} at price {price}")
Expand Down

0 comments on commit eabe946

Please sign in to comment.