On my 20-hour flight back to China, I created a simple financial exchange in python which multiple traders can connect to and place limit order on. This exchange will carry a basic order book, in order to process new orders and keep track of existing orders. The codes are uploaded to this repo.

Socket.Socket

The backbone of this exercise is built on the communication between an exchange (server) and a trader (client). The python socket library is used. Here we created a socket instance by passing two parameters: AF_INET refers to the address family ipv4; SOCK_STREAM refers to TCP protocols. We then set the options for this socket: here SO_REUSEADDR tells the kernel to reuse a local socket in TIME_WAIT state before timeout.

1
2
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

For a server, we want to bind to all interfaces and not just the local host. Therefor we specify an empty string and a randomly chosen port when binding the server.

1
2
port = 8000
server.bind(("", port))

For the client, we need to bind and communicate to the server created above. We create another socket instance also called server in a separte file, and connect to the local host with the same port.

1
2
3
4
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = 'localhost'
port = 8000
server.connect((host, port))

Select.Select

We have now created server and client files and need to let them communicate in time. We use the select method from the select library to listen to a list of sockets while filtering out the ones which new activities. We will have infinite loops in both the serve and client files so that we are always listening.

The server will listen to itself (in order to broadcast new trader entrance) and all the clients.

1
2
3
4
5
connection_list = [server, <potential new clients>]
read_sockets, write_sockets, error_sockets = select.select(
connection_list, [], [])
for socket in read_sockets:
...

The client will listen to the console input (for user input) and the server (for responses on previous request).

1
2
3
4
5
socket_list = [server, sys.stdin]
read_sockets, write_sockets, error_sockets = select.select(
socket_list, [], [])
for socket in read_sockets:
...

Code file & Illustration

We now open up the exchange.py and trader.py files in order.

exchange.py:

1
2
3
Liu at RONGJIAs-MacBook in ~/Desktop
$ python3 exchange.py
--- exchange initiated ---

trader.py:

1
2
3
4
5
6
7
Liu at RONGJIAs-MacBook in ~/Desktop
$ python3 trader.py localhost 8000
--- connected to exchange ---
trader (127.0.0.1:54945) successfully connected to the exchange
instructions:
enter add_order <price> <quantity> <is_bid> to add limit orders
enter bid_ask to request the current bid-ask spread on the exchange

Acting as trader, we added 6 new limit orders and requesting the current bid-ask spread information, which prints the top 3 levels from the bid and ask books.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[127.0.0.1:54945]: add_order 80 100 1
[exchange]: trader (127.0.0.1:54945) has successfully added an limit order (price:80 quantity:100)
[127.0.0.1:54945]: add_order 90 100 1
[exchange]: trader (127.0.0.1:54945) has successfully added an limit order (price:90 quantity:100)
[127.0.0.1:54945]: add_order 100 100 1
[exchange]: trader (127.0.0.1:54945) has successfully added an limit order (price:100 quantity:100)
[127.0.0.1:54945]: add_order 110 100 0
[exchange]: trader (127.0.0.1:54945) has successfully added an limit order (price:110 quantity:100)
[127.0.0.1:54945]: add_order 120 100 0
[exchange]: trader (127.0.0.1:54945) has successfully added an limit order (price:120 quantity:100)
[127.0.0.1:54945]: add_order 130 100 0
[exchange]: trader (127.0.0.1:54945) has successfully added an limit order (price:130 quantity:100)
[127.0.0.1:54945]: bid_ask
{
"bids": {
"100": 100,
"90": 100,
"80": 100
},
"asks": {
"110": 100,
"120": 100,
"130": 100
}
}

We can open up another terminal session and run the trader.py to simulate the entrance of a second trader (do not close the previous sessions). We can check that the bid-ask spread is what we last saw from trader 1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Liu at RONGJIAs-MacBook in ~/Desktop
$ python3 trader.py localhost 8000
--- connected to exchange ---
trader (127.0.0.1:54950) successfully connected to the exchange
instructions:
enter add_order <price> <quantity> <is_bid> to add limit orders
enter bid_ask to request the current bid-ask spread on the exchange
[127.0.0.1:54950]: bid_ask
{
"bids": {
"100": 100,
"90": 100,
"80": 100
},
"asks": {
"110": 100,
"120": 100,
"130": 100
}
}

The second trader then add a bid order with price equals to 110 which is higher or equal to the current lowest ask price. This triggers an order execution on the exchange, and the order book ask quantity at price 100 is reduced by 10.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[127.0.0.1:54950]: add_order 110 10 1
[exchange]: trader (127.0.0.1:54950) has successfully executed an limit order (price:110 quantity:10)
[127.0.0.1:54950]: bid_ask
{
"bids": {
"100": 100,
"90": 100,
"80": 100
},
"asks": {
"110": 90,
"120": 100,
"130": 100
}
}

Further Improvements

Obviously this is just a starting point towards building a more robust financial exchanges. Many improvements can be made to exchange functionality, orderbook data structure and user interface. Another interesting application would be extending this to create a strategy backtester.

During this project I came across Beej’s Guide to Network Programming, which is a valuable online resource for further studies on network programming. I also found the networking examples in this repo super helpful.