Python quantitative trading open source framework: AmazingQuant

Posted by dcmbrown on Wed, 26 Jan 2022 10:45:28 +0100

1. Introduction
Open source address:

https://github.com/zhanggao2013/AmazingQuant

AmazingQuant is an open source framework for quantitative backtesting transactions based on event driven. The following figure shows the overall framework.

data_center
to_mongoDB stores market, financial and other data to the storage module of MongoDB
get_ Interface module for fetching data from database in data policy
trade_center
mossion_engine includes two parts: event_order and event_risk_management, which respectively complete the inspection and risk control before ordering
broker_engine is divided into two parts: simulate d broker (mainly event_deal) for matching transactions during backtesting and interfaces for connecting CTP, xSpeed and other real offer transactions
strategy_center
bar_ In the back test or trading mode, engine executes the trading logic of each bar by K-line, and can run under daily line, minute line and sub pen
analysis_center
analysis_engine analyzes and visualizes the transaction records formed by the back test, including net worth, annualized income, alpha, beta, pullback and other indicators, and the implementation of classical models such as brison and Fama
2. Installation configuration

MongoDB 3.4

It is recommended to use shard to configure the startup item example
pymongo

python calls MongoDB
talib

Technical index calculation library
anaconda

For Python version 3.5, if it is greater than 3.5, the ctp interface cannot be used temporarily because of the compilation problem, which can be solved later
Linux Ubuntu

The development environment is ubuntu. Of course, it can also be used under windows, but the database configuration and transaction interfaces such as ctp need to be redone
Installing AmazingQuant

pip install AmazingQuant direct installation
3. Strategy preparation

# -*- coding: utf-8 -*-

__author__ = "gao"

import numpy as np
import talib

# import strategy base class
from AmazingQuant.strategy_center.strategy import *

# import transaction module
from AmazingQuant.trade_center.trade import Trade


# Inherit strategy base class
class MaStrategy(StrategyBase):
    def initialize(self):
        # Set operation mode, back test or transaction
        self.run_mode = RunMode.BACKTESTING.value
        # Set back test fund account number
        self.account = ["test0", "test1"]
        # Set the fund amount of the back test fund account
        self.capital = {"test0": 2000000, "test1": 1000}
        # Set back survey datum
        self.benchmark = "000300.SH"
        # Set weight recovery method
        self.rights_adjustment = RightsAdjustment.NONE.value
        # Set the start and end time of back measurement
        self.start = "2015-01-11"
        self.end = "2016-01-16"
        # Set running cycle
        self.period = "daily"
        # Set stock pool
        self.universe = ['000001.SZ', '000002.SZ', '000008.SZ', '000060.SZ', '000063.SZ', '000069.SZ', '000100.SZ',
                         '000157.SZ', '000166.SZ', '000333.SZ', '000338.SZ', '000402.SZ', '000413.SZ', '000415.SZ',
                         '000423.SZ', '000425.SZ', '000503.SZ', '000538.SZ', '000540.SZ', '000559.SZ', '000568.SZ',
                         '000623.SZ', '000625.SZ', '000627.SZ', '000630.SZ', '000651.SZ', '000671.SZ', '000686.SZ',
                         '000709.SZ', '000723.SZ', '000725.SZ', '000728.SZ', '000738.SZ', '000750.SZ', '000768.SZ',
                         '000776.SZ', '000783.SZ', '000792.SZ', '000826.SZ', '000839.SZ', '000858.SZ', '000876.SZ',
                         '000895.SZ', '000898.SZ', '000938.SZ', '000959.SZ', '000961.SZ', '000963.SZ', '000983.SZ',
                         '001979.SZ', '002007.SZ', '002008.SZ', '002024.SZ', '002027.SZ', '002044.SZ', '002065.SZ',
                         '002074.SZ', '002081.SZ', '002142.SZ', '002146.SZ', '002153.SZ', '002174.SZ', '002202.SZ',
                         '002230.SZ', '002236.SZ', '002241.SZ', '002252.SZ', '002292.SZ', '002294.SZ', '002304.SZ',
                         '002310.SZ', '002352.SZ', '002385.SZ', '002411.SZ', '002415.SZ', '002424.SZ', '002426.SZ',
                         '002450.SZ', '002456.SZ', '002460.SZ', '002465.SZ', '002466.SZ', '002468.SZ', '002470.SZ',
                         '002475.SZ', '002500.SZ', '002508.SZ', '002555.SZ', '002558.SZ', '002572.SZ', '002594.SZ',
                         '002601.SZ', '002602.SZ', '002608.SZ', '002624.SZ', '002673.SZ', '002714.SZ', '002736.SZ',
                         '002739.SZ', '002797.SZ', '002831.SZ', '002839.SZ', '002841.SZ', '300003.SZ', '300015.SZ',
                         '300017.SZ', '300024.SZ', '300027.SZ', '300033.SZ', '300059.SZ', '300070.SZ', '300072.SZ',
                         '300122.SZ', '300124.SZ', '300136.SZ', '300144.SZ', '300251.SZ', '300315.SZ', '600000.SH',
                         '600008.SH', '600009.SH', '600010.SH', '600011.SH', '600015.SH', '600016.SH', '600018.SH',
                         '600019.SH', '600021.SH', '600023.SH', '600028.SH', '600029.SH', '600030.SH', '600031.SH',
                         '600036.SH', '600038.SH', '600048.SH', '600050.SH', '600061.SH', '600066.SH', '600068.SH',
                         '600074.SH', '600085.SH', '600089.SH', '600100.SH', '600104.SH', '600109.SH', '600111.SH',
                         '600115.SH', '600118.SH', '600153.SH', '600157.SH', '600170.SH', '600177.SH', '600188.SH',
                         '600196.SH', '600208.SH', '600219.SH', '600221.SH', '600233.SH', '600271.SH', '600276.SH',
                         '600297.SH', '600309.SH', '600332.SH', '600340.SH', '600352.SH', '600362.SH', '600369.SH',
                         '600372.SH', '600373.SH', '600376.SH', '600383.SH', '600390.SH', '600406.SH', '600415.SH',
                         '600436.SH', '600482.SH', '600485.SH', '600489.SH', '600498.SH', '600518.SH', '600519.SH',
                         '600522.SH', '600535.SH', '600547.SH', '600549.SH', '600570.SH', '600583.SH', '600585.SH',
                         '600588.SH', '600606.SH', '600637.SH', '600649.SH', '600660.SH', '600663.SH', '600674.SH',
                         '600682.SH', '600685.SH', '600688.SH', '600690.SH', '600703.SH', '600704.SH', '600705.SH',
                         '600739.SH', '600741.SH', '600795.SH', '600804.SH', '600816.SH', '600820.SH', '600827.SH',
                         '600837.SH', '600871.SH', '600886.SH', '600887.SH', '600893.SH', '600895.SH', '600900.SH',
                         '600909.SH', '600919.SH', '600926.SH', '600958.SH', '600959.SH', '600977.SH', '600999.SH',
                         '601006.SH', '601009.SH', '601012.SH', '601018.SH', '601021.SH', '601088.SH', '601099.SH',
                         '601111.SH', '601117.SH', '601118.SH', '601155.SH', '601163.SH', '601166.SH', '601169.SH',
                         '601186.SH', '601198.SH', '601211.SH', '601212.SH', '601216.SH', '601225.SH', '601228.SH',
                         '601229.SH', '601288.SH', '601318.SH', '601328.SH', '601333.SH', '601336.SH', '601375.SH',
                         '601377.SH', '601390.SH', '601398.SH', '601555.SH', '601600.SH', '601601.SH', '601607.SH',
                         '601608.SH', '601611.SH', '601618.SH', '601628.SH', '601633.SH', '601668.SH', '601669.SH',
                         '601688.SH', '601718.SH', '601727.SH', '601766.SH', '601788.SH', '601800.SH', '601818.SH',
                         '601857.SH', '601866.SH', '601872.SH', '601877.SH', '601878.SH', '601881.SH', '601888.SH',
                         '601898.SH', '601899.SH', '601901.SH', '601919.SH', '601933.SH', '601939.SH', '601958.SH',
                         '601966.SH', '601985.SH', '601988.SH', '601989.SH', '601991.SH', '601992.SH', '601997.SH',
                         '601998.SH', '603160.SH', '603799.SH', '603833.SH', '603858.SH', '603993.SH']

        # Set whether to cache the daily line, minute line and other cycle data before running
        self.daily_data_cache = True
        print(self.universe)

        # Set the back sliding point according to the fixed value of 0.01,20-0.01 = 19.99; Percentage 0.01,20 * (1-0.01) = 19.98; Use "+" when closing positions
        self.set_slippage(stock_type=StockType.STOCK.value, slippage_type=SlippageType.SLIPPAGE_FIX.value, value=0.01)

        # Back test of stock handling fee and stamp duty, sales stamp duty, one thousandth; Opening fee, 3 / 10000; Closing fee, 3 / 10000, minimum fee, 5 yuan
        # In the Shanghai stock market, there is a transfer fee of 2 / 10000 for sales, which is added to the selling handling fee
        self.set_commission(stock_type=StockType.STOCK_SH.value, tax=0.001, open_commission=0.0003,
                            close_commission=0.0003,
                            close_today_commission=0, min_commission=5)
        # Shenzhen does not add transfer fee
        self.set_commission(stock_type=StockType.STOCK_SZ.value, tax=0.001, open_commission=0.0003,
                            close_commission=0.0005,
                            close_today_commission=0, min_commission=5)

    def handle_bar(self, event):
        # Take the current position
        available_position_dict = {}
        for position in Environment.bar_position_data_list:
            available_position_dict[position.instrument + "." + position.exchange] = position.position - position.frozen
        # The specific timestamp of the current bar
        current_date = data_transfer.millisecond_to_date(millisecond=self.timetag, format="%Y-%m-%d")
        # The timestamp is converted to int to facilitate subsequent data retrieval
        current_date_int = data_transfer.date_str_to_int(current_date)
        print(current_date)
        # Fetch data instance
        data_class = GetData()
        # Loop through the stock pool
        for stock in self.universe:
            # Take the closing price of the current stock
            close_price = data_class.get_market_data(Environment.daily_data, stock_code=[stock], field=["close"],
                                                     end=current_date)
            # print(self.start, current_date)
            close_array = np.array(close_price)
            if len(close_array) > 0:
                # Calculating MA using talib
                ma5 = talib.MA(np.array(close_price), timeperiod=5)
                ma20 = talib.MA(np.array(close_price), timeperiod=20)
                # print(type(close_price.keys()))
                # Filter because the suspension has no data
                if current_date_int in close_price.keys():
                    # If the 5-day moving average breaks through the 20 day moving average and there is no position, buy 100 shares of this stock and trade at the closing price
                    if ma5[-1] > ma20[-1] and stock not in available_position_dict.keys():
                        Trade(self).order_shares(stock_code=stock, shares=100, price_type="fix",
                                                 order_price=close_price[current_date_int],
                                                 account=self.account[0])
                        print("buy", stock, 1, "fix", close_price[current_date_int], self.account)
                    # If the 20 day moving average breaks through the 5-day moving average and there is a position, sell 100 shares of the stock and trade at the closing price
                    elif ma5[-1] < ma20[-1] and stock in available_position_dict.keys():
                        Trade(self).order_shares(stock_code=stock, shares=-100, price_type="fix",
                                                 order_price=close_price[current_date_int],
                                                 account=self.account[0])
                        print("sell", stock, -1, "fix", close_price[current_date_int], self.account)


if __name__ == "__main__":
    # The time required to test and run a complete strategy has not been optimized at present,
    # 300 stocks, daily data, a year's time interval, more than 4000 transaction records, about 80s in my virtual machine. It should be much faster to change a machine with better personality
    from AmazingQuant.utils.performance_test import Timer

    time_test = Timer(True)
    with time_test:
        # Operation strategy, set whether to save entrustment, transaction, capital and position
        MaStrategy().run(save_trade_record=True)

4. Analysis of back test results

Automatically generate back test results

The generated cvs files of entrustment, transaction, fund and position are written to the folder where the strategy is located
Automatically generate back test report

The back test report is in html format and can be opened and viewed in the browser. The effect is as follows:

5. Firm offer transaction

At present, it has been implemented to package according to vnpy, and use boost to python3.0 for the C + + interface of CTP 5, which will be implemented with broker later_ Docking of engine

6. Implemented and upcoming functions

Implemented
Database construction
Read data
Policy run back test
Preservation and analysis of back test transaction records
Packaging of solid CTP interface
Coming soon
Docking of various data

For example, minute data of stocks, financial data of stocks, component stocks of stock sector, minute data of futures, daily data, etc
CTP and other transaction interfaces and brokers_ Engine docking

CTP, xSpeed, etc
Visualize the trading and position of each bar in the back test range
Enrichment of back test analysis module

Increase the analysis and visualization of various performance attribution models such as brison and FAMA

Topics: Python