标签归档:量化投资

教你用Python计算对量化交易至关重要的VWAP指标

成交量加权平均价格 (VWAP) 在金融业中是指特定时间范围内交易价值与交易总数量的比率。它具有三个重要的特点和优势,为交易者提供了对价格趋势的洞察方法。机构和交易者使用 VWAP 来识别买卖区域,并帮助衡量市场情绪。

1.为什么要用VWAP?

VWAP有三个重要的特点:

1. VWAP可以帮助我们了解市场情绪。当证券价格高于VWAP线时,市场对它是乐观看涨的。当价格低于VWAP线时,市场是悲观看跌的。这一点我们可以从下图直观地了解。

2. 许多日内交易者和大型机构投资者以及养老金计划都使用VWAP来作为衡量自己的交易是否会影响市场的重要指标。 比如机构交易者想要卖出自己重要的头寸时,他们的目标是以VWAP或更高的价格卖出。他们会用几种VWAP盘中策略来确定三件事(趋势、谁在影响价格、确定支撑位和压力位)。

3. VWAP及其与证券价格平均值(HLC)的1个标准差可以作为潜在的支撑和阻力,如下图所示。

2 如何用Python计算VWAP

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。

(可选1) 如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

(可选2) 此外,推荐大家用VSCode编辑器来编写小型Python项目:Python 编程的最好搭档—VSCode 详细指南

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),输入命令安装依赖:

pip install pandas

VWAP的计算公式如下:

TP =(最高价+最低价+收盘价)/3

V = 成交量

VWAP = (TP_1 * V_1 + TP_2 * V_2 + TP_n * V_n)/n

例如,如果一只股票以 10 美元交易 1000 股,然后以 11 美元交易 100 股,则最终交易价格为 11 美元;但是,VWAP 将更接近 10:

(1000 * 10 + 100 * 11)/(1000 + 100)) = 10.09

接下来,我们制造一些假数据来准备计算VWAP:

# Get imports
import datetime
import pandas as pd

# Create example dataframe
df = pd.DataFrame(
index=[datetime.datetime(2021,1,1,1),
datetime.datetime(2021,1,1,2),
datetime.datetime(2021,1,1,3),
datetime.datetime(2021,1,1,4)],
data={
  'low':[9,10,11,12],
  'close':[10,11,12,13],
  'high':[11,12,13,14],
  'volume':[1000,750,500,250]
  }
)
df.index.rename('date', inplace=True)

数据如下:

                    low  close  high  volume
date
2021-01-01 01:00:00    9     10    11    1000
2021-01-01 02:00:00   10     11    12     750
2021-01-01 03:00:00   11     12    13     500
2021-01-01 04:00:00   12     13    14     250

VWAP的计算方法如下,这里采用了HLC(open、low、close)的平均值作为基准计算对象:

# Create VWAP function
def vwap(df):
    v = df['volume'].values
    tp = (df['low'] + df['close'] + df['high']).div(3).values
    return df.assign(vwap=(tp * v).cumsum() / v.cumsum())

vwap(df)

计算完成后会在原来的数据上添加一列vwap列:

                     low  close  high  volume       vwap
date
2021-01-01 01:00:00    9     10    11    1000  10.000000
2021-01-01 02:00:00   10     11    12     750  10.428571
2021-01-01 03:00:00   11     12    13     500  10.777778
2021-01-01 04:00:00   12     13    14     250  11.000000

验证一下:

# Verify VWAP
## 以第二行为例
(10*1000 + 11*750) / (1000+750)
10.428571 # 正确

3.VWAP的缺点

没有全能的指标,VWAP也有其自身的缺点。

1.滞后性。和其他的移动平均线一样,VWAP也是一个滞后的指标,而且随着日内交易量的累计,滞后性会越来越严重。

2.仅适用于短期图表,如秒级、分钟级。

4.VWAP 策略

我们已经知道VWAP的运行特点,那么如何利用这些特点进行交易呢?

利用其回调的特点。当股价在一天内显着超过 VWAP 和移动平均线时,它们可能会回调。你可以选择在股价大幅度上涨时卖空股票,也可以选择在回调时等待入场。

Fade策略。这个策略是一个逆势策略,它在强劲势头的运动后采取相反的立场。利用VWAP发的支撑和压力作为其入场和出场的信号。

午后走高策略。这是一个油管老哥(Tim Bohen)观察出来的策略,他发现热门股票早盘走高,并且价格持续保持在vwap上方的股票,午后走高突破的几率非常大。

当然,所有策略都应该被回测后再确定是否有效。以上策略只是一个根据VWAP做交易的思路,你还可以结合其他指标进行策略的开发和回测,有兴趣的同学可以试试看。

本文参考文章:https://analyzingalpha.com/blog/vwap

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

FreqTrade—又强又简单的Python加密货币自动交易机器人

Freqtrade 是一个用 Python 编写的免费开源加密货币交易机器人。它旨在支持所有主要交易所并通过 Telegram 或 webUI 进行控制。功能包含回测、绘图和资金管理工具以及通过机器学习的策略优化。

目前支持的交易所:

特性:

  •  基于 Python 3.8+:适用于任何操作系统 – Windows、macOS 和 Linux。
  •  持久性:持久性是通过 sqlite 实现的。
  •  Dry-run:不花钱运行机器人。
  •  回测:模拟买入/卖出策略。
  •  通过机器学习进行策略优化:使用机器学习通过真实的交易所数据优化买入/卖出策略参数。
  •  边缘头寸规模计算您的胜率、风险回报率、最佳止损位并在为每个特定市场建立头寸之前调整头寸规模。
  •  白名单加密货币:选择你要交易的加密货币或使用动态白名单。
  •  黑名单加密货币:选择你想要避免的加密货币。
  •  内置 WebUI:内置 Web UI 来管理你的机器人。
  •  可通过 Telegram管理:使用 Telegram 管理机器人。
  •  以法定货币显示盈亏:以法定货币显示你的盈亏。
  •  表现状态报告:提供你当前交易的表现状态。

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。

(可选1) 如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

(可选2) 此外,推荐大家用VSCode编辑器来编写小型Python项目:Python 编程的最好搭档—VSCode 详细指南

在Linux/MacOS下,三行命令就能完成安装:

git clone -b develop https://github.com/freqtrade/freqtrade.git
cd freqtrade
./setup.sh --install

如果你无法克隆此项目,请在Python实用宝典公众号后台回复:freqtrade 下载。

Windows环境下打开Cmd(开始—运行—CMD),输入命令安装依赖:

git clone https://github.com/freqtrade/freqtrade.git
cd freqtrade
# 安装ta-lib
pip install build_helpers/TA_Lib-0.4.24-cp38-cp38-win_amd64.whl
pip install -r requirements.txt
pip install -e .
freqtrade

请注意,此处安装ta-lib时项目方提供了python3.8/3.9/3.10,其他Python版本请自行搜索下载。

输入freqtrade时,显示以下信息说明安装成功:

(freqtrade) D:\CODE\trader\freqtrade>freqtrade
2022-02-17 19:40:50,174 - freqtrade - ERROR - Usage of Freqtrade requires a subcommand to be specified.
To have the bot executing trades in live/dry-run modes, depending on the value of the `dry_run` setting in the config, run Freqtrade as `freqtrade trade [options...]`.
To see the full list of options available, please use `freqtrade --help` or `freqtrade <command> --help`.

2.Freqtrade 快速开始

下面教你如何开发一个简单的交易策略。

一个策略文件往往包含这些东西:

  • 指标
  • 购买规则
  • 卖出规则
  • 建议最低投资回报率
  • 强烈推荐止损

Freqtrade使用 Pandas 作为基础数据结构,它底层的OHLCV都是以Dataframe的格式存储的。

Dataframe数据流中每一行数据代表图表上的一根K线,最新的K线始终是数据库中最后一根。

> dataframe.head()
                       date      open      high       low     close     volume
0 2021-11-09 23:25:00+00:00  67279.67  67321.84  67255.01  67300.97   44.62253
1 2021-11-09 23:30:00+00:00  67300.97  67301.34  67183.03  67187.01   61.38076
2 2021-11-09 23:35:00+00:00  67187.02  67187.02  67031.93  67123.81  113.42728
3 2021-11-09 23:40:00+00:00  67123.80  67222.40  67080.33  67160.48   78.96008
4 2021-11-09 23:45:00+00:00  67160.48  67160.48  66901.26  66943.37  111.39292

Pandas 提供了计算指标的快速方法。为了从这种速度中受益,建议不要使用循环,而是使用矢量化方法。

矢量化操作在整个数据范围内执行计算,因此,与遍历每一行相比,在计算指标时要快得多。

dataframe.loc[(dataframe['rsi'] > 30), 'buy'] = 1

类似于上面这样的赋值方法,会自动设置rsi大于30的数据的buy列的值为1。

买入规则

def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    """
    Based on TA indicators, populates the buy signal for the given dataframe
    :param dataframe: DataFrame populated with indicators
    :param metadata: Additional information, like the currently traded pair
    :return: DataFrame with buy column
    """
    dataframe.loc[
        (
            (qtpylib.crossed_above(dataframe['rsi'], 30)) &  # Signal: RSI crosses above 30
            (dataframe['tema'] <= dataframe['bb_middleband']) &  # Guard
            (dataframe['tema'] > dataframe['tema'].shift(1)) &  # Guard
            (dataframe['volume'] > 0)  # Make sure Volume is not 0
        ),
        'buy'] = 1

    return dataframe

请注意,一定要不修改并返回”open”, “high”, “low”, “close”, “volume”列,这些是基础行情数据,如果返回错误的数据将可能会导致一些奇怪数据的产生。

如上所示的方法中,符合条件的数据的buy值会被设为1代表买入,否则为0或nan值。

卖出规则

def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
    """
    Based on TA indicators, populates the sell signal for the given dataframe
    :param dataframe: DataFrame populated with indicators
    :param metadata: Additional information, like the currently traded pair
    :return: DataFrame with buy column
    """
    dataframe.loc[
        (
            (qtpylib.crossed_above(dataframe['rsi'], 70)) &  # Signal: RSI crosses above 70
            (dataframe['tema'] > dataframe['bb_middleband']) &  # Guard
            (dataframe['tema'] < dataframe['tema'].shift(1)) &  # Guard
            (dataframe['volume'] > 0)  # Make sure Volume is not 0
        ),
        'sell'] = 1
    return dataframe

与买入类似,这里不赘述了。

最小投资回报率

在类中增加这个初始化变量,能控制投资回报率:

minimal_roi = {
    "40": 0.0,
    "30": 0.01,
    "20": 0.02,
    "0": 0.04
}

上述配置意味着:

  • 只要达到 4% 的利润就卖出
  • 达到 2% 利润时卖出(20 分钟后生效)
  • 达到 1% 利润时卖出(30 分钟后生效)
  • 交易未亏损时卖出(40 分钟后生效)

此处的计算包含费用。

要完全禁用 ROI,请将其设置为一个非常高的数字:

minimal_roi = {
    "0": 100
}

虽然从技术上讲并没有完全禁用,但一旦交易达到 10000% 利润,它就会卖出。

止损

强烈建议设置止损,以保护资金免受不利的剧烈波动。

设置 10% 止损的示例:

stoploss = -0.10

一个完整代码如下:

# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these libs ---
from re import A
import numpy as np  # noqa
import pandas as pd  # noqa
from pandas import DataFrame

from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
                                IStrategy, IntParameter)

# --------------------------------
# 你自己所需要的模块放在这里
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib


# This class is a sample. Feel free to customize it.
class SampleStrategy(IStrategy):
    """
    This is a sample strategy to inspire you.
    More information in https://www.freqtrade.io/en/latest/strategy-customization/
    You can:
        :return: a Dataframe with all mandatory indicators for the strategies
    - Rename the class name (Do not forget to update class_name)
    - Add any methods you want to build your strategy
    - Add any lib you need to build your strategy
    You must keep:
    - the lib in the section "Do not remove these libs"
    - the methods: populate_indicators, populate_buy_trend, populate_sell_trend
    You should keep:
    - timeframe, minimal_roi, stoploss, trailing_*
    """
    # Strategy interface version - allow new iterations of the strategy interface.
    # Check the documentation or the Sample strategy to get the latest version.
    INTERFACE_VERSION = 2

    # 设定最小投资回报
    minimal_roi = {
        "60": 0.01,
        "30": 0.02,
        "0": 0.04
    }

    # 止损
    stoploss = -0.10

    # 指标参数
    buy_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True)
    sell_rsi = IntParameter(low=50, high=100, default=70, space='sell', optimize=True, load=True)

    # K线时间
    timeframe = '5m'

    # 在新K线出现时执行
    process_only_new_candles = False

    # These values can be overridden in the "ask_strategy" section in the config.
    use_sell_signal = True
    sell_profit_only = False
    ignore_roi_if_buy_signal = False

    # 预准备K线数
    startup_candle_count: int = 30

    # 下单类型
    order_types = {
        'buy': 'limit',
        'sell': 'limit',
        'stoploss': 'market',
        'stoploss_on_exchange': False
    }

    # 订单有效时间(gtc: 除非取消否则一直有效)
    order_time_in_force = {
        'buy': 'gtc',
        'sell': 'gtc'
    }

    plot_config = {
        'main_plot': {
            'tema': {},
            'sar': {'color': 'white'},
        },
        'subplots': {
            "MACD": {
                'macd': {'color': 'blue'},
                'macdsignal': {'color': 'orange'},
            },
            "RSI": {
                'rsi': {'color': 'red'},
            }
        }
    }

    def informative_pairs(self):
        """
        Define additional, informative pair/interval combinations to be cached from the exchange.
        These pair/interval combinations are non-tradeable, unless they are part
        of the whitelist as well.
        For more information, please consult the documentation
        :return: List of tuples in the format (pair, interval)
            Sample: return [("ETH/USDT", "5m"),
                            ("BTC/USDT", "15m"),
                            ]
        """
        return []

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Adds several different TA indicators to the given DataFrame
        Performance Note: For the best performance be frugal on the number of indicators
        you are using. Let uncomment only the indicator you are using in your strategies
        or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
        :param dataframe: Dataframe with data from the exchange
        :param metadata: Additional information, like the currently traded pair
        :return: a Dataframe with all mandatory indicators for the strategies
        """

        # Momentum Indicators
        # ------------------------------------

        dataframe['adx'] = ta.ADX(dataframe)
        dataframe['rsi'] = ta.RSI(dataframe)
        stoch_fast = ta.STOCHF(dataframe)
        dataframe['fastd'] = stoch_fast['fastd']
        dataframe['fastk'] = stoch_fast['fastk']

        # MACD
        macd = ta.MACD(dataframe)
        dataframe['macd'] = macd['macd']
        dataframe['macdsignal'] = macd['macdsignal']
        dataframe['macdhist'] = macd['macdhist']

        # MFI
        dataframe['mfi'] = ta.MFI(dataframe)

        # Bollinger Bands
        bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
        dataframe['bb_lowerband'] = bollinger['lower']
        dataframe['bb_middleband'] = bollinger['mid']
        dataframe['bb_upperband'] = bollinger['upper']
        dataframe["bb_percent"] = (
            (dataframe["close"] - dataframe["bb_lowerband"]) /
            (dataframe["bb_upperband"] - dataframe["bb_lowerband"])
        )
        dataframe["bb_width"] = (
            (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe["bb_middleband"]
        )

        # Parabolic SAR
        dataframe['sar'] = ta.SAR(dataframe)

        # TEMA - Triple Exponential Moving Average
        dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)

        hilbert = ta.HT_SINE(dataframe)
        dataframe['htsine'] = hilbert['sine']
        dataframe['htleadsine'] = hilbert['leadsine']

        return dataframe

    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Based on TA indicators, populates the buy signal for the given dataframe
        :param dataframe: DataFrame populated with indicators
        :param metadata: Additional information, like the currently traded pair
        :return: DataFrame with buy column
        """
        dataframe.loc[
            (
                # Signal: RSI crosses above 30
                (qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) &
                (dataframe['tema'] <= dataframe['bb_middleband']) &  # Guard: tema below BB middle
                (dataframe['tema'] > dataframe['tema'].shift(1)) &  # Guard: tema is raising
                (dataframe['volume'] > 0)  # Make sure Volume is not 0
            ), 'buy'] = 1

        return dataframe

    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Based on TA indicators, populates the sell signal for the given dataframe
        :param dataframe: DataFrame populated with indicators
        :param metadata: Additional information, like the currently traded pair
        :return: DataFrame with sell column
        """
        dataframe.loc[
            (
                # Signal: RSI crosses above 70
                (qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) &
                (dataframe['tema'] > dataframe['bb_middleband']) &  # Guard: tema above BB middle
                (dataframe['tema'] < dataframe['tema'].shift(1)) &  # Guard: tema is falling
                (dataframe['volume'] > 0)  # Make sure Volume is not 0
            ), 'sell'] = 1
        return dataframe
        

3.启动机器人

启动机器人前还需要设定配置,配置模板在 config/examples 下面。

比如币安的配置,你还需要输入key和secret:

"exchange": {
        "name": "binance",
        "key": "your_exchange_key",
        "secret": "your_exchange_secret",
  	    ......
	}
}

启动机器人:

freqtrade trade --strategy AwesomeStrategy --strategy-path /some/directory  -c path/far/far/away/config.json

–strategy-path 指定策略文件位置

-c 参数指定配置文件位置

比如我把策略放在了user_data/strategies下,配置放在了config_examples下,这么输入命令启动机器人即可:

freqtrade trade --strategy SampleStrategy --strategy-path user_data/strategies  -c config_examples/config_binance.example.json

由于篇幅问题,本文只是介绍了freqtrade的冰山一角,在启动机器人前,一定要进行回测并进行模拟交易。它还有TG通知功能、WebUI管理界面,详细的使用方法大家可以参考官方教程:

https://www.freqtrade.io/en/stable/

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

什么是强化学习?量化投资领域预测股票的效果如何?

强化学习是机器学习的方式之一,它与监督学习、无监督学习并列,是三种机器学习训练方法之一。

在围棋上击败世界第一的李世石的 AlphaGo、在《星际争霸2》中以 10:1 击败了人类顶级职业玩家的AlphaStar,他们都是强化学习模型。诸如此类的模型还有 AlphaGo Zero 等。

强化学习的原理非常简单,它非常像心理学中新行为主义派的斯金纳发现的操作性条件反射。

操作性条件反射是什么?当年斯金纳做了一个箱子,进行了两次实验。

第一次实验,箱子里放了一只饥饿的老鼠,在箱子的一边有一个可供按压的杠杆,在杠杆旁边有一个放置食物的小盒子。动物在箱内按下杠杆,食盒就会释放食物进入箱内,动物可以取食。结果:小鼠自发学会了按按钮。这是积极强化。

另一次实验是,每次小白鼠不按下按钮,则给箱子通电,小白鼠因此学会了按按钮以防自己遭受电击。这是消极强化(负向强化)。

这就是斯金纳发现的操作性条件反射,当行为得到奖励或惩罚时出现刺激,反过来控制这种行为。

强化学习与操作性条件反射有异曲同工之妙,以人类玩游戏为例,如果在游戏中采取某种策略购买某种类型的装备可以取得较高的得分,那么就会进一步“强化”这种策略,以期继续取得较好的结果。

网上有不少强化学习的例子,鉴于读者中对股票感兴趣的同学比较多,我们以股票预测为例,实验一下 wangshubRL-Stock 项目。

使用强化学习预测股价,需要在决策的时候采取合适的行动 (Action) 使最后的奖励最大化。与监督学习预测未来的数值不同,强化学习根据输入的状态(如当日开盘价、收盘价等),输出系列动作(例如:买进、持有、卖出),并对好的动作结果不断进行奖励,对差的动作结果不断进行惩罚,使得最后的收益最大化,实现自动交易。

下面就试一下这个强化学习项目,前往GitHub下载 RL-Stock

如果你无法使用GitHub,也可以在Python实用宝典公众号后台回复:股票强化学习1 下载全文完整代码,包括第三部分的多进程优化逻辑。

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。

(可选1) 如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

(可选2) 此外,推荐大家用VSCode编辑器来编写小型Python项目:Python 编程的最好搭档—VSCode 详细指南

请注意,由于TensorFlow版本限制,这个强化学习项目只支持 Python3 以上,Python3.6 及以下的版本,因此我建议使用Anaconda创建一个新的虚拟环境运行这个项目:

conda create -n rlstock python=3.6

另外,实测依赖需要改动 requirements.txt 的tensorflow-gpu版本至1.14:

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),进入 RL-Stock 项目文件夹输入命令安装依赖:

pip install -r requirements.txt

2.小试强化学习预测股票价格

运行RL-Stock项目前,需要下载数据。进入刚创建的虚拟环境,运行get_stock_data.py代码会自动下载数据到 stockdata 目录中:

python get_stock_data.py

如果你使用的是在Github上下载的代码而不是Python实用宝典后台改好的代码,请注意 get_stock_data.py 的第46行,必须对 row[“code_name”] 去除 * 号,否则Windows系统下可能会存在问题:

df_code.to_csv(f'{self.output_dir}/{row["code"]}.{row["code_name"].strip("*")}.csv', index=False)

数据下载完成后就可以运行 main.py 执行强化学习训练和测试,不过在训练之前,我们先简单了解下整个项目的输入状态、动作、和奖励函数。

输入状态(观测 Observation)

策略网络观测的就是一只股票的各类数据,比如开盘价、收盘价、成交量等,它可以由许多因子组成。为了训练时网络收敛,观测状态数据输入时必须要进行归一化,变换到 [-1, 1] 的区间内。RL-Stock输入的观测数据字段如下:

参数名称参数描述说明
date交易所行情日期格式:YYYY-MM-DD
code证券代码格式:sh.600000。sh:上海,sz:深圳
open今开盘价格精度:小数点后4位;单位:人民币元
high最高价精度:小数点后4位;单位:人民币元
low最低价精度:小数点后4位;单位:人民币元
close今收盘价精度:小数点后4位;单位:人民币元
preclose昨日收盘价精度:小数点后4位;单位:人民币元
volume成交数量单位:股
amount成交金额精度:小数点后4位;单位:人民币元
adjustflag复权状态不复权、前复权、后复权
turn换手率精度:小数点后6位;单位:%
tradestatus交易状态1:正常交易 0:停牌
pctChg涨跌幅(百分比)精度:小数点后6位
peTTM滚动市盈率精度:小数点后6位
psTTM滚动市销率精度:小数点后6位
pcfNcfTTM滚动市现率精度:小数点后6位
pbMRQ市净率精度:小数点后6位

动作 Action

共有买入卖出持有 3 种动作,定义动作(action)为长度为 2 的数组

  • action[0] 为操作类型;
  • action[1] 表示买入或卖出百分比;
动作类型 action[0]说明
1买入 action[1]
2卖出 action[1]
3持有

注意,当动作类型 action[0] = 3 时,表示不买也不抛售股票,此时 action[1] 的值无实际意义,网络在训练过程中,Agent 会慢慢学习到这一信息。Agent,实称代理,在我们的上下文中,你可以视其为策略。

奖励 Reward

奖励函数的设计,对强化学习的目标至关重要。在股票交易的环境下,最应该关心的就是当前的盈利情况,故用当前的利润作为奖励函数。

# profits
reward = self.net_worth - INITIAL_ACCOUNT_BALANCE
reward = 1 if reward > 0 else -100

为了使网络更快学习到盈利的策略,当利润为负值时,给予网络一个较大的惩罚 (-100)。

梯度策略

作者采用了基于策略梯度的PPO 算法,OpenAI 和许多文献已把 PPO 作为强化学习研究中首选的算法。PPO 优化算法 Python 实现参考 stable-baselines

数据集及自定义

在数据集上,作者使用了1990年至2019年11月作为训练集,2019年12月作为测试集。

1990-01-01 ~ 2019-11-292019-12-01 ~ 2019-12-31
训练集测试集

如果你要调整这个训练集和测试集的时间,可以更改 get_stock_data.py 的以下部分:

if __name__ == '__main__':
    # 获取全部股票的日K线数据
    
    # 训练集
    mkdir('./stockdata/train')
    downloader = Downloader('./stockdata/train', date_start='1990-01-01', date_end='2019-11-29')
    downloader.run()
	# 测试集
    mkdir('./stockdata/test')
    downloader = Downloader('./stockdata/test', date_start='2019-12-01', date_end='2019-12-31')
    downloader.run()

训练并测试

首先,我们尝试一下单一代码的训练和测试,修改main.py里的股票代码,比如我这里修改为601919中远海控:

if __name__ == '__main__':
    # multi_stock_trade()
    test_a_stock_trade('sh.601919')
    # ret = find_file('./stockdata/train', '601919')
    # print(ret)

运行下面的命令,执行此深度学习模型的训练和测试。

python main.py

训练完成后,会自动进行模拟操作测试集这20个交易日,然后会输出这20个交易日的测试结果:

------------------------------
Step: 20
Balance: 0.713083354256014
Shares held: 2060 (Total sold: 2392)
Avg cost for held shares: 5.072161917927474 (Total sales value: 12195.091008936648)
Net worth: 10930.56492977963 (Max net worth: 10930.56492977963)
Profit: 930.5649297796299
------------------------------
Step: 21
Balance: 0.713083354256014
Shares held: 2060 (Total sold: 2392)
Avg cost for held shares: 5.072161917927474 (Total sales value: 12195.091008936648)
Net worth: 10815.713083354256 (Max net worth: 10930.56492977963)
Profit: 815.713083354256

利润图如下:

然后我们看一下中远海控2019年12月的走势:

可以看到这个月的中远海控是一个上升趋势,一共上涨了12%,而这个模型捕捉到其中8%左右的利润,还是相当不错的。当然,凡事不能只看个体,下面我们修改下作者的源代码,试一下其在市场里的整体表现。

3.强化学习模型整体表现

由于作者原有的模型是单进程的计算,为了测试全市场的表现,我进行了多进程改造。

我将作者的训练及测试任务集成到一个函数中,并使用celery做并行:

@app.task
def multi_stock_trade(code):
    stock_file = find_file('./stockdata/train', str(code))
    if stock_file:
        try:
            profits = stock_trade(stock_file)
            with open(f'result/code-{code}.pkl', 'wb') as f:
                pickle.dump(profits, f)
        except Exception as err:
            print(err)

将测试集的测试周期改为最近一个月:

1990-01-01 ~ 2021-11-252021-11-26 ~ 2021-12-25
训练集测试集

开启redis-server 及 Celery Worker:

# redis-server 独占一个进程,所以需要另开一个窗口
celery -A tasks worker -l info

遍历所有的股票代码做并发测试:

files = os.listdir("stockdata/train")
files_test = os.listdir("stockdata/test")
all_files_list = list(set(files) & set(files_test))
for i in all_files_list:
    # 使用celery做并发
    code = ".".join(i.split(".")[:2])
    # multi_stock_trade.apply_async(args=(code,))
    multi_stock_trade(code)

再对生成的结果进行统计,测试结果如下:

对这个模型在2021-11-26到2021-12-25的测试结果表明,有40.8%的股票进行了交易并且获利,有49.9%的股票没有进行操作,有9.4%的股票进行了交易并亏损。平均每次交易利润为445元,作为一个测试策略,这个结果已经很不错了。

由于只是一个测试策略,这里就不做详细的风险分析了,实际上我们还需要观察这个策略的最大回撤率、夏普率等指标才能更好地评判此策略的好坏。

我认为这个项目还有很大的改造空间,原逻辑中只观察了OHLC等基本数据,我们还可以增加很多指标,比如基于Ta-lib,算出MACD、RSI等技术指标,再将其加入Observation中,让模型观察学习这些数据的特征,可能会有不错的表现。有兴趣的同学可以试一下,本文源代码存放于:

https://github.com/Ckend/pythondict-quant

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

量化投资单因子回测神器 — Alphalens

还记得我们在前面采用的回测工具Backtrader吗?Backtrader是一款非常灵活的回测工具,基于它你能回测任何你想要测试的idea.

但是针对单因子回测,Backtrader 开发回测代码以及生成报告上并不算很方便,我们需要自己编写买卖逻辑,在生成的报告上也没有IC、IR、回撤等的数据分析,而实际上,从单因子回测的技术实现角度上来说,这些都是可以自动化生成的。

Alphalens就是一个专门实现单因子自动回测的神器,我们只要给它输入因子值的列,还有每支股票收盘价的数据,它就能自动生成数据分析及报告,并带有十几张可视化的报告数据统计图:

下面就带大家入门使用一下Alphalens,如果对你有帮助的话,记得点一下赞/在看哦。

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。

(可选1) 如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

(可选2) 此外,推荐大家用VSCode编辑器来编写小型Python项目:Python 编程的最好搭档—VSCode 详细指南

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),输入命令安装依赖:

pip install alphalens
pip install tushare
pip install pandas

2.数据预处理

正如前面所说,我们只需要把因子值和收盘价放入Alphalens中,就能自动生成回测和报告结果。

所以,我们90%的工作都会在数据处理这一部分,回测和分析都是抽象封装好的,并不需要太多地去担心它。

为了测试,我们导入tushare的数据进行测试:

import pandas as pd
import tushare as ts
from alphalens.utils import get_clean_factor_and_forward_returns
from alphalens.tears import create_full_tear_sheet

pro = ts.pro_api()
# 此接口获取的数据为未复权数据,回测建议使用复权数据,这里为批量获取股票数据做了简化
df = pro.daily(ts_code='000001.SZ,600982.SH', start_date='20200101', end_date='20211122')
df.index = pd.to_datetime(df['trade_date'])
df.index.name = None
df.sort_index(inplace=True)

这里获取了000001.SZ,600982.SH两只股票在2020-01-01到2021-11-22的日线数据,将交易日期设为了索引并排序。效果如下:

然后需要设置多索引的因子列 assets,第一个索引为日期,第二个索引为股票代码:

# 多索引的因子列,第一个索引为日期,第二个索引为股票代码
assets = df.set_index([df.index, df['ts_code']], drop=True)

​效果如下,仔细观察的话能发现其与导入的数据只有索引的不同:

然后,设置收盘价的Dataframe,这个与因子数据的格式不同,索引是时间,每一列是每只股票对应的收盘价:

# column为股票代码,index为日期,值为收盘价
close = df.pivot_table(index='trade_date', columns='ts_code', values='close')
close.index = pd.to_datetime(close.index)

到这一步,我们的初始化工作就完成了,下面就放到 Alphalens 进行测试。

3.Alphalens回测及报告

使用Alphalens进行回测,是非常轻松而写意的,只需要导入包,给它传递因子数据和收盘价数据即可:

from alphalens.utils import get_clean_factor_and_forward_returns
from alphalens.tears import create_full_tear_sheet

ret = get_clean_factor_and_forward_returns(assets[['pct_chg']], close)
create_full_tear_sheet(ret, long_short=False)

get_clean_factor_and_forward_returns 接受的第一个参数就是因子的列,我们只需要从前面预处理好的 assets 中任取一列作为因子进行回测即可,第二列是收盘价。

值得注意的是,因子数据在回测的时候,注意不要使用到未来数据,因为我们是用前一天的数据预测下一天的收盘价,所以要对因子列进行移位处理,这点一定要注意。

运行程序,就能生成如下的报告:

还有一点需要提醒大家的是,开源Alphalens的Quantopian公司已经倒闭,所以项目暂时没人维护了,部分代码没有适配最新的依赖,所以可能会有问题,比如下面的:

原本是通过 .get_values() 获得 input_periods, 但是 get_values 在 pandas 0.25.0 中已经被弃用,最新的pandas版本这里需要改成 .to_numpy() 才能生效。

除了这个小缺点,Alphalens整体上是非常符合大家单因子测试的需求的。它的分析报告可能没有那么齐全,我们也可以考虑在Alphalens的基础上增加其他的分析内容,如果能开源出来则更好了。

考虑到后续Alphalens没人维护,我fork了Alphalens,并增加了自己的改动,希望有余力的同学也能来一起贡献代码:
https://github.com/Ckend/alphalens

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

微软开源!世界首个AI 量化投资平台 Qlib 使用教程

2020年9月,微软开源了AI量化投资平台Qlib的源代码,随后得到了不少的关注,Qlib的主要优势在于:

1.Python覆盖量化投资全过程,用户无需切换语言;​内置许多深度学习算法模型,降低AI算法使用的门槛。

2.内置A股、美股数据接入通道,基于qrun能够自动运行整个工作流程,大大提高开发效率。

3.每个组件都是松耦合可以独立使用,用户能够自行选用某些组件。

Qlib相比于我们之前介绍的backtrader,那功能完善太多。backtrader相当于给你提供一个基本的量化框架,数据、策略、算法,你全部自己搞定。而Qlib则从数据、到策略、到算法都给了你全套的解决方案,你只需要加一点自己的想法,不需要管其他细枝末节的东西就能完成AI量化研究,非常方便。

下面我们就来试一下 Qlib 的安装和运行内置算法策略。

1. 安装

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 (传统) 或 Python数据分析与挖掘好帮手—Anaconda 进行安装,本文建议使用Anaconda。

由于qlib有许多许多依赖,如果你不想安装过程中出现问题,或者引起其他程序的运行问题,建议使用Conda创建一个你的量化投资虚拟环境:

conda create -n my_quant python=3.8

Qlib 仅支持 Python3.7以上的版本且暂不支持 Python3.10. 另外Python 3.9 版本不支持模型性能绘制,因此我选择创建Python3.8版本的虚拟环境。

(安装方式一)pip 安装:

pip install pyqlib

在pip安装的过程中如果遇到任何问题,请搜索引擎解决,如果无法解决,可以尝试下面的源码安装:

(安装方式二)源码安装:

# 提前安装一些依赖
pip install numpy
pip install --upgrade  cython

# clone and install qlib
git clone https://github.com/microsoft/qlib.git && cd qlib
python setup.py install

Windows 机器在安装的时候可能会遇到下面这个问题:

这是因为安装 qlib 的依赖 — tables 时出现了编译错误,原因很多,我选择逃学,因此建议使用 tables 的 wheel 文件进行安装,这样就不需要编译了:

https://www.lfd.uci.edu/~gohlke/pythonlibs/#pytables

在上述网站下载适合你系统的 wheel 文件:

下载完毕后,输入以下命令:

pip install 你的文件路径/tables-3.6.1-cp39-cp39-win_amd64.whl

即可完成 tables 的安装,然后再执行一遍 python setup.py install 即可。

2. 数据准备

由于这套量化开源平台的作者是中国人,所以非常贴心地准备好了A股数据,大家可以输入命令直接下载:

# 1天级别数据
python scripts/get_data.py qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn

# 1分钟级别数据
python scripts/get_data.py qlib_data --target_dir ~/.qlib/qlib_data/cn_data_1min --region cn --interval 1min

如果你需要其他分钟级的数据,修改interval即可。

你可以使用crontab定时自动更新数据(来自雅虎财经):

* * * * 1-5 python <script path> update_data_to_bin --qlib_data_1d_dir <user data dir>

手动更新数据:

python scripts/data_collector/yahoo/collector.py update_data_to_bin --qlib_data_1d_dir <user data dir> --trading_date <start date> --end_date <end date>

3. 运行量化回测流程示例

Qlib 提供了一个名为 qrun 自动运行整个工作流程的工具(包括构建数据集、训练模型、回测和评估)。

你可以按照以下步骤启动自动量化研究工作流程并进行图形报告分析,Quant Research 工作流程:

Qrun 运行 lightgbm 工作流程的配置 workflow_config_lightgbm_Alpha158.yaml 如下所示:

cd examples  # Avoid running program under the directory contains `qlib`
qrun benchmarks/LightGBM/workflow_config_lightgbm_Alpha158.yaml

结果如下:

可以看到这里包括三个统计分析: benchmark return (基准收益) / excess return without cost(除去手续费的超额收益)) / excess return with cost(包含手续费的超额收益)。每个统计分析中都有如下5个参数:

  • mean: 异常收益的平均值
  • std: 异常收益的标准差
  • annualized_return: 年化回报
  • information_ratio: 信息比率
  • max_drawdown: 最大回撤

​如果你想要自定义这个策略和算法的回测参数,你可以查看 workflow_config_lightgbm_Alpha158.yaml 的内容:

qlib_init:
    provider_uri: "~/.qlib/qlib_data/cn_data"
    region: cn
market: &market csi300
benchmark: &benchmark SH000300
data_handler_config: &data_handler_config
    start_time: 2008-01-01
    end_time: 2020-08-01
    fit_start_time: 2008-01-01
    fit_end_time: 2014-12-31
    instruments: *market
port_analysis_config: &port_analysis_config
    strategy:
        class: TopkDropoutStrategy
        module_path: qlib.contrib.strategy
        kwargs:
            model: <MODEL>
            dataset: <DATASET>
            topk: 50
            n_drop: 5
    backtest:
        start_time: 2017-01-01
        end_time: 2020-08-01
        account: 100000000
        benchmark: *benchmark
        exchange_kwargs:
            limit_threshold: 0.095
            deal_price: close
            open_cost: 0.0005
            close_cost: 0.0015
            min_cost: 5
task:
    model:
        class: LGBModel
        module_path: qlib.contrib.model.gbdt
        kwargs:
            loss: mse
            colsample_bytree: 0.8879
            learning_rate: 0.2
            subsample: 0.8789
            lambda_l1: 205.6999
            lambda_l2: 580.9768
            max_depth: 8
            num_leaves: 210
            num_threads: 20
    dataset:
        class: DatasetH
        module_path: qlib.data.dataset
        kwargs:
            handler:
                class: Alpha158
                module_path: qlib.contrib.data.handler
                kwargs: *data_handler_config
            segments:
                train: [2008-01-01, 2014-12-31]
                valid: [2015-01-01, 2016-12-31]
                test: [2017-01-01, 2020-08-01]
    record: 
        - class: SignalRecord
          module_path: qlib.workflow.record_temp
          kwargs: 
            model: <MODEL>
            dataset: <DATASET>
        - class: SigAnaRecord
          module_path: qlib.workflow.record_temp
          kwargs: 
            ana_long_short: False
            ann_scaler: 252
        - class: PortAnaRecord
          module_path: qlib.workflow.record_temp
          kwargs: 
            config: *port_analysis_config

参数比较多,大家翻译一下应该都能看懂。这里摘取华泰的一个研究报告,里面对参数做了具体的翻译:

为了方便用户的使用,微软内置了许多模型,如上文我们用到的 gbdt 位于你克隆的文件夹下的 qlib/contrib/model/gbdt.py:

注意:pytorch 开头的模型需要预先安装pytorch.

Qlib里,策略和算法的区别是什么?

大家注意到,Qlib这里,必须定义策略和算法两个配置,而在backtrader里面,我们更加重视策略,而非“算法”这个概念。那么这两者在Qlib中的区别是什么?我们看默认TOPK策略的源代码:

可以看到,默认的这个策略,选择了算法预测分数结果中排名 TOP K 的股票,也就是策略从算法得到的结果中去做筛选需要交易的股票。算法相当于生成一个新的可用于判断买入卖出的评判标准。这就是策略和AI算法这两者的最重要区别。

最后,得益于松耦合的代码设计,我认为 Qlib 是一个能够让不同层次的研究者各取所需的开源项目,是一个不可多得的量化开源平台,特别适合重度Python使用者,有兴趣的朋友可以试一下,我未来也会考虑出 Qlib 相关的使用教程,敬请期待。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Lean — 优秀好用的开源量化交易平台

Lean 是 QuantConnect 开源的一款非常强大的开源量化交易平台,可以回测或运行Python或者C#写的策略,并内置了上百个C#和Python写的策略算法在代码仓库中。

这个开源算法交易引擎,专为轻松地进行策略研究、回测和实时交易而构建。它集成了常见的数据提供商和券商,因此可以快速部署算法交易策略。

LEAN Engine 的核心是用 C# 编写的;但它可以在 Linux、Mac 和 Windows 操作系统上无缝运行。它支持用 Python 3.6 或 C# 编写的算法。

引擎分为许多模块化部分,可以在不接触其他文件的情况下对某个模块进行扩展。

最重要的几个模块是:

  • 结果处理(IResultHandler)处理来自算法交易引擎的所有消息。决定应该发送什么,以及消息应该去哪里。结果处理系统可以将消息发送到本地 GUI 或 Web 界面。
  • 数据源(IDataFeed)连接并下载算法交易引擎所需的数据。从磁盘文件中读取文件进行回测;实时交易则连接到一个流并生成数据对象。
  • 事务处理(ITransactionHandler)处理新的订单请求;要么使用算法提供的模拟模型,要么使用实际券商。
  • 实时事件管理(IRealtimeHandler)生成实时事件 – 例如一天结束的事件。触发对实时事件处理程序的回调。
  • 算法状态设置(ISetupHandler)配置算法资金、投资组合和请求的数据。初始化所需的所有状态参数。

这些都可以从 Launcher 项目中的 config.json 文件进行配置。

1. Leon 安装教程

由于Leon是基于C#开发的,因此我推荐使用Visual Studio进行开发。

1、克隆项目。从 https://github.com/QuantConnect/Lean 克隆项目到本地(如果你网络不通可在公众号后台回复 Lean 下载)。

2、使用 Visual Studio 打开项目中的 QuantConnect.Lean.sln

3、点击 生成 – 生成解决方案

4、点击 F5 则可以运行程序。

如果你在生成解决方案的过程中遇到了类似于如下的错误:

请在工具 – NuGet包管理器 – 程序包管理器设置 中 添加如下的源, 名字任取,链接对了就行: https://api.nuget.org/v3/index.json

2. 回测 Lean 内置的C#策略

Lean 中比较有意思的一点是,其所有C#策略算法都位于 QuantConnect.Algorithm.CSharp 中,所有的Python策略算法都位于 QuantConnect.Algorithm.Python 中:

如果你想回测C#的策略,你只需要修改 QuantConnect.Lean.Launcher 中的 config.json,将 QuantConnect.Algorithm.CSharp 中对应策略名称,修改到 algorithm-type-name 字段对应的值中,如图所示:

然后按 F5 运行程序,回测开始,此时会弹出一个cmd窗口,里面有本次回测的统计数据:

3. 回测 Lean 内置的 Python策略

如果你想要回测内置的Python策略,我们需要先指定Lean使用的Python环境位置:

1.打开系统变量(我的电脑-右键属性-高级系统设置->环境变量->系统变量)

2.点击新建变量,name为 PYTHONNET_PYDLL;value则为你的Python环境的dll文件所在文件夹,如我的为 G:\Anaconda3\python36.dll

3.在此Python环境中安装Lean的依赖:

pip install pandas
pip install wrapt==1.11.2

然后在项目的 config.json 中需要多改几个配置:

然后按F5进行回测,效果如下:

这些统计指标令人眼花缭乱,对于股票的回测我们只要重点关注这些即可:

  • Total Trades: 总交易量
  • Average Win: 平均盈利率
  • Average Loss: 平均亏损率
  • Compounding Annual Return: 复合年回报率
  • Drawdown: 最大回撤率
  • Expectancy: 期望值
  • Net Profit: 净利润
  • Sharpe Ratio: 夏普比率
  • Probabilistic Sharpe Ratio: 概率性夏普比率
  • Loss Rate: 失败率
  • Win Rate: 胜率
  • Profit-Loss Ratio: 盈亏比
  • Alpha: Alpha值
  • Beta: Beta值
  • Total Fees: 总手续费

其他的,按需关注即可。

4. Lean 策略是怎么写的?

开始之前,让我们先学习下 Lean 内置策略的写法:

from AlgorithmImports import *


class MACDTrendAlgorithm(QCAlgorithm):

    def Initialize(self):
        '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''

        self.SetStartDate(2004, 1, 1)    #Set Start Date
        self.SetEndDate(2015, 1, 1)      #Set End Date
        self.SetCash(100000)             #Set Strategy Cash
        # Find more symbols here: http://quantconnect.com/data
        self.AddEquity("SPY", Resolution.Daily)

        # define our daily macd(12,26) with a 9 day signal
        self.__macd = self.MACD("SPY", 12, 26, 9, MovingAverageType.Exponential, Resolution.Daily)
        self.__previous = datetime.min
        self.PlotIndicator("MACD", True, self.__macd, self.__macd.Signal)
        self.PlotIndicator("SPY", self.__macd.Fast, self.__macd.Slow)


    def OnData(self, data):
        '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.'''
        # wait for our macd to fully initialize
        if not self.__macd.IsReady: return

        # only once per day
        if self.__previous.date() == self.Time.date(): return

        # define a small tolerance on our checks to avoid bouncing
        tolerance = 0.0025

        holdings = self.Portfolio["SPY"].Quantity

        signalDeltaPercent = (self.__macd.Current.Value - self.__macd.Signal.Current.Value)/self.__macd.Fast.Current.Value

        # if our macd is greater than our signal, then let's go long
        if holdings <= 0 and signalDeltaPercent > tolerance:  # 0.01%
            # longterm says buy as well
            self.SetHoldings("SPY", 1.0)

        # of our macd is less than our signal, then let's go short
        elif holdings >= 0 and signalDeltaPercent < -tolerance:
            self.Liquidate("SPY")


        self.__previous = self.Time

可以看到,其实它和Backtrader的写法相差无几,Initialize 函数设置基本的回测参数,如:

  • self.SetStartDate: 回测起始时间
  • self.SetEndDate: 回测结束时间
  • self.setCash: 回测资金
  • self.AddEquity: 回测对象(Resolution.Daily 是指按日回测)
  • self.PlotIndicator: 绘图时添加指标

而 onData 函数则会在每个数据点上做操作,如果是日线,则每天的数据都会流入到这个函数并运行一遍。因此 onData 就是算法分析的主逻辑。

在这里,你可以检查需要的指标是否已经准备完毕,因为可能存在一些滞后性指标在回测刚开始的时候并没有对应的值;此外,在日线的情况下,你还可以检测上一个数据点是不是和这个点在同一天上,如果是的话则不作任何操作返回:

if not self.__macd.IsReady: return
if self.__previous.date() == self.Time.date(): return

然后就是核心的买入卖出逻辑:

tolerance = 0.0025

holdings = self.Portfolio["SPY"].Quantity

signalDeltaPercent = (self.__macd.Current.Value - self.__macd.Signal.Current.Value)/self.__macd.Fast.Current.Value

# if our macd is greater than our signal, then let's go long
if holdings <= 0 and signalDeltaPercent > tolerance:  # 0.01%
    # longterm says buy as well
    self.SetHoldings("SPY", 1.0)

# of our macd is less than our signal, then let's go short
elif holdings >= 0 and signalDeltaPercent < -tolerance:
    self.Liquidate("SPY")
    
self.__previous = self.Time

如果我持仓的股数<=0, 且信号值大于我设定的阈值,则将我资产的1%买入这只股票。这里和backtrader最大的不同,买入是以资产的百分比为单位的动态买入。当然,你也可以使用限定数量的买入方式:

self.LimitOrder("IBM", 100, self.Securities["IBM"].Price)

如果持仓股市>=0, 且触发卖出信号,则进行清仓操作:

elif holdings >= 0 and signalDeltaPercent < -tolerance:
    self.Liquidate("SPY")

如果你不希望全部清仓,也可以使用 SetHoldings 来调整仓位。

可以看到,Lean相对于Backtrader有更灵活的仓位管理方式,甚至能够进行自动仓位调整、构建投资组合、实时交易等等。而且针对一些比较复杂的策略,你还可以用C#而不是Python来编写以提高运行速度。

综上所述,Lean是一个非常值得深入学习的量化交易平台,有兴趣的同学可以在他们官网学习到更多的内容:

https://www.quantconnect.com/docs

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Prometheus 实战教程 + Grafana + Python — 实时监控东方财富人气榜股票

上次我们讲过普罗米修斯(prometheus)这个接近完美的监控系统,有很多读者不了解它到底要如何搭建、应用,需要一篇 Prometheus 实战教程。今天我们就结合普罗米修斯、Grafana和Python采集脚本,写一个小小的东方财富人气榜 TOP100监控系统。

跟着本文的教程耐心往下走,你可能只需要花30分钟便可完成环境的搭建,非常舒服,下面先介绍基本概念。

普罗米修斯(prometheus)上次我们已经使用一整篇文章介绍过了,它是一个开源监控报警系统和时序列数据库。如果你没有阅读过这篇文章,请花五分钟读一下:

Grafana 是一个开源的数据可视化网络应用程序平台。用户配置连接的数据源之后,Grafana可以在网络浏览器里显示数据图表和警告。

比如说我基于 普罗米修斯(prometheus) + node_exporter 监控主机性能指标,然后由Grafana构建主机实时监控仪表盘,它是长这样的:

至于东方财富人气榜,指的是这个:

它能将市场目前最活跃的一些股票提取出来,可供我们作为投资的一种参考。

而我们今天要做的,就是自己搭建一套监控系统,实时监控某只股票在TOP100上的排名变化。

1.Prometheus 安装教程

创建 Prometheus 安装目录并添加 promethus 用户:

PROM_PATH='/data/prometheus'
mkdir -p ${PROM_PATH}
mkdir -p ${PROM_PATH}/{data,conf,logs,bin}
useradd prometheus
cd /usr/local/src

下载解压 prometheus, 这里我们选用2021年5月18日更新的最新版 v2.27.1:

wget https://github.com/prometheus/prometheus/releases/download/v2.27.1/prometheus-2.27.1.linux-amd64.tar.gz
tar -xvf prometheus-2.27.1.linux-amd64.tar.gz
cd prometheus-2.27.1.linux-amd64/
cp prometheus promtool ${PROM_PATH}/bin/
cp prometheus.yml ${PROM_PATH}/conf/
chown -R prometheus.prometheus /data/prometheus

设置环境变量:

cat >> /etc/profile <<EOF
PATH=/data/prometheus/bin:$PATH:$HOME/bin
EOF

将 Promethus 配置为系统服务之一,以便使用 systemctl 命令管控服务:

cat >>/etc/systemd/system/prometheus.service <<EOF
[Unit]
Description=Prometheus
Documentation=https://prometheus.io/
After=network.target

[Service]
Type=simple
User=prometheus
ExecStart=/data/prometheus/bin/prometheus --config.file=/data/prometheus/conf/prometheus.yml --storage.tsdb.path=/data/prometheus/data --storage.tsdb.retention=90d
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

现在使用下面的systemctl命令重新加载systemd系统,并查看服务是否启动:

systemctl daemon-reload
systemctl enable prometheus
systemctl start prometheus
systemctl status prometheus

看到 running 状态说明一切正常:

记得开放9090端口,这样才可以访问 Prometheus 的 Web 端,访问 http://服务器IP:9090 查看得到 Prometheus Web界面,说明安装成功:

2.Grafana 安装教程

Grafana 我们也使用最新的 8.0.1 版本,安装方式如下:

CentOS系列系统使用以下命令安装:

cd /usr/local/src
wget https://dl.grafana.com/oss/release/grafana-8.0.1-1.x86_64.rpm
sudo yum localinstall grafana-6.5.2-1.x86_64.rpm

Ubuntu和Debian系列系统使用以下命令安装:

cd /usr/local/src
sudo apt-get install -y adduser libfontconfig1
wget https://dl.grafana.com/oss/release/grafana_8.0.1_amd64.deb
sudo dpkg -i grafana_8.0.1_amd64.deb

然后启动系统服务即可:

systemctl start grafana-server
systemctl status grafana-server

看到 running 状态说明一切正常:

记得开放3000端口,这样你才可以访问你的Grafana: http://你的服务器IP:3000 如下所示:

输入用户名,密码登录系统。用户名与密码都是”admin”,如果能打开页面则已经安装成功了。

3.初尝Grafana+Prometheus实战教程

为了初步尝试这套系统,我们可以通过简单的采集主机性能数据开始。Node_exporter是一个Prometheus推出的官方主机性能采集工具。通过它我们能很方便地输出主机性能指标到Prometheus.

3.1 下载安装Node_Exporter:

NODE_PATH='/data/prometheus/node_exporter/'
cd /usr/local/src/
mkdir -p ${NODE_PATH}
wget https://github.com/prometheus/node_exporter/releases/download/v1.1.2/node_exporter-1.1.2.linux-amd64.tar.gz
tar -xvf node_exporter-1.1.2.linux-amd64.tar.gz
cp node_exporter-1.1.2.linux-amd64/node_exporter ${NODE_PATH}
chown -R prometheus.prometheus ${NODE_PATH}

配置node_exporter为系统服务:

cat > /lib/systemd/system/node_exporter.service <<EOF
[Unit]
Description=node_exporter
Documentation=https://prometheus.io/
After=network.target
 
[Service]
Type=simple
User=prometheus
ExecStart=/data/prometheus/node_exporter/node_exporter
Restart=on-failure
 
[Install]
WantedBy=multi-user.target
EOF

现在使用systemctl命令重新加载系统命令,并查看服务是否启动:

systemctl daemon-reload
systemctl enable node_exporter
systemctl start node_exporter
systemctl status node_exporter

看到如下图的状态说明启动成功。

放行9100端口,访问http://你的服务器地址:9100/metrics 看到如下指标页面说明安装成功:

配置 prometheus.yaml (ubuntu 下为 prometheus.yml), 让 prometheus 采集 node_exporter 输出的指标数据:

vim /data/prometheus/conf/prometheus.yml

配置如下:

# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']

   # 主要是新增了node_exporter的job,如果有多个node_exporter,在targets数组后面加即可

  - job_name: 'node_exporter'
    static_configs:
      - targets: ['localhost:9100']

保存后重启prometheus:

systemctl restart prometheus

最后配置Grafana:

然后选择 Prometheus 数据源:

输入 Prometheus url 然后点击 save&test 保存:

然后导入官方仪表盘,官方提供的模板号为8919:

然后你就能看见本机非常漂亮的性能指标数据仪表盘了。

不看不知道,一看吓一跳,看来我需要升级这台机器的内存了。

4.编写采集脚本

为了能够采集东方财富人气榜前100名,我们需要用Python编写一个人气榜采集脚本,并使其像 node_exporter 一样输出指标信息:

为了达到这个目的,我们必须安装 prometheus_client 模块:

pip3 install prometheus_client

获取股票排名的代码如下:

# Python实用宝典
# 2021-06-13
# 文件名: fetch_stock.py
import time
import requests
from prometheus_client import start_http_server, CollectorRegistry, Gauge


reg = CollectorRegistry()
gauge = Gauge(
    'rank', '人气榜排名',
    ['stock_id'], registry=reg
)


def process_request():
    url = "https://emappdata.eastmoney.com/stockrank/getAllCurrentList"
    kwargs = {
        "appId": "appId01",
        "pageNo": 1,
        "pageSize": "100",
    }
    result = requests.post(url, json=kwargs).json()
    for i in result.get("data", []):
        gauge.labels(stock_id=i["sc"]).set(i["rk"])
    time.sleep(60)


if __name__ == '__main__':
    start_http_server(8000, registry=reg)
    while True:
        process_request()

这里我们只捕获人气榜前100名,并通过Prometheus客户端的start_http_server开启一个Web服务,这样你通过http服务访问8000端口的时候就能输出这些指标。

为了让其能持续输出指标数据,我们要用nohup使其成为一个常驻进程:

nohup python3 fetch_stock.py &

开放8000端口,访问 http://你的服务器IP:8000 就能查看输出的指标:

5.应用采集脚本

同配置Node_exporter一样,我们需要将自己编写好的采集脚本落入Prometheus,配置prometheus.yaml:

配置 prometheus.yaml, 让 prometheus 采集 node_exporter 输出的指标数据:

#(CentOS) vim /data/prometheus/conf/prometheus.yaml
vim /data/prometheus/conf/prometheus.yml # ubuntu

配置如下:

# my global config
global:
  scrape_interval:     15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']

   # 主要是新增了node_exporter的job,如果有多个node_exporter,在targets数组后面加即可
  - job_name: 'node_exporter'
    static_configs:
      - targets: ['localhost:9100']

   # 新增我们的Python股票采集脚本
  - job_name: 'hot_list'
    static_configs:
      - targets: ['localhost:8000']

保存后重启prometheus:

systemctl restart prometheus

最后配置Grafana, 选择新建一个dashboard:

然后选择rank指标:

点击 Use query 就能获取所有股票的排名曲线:

6.配置Grafana告警

为了在某只股票达到某种排名的时候触发通知,我们需要先配置好告警渠道:

然后配置邮件告警,点击 Test, 此时 Grafana 会告诉你一个错误:

就是我们还没有配置好 SMTP 相关服务,需要配置 SMTP 相关服务才能正常发送邮件,如果你是按照本文按照Grafana的教程走下来的,那么Grafana.ini的文件位于 /etc/grafana/grafana.ini.

vim /etc/grafana/grafana.ini

然后在 smtp 部分配置你的 host、user、password、from_address、from_name,并打开 enabled 如下图所示:

然后重启 Grafana-server

systemctl restart grafana-server

再点击Test,你的邮箱里收到这样的邮件说明通知可以正常发送了:

然后我们进入正题,监控某只股票的排名变化,比如 SH600070:

然后点击 Alert 配置告警,一旦其排名高于65名则发送邮件通知:

完成后点击右上角的 save 保存即可:

然后进入 Alerting 告警中心,你会看到刚刚配置的告警规则在这里可以进行管控:

点击Pause可以暂停这个告警,Edit alert可以去更改告警条件。

一旦触发告警,这个状态便会更改,你就会收到邮件:

邮件效果如下:

邮件里的告警图片没显示出来,因为我们没有安装 “grafana image renderer”, 需要在你的服务器执行以下命令安装并重启 Grafana:

grafana-cli plugins install grafana-image-renderer
systemctl restart grafana-server

新的告警邮件便能看到图片了:

怎么样,用Prometheus+Grafana+Python采集搭建一个股票监控系统还是非常简单的吧?创新性地监控东方财富人气榜上某只股票的变化并产生告警,能让你熟悉监控策略的配置,见微知著。跟着本文的教程走,相信你会有不少收获。

如果我们延伸一下,结合量化投资系列教程的可转债交易策略 — Python 量化投资实战教程(10),是否可以构建一些更有意义的策略?答案是肯定的。

我们可以监控所有100元以下的可转债对应的股票,如果这些股票进入了人气榜TOP100或者飙升榜(本文没有采集,有兴趣的读者可以自行采集),就购入这些低价可转债,这种买入策略或许也不错。

你也可以抛弃东方财富的榜单分类,构建自己的排名环比增长买入策略,环比下跌卖出策略,我相信这会非常有意思。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

可转债交易策略 — Python 量化投资实战教程(10)

以下内容仅供参考学习,不构成投资意见。

可转债,是一种具有中国特色的、受到国家管理和保护的债券,之所以说它很有特色,是因为它身上具备了两种闪闪发光的特性:

1.债性 — 安全保本

可转债本质上是一种公司的债券,也就是我们生活中所说的“欠条”,每张“欠条”的价值是100块钱。

如果你持有一张可转债,它就代表着上市公司欠你100块钱,而且未来必须偿还这笔钱并附带利息。截止到目前,所有上市的可转债,都完成了保本的使命。

也就是说,如果你在转债价格低于100元的时候买入,此时就是保本的。而且公司在赎回时,会付你一定的利息,所以当你在可转债价格低于100元的时候买入,就是保本保利的。

2.股性(可转换) — 存在套利空间

可转债是一张可转换为股票的公司债券。比如目前 晨光转债 转股价为 12.25 元,100/12.25=8.16,那么一张晨光转债会被取证转为8股晨光生物,其余的尾数会被转换为证券账户资金。

目前晨光生物正股16.10元,转换后,你相当于获得了8*16.10=128.8元的股票,加上刚刚尾数补充的账户资金,转股后你相当于获得了131.43元,131.43元被称为转股价值。

也就是说,如果你在前一天收盘前买入了1张晨光转债,并在当前转换为股票,第二天你会获得价值为131.43元的股票+证券账户资金。目前晨光转债的价格为 130.2 元,净利0.93%,但这个套利逻辑有个大前提:第二天开盘股价不会跌,如果跌了,你就拿不到这么多的价值,甚至有可能亏本。

下面本文要研究和利用的,不是可转债的股性,而是可转债的债性。

就如前面所说的,如果你买入100元以下的可转债,除非公司老板带着小姨子跑路,否则都是保本的。于是就有了下面这个自动交易的逻辑:

对于100元以下的可转债,触发某种上涨信号时,买入。上涨0.5%则卖出。如果下跌则一直持有。

上涨就赚了,下跌的话长期持有也不亏,利用可转债的债性及T+0交易的特性实行日内高频率交易就是这个策略的主要逻辑。

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。

(可选1) 如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

(可选2) 此外,推荐大家用VSCode编辑器来编写小型Python项目:Python 编程的最好搭档—VSCode 详细指南

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),输入命令安装依赖:

在终端输入以下命令安装我们所需要的依赖模块:

pip install backtrader
pip install easytrader

看到 Successfully installed xxx 则说明安装成功。

某些券商在登录的时候可能需要识别验证码,这时候需要下载tesseract

1.下载并安装tesseract

前往 tesseract-ocr 官网下载二进制包,此外你也可以在Python实用宝典公众号后台回复: 量化投资10,直接获得本文源代码和tesseract的安装包。

双击下载下来的安装包,然后傻瓜式安装就可以,这里只需要注意一点:安装过程中有一个让你选择 Additional language data(download) 表示选择的话帮你下载语言包,这里最好不要选择勾选,因为勾选的话,安装过程非常慢,本教程只需要用到数字和英文识别而已。

2.配置环境变量

右击我的电脑/计算机,选择属性,然后选择高级属性设置,选择环境变量,在系统变量的path变量中添加你的 tesseract 目录就可以了

3.判断是否安装成功

在命令行中输入:

tesseract --version

出现下面的提示说明安装成功:

2.回测

按照这个策略的逻辑,回测的目的不在于探讨是否会亏损,而在于最高能赚多少。

为什么不需要探讨亏损?因为实际上即便我们买入80元的广汇转债,一直持有,3年后它以100元的价格赎回,我们的3年收益都达到了20%,平均年化率达6.67%。

当然,风险是有的,广汇老板如果带着小姨子跑路了,那你的转债可能一文不值,但是目前还没有出现过这种情况,因为如果出现了这种情况,将出现严重的信用危机,监管部门不可能允许这种情况发生。

回测的主要目的在于:怎样进行自动化交易,能最大化我们的收益。

关于编写策略的方法我们前面九篇系列文章都讲的很清楚了,这里就不再赘述,这里只重点研究买入的逻辑。

2.1 基于分钟K线的Sma金叉策略

这是最简单的策略,如果sma5和sma10实现金叉,则买入债券。涨0.5%则卖出,否则不动。

部分代码如下:

    def golden(self, a, b):
        if a[-1] - b[-1] < 0 and a[0] - b[0] > 0:
            return True
        else:
            return False

    def next(self):
        if self.order:
            return

        if not self.position:
            if self.golden(self.sma5, self.sma10):
                self.order = self.buy()
                self.params.buydays.append(self.datas[0].datetime.date(0))

        else:
            condition = (self.dataclose[0] - self.bar_executed_close) / self.bar_executed_close
            if condition > 0.005:
                self.order = self.sell()
                self.dead = False
                self.params.selldays.append(self.datas[0].datetime.date(0))
                self.params.hold_days.append((self.params.selldays[-1] - self.params.buydays[-1]).days)
                if self.params.buydays[-1] != self.params.selldays[-1]:
                    days = get_every_day(self.params.buydays[-1], self.params.selldays[-1])
                    for day in days:
                        self.params.hold_count[day] += 1

随机抽取了15只低价债券,回测了 2020-9-15 至 2020-11-8 之间的走势,一共交易了59次,每次买入50股。

可以看到每次交易的平均持有日期为4天,这是平均值,如果你看中位数会更大,有些交易可能连续持有了30天,有些可能当天买入当天卖出,两极分化比较大。

不过即便这样,按平均每次收益0.5%、平均每支债券价格为80元的情况来看, 59次交易*0.005*80*50股 = 1180元,如果可转债每次交易的平均手续费是1元,那么除去手续费 1180-2*59=1062元。看起来是一笔可观的羊毛,但是你必须忍受有部分资金被套牢在一些低价转债的情况。

2.2 基于分钟K线的EMA金叉的策略

接下来我们测试一下使用EMA均线的效果,这里选择的是ema(12)和ema(50):

    def golden(self, a, b):
        if a[-1] - b[-1] < 0 and a[0] - b[0] > 0:
            return True
        else:
            return False

    def next(self):
        if self.order:
            return

        if not self.position:
            if self.golden(self.exp1, self.exp2):
                self.order = self.buy()
                self.params.buydays.append(self.datas[0].datetime.date(0))

        else:
            condition = (self.dataclose[0] - self.bar_executed_close) / self.bar_executed_close
            if condition > 0.005:
                self.order = self.sell()
                self.dead = False
                self.params.selldays.append(self.datas[0].datetime.date(0))
                self.params.hold_days.append((self.params.selldays[-1] - self.params.buydays[-1]).days)
                if self.params.buydays[-1] != self.params.selldays[-1]:
                    days = get_every_day(self.params.buydays[-1], self.params.selldays[-1])
                    for day in days:
                        self.params.hold_count[day] += 1

效果如下:

交易次数相对于sma策略少了14次,所以收益肯定也会下降。平均持有日期达到了5.4天,相比于sma策略也有所提高。因此ema在我们的整个策略逻辑里表现地比sma策略稍差一些。

上面展示了两种策略在我们的低价转债投资逻辑里的应用和分析,由于篇幅关系这里就不再展示一些其他策略的回测结果,大家有兴趣可以自己试一下。

3.自动交易

使用easytrader模块,可以简单实现一个单进程的自动交易程序。

对于本策略而言单进程的自动交易程序也够了,因为这个策略要求的实时性并不是很高。

由于这部分代码无法脱敏,因此不能进行深入地讲解。逻辑并不难,每分钟对指定的股票进行监控,当其符合相关策略时进行买入或卖出,下面进行简单的讲解。

1.读取今日需检测的股票

在一天开始交易之前,需要获取今日所符合条件的低价可转债:

def start():
    account = Account()
    codes = read_today_codes()
    logger.info(f"总股票数 {len(codes)}")
    last_ping_time = datetime.datetime.now().timestamp()

2.巡检

在交易时间里,循环检测可转债是否符合买入策略

    while True:
        # 是否在交易时间
        if not check_time():
            continue
        for code in codes:
            now = int(datetime.datetime.now().timestamp())
            logger.info(f"{now} - {code}")
            try:
                # TODO: 异步执行算法
                buy_dict = algorithm(code)
                if not buy_dict:
                    continue
                logger.info(buy_dict)
                buy_time = list(buy_dict.keys())[0]
                buy_value = list(buy_dict.values())[0]

                if abs(int(buy_time.timestamp()) - now) < 300:
                    logic(account, code, buy_time, buy_value)

            except Exception as e:
                traceback.print_exc()
                logger.info(e)
                # send_mail(f"算法解析失败: {traceback.print_exc()}", "WRONG", code)

如果符合买入规则,在logic函数内,便会对可转债发布买单,同时挂一个0.5%利润的卖单。

3.卖单兜底

理想化情况下,在你挂了买单后立马成交,此时顺利挂出卖单。

不理想情况下,在你挂了买单后,几分钟后才成交,出现这种异步的情况后,卖单无法顺利挂出。

所以我们需要有一个兜底的措施:

        now = datetime.datetime.now()
        new_ping_time = now.timestamp()
        if new_ping_time - last_ping_time > 15:
            # 大于15秒,检测持仓,把未委托卖出的单子委托卖出
            account.every_day_sell()
        last_ping_time = new_ping_time
        logger.info(f"{datetime.datetime.strftime(now, '%Y-%m-%d %H:%M:%S')} ping")

每次巡检任务都检查持仓,如果存在未挂出卖单的可转债,则按成本价+0.5%的利润挂出卖单。这样保证没有漏掉的可转债。

上述只是简单的几个步骤,实现上你会还有许多细节需要考虑,大家可以自己尝试实现一个这样的自动交易流程。

欢迎在公众号后台回复:加群,回答相应红字验证信息,进入互助群交流。

我们的文章到此就结束啦,如果你喜欢今天的Python 实战教程,请持续关注Python实用宝典。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

Easytrader 一个Python自动交易股票的神器

在量化投资系列教程中,我们从理论上介绍了量化回测的基本原理和思路,并进行了一定的实战分析和演练。但是并没有涉及到实际交易的场景。

在开始之前,我们需要明确什么策略需要使用自动交易的方式。我们前面的教程都是基于日线的策略,这样的日线策略并不需要自动化交易,因为你只需要提前一天算好第二天需要买入的股票即可。

什么情况下需要本文的自动交易?

1.你的策略是基于 分钟线 (1,5,15,60) 的。
2.你的日线策略在购买的时候需要判断前一天封盘前的股票数据,并提前一天买入。
3.你希望能自动打新。

除了这两种情况,其他的策略都不适合使用本文的自动交易,包括秒级别的策略,因为Easytrader是基于GUI层面(图形界面)实现的自动化接口,而非API层面,因此实时性不会很高,而且是串行形式的,并不适合秒级策略。

此外,Easytrader对券商有一定的要求:

目前支持 海通客户端、华泰客户端、国金客户端、雪球,还有通用同花顺客户端(指的是那些基于同花顺修改的券商软件版本)。

在开始自动交易前,请确认你的策略是可靠的,否则不要轻易尝试自动交易。另外交易逻辑的处理也需要非常谨慎,错一行代码可能损失的是几千块甚至不止。所以,一开始执行自动交易的时候,推荐只用很小一部分的本金。

本文只会介绍Easytrader的安装方法和基本使用方法及构建监控股票池的重要性,其他深入介绍将于后面的 量化投资系列教程10 中介绍,敬请期待。

一个小小的预告

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。

(可选1) 如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

(可选2) 此外,推荐大家用VSCode编辑器来编写小型Python项目:Python 编程的最好搭档—VSCode 详细指南

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),输入命令安装依赖:

在终端输入以下命令安装我们所需要的依赖模块:

pip install easytrader

看到 Successfully installed xxx 则说明安装成功。

某些券商在登录的时候可能需要识别验证码,这时候需要下载tesseract

1.下载并安装tesseract

前往 tesseract-ocr 官网下载二进制包,此外你也可以在Python实用宝典公众号后台回复: tesseract,直接获得tesseract的安装包。

双击下载下来的安装包,然后傻瓜式安装就可以,这里只需要注意一点:安装过程中有一个让你选择 Additional language data(download) 表示选择的话帮你下载语言包,这里最好不要选择勾选,因为勾选的话,安装过程非常慢,本教程只需要用到数字和英文识别而已。

2.配置环境变量

右击我的电脑/计算机,选择属性,然后选择高级属性设置,选择环境变量,在系统变量的path变量中添加你的 tesseract 目录就可以了

3.判断是否安装成功

在命令行中输入:

tesseract --version

出现下面的提示说明安装成功:

2.Easytrader 基本使用

这一章将介绍Easytrader的基本使用方法、包括客户端连接、获取资金状况、获取持仓、买入、卖出、一键打新、撤单。

在开始之前,请对客户端调整以下设置,不然会导致下单时价格出错以及客户端超时锁定。

  • 系统设置 > 界面设置: 界面不操作超时时间设为 0
  • 系统设置 > 交易设置: 默认买入价格/买入数量/卖出价格/卖出数量 都设置为 空

同时客户端不能最小化也不能处于精简模式。

2.1 自动登录客户端:

在Windows机器上下载好我们在文首提到的几个券商客户端、准备好Python相关的依赖,即可开始尝试自动登录客户端:

import easytrader

# 海通客户端
user = easytrader.use('htzq_client')
user.prepare(user='用户名', password='明文密码')

# 华泰客户端
user = easytrader.use('ht_client')
user.prepare(user='用户名', password='明文密码', comm_password='华泰通讯密码,其他券商不用')

# 国金客户端
user = easytrader.use('gj_client')
user.prepare(user='用户名', password='明文密码')

# 雪球
user = easytrader.use('xq')
user.prepare(user='用户名', password='明文密码')

# 通用同花顺客户端, 指对应券商官网提供的基于同花顺修改的软件版本, 如(银河的双子星)
user = easytrader.use('ths')
user.prepare(user='用户名', password='明文密码')

选择你的客户端,将其他的客户端去掉,运行这个py文件,程序就会开始自动登录,期间会识别很多次验证码,经常会识别错误,没关系,让他慢慢重试即可。

如果你识别验证码的时候报了错,程序停止了下来,那是因为你没安装 tesseract, 回到第一步安装tesseract即可。

2.2 获取资金、持仓情况

获取资金、持仓情况的时候,程序都会自动化地进入相关界面获取信息。有时候也需要输入验证码,这时候easytrader也会自动识别,请不要介入。

import easytrader
# 国金客户端
user = easytrader.use('gj_client')
user.prepare(user='用户名', password='明文密码')

# 获取资金状况
balance = user.balance
print(balance)
# [{'参考市值': 21642.0,
#   '可用资金': 28494.21,
#   '币种': '0',
#   '总资产': 50136.21,
#   '股份参考盈亏': -90.21,
#   '资金余额': 28494.21,
#   '资金帐号': 'xxx'}]

# 获取持仓情况
position = user.position
print(position)
# [{'买入冻结': 0,
#   '交易市场': '沪A',
#   '卖出冻结': '0',
#   '参考市价': 4.71,
#   '参考市值': 10362.0,
#   '参考成本价': 4.672,
#   '参考盈亏': 82.79,
#   '当前持仓': 2200,
#   '盈亏比例(%)': '0.81%',
#   '股东代码': 'xxx',
#   '股份余额': 2200,
#   '股份可用': 2200,
#   '证券代码': '601398',
#   '证券名称': '工商银行'}]

2.3 买入、卖出、一键打新、撤单

买入卖出时设定股票代码,价格和数量即可:

import easytrader
# 国金客户端
user = easytrader.use('gj_client')
user.prepare(user='用户名', password='明文密码')

# 买入
user.buy('162411', price=0.55, amount=100)
# {'entrust_no': 'xxxxxxxx'}

# 卖出
user.sell('162411', price=0.55, amount=100)
# {'entrust_no': 'xxxxxxxx'}

# 一键打新
user.auto_ipo()

# 撤单
user.cancel_entrust('buy/sell 获取的 entrust_no')
# {'message': '撤单申报成功'}

如果资金不足或可用证券数量不足,程序会报错,这时候请注意把错误except出来,不然你的检测周期可能因此中断,如果是无人值守的程序,可能整个程序都会停止。

3.构建监控股票池

为什么需要构建股票池?如我们前面提到的,Easytrader是基于GUI层面的自动化交易接口,它会进入如下模样的界面进行自动化操作:

如果你的监控对象太多,有多个对象同时符合策略,它会一个一个地进行操作,容易耽误下个周期的计算,因此推荐构建一个50只股票左右的股票池。

你可以根据基本面构造你的股票池,比如最近几年年报中,扣非净利润均有所增长的企业,或是在你的策略下表现最好的50只股票(当然未来不一定靠谱)。

无论你怎么构造,请记录下这些股票(最好是数组变量的形式),证券市场的前缀在前,代码在后,比如 sh600519:

# 以2000元的价格买入100股sh600519, 贵州茅台
user.buy('sh600519', 2000, 100)

当然,此时会报错:

毕竟我未实现100股自由,希望各位能在下方点个“在看”,让我尽早实现100股自由。

欢迎在公众号后台回复:加群,回答相应红字验证信息,进入互助群交流。

我们的文章到此就结束啦,如果你喜欢今天的Python 实战教程,请持续关注Python实用宝典。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

自定义MySQL数据流 — Python 量化投资实战教程(9)

前面八篇量化投资实战教程,我们所使用到的数据仅仅只有收盘价、成交量等普通指标,如果我们有其他的指标需要进行回测怎么办?

此外,前面使用的数据源都是基于csv文件的,我们能否从数据库(比如MySQL)中直接提取数据作为回测的数据源呢?

​事实上,backtrader虽然没有直接提供接口给我们做这样的优化,但是我们可以通过继承DataBase基类重写DataFeed实现目的。下面就给大家演示一下如何从MySQL中提取数据并增加换手率指标进行回测。

本文完整源代码和数据均在开源代码仓库中:
https://github.com/Ckend/pythondict-quant

1.准备

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,请访问这篇文章:超详细Python安装指南 进行安装。

(可选1) 如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

(可选2) 此外,推荐大家用VSCode编辑器来编写小型Python项目:Python 编程的最好搭档—VSCode 详细指南

Windows环境下打开Cmd(开始—运行—CMD),苹果系统环境下请打开Terminal(command+空格输入Terminal),输入命令安装依赖:

在终端输入以下命令安装我们所需要的依赖模块:

pip install backtrader
pip install numpy
pip install matplotlib

看到 Successfully installed xxx 则说明安装成功。

2.自定义DataFeed

何为DataFeed?DataFeed 即 backtrader 中的“数据源”,任何数据进入策略回测前都要通过DataFeed,而DataFeed中会对数据进行处理,使得策略可以高效地进行计算。

而我们今天要做的,就是增加一个基于MySQL的DataFeed,使得整个流程变得更加自动化。

首先,需要定义一个类,使其继承与backtrader的数据基类 DataBase:

from backtrader.feed import DataBase
from backtrader import date2num


class MySQLData(DataBase):
    pass

如果需要从外部传入所需股票数据的代码和其一定范围内的K线数据,需要提前定义params. 此外,如果你有除了datetime(时间)open(开盘价)close(收盘价)high(最高价)low(最低价)volume(成交量) 之外的指标。需要提前定义lines,如下所示:

class MySQLData(DataBase):
    params = (
        ('fromdate', datetime.datetime.min),
        ('todate', datetime.datetime.max),
        ('ts_code', ''),
    )
    lines = (
        "turnover",
        "turnover_rate"
    )

可见在lines中,我增加了两个指标:turnover(成交额)turnover_rate(换手率)

接下来,编写一个函数根据params参数从MySQL中获取数据:

    def load_data_from_db(self, table, ts_code, start_time, end_time):
        """
        从MySQL加载指定数据
        Args:
            table (str): 表名
            ts_code (str): 股票代码
            start_time (str): 起始时间
            end_time (str): 终止时间
        return:
            data (List): 数据集
        """
        db = pymysql.connect(
            host="localhost",
            user="root",
            password="12345678",
            db="golden_stone",
            port=3306
        )

        cur = db.cursor()
        sql = cur.execute(
            f"SELECT * FROM {table} WHERE trade_time >= '{start_time}' and trade_time < '{end_time}'"
            f"and ts_code = '{ts_code}' order by trade_time asc"
        )
        data = cur.fetchall()
        db.close()
        return iter(list(data))

代码本身没有什么可说的,记得替换你本地的mysql配置,值得注意的是最后一行,拿到mysql数据后需要转化为迭代器。

在类初始化的时候,需要定义相关的数据存放变量并调用上述函数获取数据:

    def __init__(self, *args, **kwargs):
        self.result = []
        self.empty = False

    def start(self):
        self.result = self.load_data_from_db("stock_normalk", self.p.ts_code, self.p.fromdate, self.p.todate)

接下来到了关键的步骤,在 `cerebro.adddata(data)` 的时候,cerebro会遍历Datafeed的所有数据,此时会调用_load函数, 因此我们需要在这里,将数据库中提取的每列数据对应到lines上:

    def _load(self):
        if self.empty:
            return False
        try:
            one_row = next(self.result)
        except StopIteration:
            return False
        self.lines.datetime[0] = date2num(one_row[3])
        self.lines.open[0] = float(one_row[4])
        self.lines.high[0] = float(one_row[5])
        self.lines.low[0] = float(one_row[6])
        self.lines.close[0] = float(one_row[7])
        self.lines.volume[0] = float(one_row[8])
        self.lines.turnover[0] = float(one_row[9])
        self.lines.turnover_rate[0] = float(one_row[12])
        return True

如果你完整地看完了我的上述分析,那么理解下面整个DataFeed,甚至自己写一个DataFeed,是非常容易的。

# Python 实用宝典
# 自定义数据流 — Python 量化投资实战教程(9)
import datetime
import traceback
import pymysql

from backtrader.feed import DataBase
from backtrader import date2num


class MySQLData(DataBase):
    params = (
        ('fromdate', datetime.datetime.min),
        ('todate', datetime.datetime.max),
        ('ts_code', ''),
    )
    lines = (
        "turnover",
        "turnover_rate"
    )

    def load_data_from_db(self, table, ts_code, start_time, end_time):
        """
        从MySQL加载指定数据
        Args:
            table (str): 表名
            ts_code (str): 股票代码
            start_time (str): 起始时间
            end_time (str): 终止时间
        return:
            data (List): 数据集
        """
        db = pymysql.connect(
            host="localhost",
            user="root",
            password="12345678",
            db="golden_stone",
            port=3306
        )

        cur = db.cursor()
        sql = cur.execute(
            f"SELECT * FROM {table} WHERE trade_time >= '{start_time}' and trade_time < '{end_time}'"
            f"and ts_code = '{ts_code}' order by trade_time asc"
        )
        data = cur.fetchall()
        db.close()
        return iter(list(data))

    def __init__(self, *args, **kwargs):
        self.result = []
        self.empty = False

    def start(self):
        self.result = self.load_data_from_db("stock_normalk", self.p.ts_code, self.p.fromdate, self.p.todate)

    def _load(self):
        if self.empty:
            return False
        try:
            one_row = next(self.result)
        except StopIteration:
            return False
        self.lines.datetime[0] = date2num(one_row[3])
        self.lines.open[0] = float(one_row[4])
        self.lines.high[0] = float(one_row[5])
        self.lines.low[0] = float(one_row[6])
        self.lines.close[0] = float(one_row[7])
        self.lines.volume[0] = float(one_row[8])
        self.lines.turnover[0] = float(one_row[9])
        self.lines.turnover_rate[0] = float(one_row[12])
        return True

3.使用自定义数据流进行回测

接下来,让我们尝试使用这个自定义数据流输入数据,采用第二章的macd策略辅助增加换手率指标进行回测。

这里当然需要你先读懂第二章的内容,如果有点忘记了,可以回头阅读一下,非常简单:

Python 量化投资实战教程(2) —MACD策略

首先,在回测模块及next函数中,引入换手率指标:

class TestStrategy(bt.Strategy):
    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    @staticmethod
    def percent(today, yesterday):
        return float(today - yesterday) / today

    def __init__(self):
        self.dataclose = self.datas[0].close
        self.volume = self.datas[0].volume
        # 新的变更:引入换手率指标
        self.turnover_rate = self.datas[0].turnover_rate

next函数买入时增加判断换手率必须小于3%的条件:

    # Python 实用宝典
    def next(self):
        self.log('Close, %.2f' % self.dataclose[0])
        if self.order:
            return

        if not self.position:
            condition1 = self.macd[-1] - self.signal[-1]
            condition2 = self.macd[0] - self.signal[0]
            # 增加判断换手率小于3%的条件
            if condition1 < 0 and condition2 > 0 and self.turnover_rate[0] < 3:
                self.log('BUY CREATE, %.2f' % self.dataclose[0])
                self.order = self.buy()

        else:
            condition = (self.dataclose[0] - self.bar_executed_close) / self.dataclose[0]
            if condition > 0.1 or condition < -0.1:
                self.log('SELL CREATE, %.2f' % self.dataclose[0])
                self.order = self.sell()

最后,引入我们刚刚编写完成的MySQLData Feed,传入相关参数读取股票为603520.SH的数据流,取2017年1月1日至2020年4月12日的数据,并调用回测函数:

if __name__ == '__main__':
    cerebro = bt.Cerebro()

    cerebro.addstrategy(TestStrategy)

    # 加载数据到模型中
    data = MySQLData(
        ts_code="sh603520",
        fromdate=datetime.datetime(2017, 1, 1),
        todate=datetime.datetime(2020, 4, 12),
    )
    cerebro.adddata(data)

    cerebro.broker.setcash(10000)

    cerebro.addsizer(bt.sizers.FixedSize, stake=100)

    cerebro.broker.setcommission(commission=0.005)

    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    cerebro.run()

    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

    cerebro.plot()

效果如下:

与原来相比,加入换手率指标后,利润率有一定的提高,不过就如我们之前所说的,单一股票维度的回测是不准确的,如果大家有兴趣,可以将第三章:

Python 量化投资实战教程(3) —A股回测MACD策略

改造一下并加入换手率指标进行回测,看看这个指标是否真的有正效益。

本文完整源代码和数据均在开源代码仓库中:
https://github.com/Ckend/pythondict-quant

如果你访问不了github,也可以在公众号后台回复 量化投资9 下载相关代码。

欢迎在公众号后台回复:加群,回答相应红字验证信息,进入互助群交流。

我们的文章到此就结束啦,如果你喜欢今天的Python 实战教程,请持续关注Python实用宝典。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!


​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典