0%

之前写过一篇文章实现了一个简单的双均线策略,传送门:使用Pandas开发一个双均线策略

文章最后一张图看到策略收益并没有跑赢中证500指数,想更深入的了解下具体的收益情况,可以使用pyfolio工具,这个工具是著名的量化研究平台quantopian开发的,主要用途就是对投资组合进行风险分析。

pyfolio github地址:https://github.com/quantopian/pyfolio,安装非常简单:

pip install pyfolio

安装之后导入模块

import pyfolio as pf
# silence warnings
import warnings
warnings.filterwarnings('ignore')

对上一篇文章中生成的策略回报和市场回报的series进行分析

pf.show_perf_stats(etf500['Strategy'],etf500['Market Returns'],live_start_date='2018-1-1')

live_start_date参数是模拟策略实盘开始交易的时间点,下图中 ‘2018-1-1’ 之前的样本内数据一共有55个月,'2018-1-1’之后的样本外数据一共15个月

年化回报率为17.7%

从2013年3月15日开始累计投资回报率为161.6%

整体测试时间段内的最大回撤为59.1%,夏普值只有0.47

我们再来看下最大回测的时间段,下图显示最大回撤发生在2015年6月12日 - 2019年2月20日,累计最大回撤59.12%,2015年6月12日刚好是股灾开始的时候,惨痛的回忆。。。

pf.show_worst_drawdown_periods(etf500['Strategy'])

其实这个策略还有优化的空间,下一篇文章介绍下如果对双均线策略进行参数优化。

我的微信公众号:pyquant

Python量化交易实战
欢迎您扫码订阅我的微信公众号: pyquant

最近在使用jupyter运行多进程程序时,发现重启jupyter notebook偶尔会留下一些僵尸进程,应该是python父进程被终止之后子进程没有被正确释放造成的。僵尸进程依然会占用系统资源,如果不及时清理可能会严重影响系统性能。

先解释下什么是僵尸进程

僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源。

在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他, 那么他将变成一个僵尸进程。
— 百度百科

在jupyter中使用多进程执行程序时,如果程序还没有执行完成就点击菜单栏的 Kernel -> Restart xxx,就有可能会造成僵尸进程。

如何检查僵尸进程是否存在呢?在命令行中执行如下命令就可以返回僵尸进程状态、父进程ID、进程ID和执行的具体命令:

ps -A -ostat,ppid,pid,cmd | grep -e '^[Zz]'

杀死僵尸进程也比较容易,执行如下命令就可以清理僵尸进程:

kill -9 `ps -A -ostat,ppid,pid,cmd | grep -e '^[Zz]' | awk '{print $2}'`

我的微信公众号:pyquant

Python量化交易实战
欢迎您扫码订阅我的微信公众号: pyquant

我的微信公众号:pyquant

大家好,这篇文章我将使用Pandas创建一个简单的均线交叉策略,以500ETF作为标的物进行回测

移动平均线可能是技术指标里的"hello world"了,常用的均线有5、10、20、60、120日均线,在其他时间周期上应用移动平均线指标也是类似方式。

移动平均线按时间周期长短分为:短期移动平均线,中期移动平均线,长期移动平均线;按计算方法分为:算术移动平均线,加权移动平均线,指数平滑移动平均线(EMA)

下面正式开始编写策略代码,我们使用jupyer作为研究环境,首先先导入依赖模块

import pandas as pd
import numpy as np
import tushare as ts

%matplotlib inline

接下来使用tushare下载500ETF的历史数据,500ETF是从2013年开始上市交易的,这里将start参数设置为2013,这样可以获取500ETF的全部历史数据。

etf500 = ts.get_k_data('510500',start='2013')
etf500.set_index(pd.to_datetime(etf500['date']),inplace=True) 
del etf500['date']
etf500.head()

输出结果如下,数据是从2013年3月15日开始的:

date open close high low volume code
2013-03-15 0.967 0.970 0.985 0.955 3259273.0 510500
2013-03-18 0.955 0.954 0.972 0.953 936962.0 510500
2013-03-19 0.956 0.960 0.960 0.941 1080499.0 510500
2013-03-20 0.960 0.985 0.986 0.958 501195.0 510500
2013-03-21 0.985 0.995 0.996 0.981 698243.0 510500

继续画出收盘价格曲线,对500ETF走势有个大概的了解。

etf500['close'].plot(grid=True, figsize=(8,5))

接下来是双均线策略的实现,我们使用20日均线和60日均线作为短期和长期均线,下面先分别计算20日和60日均线序列

etf500['ma20'] = etf500['close'].rolling(20).mean()
etf500['ma60'] = etf500['close'].rolling(60).mean()
etf500[['close','ma20','ma60']].plot(grid=True, figsize=(14,5))

我们已经获取了两条移动平均线序列,接下来是根据均线来生成交易信号

策略信号会有两种状态:

  1. 买入信号,当20日均线向上穿过60日均线时持有多头仓位

  2. 卖出信号,当20日均线向下穿过60日均线时平仓

etf500['Stance'] = np.where(etf500['ma20'] - etf500['ma60'] > 0, 1, 0)
etf500['Stance'].value_counts()

最后一行统计持仓和空仓的天数,输出结果如下:

1    761
0    724
Name: Stance, dtype: int64
etf500['Stance'].plot(ylim=[-0.1,1.1])

下图显示持仓日期数据

接下来,我们根据持仓数据来计算持仓的收益

etf500['Market Returns'] = np.log(etf500['close'] / etf500['close'].shift(1))
etf500['Strategy'] = etf500['Market Returns'] * etf500['Stance'].shift(1)
etf500[['Market Returns','Strategy']].cumsum().plot(grid=True,figsize=(8,5))

以上图片展示了市场回报率与策略回报曲线,可以看到20和60日双均线策略并没有跑赢500ETF,不过我们也可以测试下其他均线组合,也许会有不错的效果。

参考

https://www.pythonforfinance.net/2016/09/01/moving-average-crossover-trading-strategy-backtest-in-python/#more-15498>

Python量化交易实战
欢迎您扫码订阅我的微信公众号: pyquant

我的微信公众号:pyquant

背景

开发股票行情推送的引擎时遇到一个问题,在9:30开盘后的一段时间内行情消息总是堆积,尤其是开头15-20分钟,堆积的数据量会越来越多,经过debug发现是内部消息传输使用Queue性能问题导致了消息延迟,在stackoverflow上找到一个帖子对Queue的性能进行了测试和解释说明,下面先来介绍下Multiprocessing下的Queue和Pipe

介绍

当使用多个进程时,通常使用消息传递来进行进程之间的通信,为了不损耗性能也会尽量避免使用同步机制。对于消息传递:

* Pipe适用于两个进程间的消息传递。
* Queue适用于多个进程间的消息传递,适用于多生产者和消费者的模式。

Pipe VS Queue

from multiprocessing import Process, Pipe
import time

def reader_proc(pipe):
    ## Read from the pipe; this will be spawned as a separate Process
    p_output, p_input = pipe
    p_input.close()  # We are only reading
    while True:
        msg = p_output.recv()  # Read from the output pipe and do nothing
        if msg == 'DONE':
            break

def writer(count, p_input):
    for ii in range(0, count):
        p_input.send(ii)  # Write 'count' numbers into the input pipe
    p_input.send('DONE')

if __name__ == '__main__':
    for count in [10 ** 4, 10 ** 5, 10 ** 6]:
        # Pipes are unidirectional with two endpoints:  p_input ------> p_output
        p_output, p_input = Pipe()  # writer() writes to p_input from _this_ process
        reader_p = Process(target=reader_proc, args=((p_output, p_input),))
        reader_p.daemon = True
        reader_p.start()  # Launch the reader process

        p_output.close()  # We no longer need this part of the Pipe()
        _start = time.time()
        writer(count, p_input)  # Send a lot of stuff to reader_proc()
        p_input.close()
        reader_p.join()
        print("Sending {0} numbers to Pipe() took {1} seconds".format(count,
                                                                      (time.time() - _start)))

Pipe输出结果

Sending 10000 numbers to Pipe() took 0.0744009017944336 seconds
Sending 100000 numbers to Pipe() took 0.7794349193572998 seconds
Sending 1000000 numbers to Pipe() took 7.425454139709473 seconds
from multiprocessing import Process, Queue
import time
import sys

def reader_proc(queue):
    ## Read from the queue; this will be spawned as a separate Process
    while True:
        msg = queue.get()  # Read from the queue and do nothing
        if (msg == 'DONE'):
            break

def writer(count, queue):
    ## Write to the queue
    for ii in range(0, count):
        queue.put(ii)  # Write 'count' numbers into the queue
    queue.put('DONE')

if __name__ == '__main__':
    pqueue = Queue()  # writer() writes to pqueue from _this_ process
    for count in [10 ** 4, 10 ** 5, 10 ** 6]:
        ### reader_proc() reads from pqueue as a separate process
        reader_p = Process(target=reader_proc, args=((pqueue),))
        reader_p.daemon = True
        reader_p.start()  # Launch reader_proc() as a separate python process

        _start = time.time()
        writer(count, pqueue)  # Send a lot of stuff to reader()
        reader_p.join()  # Wait for the reader to finish
        print("Sending {0} numbers to Queue() took {1} seconds".format(count,
                                                                       (time.time() - _start)))

Queue 输出结果

Sending 10000 numbers to Queue() took 0.2558887004852295 seconds
Sending 100000 numbers to Queue() took 2.4320709705352783 seconds
Sending 1000000 numbers to Queue() took 23.602338075637817 seconds

让我们把结果整理成表格方便对比查看:

循环次数 Pipe Queue
10000 0.0744 0.2558
100000 0.7794 2.4320
1000000 7.4254 23.6023

通过对比测试可以发现,Pipe性能大约为Queue的3倍,所以在仅有两端通信的情况下应该优先使用Pipe。

源码分析

通过阅读Queue的源码,我们可以发现,其实在Queue内部是用Lock来实现对Pipe的安全读写操作的。所以相比于Pipe会有额外的锁的开销。

class Queue(object):

    def __init__(self, maxsize=0, *, ctx):
        if maxsize <= 0:
            # Can raise ImportError (see issues #3770 and #23400)
            from .synchronize import SEM_VALUE_MAX as maxsize
        self._maxsize = maxsize
        self._reader, self._writer = connection.Pipe(duplex=False) # 这里初始化了Pipe对象
        self._rlock = ctx.Lock()
        self._opid = os.getpid()
        if sys.platform == 'win32':
            self._wlock = None
        else:
            self._wlock = ctx.Lock()
        self._sem = ctx.BoundedSemaphore(maxsize)
        # For use by concurrent.futures
        self._ignore_epipe = False
        self._after_fork()
        if sys.platform != 'win32':
            register_after_fork(self, Queue._after_fork)

    def put(self, obj, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if not self._sem.acquire(block, timeout):
            raise Full

        with self._notempty:
            if self._thread is None:
                self._start_thread()
            self._buffer.append(obj)
            self._notempty.notify()

    def get(self, block=True, timeout=None):
        if self._closed:
            raise ValueError(f"Queue {self!r} is closed")
        if block and timeout is None:
            with self._rlock:
                res = self._recv_bytes()
            self._sem.release()
        else:
            if block:
                deadline = time.monotonic() + timeout
            if not self._rlock.acquire(block, timeout):
                raise Empty
            try:
                if block:
                    timeout = deadline - time.monotonic()
                    if not self._poll(timeout):
                        raise Empty
                elif not self._poll():
                    raise Empty
                res = self._recv_bytes()
                self._sem.release()
            finally:
                self._rlock.release()
        # unserialize the data after having released the lock
        return _ForkingPickler.loads(res)

参考

https://stackoverflow.com/questions/8463008/multiprocessing-pipe-vs-queue

Python量化交易实战
欢迎您扫码订阅我的微信公众号: pyquant

我的微信公众号:pyquant

virtualenv是python虚拟软件环境的管理工具,用于创建和删除虚拟环境

特性

  1. 隔离性,将python软件环境打包安装到单独的目录下,可以以项目或者脚本为单位单独创建虚拟环境,防止项目间模块版本混乱和冲突的问题。
  2. 易用性,通过一行命令即可创建虚拟环境,在虚拟环境之间切换也非常简单

安装virtualenv

pip install virtualenv

使用介绍

virtualenv --help

比较有用的几个参数:

  • -p PYTHON_EXE, --python=PYTHON_EXE,指定虚拟环境中的python版本
  • –system-site-packages, 创建的虚拟环境将使用连接的方式,添加系统默认python环境中的site-packages
  • –always-copy,使用copy的方式代替连接来添加系统默认python已安装模块

创建虚拟环境

mkdir myproject
cd myproject
virtualenv --p python3.6 venv

激活和退出虚拟环境

激活虚拟环境,系统激活之后,提示符前端有个(venv)的前缀,表示系统已经切换到venv虚拟环境目录下

source venv/bin/activate

在venv环境下,安装模块可以使用pip来进行

退出虚拟环境,退出后系统将自动选择系统默认的Python解释器,提示符前缀的(venv)也会消失

deactivate

删除虚拟环境

由于每个虚拟环境是独立部署的,所以直接将虚拟环境目录rm就可以完成清理

其他

virtualenvwrapper是virtualenv的扩展管理包,用于更方便管理虚拟环境,它可以做:

  • 将所有虚拟环境整合在一个目录下
  • 管理(新增,删除,复制)虚拟环境
  • 切换虚拟环境

另外,从python3.3之后,virtualenv已经作为python模块venv提供使用,具体信息可以参考一下网址:

https://docs.python.org/3/library/venv.html

参考

Python量化交易实战
欢迎您扫码订阅我的微信公众号: pyquant

我的微信公众号:pyquant

使用的docker镜像是 https://github.com/stilliard/docker-pure-ftpd

创建步骤如下

docker pull stilliard/pure-ftpd:hardened

docker run -d -e FTP_USER_NAME=test -e FTP_USER_PASS=test --name ftpd_server -p 21:21 -e FTP_PASSIVE_PORTS=45020:45100 --expose=45020-45100 -p 45020-45100:45020-45100 -v /home/test:/home/ftpusers -e "PUBLICHOST=10.168.2.178" -e FTP_USER_HOME=/home/ftpusers stilliard/pure-ftpd:hardened

注意:FileZilla传输模式需要选择“主动”

提供的参数解释说明

/usr/sbin/pure-ftpd # path to pure-ftpd executable
-c 5 # --maxclientsnumber (no more than 5 people at once)
-C 5 # --maxclientsperip (no more than 5 requests from the same ip)
-l puredb:/etc/pure-ftpd/pureftpd.pdb # --login (login file for virtual users)
-E # --noanonymous (only real users)
-j # --createhomedir (auto create home directory if it doesnt already exist)
-R # --nochmod (prevent usage of the CHMOD command)
-P $PUBLICHOST # IP/Host setting for PASV support, passed in your the PUBLICHOST env var
-p 30000:30009 # PASV port range (10 ports for 5 max clients)
-tls 1 # Enables optional TLS support
Python量化交易实战
欢迎您扫码订阅我的微信公众号: pyquant

开启/关闭ufw

ufw enable
ufw disable

允许某端口被访问

ufw allow 80

禁止某端口被访问

ufw deny 8888

添加规则

允许10.168.2.137访问30004端口

ufw allow from 10.168.2.137 to any port 30004

插入规则

在第二条位置插入规则,允许192.168.1.1访问8888端口

ufw insert 2 allow from 192.168.1.1 to any port 8888

按编号显示规则

ufw status numbered

按编号删除规则

ufw delete 编号

Python量化交易实战
欢迎您扫码订阅我的微信公众号: pyquant

我的微信公众号:pyquant

在写代码时经常会遇到对抛出异常的代码进行重试,常见于网页爬虫的代码中,使用计数器 + 循环的方式对抛出异常的代码进行捕获和重试。tenacity是使用Python装饰器模式对方法异常进行捕获,通过灵活的参数实现简单优雅的异常重试。

特性:

  1. 简单灵活的装饰模式api
  2. 可以指定重试停止条件(比如:设置重试次数)
  3. 也可以指定等待条件(比如:使用指数避让间隔重试)
  4. 自定义触发重试的Exception
  5. 自定义重试预期的返回结果
  6. 基于协程的重试

安装方式:

pip install tenacity

API使用介绍

1. @retry

给需要重试的方法加上@retry修饰器之后,方法抛出异常就会被装饰器捕获到并进行重试,异常抛出时会不断重试直到方法成功返回

@retry
def never_give_up_never_surrender():
    print("Retry forever ignoring Exceptions, don't wait between retries")
    raise Exception

2. 带终止条件的retry

我们也可以给retry加一个参数设置重试n次后不再重试并抛出异常

@retry(stop=stop_after_attempt(7))
def stop_after_7_attempts():
    print("Stopping after 7 attempts")
    raise Exception

使用@stop_after_delay 可以指定重试间隔,比如如下的例子指定10秒后重试

@retry(stop=stop_after_delay(10))
def stop_after_10_s():
    print("Stopping after 10 seconds")
    raise Exception

可以使用 “|” 把多个条件组合起来

@retry(stop=(stop_after_delay(10) | stop_after_attempt(5)))
def stop_after_10_s_or_5_retries():
    print("Stopping after 10 seconds or 5 retries")
    raise Exception

3. 在重试前等待

使用@wait_fixed 在重试前等待固定时间

@retry(wait=wait_fixed(2))
def wait_2_s():
    print("Wait 2 second between retries")
    raise Exception

随机等待1-2秒钟,这在爬虫爬网页时比较有用

@retry(wait=wait_random(min=1, max=2))
def wait_random_1_to_2_s():
    print("Randomly wait 1 to 2 seconds between retries")
    raise Exception

增加指数避让等待间

@retry(wait=wait_exponential(multiplier=1, min=4, max=10))
def wait_exponential_1():
    print("Wait 2^x * 1 second between each retry starting with 4 seconds, then up to 10 seconds, then 10 seconds afterwards")
    raise Exception
    
@retry(wait=wait_fixed(3) + wait_random(0, 2))
def wait_fixed_jitter():
    print("Wait at least 3 seconds, and add up to 2 seconds of random delay")
    raise Exception

看一个更复杂点的例子

@retry(wait=wait_chain(*[wait_fixed(3) for i in range(3)] +
                       [wait_fixed(7) for i in range(2)] +
                       [wait_fixed(9)]))
def wait_fixed_chained():
    print("Wait 3s for 3 attempts, 7s for the next 2 attempts and 9s for all attempts thereafter")
    raise Exception

4. 带触发条件的retry语句

@retry(retry=retry_if_exception_type(IOError))
def might_io_error():
    print("Retry forever with no wait if an IOError occurs, raise any other errors")
    raise Exception

def is_none_p(value):
    """Return True if value is None"""
    return value is None

@retry(retry=retry_if_result(is_none_p))
def might_return_none():
    print("Retry with no wait if return value is None")
    
@retry(retry=(retry_if_result(is_none_p) | retry_if_exception_type()))
def might_return_none():
    print("Retry forever ignoring Exceptions with no wait if return value is None")

5. 异常处理

虽然tenacity会帮我们处理异常,我们依然可以在重试失败后使用reraise来决定我们时候进行最后的尝试,使用reraise会把异常抛出交给我们的try except来处理

@retry(reraise=True, stop=stop_after_attempt(3))
def raise_my_exception():
    raise MyException("Fail")

try:
    raise_my_exception()
except MyException:
    # timed out retrying
    pass

6. 在retry前后增加log

logger = logging.getLogger(__name__)

@retry(stop=stop_after_attempt(3), before=before_log(logger, logging.DEBUG))
def raise_my_exception():
    raise MyException("Fail")
    
@retry(stop=stop_after_attempt(3), after=after_log(logger, logging.DEBUG))
def raise_my_exception():
    raise MyException("Fail")
    
@retry(stop=stop_after_attempt(3),
       before_sleep=before_sleep_log(logger, logging.DEBUG))
def raise_my_exception():
    raise MyException("Fail")

7. 统计异常情况

@retry(stop=stop_after_attempt(3))
def raise_my_exception():
    raise MyException("Fail")

try:
    raise_my_exception()
except Exception:
    pass

print(raise_my_exception.retry.statistics)

输出如下内容:

{'start_time': 283085.571804807, 'attempt_number': 3, 'idle_for': 0, 'delay_since_first_attempt': 0.0002240639878436923}

8. 自定义异常回调函数

from tenacity import stop_after_attempt, retry_if_result, retry

def return_last_value(retry_state):
    """return the result of the last call attempt"""
    return retry_state.result()

def is_false(value):
    """Return True if value is False"""
    return value is False

# will return False after trying 3 times to get a different result
@retry(stop=stop_after_attempt(3),
       retry_error_callback=return_last_value,
       retry=retry_if_result(is_false))
def eventually_return_false():
    return False

print(eventually_return_false())

输出结果为 False

项目Git地址

https://github.com/jd/tenacity

Python量化交易实战
欢迎您扫码订阅我的微信公众号: pyquant

我的微信公众号:pyquant

最近某天突然登录服务器变的很慢,输入ssh命令后大概要多10多秒钟才连上服务器(设置了免密码登录),并且登录之后切换到root用户也要等很久,网上搜索发现也有其他人遇到类似问题,尝试了网上提到的设置ssh_config和sshd_config的某些参数没有明显变化,登录服务器依旧很慢,最终发现问题还是通过自己排查,这里记录下排查过程。

先执行ssh命令登录服务器,增加 -v 参数打印debug信息:

ssh -v xuqi@10.168.2.178

发现在执行到“debug1: pledge: network” 这步时卡住很久,再google一次,找到了解决办法,执行如下命令后,登录时间明显缩短到约2秒钟

systemctl restart systemd-logind

大致的原因是dbus服务由于某些原因重启后,也必须重启systemd服务,否则就会出现这个bug。

参考

https://serverfault.com/questions/792486/ssh-connection-takes-forever-to-initiate-stuck-at-pledge-network

Python量化交易实战
欢迎您扫码订阅我的微信公众号: pyquant

我的微信公众号:pyquant

概念

股指期货(Stock Index Futures)的全称是股价指数期货,也可称为股价指数期货、期指,是指以股价指数为标的物的标准化期货合约,双方约定在未来的某个特定日期,可以按照事先确定的股价指数的大小,进行标的指数的买卖。期货分为商品期货和金融期货,股指期货属于金融期货,作为期货交易的一种类型,股指期货交易与普通商品期货交易具有基本相同的特征和流程。

主要作用

  1. 规避投资风险

    当投资者不看好股市,可以通过股指期货的套期保值功能在期货上做空,锁定股票的账面盈利,从而不必将所持股票抛出,造成股市恐慌性下跌

  2. 套利

    所谓套利,就是利用股指期货和现货指数基差在交割日必然收敛归零的原理,当期货升水超过一定幅度时通过做空股指期货并同时买入股指期货标的指数成分股,或者当期货贴水超过一定幅度时通过做多股指期货并同时进行融券卖空股票指数ETF,来获得无风险收益。

  3. 降低股市波动性

    股指期货可以降低股市的日均振幅和月线平均振幅,抑制股市非理性波动,比如股指期货推出之前的五年里沪深300指数日均振幅为2.51%月线平均振幅为14.9%,推出之后的五年里日均振幅为1.95%月线平均振幅为10.7%,双双出现显著下降

  4. 丰富投资策略

    股指期货等金融衍生品为投资者提供了风险对冲工具,可以丰富不同的投资策略,改变目前股市交易策略一致性的现状,为投资者提供多样化的财富管理工具,以实现长期稳定的收益目标。

风险

除了金融衍生产品的一般性风险外,由于标的物自身的特点和合约设计过程中的特殊性,股指期货还具有一些特定的风险。

基差风险

基差是某一特定地点某种商品的现货价格与同种商品的某一特定期货合约价格之间的价差。基差=现货价格-期货价格。

合约品种差异造成的风险

合约品种差异造成的风险,是指类似的合约品种,如日经225种股指期货和东京证券股指期货,在相同因素的影响下,价格变动不同。表现为两种情况:
1〉是价格变动的方向相反。
2〉是价格变动的幅度不同。类似合约品种的价格,在相同因素作用下变动幅度上的差异,也构成了合约品种差异的风险。

标的物风险

股指期货的标的物是市场上各种股票的价格总体水平,由于标的物设计的特殊性,是其特定风险无法完全锁定的原因。从套期保值的技术角度来看,商品期货、利率期货和外汇期货的套期保值者,都可以在一定期限内,通过建立现货与期货合约数量上的一致性、交易方向上的相反性来彻底锁定风险。而股指期货由于标的物的特殊性,使现货和期货合约数量上的一致仅具有理论上的意义,而不具有现实操作性。因为,股票指数设计中的综合性,以及设计中权重因素的考虑,使得在股票现货组合中,当股票品种和权数完全与指数一致时,才能真正做到完全锁定风险,而这在实际操作中的可行性几乎是零。因此,股指期货标的物的特殊性,使完全意义上的期货与现货间的套期保值成为不可能,因而风险将一直存在。

交割制度风险

股指期货采用现金交割的方式完成清算。相对于其他结合实物交割进行清算的金融衍生产品而言,存在更大的交割制度风险。如在利率期货交易中,符合规格的债券现货,无论如何也可以满足一部分交割要求。股指期货则只能是百分之百的现金交割,而不可能以对应股票完成清算。

规则制度

合约价值

沪深300和上证50一个点是300元,中证500是200元

保证金

投资者在进行期货交易时,必须按照其买卖期货合约价值的一定比例来缴纳资金,作为履行期货合约的财力保证,然后才能参与期货合约的买卖。这笔资金就是我们常说的保证金。

买卖一手股指期货合约占用的保证金比例一般为合约价值的10%-20%(具体由交易所规定),自20170918起中金所将保证金比例调整为15%,假如沪深300指数为3900点,相当于3900*300*15%=17.5万元,但在开户时账户上必须有50万的资金,完成开户验资过后留够一手保证金加上一定的余额即可,所以大致需要20万以上的资金,而中证50期指大致需要50万资金,上证50期指大致需要15万的资金。

手续费

在正常情况下手续费为合约价值的万分之零点七,比如20150302沪深300指数点位为3600点,手续费为万分之零点七,即3600*300*0.00007=75元,

20170918起平今仓手续费调整为万分之六点九,假设沪深300指数为3900点,当天买入且当天卖出一手沪深300期指需要付出3900*300*0.00069=807元的手续费,平昨仓手续费依然是万分之零点七,即昨天买入今天卖出一手沪深300期指的手续费为3900*300*0.00007=82元,等同于变相抑制T+0。

结算制度

每日无负债结算制度也称为“逐日盯市”制度,简单说来,就是期货交易所要根据每日市场的价格波动对投资者所持有的合约计算盈亏并划转保证金账户中相应的资金。

期货交易实行分级结算,交易所首先对其结算会员进行结算,结算会员再对非结算会员及其客户进行结算。交易所在每日交易结束后,按当日结算价格结算所有未平仓合约的盈亏、交易保证金及手续费、税金等费用,对应收应付的款项同时划转,相应增加或减少会员的结算准备金。

交易所将结算结果通知结算会员后,结算会员再根据交易所的结算结果对非结算会员及客户进行结算,并将结算结果及时通知非结算会员及客户。若经结算,会员的保证金不足,交易所应立即向会员发出追加保证金通知,会员应在规定时间内向交易所追加保证金。若客户的保证金不足,期货公司应立即向客户发出追加保证金通知,客户应在规定时间内追加保证金。投资者可在每日交易结束后上网查询账户的盈亏,确定是否需要追加保证金或转出盈利。

交易规则

  • 交易时间: 周一至周五,上午:9:30-11:30,下午:13:00-15:00

  • 涨跌幅: 上一个交易日收盘价的±10%

  • 最大下单数

    中金所暂时规定限价指令每次最大下单数量为20手,市价指令每次最大下单数量为10手,进行投机交易的客户单个合约的最大持仓限额为5000手,单个账户日内交易超过20手视为过度交易行为,套期保值交易开仓数量和持仓数量不受此限.

  • 合约代码

    • 沪深300股指期货:IF
    • 中证500股指期货:IC
    • 上证50股指期货:IH
  • 合约类型

    • 当月现货合约
    • 下月
    • 下季
    • 隔季

    随着每个月的交割以后,进行一次合约的滚动推进。比如在九月份,就具有九月、十月、十二月和次年三月四个合约进行交易,在十月底需要对十月合约进行交割。

  • T + 0

    T+0即当日买进当日卖出,没有时间和次数限制,而T+1即当日买进,次日卖出,买进的当日不能当日卖出,当前期货交易一律实行T+0交易,大部分国家的股票交易也是T+0的,我国的股票市场由于历史原因而实行T+1交易制度。

  • 卖空

    股指期货合约可以十分方便地卖空,等价格回落后再买回。股票融券交易也可以卖空,但难度相对较大。当然一旦卖空后价格不跌反涨,投资者会面临损失。

  • 合约交割

    股票买入后可以一直持有,正常情况下股票数量不会减少。但股指期货都有固定的到期日,到期就要进行平仓或者交割。因此交易股指期货不能象买卖股票一样,交易后就不管了,必须注意合约到期日,以决定是平仓,还是等待合约到期进行现金结算交割。

    沪深300指数期货会在每个月第三个星期五交割,并且以沪深300现货指数截止15:00之前两个小时的算术平均价作为交割结算价,所以不论平时期货和现货的基差有多大,沪深300指数期货最终必然会强制向沪深300现货指数收敛归零,导致期货会紧跟现货指数,具有很强的联动性,就好比小狗跟着主人散步时一样,小狗有时候会跑在主人前面,有时会在主人后面,但最终前进方向是由主人决定的,套利机制就是那根狗绳。

  • 合约结算

    股指期货合约采用保证金交易,一般只要付出合约面值约10-15%的资金就可以买卖一张合约,这一方面提高了盈利的空间,但另一方面也带来了风险,因此必须每日结算盈亏。买入股票后在卖出以前,账面盈亏都是不结算的。但股指期货不同,交易后每天要按照结算价对持有在手的合约进行结算,账面盈利可以提走,但账面亏损第二天开盘前必须补足(即追加保证金)。而且由于是保证金交易,亏损额甚至可能超过你的投资本金,这一点和股票交易不同。

基差

股指期货合约与对应股票指数之间的价差,基差=现货价格-期货价格,当期货价格高于现货被称为升水,当期货价格低于现货价格被称为贴水,比如20160302 IF1603股指期货的价格为3009点,沪深300指数为3051点,基差为3051-3009=42点,2015年度沪深300股指期货主力合约与现货指数的平均基差为±1.5%,这是不太正常的,原因在于国内融资与融券的比例失衡,国外的融资和融券比例一般为3:1,而国内达到300:1以上,融资融券比例失衡致使融券套利机制无法发挥正常作用,会导致基差出现偏差。

持仓量

多头和空头尚未平仓的合约总数,比如20160302 IF1603合约的持仓量为3.87万手,2015年沪深300股指期货单边日均持仓量13万手,占用的(双边)保证金规模约500亿元,假设其中一半的空单是套期保值盘则对应的股票市值约700亿元,而沪深300指数300只成分股的股票总市值达20万亿以上,也就是说依靠股指期货对冲风险的股票占比还不到1%,说明国内股指期货的规模依然不够大,需降低门槛让更多的中小投资者能够参与进来.2012年,2013年,2014年,2015年,2016年每月月末平均持仓量分别为78300手,102000手,164000手,131000手,44000手,2012年成交持仓比为5.5,2013年为7.9,2014年为5.4,2015年为8.6,2016年为0.39。

参与主体

套期保值者、投机者、套利者

主要策略

1. 套期保值

股指期货套期保值和其他期货套期保值一样,其基本原理是利用股指期货与股票现货之间的类似走势,通过在期货市场进行相应的操作来管理现货市场的头寸风险。

由于股指期货的套利操作,股指期货的价格和股票现货(股票指数)之间的走势是基本一致的,如果两者步调不一致到足够程度,就会引发套利盘入这种情况下,那么如果保值者持有一篮子股票现货,他认为当前股票市场可能会出现下跌,但如果直接卖出股票,他的成本会很高,于是他可以在股指期货市场建立空头,在股票市场出现下跌的时候,股指期货可以获利,以此可以弥补股票出现的损失。这就是所谓的空头保值

另一个基本的套期保值策略是所谓的多头保值。一个投资者预期要几个月后有一笔资金投资股票市场,但他觉得如今的股票市场很有吸引力,要等上几个月的话,可能会错失建仓良机,于是他可以在股指期货上先建立多头头寸,等到未来资金到位后,股票市场确实上涨了,建仓成本提高了,但股指期货平仓获得的的盈利可以弥补现货成本的提高,于是该投资者通过股指期货锁定了现货市场的成本。

2. 投机交易

股市指数期货提供了很高风险的机会。其中一个简单的投机策略是利用股市指数期货预测市场走势以获取利润。若预期市场价格回升,投资者便购入期货合约并预期期货合约价格将上升,相对于投资股票,其低交易成本及高杠杆比率使股票指数期货更加吸引投资者。他们亦可考虑购入那个交易月份的合约或投资于恒生指数或分类指数期货合约。

3. 套利

针对股指期货与股指现货之间、股指期货不同合约之间的不合理关系进行套利的交易行为。股指期货合约是以股票价格指数作为标的物的金融期货和约,期货指数与现货指数(沪深300)维持一定的动态联系。但是,有时期货指数与现货指数会产生偏离,当这种偏离超出一定的范围时(无套利定价区间的上限和下限),就会产生套利机会。利用期指与现指之间的不合理关系进行套利的交易行为叫无风险套利(Arbitrage) ,利用期货合约价格之间不合理关系进行套利交易的称为价差交易(Spread Trading)

Python量化交易实战
欢迎您扫码订阅我的微信公众号: pyquant