Bohr1005 / xcrypto

quant,trading system,crypto,async
MIT License
332 stars 94 forks source link

xcrypto

MIT licensed

xcrypto is a trading system with a Client/Server architecture. It is implemented based on rust tokio and can support up to 65536 strategies running simultaneously. It communicates with strategies using WebSocket, automates trading by forwarding exchange quotes, accepting order requests from strategies, and distributing order returns from exchanges. This project mainly consists of two subprojects: the trading system and the strategy package. The trading system implements Binance's spot trading and usdt future trading. The strategy package is implemented using Rust and Python, strategies can written entirely in python.

Implement strategy

from pyalgo import *

class Demo:
    """"""

    def __init__(self, sub: DepthSubscription):
        self.sub = sub
        # use to send/kill order
        self.smtord = SmartOrder(sub)

        self.fin = False

        # add trading phase if you need
        # otherwise self.sub.phase is always Phase.UNDEF
        self.sub.add_phase(0, 0, 0, Phase.OPEN)
        self.sub.add_phase(16, 0, 0, Phase.CLOSE)

        # set callback
        self.sub.on_data = self.on_depth
        self.sub.on_order = self.on_order

    def on_order(self, order: Order):
        info(f"{order}")

    def on_depth(self, depth: Depth):
        info(f"{self.sub.datetime}, {self.sub.symbol}")

        if self.fin:
            self.smtord.kill()
            return

        match self.sub.phase:
            case Phase.OPEN:
                if self.sub.net == 0:
                    self.smtord.send(
                        self.sub.bid_prc(5),
                        10,
                        Side.BUY,
                        OrderType.LIMIT,
                        Tif.GTC,
                    )
                    self.fin = True

            case Phase.CLOSE:
                # todo
                pass

if __name__ == "__main__":
    eng = Engine(0.001)
    session = eng.make_session(
        addr="ws://localhost:8111", session_id=1, name="test", trading=True
    )

    sub = session.subscribe("dogeusdt", "depth")
    demo = Demo(sub)
    eng.run()

Risk Warning

It is a personal project, use at your own risk. I will not be responsible for your investment losses (and gains). Cryptocurrency investment is subject to high market risk.

Prerequisites

Install rust

url --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Install maturin

pip install maturin

Introduction

This project consists of two subprojects. One is a Rust binary project, which serves as a bridge between trading strategies and the exchange. The other is a hybrid project of Rust and Python, which aims to provide convenience for implementing strategies.

xcrypto

An important concept is Session. Each Session represents an instance of a strategy. At any given time, a session can only be used by one strategy. The distribution of orders and the recording of positions are differentiated based on the session.

Here is an example

eng = Engine()
session = eng.make_session(
    addr="ws://localhost:8111", session_id=1, name="test", trading=True
)
sub = session.subscribe("btcusdt","depth")

In this example, session 1 is being used, and the btcusdt is subscribed through this session, its position holdings also being saved to session 1.

You can also set trading=False, in which case, the session will not support trading. Your strategy will not be able to send orders or cancel orders, but it can still receive market data.

Build rust binary

For simplicity, the system only supports trading for spot account and futures cross-margin account, and does not support trading for isolated-margin accounts.

The supported market data includes depth and kline data for all periods, which is sufficient for most strategies to use.

Spot

➜  ~ cd xcrypto
➜  ~ ls
➜  ~ Cargo.lock  Cargo.toml  binance  logger  private_key.pem  pyalgo  spot.json  src  usdt.json
➜  ~ cd binance/spot

build debug

➜  ~ cargo build

or release

➜  ~ cargo build -r

You will find the binary file in target/debug or targer/release

USDT future

➜  ~ cd xcrypto
➜  ~ ls
➜  ~ Cargo.lock  Cargo.toml  binance  logger  private_key.pem  pyalgo  spot.json  src  usdt.json
➜  ~ cd binance/usdt

build debug

➜  ~ cargo build

or release

➜  ~ cargo build -r

You will find the binary file in target/debug or targer/release

Run

Before you run the program, two files is required, one is the configuration file, and the other is your private key.

For these two files, you can refer to usdt.json and spot.json for the configuration file, and private_key.pem for the private key.

After compilation is completed, jump to the target/debug or target/release. Before you run the program, you need to do the following things.

You can run spot trading by this command

./spot -c=spot.json -l=info

The argument -c=spot.json is the abbreviation of --config=spot.json, representing the path to the configuration file. The -l=info is the abbreviation of --level=info, indicating the log level, this parameter is optional, its default value is info. The configuration file looks like this.

{
    "apikey":"your api key",
    "pem": "private_key.pem",
    "margin": false,
    "local": "ws://localhost:8111"
}

For usdt future, it is similar to spot trading.

./usdt -c=usdt.json -l=info

The configuration file looks like this.

{
    "apikey": "your api key",
    "pem": "private_key.pem",
    "local": "ws://localhost:8111"
}

The meaning of each field is the same as the spot.json

Position

After you run the binary, pos.db will appear in the current directory where you executed it. This file is a SQLite3 database that is used to store the position holdings for different sessions.

If you need to modify the position recorded by the system, you can make changes to pos.db using SQL. After completing the modifications, simply restart the system.

Market Stream

you can subscribe market stream via ssession.subscribe(symbol: str,stream: str)

sub = ssession.subscribe("btcusdt","bbo")
sub = ssession.subscribe("btcusdt","depth")
# sub = ssession.subscribe("btcusdt","depth:100ms")
sub = ssession.subscribe("btcusdt","kline:1m")

Python strategy package

➜  ~ cd xcrypto
➜  ~ ls
➜  ~ Cargo.lock  Cargo.toml  binance  logger  private_key.pem  pyalgo  spot.json  src  usdt.json
➜  ~ cd pyalgo

install debug

➜  ~ maturin develop

install release

➜  ~ maturin develop -r

You can also package as wheels

➜  ~ maturin build 

or

➜  ~ maturin build -r

The .whl file is located in the target/wheels

Implement strategy

In the pyalgo/example directory, there are some demos. To implement any strategy, the following classes is all you need.

from pyalgo import *

if __name__ == "__main__":
    # The only argument is interval
    # Because the underlying WebSocket is non-blocking 
    # In order to avoid wasted CPU cycles, an appropriate sleep interval is required. 
    # You can also set it to 0, and it will become a busy loop.
    eng = Engine(0.0001)
    # create session
    session = eng.make_session(
        addr="ws://localhost:8111", session_id=1, name="test", trading=True
    )   
    # subscribe depth, you can also subscribe kline by session.subscribe("dogeusdt","kline:1m)
    sub = session.subscribe("dogeusdt", "depth")
    # set callback
    # when you recv depth, it will print data
    sub.on_data = lambda x:print(x)
    eng.run()
from pyalgo import *

class Demo:
    """"""

    def __init__(self, sub: DepthSubscription):
        self.sub = sub
        self.smtord = SmartOrder(sub)

        self.fin = False

        self.sub.on_data = self.on_depth

    def on_order(self, order: Order):
        info(f"{order}")

    def on_depth(self, _: Depth):
        if self.fin:
            self.smtord.kill()
            return

        if self.smtord.is_active:
            # if our prc < best bid, kill it
            if self.smtord.price < self.sub.bid_prc(0):
                self.smtord.kill()

        else:
            self.smtord.send(
                self.sub.bid_prc(0), 10, Side.BUY, OrderType.LIMIT, Tif.GTC
            )
            self.fin = True

if __name__ == "__main__":
    eng = Engine(0.001)
    session = eng.make_session(
        addr="ws://localhost:8111", session_id=1, name="test", trading=True
    )

    sub = session.subscribe("dogeusdt", "depth")
    demo = Demo(sub)
    eng.run()

Donation

Open source is not easy. Please give the author some encouragement. Treat the author a cup of Mixue or Luckin Coffee, your Issues will be resolved first.

alipay alipay