# 基础库导入

from __future__ import print_function
from __future__ import division

import warnings
warnings.filterwarnings('ignore')
warnings.simplefilter('ignore')

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

import os
import sys
# 使用insert 0即只使用github，避免交叉使用了pip安装的abupy，导致的版本不一致问题
sys.path.insert(0, os.path.abspath('../'))
import abupy

# 使用沙盒数据，目的是和书中一样的数据环境
abupy.env.enable_example_env_ipython()

"""
Explanation: ABU量化系统使用文档
<center>
        <img src="./image/abu_logo.png" alt="" style="vertical-align:middle;padding:10px 20px;"><font size="6" color="black"><b>第8节 A股市场的回测</b></font>
</center>

作者: 阿布
阿布量化版权所有 未经允许 禁止转载
abu量化系统github地址 (您的star是我的动力！)
本节ipython notebook
之前的小节回测示例都是使用美股，本节示例A股市场的回测。
首先导入abupy中本节使用的模块：
End of explanation
"""


from abupy import AbuFactorAtrNStop, AbuFactorPreAtrNStop, AbuFactorCloseAtrNStop, AbuFactorBuyBreak
from abupy import abu, EMarketTargetType, AbuMetricsBase, ABuMarketDrawing, ABuProgress, ABuSymbolPd

# 设置初始资金数
read_cash = 1000000

# 买入因子依然延用向上突破因子
buy_factors = [{'xd': 60, 'class': AbuFactorBuyBreak},
               {'xd': 42, 'class': AbuFactorBuyBreak}]

# 卖出因子继续使用上一节使用的因子
sell_factors = [
    {'stop_loss_n': 1.0, 'stop_win_n': 3.0,
     'class': AbuFactorAtrNStop},
    {'class': AbuFactorPreAtrNStop, 'pre_atr_n': 1.5},
    {'class': AbuFactorCloseAtrNStop, 'close_atr_n': 1.5}
]

"""
Explanation: 买入因子，卖出因子等依然使用相同的设置，如下所示：
End of explanation
"""


# 择时股票池
choice_symbols = ['002230', '300104', '300059', '601766', '600085', '600036', '600809', '000002', '002594', '002739']

# 使用run_loop_back运行策略
abu_result_tuple, kl_pd_manger = abu.run_loop_back(read_cash,
                                                   buy_factors,
                                                   sell_factors,
                                                   n_folds=6,
                                                   choice_symbols=choice_symbols)
ABuProgress.clear_output()

AbuMetricsBase.show_general(*abu_result_tuple, only_show_returns=True)

"""
Explanation: 1. A股市场的回测示例
择时股票池使用沙盒缓存数据中的如下股票：
A股市场：

科大讯飞(002230)
乐视网(300104)
东方财富(300059)
中国中车(601766)
同仁堂(600085),
招商银行(600036)
山西汾酒(600809)
万科A(000002)
比亚迪(002594)
万达电影(002739)
上证指数(sh000001)

代码如下所示：
End of explanation
"""


abupy.env.g_market_target = EMarketTargetType.E_MARKET_TARGET_CN

abu_result_tuple, kl_pd_manger = abu.run_loop_back(read_cash,
                                                   buy_factors,
                                                   sell_factors,
                                                   n_folds=6,
                                                   choice_symbols=choice_symbols)
ABuProgress.clear_output()

AbuMetricsBase.show_general(*abu_result_tuple, only_show_returns=True)

"""
Explanation: 上面的回测结果虽然可以正常运行，但是很多交易细节还是使用的默认设置中的美股交易模式，因为默认的设置EMarketTargetType.E_MARKET_TARGET_US是美股，它会影响到一年多少个交易日等等交易细节，基准标尺等问题，如注意观察上面使用使用show_general显示的最终收益对比图，可以发现策略收益对比的是纳斯达克指数，并不是A股大盘。
正确的做法是首先将abupy量化环境设置为A股，代码如下所示：
End of explanation
"""


orders_pd = abu_result_tuple.orders_pd
view_orders = orders_pd[(orders_pd['symbol'] == '601766') & (orders_pd['buy_date'] == 20150417)]
view_orders

"""
Explanation: 2. 涨跌停的特殊处理
下面主要讲解一下A股市场中比较特殊的地方：涨停，跌停，首先看一下下面这笔交易：
End of explanation
"""


trade_df = ABuMarketDrawing.plot_candle_from_order(view_orders)

"""
Explanation: 从交易单来看似乎一切ok，虽然最终交易亏损了，下面使用plot_candle_from_order可视化view_orders，plot_candle_from_order标记出了买入和卖出点
End of explanation
"""


print('买入价格为：{}'.format(view_orders.iloc[0].buy_price))
trade_df.head()

"""
Explanation: ABuMarketDrawing.plot_candle_from_order返回的trade_df是这笔交易的持股周期内的金融时间序列，如下所示，看第一条数据
2015-04-17交易日即为买入交易日，可以发现close，high，low的价格都是一样的，这代表了在集合竞价阶段股票已经涨停，但在我们回测中默认使用的
滑点买入类依然认为可以买入。
备注：滑点类相关内容请阅读：滑点策略与交易手续费
End of explanation
"""


view_orders = orders_pd[(orders_pd['symbol'] == '000002') & (orders_pd['sell_date'] == 20160704)]
trade_df = ABuMarketDrawing.plot_candle_from_order(view_orders.iloc[0])
print('卖出价格为：{}'.format(view_orders.iloc[0].sell_price))
trade_df.tail()

"""
Explanation: 和买入类似，注意下面这笔交易：
End of explanation
"""


view_orders = orders_pd[(orders_pd['symbol'] == '300059') & (orders_pd['buy_date'] == 20120222)]
trade_df = ABuMarketDrawing.plot_candle_from_order(view_orders.iloc[0])
print('买入价格为：{}'.format(view_orders.iloc[0].buy_price))
trade_df.head(1)

"""
Explanation: 上面ABuMarketDrawing.plot_candle_from_order返回的trade_df是这笔交易的持股周期内的金融时间序列，看第最后一条数据
2016-07-04交易日即为卖出交易日，可以发现close，high，low的价格都是一样的，这代表了在集合竞价阶段股票已经跌停，但在我们回测中默认使用的
滑点卖出类依然认为可以卖出。
类似的情况还有下面这种虽然并不是在集合竞价阶段股票涨停，但是涨停下依然使用当天最高最低均价买入，买入价格为：1.175显然也不合适，同理在非集合竞价跌停的情况下以当天的平均价格卖出也不合适。
备注：默认滑点类使用均价买入卖出，详情阅读AbuSlippageSellBase，AbuSlippageBuyBase
End of explanation
"""


from abupy import slippage
# 开启针对非集合竞价阶段的涨停，滑点买入价格以高概率在接近涨停的价格买入
slippage.sbb.g_enable_limit_up = True
# 将集合竞价阶段的涨停买入成功概率设置为0，如果设置为0.2即20%概率成功买入
slippage.sbb.g_pre_limit_up_rate = 0
# 开启针对非集合竞价阶段的跌停，滑点卖出价格以高概率在接近跌停的价格卖出
slippage.ssb.g_enable_limit_down = True
# 将集合竞价阶段的跌停卖出成功概率设置为0, 如果设置为0.2即20%概率成功卖出
slippage.ssb.g_pre_limit_down_rate = 0

"""
Explanation: 为解决上述问题abupy中有针对A股涨停和跌停的特殊装饰器封装在滑点模块中：
具体实现原理不在这里展开，效果为：

针对非集合竞价阶段的涨停，滑点买入价格以高概率在接近涨停的价格买入
针对非集合竞价阶段的跌停，滑点卖出价格以高概率在接近跌停的价格卖出
集合竞价阶段的涨停根据设置中的买入成功概率进行买入决策
集合竞价阶段的跌停根据设置中的卖出成功概率进行卖出决策

具体实现请阅读源代码AbuSlippageSellBase，AbuSlippageBuyBase。
下面的代码即将上述4个针对A股涨停和跌停的特殊设置打开：
End of explanation
"""


abu_result_slippage, kl_pd_manger = abu.run_loop_back(read_cash,
                                                   buy_factors,
                                                   sell_factors,
                                                   n_folds=6,
                                                   choice_symbols=choice_symbols)
ABuProgress.clear_output()

AbuMetricsBase.show_general(*abu_result_slippage, only_show_returns=True)

"""
Explanation: 其它的回测因子等设置都不变，重新使用abu.run_loop_back进行回测，代码如下：
End of explanation
"""


from abupy import AbuOrderPdProxy, EOrderSameRule

orders_pd_slippage = abu_result_slippage.orders_pd
# 通过orders_pd构造AbuOrderPdProxy
proxy = AbuOrderPdProxy(orders_pd)
with proxy.proxy_work(abu_result_slippage.orders_pd) as (order, order_slippage):
    print('order == order_slippage: {}'.format(order == order_slippage))
    print('order > order_slippage: {}'.format(order > order_slippage))
    diff_a = order - order_slippage
    diff_b = order_slippage - order

"""
Explanation: 上面的度量显示买入后卖出的交易数量:249，之前没开启涨跌停时是252，即可知道有三笔交易由于开启了涨跌停没有进行买入，但是怎么能知道那些交易发生了变化呢?
备注：读者可尝试使用 ABU量化系统使用文档－第九节 港股市场的回测 中讲解的AbuSDBreak对上面A股交易进行回测，度量结果。
3. 对多组交易结果进行分析
AbuOrderPdProxy是abupy中内置的针对交易单对象进行并集，交集，差集等交易单分析使用的工具，通过EOrderSameRule使用不同的判断为是否相同使用的交易单规则，更多实现详情请阅读AbuOrderPdProxy源代码，下面示例使用:
End of explanation
"""


diff_a.head(1)

diff_b.head(1)

"""
Explanation: 下面对比一下两个差集的第一个数据，可以发现不同点是买入价格：

未使用涨跌停控制的diff_a的交易依然是使用1.17的价格买入股票
使用涨跌停控制的diff_b的交易使用接近涨停价格1.23的价格买入股票

备注：读者可以输出diff_a，diff_b自行一个一个对比一下看看，这里不再详对
End of explanation
"""


proxy = AbuOrderPdProxy(orders_pd, EOrderSameRule.ORDER_SAME_BD)
with proxy.proxy_work(abu_result_slippage.orders_pd) as (order, order_slippage):
    diff_c = order - order_slippage
diff_c

ABuSymbolPd.make_kl_df('601766', start='20150417', end='20150417')

ABuSymbolPd.make_kl_df('300104', start='20131009', end='20131009')

"""
Explanation: 下面通过EOrderSameRule.ORDER_SAME_BD做为AbuOrderPdProxy的参数，这样构造的AbuOrderPdProxy即切换了对交易单相同的规则：
class EOrderSameRule(Enum):
    """对order_pd中对order判断为是否相同使用的规则"""

    """order有相同的symbol和买入日期就认为是相同"""
    ORDER_SAME_BD = 0
    """order有相同的symbol, 买入日期，和卖出日期，即不考虑价格，只要日期相同就相同"""
    ORDER_SAME_BSD = 1
    """order有相同的symbol, 买入日期，相同的买入价格，即单子买入时刻都相同"""
    ORDER_SAME_BDP = 2
    """order有相同的symbol, 买入日期, 买入价格, 并且相同的卖出日期和价格才认为是相同，即买入卖出时刻都相同"""
    ORDER_SAME_BSPD = 3

使用相同的symbol和买入日期就认为是相同的规则，结果如下：
End of explanation
"""


