0%

【Quantaxis】QAAccount简单回测

# QAAccount与MongoDB

在使用QAAccoount类进行回测时,MongoDB数据库quantaxis中,会涉及到的数据表有 5 个, 分别是:

  • portfolio
  • user
  • account
  • risk
  • strategy

回测中的逻辑关系

|1000

示例

创建资产用户/资产组合/帐户

Quantaxis v1.3.0以后, QA_Account需要由组合来进行创建(推荐)。为避免出过多因使用未来版本会降级的函数导致的警告信息。

1
2
import warnings
warnings.filterwarnings('ignore')

创建用户

用户名为qaacc,密码为qaacc

1
2
3
import QUANTAXIS as QA

user = QA.QA_User(username='qaacc', password='qaacc')
### 创建资产组合
1
portfolio = user.new_portfolio('stock')

创建帐户

可以创建多市场的帐户,这里以创建股票市场帐户为例。

1
account = portfolio.new_account(account_cookie='lvjun')

账户的初始资金/初始仓位

默认账户是无仓位, 默认现金 1,000,000 RMB

1
account.init_assets
输出:
1
{'cash': 1000000, 'hold': {}}

1
account.init_cash

输出:

1
1000000

1
account.init_hold

输出:

1
Series([], Name: amount, dtype: object)

简单回测

用帐户account_cookie='lvjun'进行简单回测。Quantaxis版本为 1.10.19

新建策略

MACD_JCSC策略思想是MACD指标出现金叉(DIF向上突破DEA)买入,出现死叉(DIF向下跌破DEA)卖出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import QUANTAXIS as QA
import numpy as np
import pandas as pd
import datetime

# define the MACD strategy


def MACD_JCSC(dataframe, SHORT=12, LONG=26, M=9):
"""
1.DIF向上突破DEA,买入信号参考。
2.DIF向下跌破DEA,卖出信号参考。
"""
CLOSE = dataframe.close
DIFF = QA.EMA(CLOSE, SHORT) - QA.EMA(CLOSE, LONG)
DEA = QA.EMA(DIFF, M)
MACD = 2*(DIFF-DEA)

CROSS_JC = QA.CROSS(DIFF, DEA)
CROSS_SC = QA.CROSS(DEA, DIFF)
ZERO = 0
return pd.DataFrame({'DIFF': DIFF, 'DEA': DEA, 'MACD': MACD, 'CROSS_JC': CROSS_JC, 'CROSS_SC': CROSS_SC, 'ZERO': ZERO})

初始化回测broker及保存策略

策略源码保存可以保存到网络侧(127.0.0.1:8010),也可以保存到本地。

1
2
3
Broker = QA.QA_BacktestBroker()
# 策略保存到本地, 当含参`if_save=True`时, 会保存到本地。 在数据表·strategy`中可以看到具体信息。
QA.QA_SU_save_strategy('MACD_JCSC', 'Indicator', account.account_cookie)

选择测试标的物数据

1
2
3
4
# get data from mongodb
codelist = ['000001']
data = QA.QA_fetch_stock_day_adv(codelist, '2017-09-01', '2018-05-20')
data = data.to_qfq()

计算指标信号

指标的计算可以在回测前,也可以在回测中进行。区别在于回测前的计算则是批量计算,效率较高。而回测中进行计算,效率略低,但代码量较小,易于理解。

1
2
3
ind = data.add_func(MACD_JCSC)
print("金叉信号出现:%d 次" % ind['CROSS_JC'].value_counts()[1])
print("死叉信号出现:%d 次" % ind['CROSS_SC'].value_counts()[1])
1
2
金叉信号出现:8 次
死叉信号出现:7 次
列出出现信号时的数据。
1
ind.loc[(ind['CROSS_JC'] == 1) | (ind['CROSS_SC'] == 1)]
输出:
|500

回测

通过迭代产生Dataframe截面数据的方式,对产生信号的标的物进行交易回测。

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
26
27
28
29
30
31
32
33
34
for items in data_forbacktest.panel_gen:
for item in items.security_gen:
daily_ind = ind.loc[item.index]
if daily_ind.CROSS_JC.iloc[0] > 0:
order = account.send_order(
code=item.code[0],
time=item.date[0],
amount=1000,
towards=QA.ORDER_DIRECTION.BUY,
price=0,
order_model=QA.ORDER_MODEL.CLOSE,
amount_model=QA.AMOUNT_MODEL.BY_AMOUNT
)

Broker.receive_order(QA.QA_Event(order=order, market_data=item))
trade_mes = Broker.query_orders(account.account_cookie, 'filled')
res = trade_mes.loc[order.account_cookie, order.realorder_id]
order.trade(res.trade_id, res.trade_price,res.trade_amount, res.trade_time)
elif daily_ind.CROSS_SC.iloc[0] > 0:
if account.sell_available.get(item.code[0], 0) > 0:
order = account.send_order(
code=item.code[0],
time=item.date[0],
amount=account.sell_available.get(item.code[0], 0),
towards=QA.ORDER_DIRECTION.SELL,
price=0,
order_model=QA.ORDER_MODEL.MARKET,
amount_model=QA.AMOUNT_MODEL.BY_AMOUNT
)
Broker.receive_order(QA.QA_Event(order=order, market_data=item))
trade_mes = Broker.query_orders(account.account_cookie, 'filled')
res = trade_mes.loc[order.account_cookie, order.realorder_id]
order.trade(res.trade_id, res.trade_price,res.trade_amount, res.trade_time)
account.settle()
输出:
1
2
3
4
5
6
7
QAACCOUNT ==> receive deal  Time 2018-01-02 00:00:00/ Code:000001/ Price:12.48/ TOWARDS:1/ Amounts:1000
QAACCOUNT ==> receive deal Time 2018-01-03 00:00:00/ Code:000001/ Price:12.32/ TOWARDS:-1/ Amounts:1000
QAACCOUNT ==> receive deal Time 2018-01-12 00:00:00/ Code:000001/ Price:12.34/ TOWARDS:1/ Amounts:1000
QAACCOUNT ==> receive deal Time 2018-01-29 00:00:00/ Code:000001/ Price:12.68/ TOWARDS:-1/ Amounts:1000
QAACCOUNT ==> receive deal Time 2018-03-08 00:00:00/ Code:000001/ Price:11.03/ TOWARDS:1/ Amounts:1000
QAACCOUNT ==> receive deal Time 2018-03-26 00:00:00/ Code:000001/ Price:10.05/ TOWARDS:-1/ Amounts:1000
QAACCOUNT ==> receive deal Time 2018-04-10 00:00:00/ Code:000001/ Price:10.4/ TOWARDS:1/ Amounts:1000

风险与绩效分析

可以手工完成分析,或是进入 Web 界面调用数据分析。

风险分析

1
2
Risk = QA.QA_Risk(account)
Risk.plot_assets_curve()

输出:
|600

绩效分析

1
2
Performance = QA.QA_Performance(account)
Performance.base_message(Performance.pnl)

输出:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
{
"status": 200,
"result": {
"total_profit": 340.0,
"total_loss": -1140.0,
"total_pnl": 0.3,
"trading_amounts": 3,
"profit_amounts": 1,
"loss_amounts": 2,
"even_amounts": 0,
"profit_precentage": 0.33,
"loss_precentage": 0.67,
"even_precentage": 0.0,
"average_profit": 340.0,
"average_loss": -570.0,
"average_pnl": 0.6,
"max_profit": 340.0,
"max_loss": -980.0,
"max_pnl": 0.35,
"netprofio_maxloss_ratio": 0.82,
"continue_profit_amount": 1,
"continue_loss_amount": 1,
"average_holdgap": "12 days 00:00:00",
"average_profitholdgap": "17 days 00:00:00",
"average_losssholdgap": "9 days 12:00:00",
"buyopen": {
"total_profit": 340.0,
"total_loss": -1140.0,
"total_pnl": 0.3,
"trading_amounts": 3,
"profit_amounts": 1,
"loss_amounts": 2,
"even_amounts": 0,
"profit_precentage": 0.33,
"loss_precentage": 0.67,
"even_precentage": 0.0,
"average_profit": 340.0,
"average_loss": -570.0,
"average_pnl": 0.6,
"max_profit": 340.0,
"max_loss": -980.0,
"max_pnl": 0.35,
"netprofio_maxloss_ratio": 0.82,
"continue_profit_amount": 1,
"continue_loss_amount": 1,
"average_holdgap": "12 days 00:00:00",
"average_profitholdgap": "17 days 00:00:00",
"average_losssholdgap": "9 days 12:00:00"
},
"sellopen": {
"total_profit": 0,
"total_loss": 0,
"total_pnl": 0,
"trading_amounts": 0,
"profit_amounts": 0,
"loss_amounts": 0,
"even_amounts": 0,
"profit_precentage": 0,
"loss_precentage": 0,
"even_precentage": 0,
"average_profit": 0,
"average_loss": 0,
"average_pnl": 0,
"max_profit": 0,
"max_loss": 0,
"max_pnl": 0,
"netprofio_maxloss_ratio": 0,
"continue_profit_amount": 0,
"continue_loss_amount": 0,
"average_holdgap": "no trade",
"average_profitholdgap": "no trade",
"average_losssholdgap": "no trade"
}
}
}

Web浏览方式

浏览器输入:http://127.0.0.1:81 ,使用回测时创建用户名/密码。

  • 风险与绩效分析
    |600
  • 买卖信号记录
    |600

备注:

计算 Performance.continue_profit_amount 等指标时时,原代码中使用了  for _, item in pnl.pnl_money.items():, 在 Pandas ≥ 1.5 时,Series.iteritems(), DataFrame.iteritems()HDFStore.iteritems() 均使用 obj.items 进行替代。1

1
2
3
4
5
6
### What’s new in 1.5.0 (September 19, 2022)
- Deprecated `DataFrame.iteritems()`, `Series.iteritems()`, `HDFStore.iteritems()` in favor of [`DataFrame.items()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.items.html#pandas.DataFrame.items "pandas.DataFrame.items"), [`Series.items()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.items.html#pandas.Series.items "pandas.Series.items"), `HDFStore.items()` ([GH 45321](https://github.com/pandas-dev/pandas/issues/45321))

### What’s new in 2.0.0 (April 3, 2023)
- Removed deprecated `Series.iteritems()`, `DataFrame.iteritems()`, use `obj.items` instead ([GH 45321](https://github.com/pandas-dev/pandas/issues/45321))

参考


  1. Search - pandas 2.2.2 documentation (pydata.org)↩︎