Python 卖空算法教程(三)

原文:zh.annas-archive.org/md5/ceefdd89e585c59c20db6a7760dc11f1

译者:飞龙

协议:CC BY-NC-SA 4.0

第十二章:信号与执行

“我们有两只耳朵和一张嘴,这样我们可以听两次比说话多”,

– 司提反

这一章将告诉您如何准确地设置订单优先级及其背后的原因。我们将研究如何使用相对序列管理投资组合,但在绝对序列中执行。我们将深入研究出场,从不盈利和盈利的情况开始。我们将考虑出场的心理卫生。然后,我们将转向一个热门话题:进入和重新进入。我们将提出直觉上反直觉但有效的低风险高回报的进入技巧。整本书都铺垫到了这一点和接下来的章节,所以我们将重新讨论书中的部分内容,提出一些综合我们迄今所见的一切的代码,并可视化结果。

在此过程中,我们将讨论以下主题:

  • 导入库

  • 时间就是金钱:订单时间的重要性

  • 订单优先级

  • 出口

  • 进入

您可以通过以下链接访问本章中所有图像的彩色版本:static.packt-cdn.com/downloads/9781801815192_ColorImages.pdf。您还可以通过该书的 GitHub 存储库访问本章的源代码:github.com/PacktPublishing/Algorithmic-Short-Selling-with-Python-Published-by-Packt

导入库

对于本章和本书的其余部分,我们将使用pandasnumpyyfinancematplotlib库。我们还将使用 ScientificPython 库中的find_peaks。所以,请记得首先导入它们:

# Import Libraries
import pandas as pd
import numpy as np
import yfinance as yf
%matplotlib inline
import matplotlib.pyplot as plt
from scipy.signal import find_peaks 

时间就是金钱:订单时间的重要性

2005 年,日本市场经历了指数上涨。僵尸股再次复活了!2006 年 1 月底,警方实际上突袭了这个派对。他们逮捕了这个新日本典范的标志性人物。突然间,感觉就像是一个夜总会关门了。粗糙的霓虹灯突然间被打开。醉醺醺的人们在舞池里相互凝视,困惑不解。现实带着报复性回来了。漂浮的僵尸重新发现了牛顿物理学。不幸的是,对于任何单一问题的借贷的可用性和成本都是令人望而却步的,所以在我和美林证券股票借贷部门的伙伴们一起,我们想出了一个信用评级资产价格CRAP)篮子交换的想法。我们倾入了所有我们能找到的僵尸。借贷成本是可以负担得起的 2%。价格将是当天收盘价。容量几乎是无限的。在复合 beta 达到 2.3 时,它比指数期货具有更大的扭矩。我们已经做好了生意。

但是当是时候下手时,负责人犹豫了。他抱怨说太波动了,太有风险了,太不正统了。因此,我们最终选择了“监视”它,一个花哨的词来制造《大富翁》的货币:-2%,-4%,-5%,日复一日。但他对这笔交易仍然不感到舒适。三周后,我得出结论:异国情调是一个度假胜地,而不是一个价值三亿美元的组合投资组合中的做空位置。然后,有一天,掉期进来了。我的名字也在上面。那天晚上我睡不着。第二天,它又额外跌了-2%。第三天,它再跌了-0.5%,但然后,它开始稳定下来。它上涨了+0.5%,+3%,+6%。我们完美地把握住了底部。但现在,史上最大的做空追击正在给做空者带来民主。最终,我们以巨额亏损平仓了一笔在不到两个月内下跌了-63%的做空仪器。

故事的寓意:时间就是金钱。有正确的想法是一回事。良好地执行它是另一回事。熊市反弹定期发生。生活中有三个确定性:税收、死亡,以及在二者之间,熊市反弹。一些市场参与者声称市场无法进行时机把握。在多头方面,市场更加宽容。构思和执行可以融合在一起。支持多头的上升漂移也惩罚了空头卖家。许多基本面空头卖家经常抱怨他们太早或有时太晚了。

订单优先级

进入是一个选择,退出是一个必要性。你可能随心所欲地进入,但你很少有机会按自己的意愿退出。在执行交易员的英语中,进入是限价单,而退出是止损单或市价单。长期持有者经常苦恼于现金余额。他们需要知道每天可以购买多少。长/短组合投资组合比传统的仅长仓簿记有更多的变动部分。事情很快就会变得混乱不堪。因此,为进入和退出设置“通行权”是明智的选择。在执行交易员的英语中,退出始终排在第一位。消耗现金的订单具有“通行权”。这可以总结如下:

  1. 买入做空是最高优先级。它的功能类似于买单。它消耗现金。做空对长期购买力和总体敞口产生影响。买入做空在极少数情况下可能触发保证金调用。

  2. 卖空是次高优先级。它释放了被困住的现金。

  3. 卖空:做空生成多余的现金。这些多余的现金可以用来购买更多的股票。一些司法管辖区要求做空者在下达做空订单之前先获得股票借出。未能这样做会导致裸卖空。这种做法指的是未事先找到借出就进行卖空。这本身并不违法,但严重不受欢迎。交易通常被重新分类为“未交付”,并进行关闭。如果你想被认真对待,就不要…

  4. 买入多单:再次强调,最重要的交易是最后进行的。只有当所有交易都排好队之后,才应该进行买入多单的订单。如果空头头寸没有平仓,但进入了多头头寸,这将减少现金余额。如果需要急忙平仓空头头寸,这可能会带来一些风险。

让我们回顾一下。首先是降低风险的交易。这些交易包括买入平仓和卖出多单。然后是旨在增加现金余额的交易:卖出空单和买入多单。

重新进入信号是一个案例,其中重复犯罪者应该被安排在前排。任何重新进入信号都是更长期趋势仍然有效的确认。此外,先前头寸已经嵌入了损益。因此,综合未平仓风险低于任何新进入的风险。然而,这也有局限性。增加已有头寸的操作被称为金字塔加码。就像生活中的一切一样,趋势会成熟。每个新头寸都会更接近趋势的成熟——最终是耗尽。这增加了集中风险和波动性。因此,通过例如每次重新进入时采取更小的头寸来降低风险是明智的。头寸规模最终会变得太小而被拒绝。

接下来,我们将更详细地看一下当我们以相对系列运行系统时会发生什么。

相对价格和绝对执行

虽然你的系统是基于货币调整和基准相对价格运行的,但是以美元发送一笔关于日本股票相对于 MSCI World 的订单,确实会引起交易台上一些滑稽的表情。这对你来说可能是昭然若揭的,但需要说明的是,交易员不擅长心灵读取。在现实世界中进行交流时,订单必须以本地货币进行下达。

由于系统是基于货币调整和基准相对价格运行的,所以止损不需要被翻译。实际上,止损与本地绝对价格无关。在前面的章节中,止损是基于相对价格而不是绝对价格的。这是因为你的系统应该是基于相对价格的。这意味着止损订单不需要在经纪人的服务器上停留。不需要挂止损订单的一个好处是它们不能被掠夺性的智能资金(高频交易HFT)参与者)操纵。

订单类型

高频交易的出现严重丰富了交易订单的选项。想要优化执行超出击败成交量加权平均价格的市场参与者,欢迎探索订单执行的广阔领域。

为了本书的目的,让我们坚持一个简单的原则:入场是一个选择,退出是一个必要条件。这意味着退出交易要么是市场、止损或止损限价订单。入场要么是成交或取消,限价或止损限价订单。当我们扳动扳机时,我们希望成交。我们内心的白痴喜欢追逐,但我们的业绩记录讨厌糟糕的执行。你的入场和退出缺乏纪律将影响你的业绩记录,所以坚持计划。一点点克制会有回报。从相对系列到绝对系列的转换中会有一些小的滑点,但最终这一切都会平衡起来。

接下来,让我们讨论退出和入场——我们按照这个顺序讨论它们,因为如我们已经注意到的,退出应优先于入场。

退出

退出分为不盈利和盈利退出。前者旨在生存,而后者则建立繁荣。我们在第二部分外在游戏:打造强大的交易边缘中大量讨论了退出。以下是一个逐步简短的概括,涵盖了退出的实际方面和心理卫生。

止损

止损对交易边缘公式中的四个变量中的三个直接可测量影响:胜率、败率和平均损失。仅因为这个原因,它总是位于优先级列表的顶部。记住,将专业空头卖家与游客区分开来的是设定止损并遵守它们的能力。接下来的内容可以视为一个重要的核查表。我们在整本书中都涵盖了这些话题,特别强调了第七章提高您的交易边缘

  1. 在逻辑上的表达位置设置止损,其中论点被无效化

  2. 设置一个损失缓和的跟踪止损:这有两个积极的影响:

    • 它可以减少损失,并将盈利期望倾向于保本。

    • 在心理上更容易处理和停止较小的持仓

  3. 持仓规模:在发送交易之前运行风险管理以计算您的暴露。

    • 保守地设置固定止损

    • 在跟踪止损上积极一点

接下来是心理准备。小额损失会消耗你的情感资本。不要忽视你的心理卫生。提醒一下,所有优秀的交易者都专注于心理资本,而平庸的人则担心移动平均持续时间或其他毫无意义的琐事。

事前评估

想象自己在亏损时关闭交易,并感受相关的负面情绪。重复这个过程并调整大小,直到你感到舒适接受损失。这样可以提前处理你的悲伤。按下发送按钮,对你的资金说再见,并考虑以下因素:

  1. 时间停止:削减或减少持仓规模。时间停止在心理上很难执行:“如果我再给它多一天、一周、一个月……”一个技巧是想象一个真实的时间浪费者,并将图像锚定到持仓位置。

  2. 减少损失:冥想,保持警觉。这可以防止急切或恐惧。损失是游戏的一部分。

  3. 止损:这是最坏的情况。如果您在事前作业上做了功课,这应该是个形式上的事情。首先,原谅自己。

事前评估可追溯到斯多哥哲学家和罗马皇帝马库斯·奥里利乌斯。这是最有效的视觉化练习之一,可以在压力下保持冷静。

齐格尼克效应

提醒自己以下内容:

  1. 一旦头寸消失在视线之外,您将在 2 周内几乎不会记得那个头寸。您的资本比由单个糟糕交易引起的脑雾更重要。

  2. 做出小的损失将确保您活着去做另一次交易。带上尺子(前额叶皮质)和战士心态来执行交易。

当您定期查看交易日志时,特别注意止损。注意自己的感受。这已经不像以前那样情绪化了。你看,这并不那么难。如果你微笑了,太好了。存储那种感觉。您正在建立一个明智交易战士的新神经路径。

盈利退出

下面是盈利退出的逐步方法:

  • 提前决定风险降低的方法:目标价格、跟踪退出、期权。不要偏离。

  • 不要等待熊市反弹。随着人们继续卖出以避免市场冲击,及时平仓。

  • 这是平仓的最小数量,以保持交易盈亏平衡:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_12_001.png

  • 顺势而为熊市反弹。让它冲刷过去。如果您提前降低了风险,这不应该影响您的心态。当熊市反弹结束时,重新设置止损。

  • 如果涨势的高点低于上一个高点,则重新进场;否则,不做任何操作。

让我们用一些 Python 代码来结束这一切。首先,我们将使用三个变量来设置目标退出价格:pricestop_lossr_multiplier。我们计算 r 作为从 pricestop_loss 的距离。然后,我们应用一个乘数,恰当地命名为 r_multiplier。这个数字被添加回成本以计算目标价格。然后,我们将使用相同的 r_multiplier 计算要退出的数量。在第七章提高交易优势中,我们看到了目标价格和要退出的数量之间的反向关系。

def target_price(price, stop_loss, r_multiplier):
    r = price - stop_loss
    return price + r * r_multiplier

def partial_exit(qty, r_multiplier):
    if (qty * r_multiplier)!= 0:
        fraction = qty / r_multiplier
    else:
        fraction = 0
    return fraction

price = 100 
stop_loss = 110 
qty = 2000 
r_multiplier = 2 

pt = target_price(price, stop_loss, r_multiplier) 
exit_qty = partial_exit(qty, r_multiplier) 
print('target price', pt,'exit_quantity',exit_qty) 

输出将如下所示:

target price 80 exit_quantity 1000.0 

80%的头寸将会在此时退出。这样可以降低 100%的风险。接下来,让我们考虑如何确定进场时机。

进场

“把市场波动视为朋友而不是敌人;从愚蠢中获利,而不是参与其中。”

– 沃伦·巴菲特

优秀的扑克玩家不是打自己的牌。他们打其他玩家的牌。要成功,您需要系统地将概率倾斜到您的有利。市场碰巧是一个概率游戏。取得伟大成功的方法不仅是打出你的手牌,还要打市场的无形之手。

玩转市场的第一种方法是我们在第五章政权定义中看到的。让市场决定股票应该走哪一边。让我们诚实地说一秒钟。做空泡沫股票的市场参与者是被压抑的复仇交易者。在内心深处,他们对自己的阴影自我没有参与做多感到愤怒。也许他们关闭得太早了。也许他们完全忽视了那艘船。无论哪种方式,他们通过让别人的宝贵资金立即置于危险之中来发泄他们的挫败感。在对“公平”估值的成年人呼吁背后,隐藏着一个 6 岁孩子受伤的自尊心的“必须正确”的阴影需求。

没有比 TSLA 更好地说明这一现象的了。这是一家汽车制造商,处于一个声名狼藉的脆弱行业,被定价为科技股,其债券被定价为垃圾债。必须有所作为。这给了空头卖家充足的理由来猛烈袭击股票。在冠状病毒大流行之后,股价下跌到了$400。这给了空头卖家加倍努力的理由,只是在接下来的六个月里面临着急剧的三倍反弹。

如果做空者将跌幅视为一个机会性的买入机会,他们还会围攻吗?当然不会,他们会笑着跳着去银行。地球上的每个人,从瑙鲁到博博迪乌拉索,都会知道他们选中了那只股票有多聪明。现在,他们被苦苦降低到要求对那个粗鲁价格公正的地步。无论哪种方式,别人的钱不是一个有效的智商测试。再次,他们让自己的自尊心在市场的永恒舞蹈中踩到了自己的脚趾。当心那些玩弄伊卡洛斯股票的卡桑德拉,因为他们可能会将你拖入表现不佳的世界。在听从他们的建议时,为冥界的渡船人留下最后一枚硬币。如果做空游戏是关于玩弄市场的无形之手,那就让市场为你做繁重的工作。做空市场贬低的问题,并在需求旺盛的问题上做多。

第二种玩转市场的方法是掌握时机。多年来,我常常抱怨空头挤压。我进入一个头寸,感觉良好,然后在下一个熊市反弹中被无情地止损出局。财务资本受到损害,情感资本再次遭受侮辱。更重要的是,似乎教训被忽略了。然后有一天,空头挤压的能量突然映照在我的智力密度上。由于价格似乎会在我的“防线”止损线之外反弹,我所要做的就是等待下一个熊市反弹,看看新的海岸线,并获得更好的执行。

我开始将熊市反弹视为礼物。它们将他们的宝藏带到岸边供我们选择。一旦熊市反弹开始消退,价格就会像职业政客一样摇摆不定。做空者被困在岸上。乐观的多头持有者尚未被拖入新的深渊。分界线清晰。在执行交易员英语中,这是一个低风险高回报的入场价格,上方设有明确的止损线,并且可能会有下跌。等待市场开始下跌,然后沿着看跌趋势打印一个新的较低的高点来进入做空。顺便说一句,这是“买入跌势”技术在多头方面的镜像入场。

第三种玩市场的方式是执行。做空者面临着极不利的概率。如果你在股价下跌时做空,只要记住 90%以上的市场参与者只会做多,某个地方某人会将当前的弱势视为折价购买的机会。如果你在熊市股票反弹时做空,你可能会因为被抛售或者牛市的开始而被淘汰。作为一个做空者,只有在概率对你有利的狭窄时间窗口。为此,你需要密切关注价格走势。

一个很好的类比是豹子,是非洲四大动物中最小的。尽管豹子在智商、奔跑速度和武器方面胜过它们的猎物,但它们从不追逐。它们隐藏在树上等待猎物靠近它们。做空者需要以同样的方式行事。这是一个三步过程。

  1. 首先,你的制度分析(来自第五章制度定义)应该已经确定了股票可能处于横向或熊市模式中的一种。与市场一致通常会倾向于增加你的获胜几率。

  2. 第二,宇宙已经被缩小为可投资候选股票的短列表。

  3. 第三,熊市反弹的退潮清晰地表明了牛市并不掌控局面。

市场指导两个步骤:设置和执行。一个步骤来自于分析:可投资候选股票的短列表。我们已经讨论过如何倾斜你的交易优势在第二部分外部游戏:制定强大的交易优势中,但我们将重温一些关键想法:

  • 首先,使用相对系列。

  • 第二,通过制度对表现不佳者和表现优异者进行初步分类。

  • 第三,在高点后进入做空或在低点后进入做多。

  • 第四,将止损设置在一个合理的表达点。

  • 第五,计划部分退出以减少风险。

让我们回到豹子。一旦它扑出去,它就不会在半空中停下来反思生活的意义、动物的残酷性或细胞层面素食饮食的比较优势。反省的时机已经过去。现在,是执行时间。游客会在他们认为是正确的船之后立即跳跃和游泳。专业人士将耐心等待市场上涨并冲洗游客。当反弹进入并翻转时,他们会站在一边,只有在那时他们才会坐在渡轮上。关于卖空的最好消息是熊市反弹的节拍的规律性。

翻转:熊市反弹的合气道

每个长时间练习这门手艺的卖空者在熊市反弹中都受过伤。一个简单的观念转变可以将这种能量从毁灭转变为利用的力量。

目标是尽早以合理的成功概率进入。短线挤压可能会棘手。它们总是超出预期并持续时间更长。

进入大师级别的卖空者的标志是在下一个大幅下跌中做对,但仍然低估了熊市反弹的能量。尊重市场,因为它们肯定不会尊重你。市场不知道你的名字,也不应该在乎。接受你将不得不为确认付出代价。等待局部高峰过去和翻转开始。稍后我们将用几张图来说明这一点,但现在让我们看看这种方法的优点。

这种翻转或潮汐滚动方法有一些优点:

  • 风险管理:当前高点低于峰值。牛市不再掌控局势。如果峰值上升,则是早期迹象表明制度可能已经改变。趋势是你的朋友。

  • 入场接近顶部。熊市处于混乱状态。牛市充满乐观。你是机会主义者。现在概率正倾向于你。

  • 借贷可用性:熊市通常会冲洗掉游客。再次可以借贷。即使最喧哗的卖空者也害怕涉足水中。

  • 更大的头寸大小:接近顶部的近距离需要比崩盘水平更大的头寸。你更接近一个合乎逻辑的止损。如果价格上涨超过当前顶部,则你已经制定好了退出计划

  • 清晰的分界线:顶部是牛市和熊市的定论。有关供给、需求和波动率的信息是可用的。

以下是我们之前讨论过的一些其他经典入场方法。

移动平均线

移动平均线是简单、有效且足够稳健的信号。虽然没有完美的策略,但通常最好使用短期移动平均线,比如收盘价低于 5 天移动平均线。有关移动平均线交叉的更多信息,请参阅第五章制度定义

回撤

回调信号最高点的距离。这可以是从最高高点或最高低点开始的距离,以平均真实范围表示。把它看作是一种形式的移动止损。作为提醒,请看我们在第五章制度定义中概述的回调摆动函数:

def retracement_swing(df, _sign, _swg, _c, hh_ll_dt, hh_ll, vlty, retrace_vol, retrace_pct):
    if _sign == 1: # swing high
        retracement = df.loc[hh_ll_dt:, _c].min() - hh_ll

        if (vlty > 0) & (retrace_vol > 0) & ((abs(retracement / vlty) - retrace_vol) > 0):
            df.at[hh_ll_dt, _swg] = hh_ll
        elif (retrace_pct > 0) & ((abs(retracement / hh_ll) - retrace_pct) > 0):
            df.at[hh_ll_dt, _swg] = hh_ll

    elif _sign == -1: # swing low
        retracement = df.loc[hh_ll_dt:, _c].max() - hh_ll
        if (vlty > 0) & (retrace_vol > 0) & ((round(retracement / vlty ,1) - retrace_vol) > 0):
            df.at[hh_ll_dt, _swg] = hh_ll
        elif (retrace_pct > 0) & ((round(retracement / hh_ll , 4) - retrace_pct) > 0):
            df.at[hh_ll_dt, _swg] = hh_ll
    else:
        retracement = 0
    return df 

下面,让我们再次提醒自己关于retest_swing函数。

重新测试

提供在第五章制度定义中的摆动检测代码是基于重新测试的。价格打印出高点,然后是低点。价格试图攀升到新的高点,但失败了,并且低于先前的低点。距离前一次摆动越远,重新测试就越不嘈杂。作为提醒,请参阅源代码:

def retest_swing(df, _sign, _rt, hh_ll_dt, hh_ll, _c, _swg):
    rt_sgmt = df.loc[hh_ll_dt:, _rt] 

    if (rt_sgmt.count() > 0) & (_sign != 0): # Retests exist and distance test met    
        if _sign == 1: # swing high
            rt_list = [rt_sgmt.idxmax(), rt_sgmt.max(), df.loc[rt_sgmt.idxmax():, _c].cummin()]

        elif _sign == -1: # swing low
            rt_list = [rt_sgmt.idxmin(), rt_sgmt.min(), df.loc[rt_sgmt.idxmin():, _c].cummax()]
        rt_dt,rt_hurdle, rt_px = [rt_list[h] for h in range(len(rt_list))]

        if str(_c)[0] == 'r':
            df.loc[rt_dt,'rrt'] = rt_hurdle
        elif str(_c)[0] != 'r':
            df.loc[rt_dt,'rt'] = rt_hurdle    

        if (np.sign(rt_px - rt_hurdle) == - np.sign(_sign)).any():
            df.at[hh_ll_dt, _swg] = hh_ll 
    return df 

接下来,我们将考虑一个富国银行的例子,其中重新测试和回调摆动都在起作用。如果一个方法失败,另一个可以补偿。

把所有东西都放在一起

现在是将想法结合起来,并在一个代码块中综合方法的时候了。在第五章制度定义中,我们发布了富国银行与 S&P 500 的绝对和相对系列的图表:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_12_01.png

图 12.1:富国银行价格在绝对和相对于 S&P500 的图示从 2015 年 9 月开始

我们已经涵盖了几乎所有内容,所以没有真正需要对代码进行评论,除了两件事:

  1. 我们从初始化基准开始,并下载下面股票的数据。如果您想要为一系列股票重复此过程,请在df下载行之前插入一个循环。

  2. 其次,我们通过列表推导重新初始化了_o_h_l_c属性。这在代码的下一部分将会有意义。

接下来,我们使用摆动检测函数计算了绝对和相对系列的摆动和制度。两次运行相同函数但分配了绝对或相对系列更有效。这就是为什么我们在之前重新初始化了_o_h_l_c以及下面的摆动值。

### STEP 1: ### Graph Regimes Combo ###
def graph_regime_combo(ticker,df,_c,rg,lo,hi,slo,shi,clg,flr,rg_ch,
                       ma_st,ma_mt,ma_lt,lt_lo,lt_hi,st_lo,st_hi):

#### removed for brevity: check GitHub repo for full code ####
### Graph Regimes Combo ###

### STEP 2: ### RELATIVE
def relative(df,_o,_h,_l,_c, bm_df, bm_col, ccy_df, ccy_col, dgt, start, end,rebase=True):
#### removed for brevity: check GitHub repo for full code ####
### RELATIVE ###

### STEP 3: import library
from scipy.signal import *
### STEP 4: #### hilo_alternation(hilo, dist= None, hurdle= None) ####
def hilo_alternation(hilo, dist= None, hurdle= None):
#### removed for brevity: check GitHub repo for full code ####
#### hilo_alternation(hilo, dist= None, hurdle= None) ####

#### historical_swings(df,_o,_h,_l,_c, dist= None, hurdle= None) #### 
def historical_swings(df,_o,_h,_l,_c, dist= None, hurdle= None):

#### removed for brevity: check GitHub repo for full code ####
#### historical_swings(df,_o,_h,_l,_c, dist= None, hurdle= None) ####
### STEP 5: #### cleanup_latest_swing(df, shi, slo, rt_hi, rt_lo) ####
def cleanup_latest_swing(df, shi, slo, rt_hi, rt_lo): 
#### removed for brevity: check GitHub repo for full code ####
#### cleanup_latest_swing(df, shi, slo, rt_hi, rt_lo) ####
### STEP 6: #### latest_swings(df, shi, slo, rt_hi, rt_lo, _h, _l, _c, _vol) ####
def latest_swing_variables(df, shi, slo, rt_hi, rt_lo, _h, _l, _c):
#### removed for brevity: check GitHub repo for full code ####
#### latest_swings(df, shi, slo, rt_hi, rt_lo, _h, _l, _c, _vol) ####
### STEP 7: #### test_distance(ud, bs, hh_ll, vlty, dist_vol, dist_pct) ####
def test_distance(ud,bs, hh_ll, dist_vol, dist_pct): 
#### removed for brevity: check GitHub repo for full code ####
#### test_distance(ud, bs, hh_ll, vlty, dist_vol, dist_pct) ####

#### ATR ####
def average_true_range(df, _h, _l, _c, n):
#### removed for brevity: check GitHub repo for full code ####
#### ATR ####
### STEP 8: #### retest_swing(df, _sign, _rt, hh_ll_dt, hh_ll, _c, _swg) ####
def retest_swing(df, _sign, _rt, hh_ll_dt, hh_ll, _c, _swg):
    rt_sgmt = df.loc[hh_ll_dt:, _rt] 
#### removed for brevity: check GitHub repo for full code ####
#### retest_swing(df, _sign, _rt, hh_ll_dt, hh_ll, _c, _swg) ####
### STEP 9: #### retracement_swing(df, _sign, _swg, _c, hh_ll_dt, hh_ll, vlty, retrace_vol, retrace_pct) ####
def retracement_swing(df, _sign, _swg, _c, hh_ll_dt, hh_ll, vlty, retrace_vol, retrace_pct):
#### removed for brevity: check GitHub repo for full code ####
#### retracement_swing(df, _sign, _swg, _c, hh_ll_dt, hh_ll, vlty, retrace_vol, retrace_pct) ####
### STEP 10: #### regime_floor_ceiling(df, hi,lo,cl, slo, shi,flr,clg,rg,rg_ch,stdev,threshold) ####
def regime_floor_ceiling(df, _h,_l,_c,slo, shi,flr,clg,rg,rg_ch,stdev,threshold):
#### removed for brevity: check GitHub repo for full code ####
#### regime_floor_ceiling(df, hi,lo,cl, slo, shi,flr,clg,rg,rg_ch,stdev,threshold) #### 

下面是实际重要的代码,将打印出图 12.1所示的图形:

params = ['2014-12-31', None, 63, 0.05, 0.05, 1.5, 2]
start, end, vlty_n,dist_pct,retrace_pct,threshold,dgt= [params[h] for h in range(len(params))]

rel_var = ['^GSPC','SP500', 'USD']
bm_ticker, bm_col, ccy_col = [rel_var[h] for h in range(len(rel_var))]
bm_df = pd.DataFrame()
bm_df[bm_col] = round(yf.download(tickers= bm_ticker,start= start, end = end,interval = "1d",
                 group_by = 'column',auto_adjust = True, prepost = True, 
                 treads = True, proxy = None)['Close'],dgt)
bm_df[ccy_col] = 1

ticker = 'WFC'
df = round(yf.download(tickers= ticker,start= start, end = end,interval = "1d",
                 group_by = 'column',auto_adjust = True, prepost = True, 
                 treads = True, proxy = None),2)
#### removed for brevity: check GitHub repo for full code ####

    rohlc = ['rOpen','rHigh','rLow','rClose']
    _o,_h,_l,_c = [rohlc[h] for h in range(len(rohlc)) ]
    rswing_val = ['rrg','rL1','rH1','rL3','rH3','rclg','rflr','rrg_ch']
    rg,rt_lo,rt_hi,slo,shi,clg,flr,rg_ch = [rswing_val[s] for s in range(len(rswing_val))] 

我们首先对绝对系列运行一个循环。我们使用上下限制度方法计算摆动和制度。在第一个循环结束时,我们使用相对系列重新初始化了_o_h_l_c和摆动变量。现在我们已经计算出了富国银行在绝对和相对于 S&P 500 的摆动和制度。让我们可视化结果:

plot_abs_cols = ['Close','Hi3', 'Lo3','clg','flr','rg_ch','rg']
plot_abs_style = ['k', 'ro', 'go', 'kv', 'k^','b:','b--']
y2_abs = ['rg']
plot_rel_cols = ['rClose','rH3', 'rL3','rclg','rflr','rrg_ch','rrg']
plot_rel_style = ['grey', 'ro', 'go', 'yv', 'y^','m:','m--']
y2_rel = ['rrg']
df[plot_abs_cols].plot(secondary_y= y2_abs,figsize=(20,8),
            title = str.upper(ticker)+ ' Absolute',# grid=True,
            style=plot_abs_style)

df[plot_rel_cols].plot(secondary_y=y2_rel,figsize=(20,8),
            title = str.upper(ticker)+ ' Relative',# grid=True,
            style=plot_rel_style)

df[plot_rel_cols + plot_abs_cols].plot(secondary_y=y2_rel + y2_abs,figsize=(20,8),
            title = str.upper(ticker)+ ' Relative & Absolute',# grid=True,
            style=plot_rel_style + plot_abs_style) 

我们将绘制三个独特的图表:绝对、相对和综合。因此,我们将绝对和相对参数存储在列表中。话不多说,这是三个图表:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_12_02.png

图 12.2:富国银行的绝对波动和上下限制度

制度一直是行为的一个公平预测因素,特别是在后半段。2020 年的涨势直到 2021 年出现摆动低点才导致制度变化。接下来,我们将绘制相对系列:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_12_03.png

图 12.3:富国银行的波动和地板/天花板制度相对于标普 500 指数

正如我们在前面的章节中所看到的,当丑闻爆发时,富国银行受到了重创,直到 2021 年才恢复。制度看起来表面上更加紧张。一路上有相当多的看涨假阳性。它们的范围和持续时间都很短。请注意,价格线上方的红点如何构成良好的入场点和/或移动止损水平。当然,打印波动高点和发现它们之间存在一小段滞后时间。最后,我们将两者合并成一个图表:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_12_04.png

图 12.4:富国银行的波动和地板/天花板制度绝对和相对于标普 500 指数

这最后一个图表看起来比前两个更加嘈杂。请注意地板/天花板方法如何准确地命中了 2016 年 9 月的丑闻。相对系列在整个期间始终领先于制度。

让我们再次使用图表制度函数来可视化绝对和相对图表:

ma_st = ma_mt = ma_lt = lt_lo = lt_hi = st_lo = st_hi = 0
rg_combo = ['Close','rg','Lo3','Hi3','Lo3','Hi3','clg','flr','rg_ch']
_c,rg,lo,hi,slo,shi,clg,flr,rg_ch=[rg_combo[r] for r in range(len(rg_combo))]
graph_regime_combo(ticker,df,_c,rg,lo,hi,slo,shi,clg,flr,rg_ch,ma_st,ma_mt,ma_lt,lt_lo,lt_hi,st_lo,st_hi)

rrg_combo = ['rClose','rrg','rL3','rH3','rL3','rH3','rclg','rflr','rrg_ch']
_c,rg,lo,hi,slo,shi,clg,flr,rg_ch=[rrg_combo[r] for r in range(len(rrg_combo))]
graph_regime_combo(ticker,df,_c,rg,lo,hi,slo,shi,clg,flr,rg_ch,ma_st,ma_mt,ma_lt,lt_lo,lt_hi,st_lo,st_hi) 

以下是绝对值中绘制的制度:深色表示亏损期:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_12_05.png

图 12.5:富国银行在绝对值中着色的地板/天花板制度

接下来,让我们看看相对系列:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_12_06.png

图 12.6:富国银行在相对于标普 500 指数的地板/天花板制度着色

这最后一个图表真正展示了过去几年中表现不佳的程度。有许多识别趋势耗尽的方法,例如随机指标、相对强弱指标、移动平均收敛/散开指标、德马克指标以及后者的祖先,斐波那契回调。其中大部分技术归结为将混乱带入秩序。不幸的是,普及并不一定表示统计上的稳健性。如果你仍然相信使用预测性技术分析,你可以将订单分成两部分,第一部分使用预测性分析,第二部分在确认预测准确后添加。当它奏效时,你的平均执行会稍微好一些,并且当它不奏效时,你不会破产。

最重要的是,记住一个人的崩溃对另一个人来说是“低吸买入”。这是空头市场;市场不合作。利用熊市反弹和空头挤压来确定你的入场时机。当每个人都卖光时出局。通过减少仓位和/或重设止损来尽量降低风险。

摘要

整本书都指向了这一章和下一章。总之,为了总结我们在前面章节中所涵盖的所有内容,请切换到相对系列并根据市场制度进行分类。在高点之后进入空头,或者在低点之后进入多头。设定一个在波动之上或之下的止损价格和一个目标价格以降低风险。使用你的成本、止损和风险预算来计算你的头寸规模。

为每个策略的每个多/空侧保留单独的风险预算。尊重你的止损并尊重你的策略。保持一个干净的交易日志。在路上完善你的授权。

在下一章中,我们将研究你工具箱中最被低估的工具之一:你的投资组合管理系统。成为一名选股专家和一名投资组合经理是两种需要不同技能的不同工作。在你选择管理投资组合的那一刻,你不再是一个股票骑手。你已经成为了一个稳定的马主。你的工作不是骑在马背上冲过终点线。你的工作是培育(或淘汰)那些在可预见的未来能(或不能)冲过终点线的马匹。

一个相对的多空投资组合与传统的仅多头投资组合甚至绝对的多空投资组合是完全不同的生物。到目前为止你所使用的任何工具都迫切需要进行根本性的升级。如果你想在市场上有一线生机,你需要一套钢铁侠的装备。

第十三章:投资组合管理系统

“托托,我觉得我们不再是在堪萨斯了。”

– 多萝西,《绿野仙踪》

我在对冲基金世界的第一份工作是为一家管理资金不到 500 万美元的初创对冲基金建立和维护一个名为投资组合管理系统PMS)的系统。创始人认为风险管理不是业务的一种装饰,而是业务本身。快进到现在,他们已经成为日本替代空间的主导者。在本章中,我们将继续上两章的工作,并介绍一个定制的 PMS,它可以显示投资组合的风险可视化。

为了说明健壮的 PMS 的重要性,让我们联系一下我们在本书中探讨过的一些概念。在第一章,《股票市场游戏》中,我们设定了市场是一个无限、复杂的随机游戏的背景。在第六章,《交易优势是一个数字,这就是公式》中,我们揭示了神奇、神秘和神话般的交易优势公式。在第八章,《头寸大小:在资金管理模块中赚钱》中,我们证明了头寸大小决定了绩效。我们在第十一章,《多空工具箱》中学到了我们工具箱中的工具。所以,我们知道头寸大小是关键因素,但我们还不知道哪些头寸会奏效,然而我们必须在管理风险敞口的同时保持正面期望。每天重复这个过程几年,以吸引并保留投资者。

在我的职业生涯中,朋友和同事们慷慨地展示了他们的 PMS。生存直接与他们 PMS 的质量相关。并不是每个拥有正确系统的人都成功了。但每一个没有系统的经理最终都崩溃了。

PMS 就像飞行仪器。任何新手都可以在晴朗的天空中驾驶飞机。仪器的存在是为了防止起飞和在夜间着陆、在雾中和湍流中的坠毁。没有仪器,不是每个人都能回家。市场并非一直是晴朗和顺利的。事情会出乎意料地变得丑陋。情绪确实会使我们偏离轨道。用执行交易员的术语来说,如果你没有一个坚实的 PMS,你怎么能说自己是一个投资组合经理呢?使用 Python 构建定制的 PMS 值得一本书,甚至是自己专门的文学流派,因此超出了本书的范围。然而,在本章中,我们将简要讨论一些基本概念和指导原则,希望能指导你:

  • 糟糕的投资组合管理系统的症状

  • 你的投资组合管理系统就是你的钢铁侠战衣

  • 自动化枯燥的事情

  • 构建一个健壮的投资组合管理系统

您可以通过以下链接访问本章中所有图像的彩色版本:static.packt-cdn.com/downloads/9781801815192_ColorImages.pdf。您还可以通过该书的 GitHub 存储库访问本章的源代码:github.com/PacktPublishing/Algorithmic-Short-Selling-with-Python-Published-by-Packt

导入库

对于本章和本书的其余部分,我们将使用pandasnumpyyfinancematplotlib库。所以,请记得首先导入它们:

# Import Libraries
import pandas as pd
import numpy as np
import yfinance as yf
%matplotlib inline
import matplotlib.pyplot as plt 

低效的投资组合管理系统的症状

当谈到改善业绩时,一个好的 PMS 是最低的果实。在 2005 年,我加入了一个对 PMS 非常丑陋的对冲基金,看着它可能会导致脑损伤。我立即进行了改进,至少是为了消除健康风险。突然间,问题儿童们就像圣诞树上的花环一样亮了起来。短线股票的波动性减少了,懒惰的狗,和不被赏识的赛犬都被迅速处理了。波动性下降了。表现更加一致了。夏普比率提高了。投资者注意到了。资产管理规模AUM)增长了。就是这么简单。

每个人都想知道赢家的"秘密酱",但这是一个领域,"阿尔法挑战者"的经验可能会帮助我们避免无意中重复同样的错误。这是一个简单的练习,将帮助您判断您的系统是否需要升级。效率低下的 PMS 表现出以下一个或多个症状。

无效的资本配置

尽管投资组合经理们喜欢向公众展示出一个奥林匹克半神的形象,但只需几句话就可以将他们拉回现实:

  • “好人最后完成”:一些股票取得了令人印象深刻的回报,但由于头寸较小而未被注意到。这些往往是探索性的小头寸。它们获得高回报,但由于没有足够的规模而贡献不佳。

  • “肥猫不捉老鼠”:一些大头寸之所以有所贡献,只是因为它们的重量巨大。这是 PMS 的一个经典问题。它直接与上述问题相关。这些往往是占用了太多空间但回报微薄的高信仰度想法。

  • “曾经我们是勇士”:这些是现在免费吃白食的老牌贡献者。这些是一段时间以来变得陈旧的想法。它们有很多嵌入式的历史贡献,但不再产生收益。我们在第七章提升您的交易优势中已经解决了这种情况。

监测不足的风险检测

一个需要密切关注的关键指标是流动性,以避免“加州酒店综合症”。换句话说,你不能清算的东西就不是你拥有的,而是拥有你的。流动性是熊市的货币。是否有头寸不能在不产生严重市场冲击的情况下清算?以下是一个重新计算头寸规模所需天数以便以平均交易量的一部分退出的函数。对于一个空头卖方来说,最可怕的事情就是看到熊市反弹获得动力,并意识到你的头寸过大以至于无法幸免于难。很少有比被迫清仓更糟糕的感觉,只见其在熊市海啸后重新开始向下滑落。

一个简单的风险度量是将头寸规模重新表述为以平均交易量的一部分进行清算所需天数。以下代码简单地计算了平均交易量的一部分。头寸数量除以此分数:

ticker = 'UNG' #ETF natural gas 
volume = yf.download(tickers= ticker,start= '2021-01-01', end = None,                     interval = "1d",group_by = 'column',                     auto_adjust = True, prepost = True,treads = True,                      proxy = None)['Volume']

def days_liquidation(quantity,volume,window,fraction):
    avg_vol_fraction = volume.rolling(window).mean()* fraction
    return round(quantity/avg_vol_fraction,2)
quantity = 100000
window = 63
fraction =0.01
days_liquidation(quantity,volume,window,fraction)[-1] 

这产生了类似以下的输出:

3.52 

我们可以生成图 13.1所示的图表:

plt.plot(days_liquidation(quantity,volume,window,fraction)) 

我们从2021 年初开始下载了交易量数据。该函数将一个固定百分比乘以交易量的移动平均值。这一股数除以交易量的一部分:0.01,或1%。这个值是任意的,且故意设得很低以展示原理。在实践中,5-10%是合理的阈值。截至2021 年 6 月底,如果订单量不超过平均交易量的1%,则大约需要5 天时间来清算整个头寸。我们将系列中的最后一个数字打印出来并绘制了移动平均线:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_13_01.png

图 13.1: 以 3 个月平均交易量的 1%进行清算所需天数

上图显示流动性并非静态。最初,对于交易量占平均日交易量1%的头寸,清算大约需要2 天。随着流动性减少,清算所需天数增加到5 天,然后又回落到3.5 天左右。当价格朝着你不利的方向波动时,一周就仿佛是永恒。因此,应该密切监视流动性。

市场冲击是由于大宗交易引起的价格不利变动。当头寸超过日交易量的5%时,市场冲击就会开始。如果头寸量在日交易量的10%下持续时间超过5 天,那么就预期会产生一些严重的市场冲击。此外,熊市反弹的激烈程度不容小觑。在尝试从困境中解脱时,你将心理上处于被动状态。因此,始终要密切监视流动性,并在交易量减少时减少头寸。

高波动性

拥挤的空头有较高的平均借贷利用率。流动性将变薄,波动性将增加,回报将恶化。利用率或借贷费用的热度图是非常有价值的工具。在发布日期,遗憾的是没有免费的资源可以提供借贷利用率。作为一个捷径,当借贷费用大大高于平均水平时,可以假设受欢迎。处理拥挤的空头的最简单方法是随着受欢迎程度的提高减少头寸规模。再次强调,制定简单的规则,例如,如果利用率上升到 40%以上,则将头寸减半,每增加 10%再减半。到达 60%的利用率时,曝光将是原始规模的 25%。这只是一些思考。

爆炸并不显眼:人们希望感觉良好。在市场上,做自己感觉良好的事情很少是最赚钱的行动。处于风险中的股票必须在视觉上促使管理者采取行动。股票很少出现意外爆炸。它们通常会发出微妙的信号表明事情不如预期发展。

高相关性

多元化的目标是降低相关性。相关性很难避免。在第四章多空方法:绝对和相对中,我们介绍了相对系列以减少与基准的相关性。然而,焦点从确定绝对顶部或底部转移到捕捉行业轮换,并相应地分配资产。投资组合内的相关性很难消除。坦率地说,我不知道如何在实践中做到这一点。迄今为止,我也没有遇到成功的方法。

曝光管理不善

有几种不同类型的曝光,可以通过以下方式表明投资组合管理不善:

  • 净曝光:当投资组合的净曝光与当前的看涨/看跌观点不匹配时。这在熊市阶段尤其明显,当环境愁云密布时,净曝光仍然指向残存的乐观情绪。投资组合认知失调是指曝光不反映管理者的说法。

  • 净贝塔暴露:大多数不足系统中缺乏真正的市场敏感性。当管理者只关注净曝光时,他们将低方向性与净市场敏感性混为一谈。净贝塔进一步超越净曝光,以反映对市场的敏感性。

  • 市值暴露:做空期货是对冲的垃圾食品。正如我们在第十一章多空对冲工具箱中看到的那样,小盘股和大盘股具有不同的风险特征。当市场整体情绪乐观时,小盘股表现良好,而大盘股是更安全的选择。净市值暴露反映了投资组合的整体乐观情绪。

  • 总曝光:在好时机杠杆过多,在坏时机现金过多。总曝光是最容易减少或增加风险的杠杆。犹豫不决时,崩溃杠杆是最简单的风险政策。

  • 交易所暴露:按市值和交易所暴露的暴露解释了很多好/坏的表现。正如我们在 第四章长/短期方法论:绝对和相对 中所看到的,纳斯达克多年来一直优于标普 500。多年的轻松赚钱奖励了投机问题和那些押注于它们的人。当钱容易赚时,就容易赚钱。没有牛市曾经提高过任何人的智商。

总的来说,在资产管理行业,投资回报率最高的是定制的 PMS。随着信息差的缩小,市场参与者争先恐后地寻找优势。如果你能比你的同行更早地发现和解决问题,你就能获得可持续的竞争优势。

你的投资组合管理系统就是你的钢铁侠服

“你知道,我经常被问到的问题是,“托尼,你怎么在你的衣服里上厕所?” [长时间的停顿] 就是这样。”

– 托尼·斯塔克,《钢铁侠》

托尼·斯塔克可能是个天才、亿万富翁、花花公子和慈善家。真正赋予他超能力的是他定制的钢铁侠服。他没有在 Macy’s 的货架上买他的衣服。他在车库里摧毁了几辆兰博基尼,打造了一些独特增强他战斗风格的东西。同样,你的 PMS 就是你的钢铁侠服。它是你交易策略的延伸。它的唯一目的是帮助你在不确定性下做出交易决策。因为没有人像你一样交易,所以它需要高度定制化。

经典错误是购买昂贵的现成软件包,认为更多的信息会导致更好的交易决策。软件解决方案主要是由中后台人员为前台专业人员设计的报告工具。你的 PMS 是一个交易工具,而不是报告工具。Barra 因子分解或街头共识每股收益不会告诉你今天应该买入或卖空哪家公司的多少股票。信息转化为知识,而知识并不直接转化为技能。建立技能的关键是专注于直接导致行动的信息。

在实践中,系统整合几个月后,投资组合经理经常以低技术的 Excel 电子表格来管理他们的投资组合,这些电子表格从那些闪闪发光的玩具中获取数据。他们买了一辆兰博基尼去挑牛奶。因此,在你急于购买市场上最昂贵的玩具之前,这里有几个原则可以帮助你澄清自己的需求。信息必须根据四个原则组织:清晰、相关、简单和灵活。

清晰:绕过左脑

在压力下,你的杏仁核会释放化学物质,降低前额叶皮层或“思考大脑”的活动。你的思维能力会受到影响。你的 PMS 需要清晰度:信息必须组织得让问题迅速显现出来。其次,左脑无法处理数字的行和列。它慢慢的,每秒 5 到 7 位。它不会做关联。一切都变得模糊成无生机的数字汤。与此同时,右脑处理信息的速度快一百倍,寻找模式,并做关联。幸运的是,数据可视化是数据科学的新趋势。视觉线索能够绕过思考较慢的大脑。当决策涉及多个因素时,按照数字进行绘画有助于建立关联。例如,将你在投资领域内的位置可视化为热图会立即告诉你是否在正确的轨道上,并在每个行业内选择最佳策略。我们将在本章结束时查看投资组合热图。

另一个话题是警报。你是否曾想过为什么灾难警报是如此讨厌?如果《欢乐颂》宣布即将发生地震、海啸或火山喷发,人们可能不会感到同样的紧迫感。我们天生倾向于远离痛苦,向着愉悦之处前进。警报的存在是为了迫使最不情愿行动的人采取行动。当一个位置需要处理时,尽可能地让警报烦人。警报越烦人,解决问题的可能性就越大。最好的警报是你必须在执行日常任务之前手动确认的那些。例如:假设某只股票已达到止损位。如果设置了警报,你必须点击它才能继续查看你的投资组合,那么可能会发生两种情况。要么你会迅速解决问题,要么你会彻底避免使用这个工具。当正确的需求超过了赚钱的必要性时,我们内心的白痴就会更加努力地自我破坏。

经典错误之一是为每个风险因素都设立多个页面。这意味着用户必须在页面之间来回切换并做笔记。每增加一点摩擦力都会增加信息泄漏的可能性。飞行员不应该在飞机上上上下下地查看液压和燃油表是否一切正常。如果你想要将不同的风险联系起来,你需要将所有相关信息汇总到一页摘要中,以便在后续页面进行深入了解。可以把它想象成汽车或飞机的仪表板。你需要的所有信息都在你面前展示着。

将事物放在上下文中。左脑不能有效处理数字。295%的总暴露只是一个数字。当它在显示基准、总和净暴露以及表现的图表上绘制时,这个数字就在上下文中了。它是市场表现、你的位置以及随时间的结果表现的视觉呈现。一张图胜过千言万语。让我们回顾一下在第十一章多空工具箱中看到的例子。我们从以下股票代码模拟了一个投资组合。我们假设从 2020 年 12 月 31 日到 2021 年 6 月期间没有交易:

# Chapter 13: Portfolio Management System
K = 1000000
lot = 100
port_tickers = ['QCOM','TSLA','NFLX','DIS','PG', 'MMM','IBM','BRK-B','UPS','F']
bm_ticker= '^GSPC'
tickers_list = [bm_ticker] + port_tickers
df_data= { 
'Beta':[1.34,2,0.75,1.2,0.41,0.95,1.23,0.9,1.05,1.15],
'Shares':[-1900,-100,-400,-800,-5500,1600,1800,2800,1100,20800],
'rSL':[42.75,231,156,54.2,37.5,42.75,29.97,59.97,39.97,2.10]
}
port = pd.DataFrame(df_data,index=port_tickers)
port['Side'] = np.sign(port['Shares'])

start_dt = '2021-01-01'
end_dt = '2021-07-01'
price_df = round( yf.download(tickers= tickers_list,start= '2021-01-01', 
                              end = '2021-07-01', interval = "1d",                               group_by = 'column',auto_adjust = True,                               prepost = True, treads = True,                               proxy = None)['Close'],2)

bm_cost = price_df[bm_ticker][0]
bm_price = price_df[bm_ticker][-1]

port['rCost'] = round(price_df.iloc[0,:].div(bm_cost) *1000,2)
port['rPrice'] = round(price_df.iloc[-1,:].div(bm_price) *1000,2)
port['Cost'] = price_df.iloc[0,:]
port['Price'] = price_df.iloc[-1,:] 

第十一章多空工具箱中,我们处理了port数据框。这次,我们将使用price_df可视化曝光。我们首先计算基准收益。然后,我们将计算相对和绝对的长期和短期市值。

我们将计算绝对和相对的曝光和收益。

price_df['bm returns'] = round(np.exp(np.log(price_df[bm_ticker]/price_df[bm_ticker].shift()).cumsum()) - 1, 3)
rel_price = round(price_df.div(price_df['^GSPC'],axis=0 )*1000,2)

rMV = rel_price.mul(port['Shares'])
rLong_MV = rMV[rMV >0].sum(axis=1)
rShort_MV = rMV[rMV <0].sum(axis=1)
rMV_Beta = rMV.mul(port['Beta'])
rLong_MV_Beta = rMV_Beta[rMV_Beta >0].sum(axis=1) / rLong_MV
rShort_MV_Beta = rMV_Beta[rMV_Beta <0].sum(axis=1)/ rShort_MV

price_df['rNet_Beta'] = rLong_MV_Beta - rShort_MV_Beta
price_df['rNet'] = round((rLong_MV + rShort_MV).div(abs(rMV).sum(axis=1)),3)

price_df['rReturns_Long'] = round(np.exp(np.log(rLong_MV/rLong_MV.shift()).cumsum())-1,3)
price_df['rReturns_Short'] = - round(np.exp(np.log(rShort_MV/rShort_MV.shift()).cumsum())-1,3)
price_df['rReturns'] = price_df['rReturns_Long'] + price_df['rReturns_Short']

MV = price_df.mul(port['Shares'])
Long_MV = MV[MV >0].sum(axis=1)
Short_MV = MV[MV <0].sum(axis=1)
price_df['Gross'] = round((Long_MV - Short_MV).div(K),3)
price_df['Net'] = round((Long_MV + Short_MV).div(abs(MV).sum(axis=1)),3)

price_df['Returns_Long'] = round(np.exp(np.log(Long_MV/Long_MV.shift()).cumsum())-1,3)
price_df['Returns_Short'] = - round(np.exp(np.log(Short_MV/Short_MV.shift()).cumsum())-1,3)
price_df['Returns'] = price_df['Returns_Long'] + price_df['Returns_Short']

MV_Beta = MV.mul(port['Beta'])
Long_MV_Beta = MV_Beta[MV_Beta >0].sum(axis=1) / Long_MV
Short_MV_Beta = MV_Beta[MV_Beta <0].sum(axis=1)/ Short_MV
price_df['Net_Beta'] = Long_MV_Beta - Short_MV_Beta 

rel_price框架是price_df数据框除以基准收盘价乘以 1,000 的结果。我们使用连续系列,不重新调整到系列的开始。索引在开始时的值有 4 位小数。因此,我们将相对价格乘以 1,000 以获得更容易处理的数字。我们将价格乘以股份数,按持仓的符号汇总,然后求和以获得相对的长期和短期市值。由于没有交易,除了收益外,市值没有变化。因此,我们使用市值的日变化来计算累积收益。正如我们在第十一章多空工具箱中看到的,总暴露使用绝对系列。这是实际的资产负债表使用情况。

我们将主要的曝光和收益绘制在一张图上。然后我们绘制相对系列:

price_df[['bm returns','Returns','Gross','rNet_Beta','rNet' ]].plot(
    figsize=(20,8),grid=True, secondary_y=['Gross'],
    style= ['r.-','k','g--','g-.','g:','b:','c','c:'],
    title = 'bm returns, Returns, Gross, rNet_Beta, rNet')

price_df[['bm returns', 'Returns', 'rReturns', 'rReturns_Long',
          'rReturns_Short']].plot(figsize=(20,8),grid=True,
             style= ['r.-','k','b--o','b--^','b--v','g-.','g:','b:'],
             title= 'bm returns, Returns, rReturns, rReturns_Long, rReturns_Short')
price_df[['bm returns','Returns','rReturns',
          'rReturns_Long','rReturns_Short','Returns_Long',          'Returns_Short']].plot(
            figsize=(20,8),grid=True,secondary_y=['Gross'], 
            style= ['r.-','k','b--o','b--^','b--v','k:^','k:v',],
title= 'Returns: benchmark, Long / Short absolute & relative')
price_df[['bm returns',
          'rReturns_Long','rReturns_Short','Returns_Long',          'Returns_Short']].plot(
            figsize=(20,8),grid=True,secondary_y=['Gross'], 
            style= ['r.-','b--^','b--v','k:^','k:v',],
title= 'Returns: benchmark, Long / Short absolute & relative') 

我们将有四张图。第一张是"你是谁?"图表:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_13_02.png

图 13.2:"你是谁"图表:市场回报和曝光

上面的图表是我个人最喜欢的。如果有一张图能总结一个经理的投资组合管理技能,那就是这张图。它展示了经理如何应对市场的挑战。基准市场回报是实线红色线,所有其他线都是经理的反应:

  • 总暴露是虚线。杠杆是自信的直接体现。上升的总暴露显示了信心。

  • 净暴露是虚线。净暴露象征着乐观情绪。上升的净暴露表明看涨情绪增长。由于投资组合的两边都表现不错,空头减少,而多头增值。这导致了净多头漂移。

  • 净β是虚线。净β象征着风险偏好。当风险增加时,更多的投机性问题将出现在多头股票中,而防御性行业和股票将出现在空头。没有交易,所以净β只是市值演变的反映。

  • 最后,实线黑线是回报。以这种方式管理曝光带来了这样的表现。

  • 这张图表中唯一缺失的组件是净交易。将净多头/空头交易绘制成条形图,这将显示出响应。由于这个例子中没有交易,所以没有条形图。

接下来,让我们看看长头寸和空头寸如何影响绩效。我们最初的前提是,多空投资组合是两个相对头寸的总和。多头头寸必须胜过指数,而空头头寸必须落后于基准。这是它是如何实现的:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_13_03.png

图 13.3:回报:基准、绝对、相对、多头和空头

在实践中,短时间内持续跑赢指数是极为罕见的。红色实线是基准。虚线蓝线是相对绩效,实线黑线是绝对绩效。

首先,多空绩效超过了指数。这是这本书将会得到的最接近虚构的部分。相对多头回报是向上的三角形。空头相对回报是向下的三角形。其次,请注意多头和空头相对回报与指数的接近程度。它们是超过指数的多余回报。将这与我们在《第四章》多空方法论:绝对和相对中看到的相对牛市或熊市中的股票数量相比较。它们在基准周围振荡。

要充分理解超额回报的含义,让我们将相对多头/空头回报与绝对回报进行比较:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_13_04.png

图 13.4:回报:基准、绝对、相对、多头和空头

这张嘈杂的图表显示了绝对多头和空头回报。这展示了一个原则,对许多绝对交易者来说仍然陌生。在牛市中从空头赚钱意味着尽可能少地亏损。保持平衡是一件好事。从你在熊市中的多头头寸来考虑一下。你不指望赚到任何钱,而且坦率地说,如果你的多头头寸不被清仓,你会感到高兴。

让我们删除绝对和相对净回报,集中关注多头和空头的绝对和相对回报,以强调这一点:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_13_05.png

图 13.5:回报:基准、绝对、相对、多头和空头

用执行交易员的英语来说,在牛市中期望你的空头亏损。只需确保不让小伤口变成感染并使整个投资组合坏死。这是许多新手空头卖方犯的经典错误,他们经常缩小空头头寸。以下是这种三段论通常的推理方式:

  1. 多空领域是一项竞争激烈的运动。每一个基点的绩效都很重要。

  2. 这本短小的书正在泄漏阿尔法,金融黑话中意味着亏钱。

  3. 所以,让我们缩小空头头寸来止血阿尔法出血,并专注于多头。

这种思维方式通常会导致空头市场萎缩。这扩大了净暴露度,并增加了净贝塔。这直接增加了与市场的相关性和收益的波动性。这两个正是投资者愿意支付溢价以避免的。卖空是一种在不使用时会萎缩的肌肉。总之,专注于你的授权、你的暴露度以及相对于指数的超额收益。

关联性:钢铁侠自动收音机效应

尽管他的服装里有很多小工具,但你是否曾想过为什么托尼·斯塔克的钢铁侠装备中没有低科技的自动收音机?他还是 Stark Industries 的 CEO,也许他想知道市场专家对他的股价有什么看法。然而,在与坏人战斗时,倾听 CNBC 上的金融噪音显然是托尼·斯塔克无法承受的干扰。

钢铁侠自动收音机效应是市场参与者有时会引用人类对他们可以管理的股票数量的限制的原因。你的 PMS 并不在这里提供信息。它是你交易策略的延伸,这里是告诉你该做什么的地方。它将输出订单,这些订单要么来自你的策略信号,要么来自你的风险管理。你不需要知道你的夏普比率或共识 EPS 2 年后才能执行止损。你需要知道你的主要暴露度,以及根据你在第十一章长/短工具箱中设计的授权来对冲你的投资组合需要交易多少股票。

每个人都有一个监视屏幕,显示他们的投资组合的情况。很少有市场参与者采取额外步骤构建一个交易屏幕,将他们的交易规则嵌入其中,并将信息处理成交易决策。

构建一个单独的交易表,每个交易决策都有一个列:止损,获利,时间退出,重新进入等等。让你的交易规则或投资流程在后台运行。当需要做出决策时,在相应的单元格中显示股票数量,否则留空。

例如,当一支股票达到了盈利目标时,只需打印出需要关闭的股票数量。

第十一章长/短工具箱中,我们进行了比例风险调整。首先,我们将重新发布代码。我们将展示调整前后的聚合数据。然后我们将打印出订单:

# Chapter 13: Portfolio Management System
adjust_long = adjust_short  =  -0.01 

MV = port['Shares'] * port['Price']
port['Weight'] = round(MV.div(abs(MV).sum()), 3)
port['rR'] = (port['rCost'] - port['rSL'])
port['rRisk'] = -round(np.maximum(0,(port['rR'] * port['Shares'])/K), 4)
port['rRAR'] = round( (port['rPrice'] - port['rCost'])/port['rR'], 1)
port['rCTR'] = round(port['Shares'] * (port['rPrice']-port['rCost'])/ K,4)
port['CTR'] = round(port['Shares'] * (port['Price']-port['Cost'])/ K,4)
port_long = port[port['Side'] > 0]
port_short = port[port['Side'] < 0]
pro_rata_long = port_long['rRisk'] / (port_long['rRisk'].sum() * port_long['rRAR'])
risk_adj_long = (abs(adjust_long) * pro_rata_long * K / port_long['rR'] // lot) * lot
shares_adj_long =  np.minimum(risk_adj_long, port_long['Shares'])*np.sign(adjust_long)

pro_rata_short = port_short['rRisk'] / (port_short['rRisk'].sum() * port_short['rRAR'])
risk_adj_short = (abs(adjust_short) * pro_rata_short * K / port_short['rR'] // lot)*lot
shares_adj_short = np.maximum(risk_adj_short,port_short['Shares'])*np.sign(adjust_short)

port['Qty_adj'] = shares_adj_short.append(shares_adj_long)
port['Shares_adj'] = port['Shares'] + port['Qty_adj']
port['rRisk_adj'] = -round(np.maximum(0,(port['rR'] * port['Shares_adj'])/K),4)
MV_adj= port['Shares_adj'] * port['Price']
rMV_adj = port['Shares_adj'] * port['rPrice']
port['Weight_adj'] = round(MV_adj.div(abs(MV_adj).sum()),3)
port.loc[port['Shares_adj'] != 0,'Shares_adj'] 

我们决定在长空两侧风险上等比例更新,减少-0.01,这相当于将风险降低了 1%。比例调整的排序关键是相对风险调整收益。相对风险单位的相对表现越低,调整就越大。调整后的股票和需要交易的数量是:

QCOM     -1500 
NFLX      -200 
DIS       -500 
PG       -5100 
IBM        500 
BRK-B     2100 
UPS        800
F        19200 

你可以调整 adjust_longadjust_short 变量的值,以适应你想要进行的风险调整(正或负)。添加一个价格列,另一个订单类型列,这就是系统必须输出的全部内容。每次喝一杯葡萄酒时,你不需要了解关于苹果酸乳酸发酵和桶中陈年的任何知识。

简单:复杂性是一种懒惰的表现

当你为生命而战时,你不需要一身沉重、闪闪发光的阅兵服。你需要斯巴达装甲——轻便、有效、经过战斗考验。

当你开始构建你的 PMS 时,你会忍不住想要添加更多的花哨功能。复杂性是一种懒惰的表现。只构建必要的东西,而不是好看的东西。例如,如果你交易移动平均线,你不需要四列价格、第一和第二个移动平均线以及股票。这是信息过载。你只需要一列股数或交易的权重,只有当条件满足时才显示。

你的 PMS 不需要重复发明轮子。Prime 经纪人通过他们的 API 提供了许多你所需要的信息。例如,性能计算是一种资源浪费。它涉及公司行为,如股票拆分、股利和认购/赎回。这些都纳入修改后的 Dietz 时间序列性能计算,按股份类别分开。与此同时,你的 prime 经纪人将在每天早餐前通过他们的 API 提供净资产值NAV)和性能计算。

经验法则是只显示会导致决策的信息,直接的,比如要进行的交易,或者间接的,比如开放风险水平。另一个例子是借用利用率。你的系统为 x 千股的做空交易。你开始流口水,但是系统也显示借用利用率大约是 40%。交易较小的规模可能更明智。

灵活性:信息不能转化为决策

使用 PMS 配备经理就像给近视眼人士配眼镜。起初,他们很高兴能看到任何东西。然后,他们开始要求更多。视力带来视野。一个经典错误是在弄清楚你需要什么之前购买昂贵的软件。软件解决方案是由中后台人员为前台战士构思的。这往往导致前台人员只使用软件功能的一小部分,并在 Excel 上构建一个独立的工具。

另一个错误是从一开始就将所有内容硬编码。您的 PMS 将随着您的发展而发展。每一个小的改进都会产生连锁反应。当人们开始锻炼时,晚上出去喝酒的前景就不那么吸引人了。没有人像你一样交易,所以没有人能够替代你。它不必完美,但必须首先工作。开始时,使其廉价和灵活。Excel、Google 表格、R、Python 和 Jupyter 笔记本足以开始实验。理论上,C++比 Python 更快。在实践中,C++比 Python 慢几个月,甚至几年。Python 是新的 Excel!

自动化枯燥的事情

所有市场参与者都想成为下一个吉姆·西蒙斯。没有人想要照顾管道。人们想做激动人心的事情。他们想要拿出十亿美元的创意来交易。他们不想照顾风险管理。本节介绍了一些耗时且最终令人厌烦的流程的示例,应该进行自动化以及原因。

交易对账是将下单与执行交易进行对账的过程。它显示了哪些交易以什么价格进行了。这是少数几个会让人觉得阅读税法是一项激动人心事业的任务之一。然而,这是一项重要的任务,因为这些数据被用于执行交易分析。价格是否击败了成交量加权平均价格VWAP)?

更新 PMS 很无聊,但并不像看起来那么容易。公司会经历各种各样的公司行动,比如股息和股票拆分。这些公司行动会影响数据的一致性。这需要注意细节。

自动化交易订单路由。绝大多数算法交易员都已经自动化执行。毕竟,如果系统说“买这么多”或“卖那么多”,那就这么做。执行是自动的。自主交易员仍然在全面信任他们的系统方面有困难。如果系统输出一个荒谬的数字怎么办?或者,市场做了这个或那个?好吧,信任是建立的。一个简单的解决方案是默认进行自动执行,除非有手动覆盖。

构建健壮的投资组合管理系统

以下是构建健壮的 PMS 的快速逐步指南。请记住,每个 PMS 都需要高度定制。你正在构建自己的钢铁侠战衣。因此,本指南将保持相当模糊。

  • 将您的策略形式化为简单的交易决策:买入/卖出、数量、价格。交易序列相当简单。绘制一个流程图并填充序列。

  • 添加源自风险管理的决策:流动性、权重、借用利用率、净β值和总体和净敞口。我们之前看到整个投资组合的风险降低。

  • 从您的监控工具中,为每个交易决策创建一个单独的交易表格,每个交易决策在单独的列中。这种极简监控工具对于大型投资组合尤其有效。

  • 在你的视野之外,嵌入你的交易规则。这可以是表格最右侧的列,Excel 中的宏,以及 Python 中的脚本。你只需要你盘子上的香肠,而不是整个烹饪过程的视觉。

  • 在你的视野中,使用股票代码作为行索引,使用交易决策作为列索引来索引你的投资组合。

  • 决策可视化:如果触发了交易决策,请在相应的单元格中打印股票数量或权重,并将其他所有内容留空。这会增加视觉冲击力。

  • 从基于规则的操作开始小规模尝试:止损或获利都是低成本高影响的决策。

  • 随着时间的推移,建立一个综合工具。将你的交易历史添加到直接计算头寸大小。一个强大的工具是将你的投资组合监控工具和观察清单整合到市场热图中。

  • 丢掉汽车收音机。随着你完善流程,你会有添加单元格和列的冲动。再努力一点,尽量保持尽可能多的内容在后台运行。

第十一章多空工具箱 中,有一张扩展表格,其中列出了要交易的股票。我们按方向(多头或空头)和相对风险调整收益 (rRAR) 重新排序数据框。我们切片数据框以仅显示相关列:

port = port.sort_values(by=['Side','rRAR'])
port_cols = ['Side','Beta','Shares_adj','Weight',             'Weight_adj','CTR','rCTR',
             'rRAR','rRisk','rRisk_adj']
port[port_cols] 

一切看起来都像这样:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_13_10.png

让我们将上述表格放在投资组合管理框架中加以解释。

  • 让我们首先将投资组合的一侧和对市场的敏感性可视化。我们想知道高/低 beta 位于哪一侧。我们是看涨还是看跌?

  • 接下来,我们想要做出我们的交易决策。我们不需要最终在最后一列得出结论。我们想要知道首先要做什么。然后,我们可以检查系统是否按预期工作。

  • 我们已经知道调整会减少我们的总敞口。现在,我们想知道每个头寸的调整前后影响。这些是“权重”和“权重调整”列。

  • 风险纯粹主义者会在权重旁边显示风险的影响。我们的资产配置重点是按绩效比例减少风险而不是按权重。我们首先想要修剪表现不佳的头寸。因此,我们显示贡献,“CTR”,“rCTR”以及相对风险调整收益,“rRAR”。

  • 最后,我们显示“rRISK”和“rRisk_adj”之前和之后的相对风险。最后一列显示了股票级别的投资组合剩余风险。

这个概念框架已经被简化到其最基本的组成部分。每一列中的每个数字都有其存在的理由。即使是办公室纸牌和俄罗斯方块大师的顶尖人物,也就是合规和风险官员,也会同意这是根据行业中最严格的风险管理指南编制的,用俚语说就是“我不知道,看起来似乎不会炸掉农场”。

唯一的问题是,我们的左脑半球不具备处理这些信息的能力。这是一个大脑无法处理的单调数字汤,更不用说与之相关了。有意识的大脑每秒只能处理 5 +/-2 位信息。用执行交易员的术语来说,当你的眼睛从表格的一端移到另一端时,你的大脑已经忘记了它一开始需要寻找的东西。另一个不好的消息:你并不特殊。这种情况发生在每个人身上。此外,雷曼,如果你能记住这个表格,你会在二十一点桌上数牌时过得更好。

真正的问题是,我们如何将这种单调的数字汤转化为我们一瞥就能准确处理的东西?原来我们的大脑配备了右脑半球。它以闪电般的速度处理信息,将看似随机的数据片段进行关联。唯一的缺点是,数据需要重新打包成这种大脑能够处理的格式。用执行交易员的术语来说,让我们按照数字来绘制。我们将使用一个名为 Styler 的内置构造器。只需几行代码就能返回一个多功能的热图。这几行代码可以循环利用,以绘制市场回报的快速有效的热图。

perf_cols= ['rCTR', 'CTR','rRisk', 'rRisk_adj','rRAR']
desc_cols= ['Side','Beta','Weight','Weight_adj',]
sort_cols = ['Side','rRAR']
asc = [True,True]
port[port_cols].sort_values(by = sort_cols,ascending= asc).style.background_gradient(
    subset = desc_cols, cmap = 'viridis_r').background_gradient(
    subset = perf_cols, cmap = 'RdYlGn').format('{:.5g}') 

我们运行一个多空头投资组合。出现在空头的数字通常带有负号。我们不希望我们的潜意识将空头仓位与负收益联系起来。因此,我们将描述性字段(desc_cols)与与绩效相关的字段(perf_cols)分离开来。您想要在单个数据框上显示的颜色图数量越多,就越简单地将它们堆叠在一起。

sort_values 方法被添加以进一步增强热图的多功能性。这个单调的数字汤只有在正确绘制时才会变得生动起来。西斯廷教堂的壁画可能看起来神圣,但它们背后只是一堵墙。

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_13_06.png

图 13.6:投资组合热图

这个热图按照多空头(long/short)和相对风险调整回报(rRAR)排序。数字被绘制后变得生动起来。这张表格可以进一步简化。再次指导原则是尽可能少地显示干扰。让我们注意到这个显示的改进:

  • 首先,数量、成本、价格和止损列被隐藏起来。它们是重要信息,但不是关键信息。不关键的信息可能会分散注意力。如果触发了止损,您需要知道需要交易多少股票。在那之前,它们只是干扰。

  • 在这个例子中,前五列严格是描述性的。要交易的股票数量没有额外的含义。因此,它不需要上色。

  • 其他描述性列采用黄色到绿松石到紫色的颜色代码。侧面立即显示我们正在处理书的哪一面。贝塔显示我们是如何被斧头砍的。看起来大部分低贝塔的东西都在空头方向上。调整后的风险仍然很高的股票仍然是调整前/调整后最大的权重。

  • 其他所有内容都采用了三色图案的颜色编码,从红色到橙色到绿色,表示每个单元格的健康状态或权重。这个系统有效。

这里有其他可视化想法。构建 PMS 时,专注于对你有共鸣的内容。这是你的钢铁侠装甲。有些人喜欢表格,并使用热图。有些人对图表有反应。在下一个代码块中,我们将以条形图的形式可视化信息。我们将使用不同的键对数据进行排序,以展示不同的视角。

bar_cols= ['Weight', 'Weight_adj','rRisk','rRisk_adj','rCTR',            'CTR','rRAR','Side']
col_style= ['lightgrey','dimgrey','lightcoral','red',
            'forestgreen','lightseagreen','yellowgreen','whitesmoke']
sort_keys= ['Weight_adj','rCTR']
sec_y=['rRAR','Side']
sort= ['rRAR']
port[bar_cols].sort_values(by=sort).plot(kind='bar',
            grid=True,figsize=(20,5),
            secondary_y=sec_y,color=col_style, title= 'PORT, by '+str(sort))

sort= ['Weight_adj','rRAR']
port[bar_cols].sort_values(by=sort).plot(kind='bar',
            grid=True,figsize=(20,5),
            secondary_y=sec_y,color=col_style, title= 'PORT, by '+str(sort))

sort= ['Side','rRisk_adj']
port[bar_cols].sort_values(by=sort).plot(kind='bar',
            grid=True,figsize=(20,5),
            secondary_y=sec_y,color=col_style, title= 'PORT, by '+str(sort)) 

代码中没有什么特别的,除了颜色代码。把颜色编码交给一个法国人,那个传说中的猪会有浅粉色和薰衣草色的口红,让人联想到夏季的莫奈花园。

我们使用了三个不同的排序键来展示三个不同的视角下的数据:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_13_07.png

图 13.7:按“rRAR”排序的投资组合

排序键是 rRAR,无论侧面如何都按升序排序。总体而言,图表的前半部分被多头占据。牛市的潮水会推高所有股票。尽管空头书籍尚未产生大量绝对资金,但已经实现了巨额回报。

其次,我们按“权重”和“rRAR”排序:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_13_08.png

图 13.8:按“权重”和“rRAR”排序的投资组合

这将所有东西重新分类为长/短期书籍,并为投资组合提供“rRAR”视角。最重要的职位是最好的风险调整贡献者,这与该投资组合管理策略一致。

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_13_09.png

图 13.9:按“侧面”和“rRisk_adj”排序的投资组合

这按“侧面”和相对风险后调整的“rRisk_adj”排序。这将产生一个两面的图表,左边是空头,右边是多头。结果与该策略一致。表现最佳者拥有更大的权重和更高的风险。接下来,让我们看一下市场参与者最喜欢的跟踪工具:观察名单。

除非嵌入了一些交易决策,否则观察名单是惰性的。我们都错过了一些交易,只是因为我们没有关注。以下是一种逐步方法,可以确保您保持健康的可操作观察名单:

  • 输入股票代码、目标水平、入场日期

  • 自动化触发器:当价格达到目标水平时,打印要交易的股票数量,否则留空

  • 淘汰陈旧的想法:定期按日期排序。每次审查后删除陈旧的想法

  • 每天对列表进行一次排序

这个半自动化的观察清单将使您与竞争对手区分开来。市场参与者很少花时间正式化他们的计划。当其他人试图在恐惧中即兴发挥时,您将以合理的计划出现。

这个过程可能需要几周,甚至几个月,具体取决于您的正式化水平。定制的 PMS 将带来以下积极效果:

  • 它将迫使您正式化您的过程。如果投资是一个过程,那么自动化就是合乎逻辑的结论。这个原则自工业革命开始就经受了时间的考验。计算机处理的发展已经加强了它。

  • 它将揭示您的过程中的缺陷。自由裁量和半自动化的市场参与者有盲点。

  • 它将减少情绪干扰:在监视屏幕上做出决策是 100%自由裁量的。运行算法是 100%系统化的。您将处于这个谱系的某个位置。

  • 它减轻了压力:当所有决策在后台运行并且只在需要时显示时,您不需要担心并且采取预防性措施。

  • 它释放了精神带宽,可以管理更多的名称或花更多时间思考。当您的系统与您的过程保持一致时,您将花费更少的时间担心,换句话说,消耗精神带宽,更多时间规划。

当您是一名分析师或业余零售交易者时,您是一名股票骑师。您支持一些股票。您参与其中,但您不会因为挂着百万美元的股票而难以入眠。当您选择成为投资组合经理时,您不再是一名骑师。您承担了稳定大师的责任。您的工作是在您的马厩中优化有限的资源。这就是为什么您需要一个 PMS。

概要

投资于 PMS 是您将会做出的最高回报投资决策。没有 PMS,您可能会偶尔运气好地挑选到一些正确的股票,但与稳健的 PMS 的纪律性一比,这些都不值一提。在感染坏疽并限制绩效之前,您将会补充优胜者并处理肉体创伤。您将成为更加纪律性的市场参与者。风险管理不是业务的一部分。它就是业务。投资组合经理不是从事挑选股票的业务。他们的业务是用他们要么挑选要么放弃的股票来管理风险。随着时间的推移,小的好处会复利,并使您与仍然相信挑选股票是成功关键的竞争对手有所区别。当您的投资组合管理成为您的钢铁侠装备时,您将与认为他们在交易一个系统的人竞争。

卖空是有风险的。市场不合作。然而,选择不学习卖空更加危险。你可能选择永远不参与卖空,但无论如何,学会卖空只会丰富你的技能库。我们真诚地希望这本书能引发关于卖空的讨论。这个领域已经被妖魔化和研究不足太久了。我们希望已经给予它应有的荣誉。

我们开始通过消除关于卖空的谬论,并解释为什么长期思维在卖空方面通常会失败。我们继续通过研究相对序列和制度定义来进行想法生成。这也涉及到头寸大小和风险管理。这本书的最后一部分远不止于长/短期投资组合的构建。它是关于设计一种投资者感兴趣的产品——无关联低波动回报——使用长/短期交易工具。

你将在附录,股票筛选中找到一个自动化的股票筛选工具,我们希望你会觉得有用!

附录:股票筛选

这个附录提供了一个股票筛选工具,它将让你把我们在本书中学到的一切付诸实践。它解决了市场参与者最迫切的问题:创意生成。我们将在标普 500 指数的所有成分股上建立一个筛选器。

事件的顺序如下:

  1. 从其维基百科页面下载所有当前标普 500 指数的成分股。

  2. 从 Yahoo Finance 批量下载 OHLCV 价格数据。我们将降低级别以处理每只股票。

  3. 计算调整后的相对系列。

  4. 计算制度——突破口、海龟、移动平均线(简单移动平均线SMA)和指数移动平均线EMA)),以及地板/天花板——在绝对和相对系列上。将有一个选项将每只股票保存为 CSV 文件。

  5. 创建包含每只股票最后一行的字典,并附加一个列表,从中我们将创建一个数据框。

  6. 总结制度方法并对数据框进行排序。你将有一个选项将这个最后一行数据框保存为 CSV 文件。

  7. 将包含维基百科信息的原始数据框与最后一行数据框合并。你将有一个选项将此数据框保存为 CSV 文件。

  8. 生成按行业和子行业分类的热力图。

  9. 如果你想要特别可视化任何股票,将会有一个单独的股票下载、处理和可视化模块在筛选的最后。

    你可以通过以下链接访问本章节所有图像的彩色版本:static.packt-cdn.com/downloads/9781801815192_ColorImages.pdf。你也可以通过本书的 GitHub 存储库访问本章的源代码:github.com/PacktPublishing/Algorithmic-Short-Selling-with-Python-Published-by-Packt

导入库

我们首先导入标准库。pathlib已经被注释掉了。如果你想要在计算机或服务器上的某个地方保存 CSV 文件,你可以使用诸如pathlibos之类的库。

# Appendix 
# Data manipulation
import pandas as pd
import numpy as np
from scipy.signal import *
# import pathlib
# Data download
import yfinance as yf
# Data visualisation
%matplotlib inline
import matplotlib.pyplot as plt 

当然,这是极具影响力的——我们将利用随后的片刻理智的消失迅速进行下一步。

定义函数

以下是本书中一直使用的函数。你可以在 GitHub 上找到完整版本。函数通常会以它们出现的章节为前缀。筛选将包括绝对和相对系列,因此我们需要相对函数。这将跟随经典的制度定义函数:

# CHAPTER 5: Regime Definition 
### RELATIVE
def relative(df,_o,_h,_l,_c, bm_df, bm_col, ccy_df, ccy_col, dgt, start, end,rebase=True):
    #### removed for brevity: check GitHub repo for full code ####
### RELATIVE ###
def lower_upper_OHLC(df,relative = False):
    if relative==True:
        rel = 'r'
    else:
        rel= ''      
    if 'Open' in df.columns:
        ohlc = [rel+'Open',rel+'High',rel+'Low',rel+'Close']       
    elif 'open' in df.columns:
        ohlc = [rel+'open',rel+'high',rel+'low',rel+'close']

    try:
        _o,_h,_l,_c = [ohlc[h] for h in range(len(ohlc))]
    except:
        _o=_h=_l=_c= np.nan
    return _o,_h,_l,_c

def  regime_args(df,lvl,relative= False):
    if ('Low' in df.columns) & (relative == False):
        reg_val = ['Lo1','Hi1','Lo'+str(lvl),'Hi'+str(lvl),'rg','clg','flr','rg_ch']
    elif ('low' in df.columns) & (relative == False):
        reg_val = ['lo1','hi1','lo'+str(lvl),'hi'+str(lvl),'rg','clg','flr','rg_ch']
    elif ('Low' in df.columns) & (relative == True):
        reg_val = ['rL1','rH1','rL'+str(lvl),'rH'+str(lvl),'rrg','rclg','rflr','rrg_ch']
    elif ('low' in df.columns) & (relative == True):
        reg_val = ['rl1','rh1','rl'+str(lvl),'rh'+str(lvl),'rrg','rclg','rflr','rrg_ch']

    try: 
        rt_lo,rt_hi,slo,shi,rg,clg,flr,rg_ch = [reg_val[s] for s in range(len(reg_val))]
    except:
        rt_lo=rt_hi=slo=shi=rg=clg=flr=rg_ch= np.nan
    return rt_lo,rt_hi,slo,shi,rg,clg,flr,rg_ch
# CHAPTER 5: Regime Definition 
#### regime_breakout(df,_h,_l,window) ####
def regime_breakout(df,_h,_l,window):
    #### removed for brevity: check GitHub repo for full code ####
#### turtle_trader(df, _h, _l, slow, fast) ####
#### removed for brevity: check GitHub repo for full code ####
#### regime_sma(df,_c,st,lt) ####
#### removed for brevity: check GitHub repo for full code ####
#### regime_ema(df,_c,st,lt) #### 

地板/天花板方法论计算量更大。因此,它需要有自己的沙盒:

# CHAPTER 5: Regime Definition 
#### hilo_alternation(hilo, dist= None, hurdle= None) ####
def hilo_alternation(hilo, dist= None, hurdle= None):
    i=0    
    while (np.sign(hilo.shift(1)) == np.sign(hilo)).any(): # runs until duplicates are eliminated
        #### removed for brevity: check GitHub repo for full code ####
#### historical_swings(df,_o,_h,_l,_c, dist= None, hurdle= None) #### 
def historical_swings(df,_o,_h,_l,_c, dist= None, hurdle= None):

    reduction = df[[_o,_h,_l,_c]].copy() 
    reduction['avg_px'] = round(reduction[[_h,_l,_c]].mean(axis=1),2)
    highs = reduction['avg_px'].values
    lows = - reduction['avg_px'].values
    reduction_target =  len(reduction) // 100
#     print(reduction_target )
#### removed for brevity: check GitHub repo for full code ####
#### cleanup_latest_swing(df, shi, slo, rt_hi, rt_lo) ####
def cleanup_latest_swing(df, shi, slo, rt_hi, rt_lo): 
    '''
    removes false positives
    '''
    # latest swing
    shi_dt = df.loc[pd.notnull(df[shi]), shi].index[-1]
    s_hi = df.loc[pd.notnull(df[shi]), shi][-1]
    slo_dt = df.loc[pd.notnull(df[slo]), slo].index[-1] 
    s_lo = df.loc[pd.notnull(df[slo]), slo][-1] 
    len_shi_dt = len(df[:shi_dt])
    len_slo_dt = len(df[:slo_dt])

#### removed for brevity: check GitHub repo for full code ####
#### latest_swings(df, shi, slo, rt_hi, rt_lo, _h, _l, _c, _vol) ####
def latest_swing_variables(df, shi, slo, rt_hi, rt_lo, _h, _l, _c):
    '''
    Latest swings dates & values
    '''
    shi_dt = df.loc[pd.notnull(df[shi]), shi].index[-1]
    slo_dt = df.loc[pd.notnull(df[slo]), slo].index[-1]
    s_hi = df.loc[pd.notnull(df[shi]), shi][-1]
    s_lo = df.loc[pd.notnull(df[slo]), slo][-1]

    #### removed for brevity: check GitHub repo for full code ####
#### test_distance(ud, bs, hh_ll, vlty, dist_vol, dist_pct) ####
def test_distance(ud,bs, hh_ll, dist_vol, dist_pct): 

#### removed for brevity: check GitHub repo for full code ####
#### ATR ####
def average_true_range(df, _h, _l, _c, n):
    atr =  (df[_h].combine(df[_c].shift(), max) - df[_l].combine(df[_c].shift(), min)).rolling(window=n).mean()
    return atr
#### ATR ####
#### retest_swing(df, _sign, _rt, hh_ll_dt, hh_ll, _c, _swg) ####
def retest_swing(df, _sign, _rt, hh_ll_dt, hh_ll, _c, _swg):
    rt_sgmt = df.loc[hh_ll_dt:, _rt] 
    #### removed for brevity: check GitHub repo for full code ####
#### retracement_swing(df, _sign, _swg, _c, hh_ll_dt, hh_ll, vlty, retrace_vol, retrace_pct) ####
def retracement_swing(df, _sign, _swg, _c, hh_ll_dt, hh_ll, vlty, retrace_vol, retrace_pct):
    if _sign == 1: #
        retracement = df.loc[hh_ll_dt:, _c].min() - hh_ll
#### removed for brevity: check GitHub repo for full code ####
# CHAPTER 5: Regime Definition 
#### regime_floor_ceiling(df, hi,lo,cl, slo, shi,flr,clg,rg,rg_ch,stdev,threshold) ####
def regime_floor_ceiling(df, _h,_l,_c,slo, shi,flr,clg,rg,rg_ch,stdev,threshold):
#### removed for brevity: check GitHub repo for full code #### 

让我们将这段难以消化的代码分成两个简单的函数,swings()regime()。我们所需要做的就是传递relative参数以获取绝对或相对系列。

def swings(df,rel = False):
    _o,_h,_l,_c = lower_upper_OHLC(df,relative= False)
    if rel == True:
        df = relative(df=df,_o=_o,_h=_h,_l=_l,_c=_c, bm_df=bm_df, bm_col= bm_col, ccy_df=bm_df, 
                            ccy_col=ccy_col, dgt= dgt, start=start, end= end,rebase=True)
        _o,_h,_l,_c = lower_upper_OHLC(df,relative= True)    
        rt_lo,rt_hi,slo,shi,rg,clg,flr,rg_ch = regime_args(df,lvl,relative= True)
    else :
        rt_lo,rt_hi,slo,shi,rg,clg,flr,rg_ch = regime_args(df,lvl,relative= False)
    df= historical_swings(df,_o,_h,_l,_c, dist= None, hurdle= None)
    df= cleanup_latest_swing(df,shi,slo,rt_hi,rt_lo)
    ud, bs, bs_dt, _rt, _swg, hh_ll, hh_ll_dt = latest_swing_variables(df, shi,slo,rt_hi,rt_lo,_h,_l, _c)
    vlty = round(average_true_range(df,_h,_l,_c, n= vlty_n)[hh_ll_dt],dgt)
    dist_vol = d_vol * vlty
    _sign = test_distance(ud,bs, hh_ll, dist_vol, dist_pct)
    df = retest_swing(df, _sign, _rt, hh_ll_dt, hh_ll, _c, _swg)
    retrace_vol = r_vol * vlty
    df = retracement_swing(df, _sign, _swg, _c, hh_ll_dt, hh_ll, vlty, retrace_vol, retrace_pct)

    return df

def regime(df,lvl,rel=False):   
    _o,_h,_l,_c = lower_upper_OHLC(df,relative= rel)    
    rt_lo,rt_hi,slo,shi,rg,clg,flr,rg_ch = regime_args(df,lvl,relative= rel)
    stdev = df[_c].rolling(vlty_n).std(ddof=0)
    df = regime_floor_ceiling(df,_h,_l,_c,slo, shi,flr,clg,rg,rg_ch,stdev,threshold) 

    return df
# df[rg+'_no_fill'] = df[rg]
    return df 

这个筛选也允许对个别股票进行精彩的可视化。 要实现这一点,请运行 graph_regime_combo() 函数:

# CHAPTER 5: Regime Definition 
### Graph Regimes ###
def graph_regime_combo(ticker,df,_c,rg,lo,hi,slo,shi,clg,flr,rg_ch,
                       ma_st,ma_mt,ma_lt,lt_lo,lt_hi,st_lo,st_hi):

    '''
    https://www.color-hex.com/color-names.html
    ticker,df,_c: _c is closing price
    rg: regime -1/0/1 using floor/ceiling method
    lo,hi: small, noisy highs/lows
    slo,shi: swing lows/highs
    clg,flr: ceiling/floor

    rg_ch: regime change base
    ma_st,ma_mt,ma_lt: moving averages ST/MT/LT
    lt_lo,lt_hi: range breakout High/Low LT 
    st_lo,st_hi: range breakout High/Low ST 
    '''
#### removed for brevity: check GitHub repo for full code #### 

下面的两个函数尚未在书中提及。 使用它们,我们需要提取单个股票数据并将其聚合成一个数据框。 yf_droplevel() 函数从 batch_download 中获取多指数数据框的单个代码的 OHLC 列,并创建一个 OHLCV 数据框:

def yf_droplevel(batch_download,ticker):
    df = batch_download.iloc[:, batch_download.columns.get_level_values(1)==ticker]
    df.columns = df.columns.droplevel(1)
    df = df.dropna()
    return df 

此函数插入到一个循环中,该循环将运行 batch_download 的长度。 last_row_dictionary(df) 函数将数据框中的最后一行创建为一个字典:

def last_row_dictionary(df):    
    df_cols = list(df.columns)
    col_dict = {'Symbol':str.upper(ticker),'date':df.index.max().strftime('%Y%m%d')}
    for i, col_name in enumerate(df_cols):
        if pd.isnull(df.iloc[-1,i]):
            try:
                last_index = df[pd.notnull(df.iloc[:,i])].index[-1]
                len_last_index = len(df[:last_index]) - 1
                col_dict.update({col_name + '_dt': last_index.strftime('%Y%m%d')})
                col_dict.update({col_name : df.iloc[len_last_index,i]})
            except:
                col_dict.update({col_name + '_dt':np.nan})
                col_dict.update({col_name : np.nan})
        else:
            col_dict.update({col_name : df.iloc[-1,i]})
    return col_dict 

首先,我们列出列。 其次,我们用代码和日期填充它们,使每一行都能唯一识别。 第三,我们使用 enumerate 迭代返回索引和列名。 如果最后一行包含缺失值,我们将向列名添加 _dt 并查找最后一个出现的索引。 如果最后一行包含值,我们只需将列名添加为键和值。

这个字典将附加一个最后一行字典的列表。 然后,我们将从此列表创建一个数据框。 另一种方法是为每只股票创建一个数据框并进行追加,这种方法效果很好,但稍微耗时一些。

控制面板

在笔记本中散布的变量是错误的来源。 在处理数据之前,所有参数、变量、网站、列表和布尔值都集中在一个地方。 这是您可以在其中调整设置的地方:

website = 'https://en.wikipedia.org/wiki/List_of_S%26P_500_companies'
params = ['2014-12-31', None, 63, 0.05, 0.05, 1.5, 2,5,2.5,3]
start,end,vlty_n,dist_pct,retrace_pct,threshold,dgt,d_vol,r_vol,lvl= [params[h] for h in range(len(params))]
rel_var = ['^GSPC','SP500', 'USD']
bm_ticker, bm_col, ccy_col = [rel_var[h] for h in range(len(rel_var))]
window = 100
st= fast = 50
lt = slow = 200
batch_size = 20
show_batch = True
save_ticker_df = False
save_last_row_df = False
save_regime_df = False
web_df_cols = ['Symbol','Security','GICS Sector','GICS Sub-Industry']
regime_cols = ['rg','rrg',
    'smaC'+str(st)+str(lt),'smar'+str(st)+str(lt), 'boHL'+str(slow),
'borr'+str(slow),'ttH'+str(fast)+'L'+str(slow),'ttr'+str(fast)+'r'+str(slow)]
swings_cols = ['flr_dt','flr','clg_dt', 'clg', 'rg_ch', 
    'Hi'+str(lvl)+'_dt','Hi'+str(lvl),'Lo'+str(lvl)+'_dt','Lo'+str(lvl),
      'rflr_dt', 'rflr', 'rclg_dt', 'rclg', 'rrg_ch',
    'rH'+str(lvl)+'_dt','rH'+str(lvl),'rL'+str(lvl)+'_dt','rL'+str(lvl) ]
symbol_cols = ['Symbol','date','Close']
last_row_df_cols = symbol_cols+['score']+regime_cols+swings_cols 

我们使用的网站是 S&P500 的维基百科网页。 参数如下:

  • start: yfinance 下载开始日期

  • end: yfinance 下载结束日期

  • vlty_n: 持续时间,用于平均真实范围和标准偏差计算

  • dist_pct: test_distance() 函数中的变量

  • retrace_pct: retracement_swing() 函数中的变量

  • threshold: 用于地板/天花板制度定义的波动性单位

  • dgt: round() 函数中的小数位数

  • d_vol: test_distance() 函数中的波动性单位

  • r_vol: retracement_swing() 函数中的变量

  • lvl: 指示应使用哪些摆动水平来计算制度定义—Hi2/Lo2 还是 Hi3/Lo3

rel_var 参数解释如下:

  • bm_ticker: 基准的 Yahoo Finance 代码

  • bm_col: 基准列的名称

  • ccy_col: 货币名称

  • windowstfastltslow: 突破和移动平均值的变量

  • batch_size: 从 yfinance 下载的批量大小

  • show_batch: 布尔值,显示下载的股票

  • save_ticker_df: 布尔值,提供保存个别股票数据框的选项

  • save_last_row_df: 布尔值,提供保存最后一行数据框的选项

  • save_regime_df: 布尔值,提供保存最后一行数据框的选项

  • web_df_cols: 要从原始维基百科数据框中显示的列

  • regime_cols: 重新排序的制度定义列

  • swings_cols: 地板/天花板列

  • symbol_cols:描述性字段,SymboldateClose

  • last_row_df_cols:最后一行数据帧有 50 多列。 这将列数减少到最小。

数据下载和处理

我们将从维基百科下载股票列表开始。 这使用了我们在第四章中看到的强大的pd.read_html方法,Long/Short Methodologies: Absolute and Relative

web_df = pd.read_html(website)[0]
tickers_list =  list(web_df['Symbol'])
tickers_list = tickers_list[:]
print('tickers_list',len(tickers_list))
web_df.head() 

tickers_list可以通过填写tickers_list[:]的括号部分来截断。

现在,这就是发生动作的地方。 引擎室中有几个嵌套的循环。

  1. 批量下载:这是高级别循环。 OHLCV 以一系列批次的多索引数据帧下载。 迭代次数是股票清单长度和批量大小的函数。 505 个组成部分除以 20 的批量大小是 26(最后一批为 6 个股票长)。

  2. 删除级别循环:这将多索引数据帧分解为单个股票 OHLCV 数据帧。 迭代次数等于批量大小。 制度在此级别处理。

  3. 绝对/相对处理:有 2 个通过。 第一遍处理绝对系列的数据。 变量在最后重置为相对系列,然后在第二遍中相应处理。 有一个选项将股票信息保存为 CSV 文件。 最后一行字典在第二遍结束时创建。

接下来,让我们一步一步地进行过程:

  1. 基准下载收盘价和货币调整。 这需要执行一次,因此它放在序列的开头。

  2. 数据帧和列表实例化。

  3. 循环大小:循环tickers_list所需的迭代次数。

  4. 外循环:批量下载:

    1. m,n:沿着batch_list的索引。

    2. batch_download:使用yfinance下载。

    3. 打印批次股票,如果要查看股票名称,则带有布尔值

    4. 下载批量。

    5. try/except:附加失败列表。

  5. 第二个循环:单股票删除级别循环:

    1. 降级到股票级别。

    2. 计算摆动和制度:abs/rel

  6. 第三个循环:绝对/相对系列:

    1. 在绝对系列中处理制度。

    2. 重置变量为相对系列并第二次处理制度。

  7. 提供布尔值以提供save_ticker_df选项。

  8. 使用最后一行值创建字典。

  9. 附加字典行的列表。

  10. 从字典创建数据帧last_row_df

  11. score列:绝对和相对制度方法的横向求和。

  12. last_row_dfweb_df合并。

  13. 布尔值save_regime_df

让我们发布代码,然后进行进一步的解释:

# Appendix: The Engine Room

bm_df = pd.DataFrame()
bm_df[bm_col] = round(yf.download(tickers= bm_ticker,start= start, end = end,interval = "1d",
                 group_by = 'column',auto_adjust = True, prepost = True, 
                 treads = True, proxy = None)['Close'],dgt)
bm_df[ccy_col] = 1
print('benchmark',bm_df.tail(1))

regime_df = pd.DataFrame()
last_row_df = pd.DataFrame()
last_row_list = []
failed = []

loop_size = int(len(tickers_list) // batch_size) + 2
for t in range(1,loop_size): 
    m = (t - 1) * batch_size
    n = t * batch_size
    batch_list = tickers_list[m:n]
    if show_batch:
        print(batch_list,m,n)

    try:
        batch_download = round(yf.download(tickers= batch_list,start= start, end = end, 
                            interval = "1d",group_by = 'column',auto_adjust = True, 
                                  prepost = True, treads = True, proxy = None),dgt)        

        for flat, ticker in enumerate(batch_list):
            df = yf_droplevel(batch_download,ticker)           
            df = swings(df,rel = False)
            df = regime(df,lvl = 3,rel = False)
            df = swings(df,rel = True)
            df = regime(df,lvl = 3,rel= True)            
            _o,_h,_l,_c = lower_upper_OHLC(df,relative = False)

            for a in range(2): 
                df['sma'+str(_c)[:1]+str(st)+str(lt)] = regime_sma(df,_c,st,lt)
                df['bo'+str(_h)[:1]+str(_l)[:1]+ str(slow)] = regime_breakout(df,_h,_l,window)
                df['tt'+str(_h)[:1]+str(fast)+str(_l)[:1]+ str(slow)] = turtle_trader(df, _h, _l, slow, fast)
                _o,_h,_l,_c = lower_upper_OHLC(df,relative = True)                
            try: 
                last_row_list.append(last_row_dictionary(df))
            except:
                failed.append(ticker) 
    except:
        failed.append(ticker)
last_row_df = pd.DataFrame.from_dict(last_row_list)

if save_last_row_df:
    last_row_df.to_csv('last_row_df_'+ str(last_row_df['date'].max())+'.csv', date_format='%Y%m%d')
print('failed',failed)

last_row_df['score']= last_row_df[regime_cols].sum(axis=1)
regime_df = web_df[web_df_cols].set_index('Symbol').join(
    last_row_df[last_row_df_cols].set_index('Symbol'), how='inner').sort_values(by='score')

if save_regime_df:
    regime_df.to_csv('regime_df_'+ str(last_row_df['date'].max())+'.csv', date_format='%Y%m%d') 

last_row_list.append(last_row_dictionary(df)) 在第三次循环结束时发生,一旦每个个股都被完全处理完毕。这个列表会自动更新每个个股和每个批次。三次循环完成后,我们使用 pd.DataFrame.from_dict(last_row_list) 从这个字典列表创建 last_row_df 数据帧。将字典列表创建为数据帧并将其合并的过程比直接将其附加到数据帧稍快一些。score 列是所有区域方法的横向总和。然后按 score 按升序对最后一行数据帧进行排序。有保存日期版本的选项。regime 数据帧是通过将维基百科网络数据帧和最后一行数据帧连接而创建的。请注意,Symbol 列被设置为 index。同样,有保存日期版本的选项。

接下来,让我们用几个热图来可视化市场的情况。

热图

维基百科页面展示了 全球行业分类标准GICS)部门和子行业的结构。我们将按以下方式汇总数据:

  • 部门,用于自上而下的视角

  • 子行业,用于自下而上的视角

  • 最后,部门 子行业,以在每个部门内选择赢家和输家。

我们使用 .groupby() 方法并按 score 排序。然后我们使用 Styler 构造函数 .style.background_gradient() 根据数字绘制市场:

groupby_cols = ['score'] + regime_cols
sort_key = ['GICS Sector']
regime_df.groupby(sort_key)[groupby_cols].mean().sort_values(
    by= 'score').style.background_gradient(
    subset= groupby_cols,cmap= 'RdYlGn').format('{:.1g}') 

热力图覆盖了所有区域方法,包括绝对和相对:

  • score:股票级别所有方法的横向总和。

  • rg:绝对的底部/顶部区域。

  • rrg:相对的底部/顶部区域。

  • smaC50200:移动平均线交叉 ST/LT 绝对。

  • smar50200:移动平均线交叉 ST/LT 相对。

  • bohl200:范围突破(200 天)。

  • ttH50L200:绝对的对于菜鸟的乌龟 50/200(快速/慢速)。

  • ttr50r200:相对的对于菜鸟的乌龟 50/200(快速/慢速)。

让我们看看它是什么样子的:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_Appendix_01.png

图 1:行业水平的区域分数热图

部门热图为市场提供了一个鸟瞰图。高度杠杆的部门,如金融、房地产和科技仍然处于金字塔顶部。与此同时,消费品行业等防御性行业落后。在撰写本文时,这个牛市仍然十分活跃。实际上就是这么简单。

然后我们深入到子行业:

sort_key = ['GICS Sub-Industry']
regime_df.groupby(sort_key)[groupby_cols].mean().sort_values(
    by= 'score').style.background_gradient(
    subset= groupby_cols,cmap= 'RdYlGn').format('{:.1g}') 

这给了我们市场的一个像素化图片,较差表现的子行业位于顶部:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_Appendix_02.png

图 2:区域分数的子行业水平

标普 500 指数是一个广泛而深入的指数。 市场的这种细粒度图像显示了每个子行业目前的状况。 特别关注绝对/相对二元性。 记住,相对表现领先于绝对表现。 这就是您如何捕捉拐点并相应地建立或退出仓位,并等待其他人群的到来的方式。

这个详细的图像是信息与决策的经典例子。 这个热图将使您了解市场的情况。 但是,它的格式不够高效,不能让您根据信息采取行动。

这将我们带到最终的排序,按部门和子行业。

sort_key = ['GICS Sector','GICS Sub-Industry']
regime_df.groupby(sort_key)[groupby_cols].mean().sort_values(
    by= ['GICS Sector','score']).style.background_gradient(
    subset= groupby_cols,cmap= 'RdYlGn').format('{:.1g}') 

这会生成一个热图,其中子行业按其行业内的升序排序。 与此同时,部门按字母顺序分类。

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_Appendix_03.png

图 3:部门和子行业级别的热图

这个最终的热图提供了可行的信息。 子行业按其部门内的升序排名。 这允许在表现不佳者和表现良好者之间进行套利。 在不同部门和时间内重复这个过程,您将顺利地遵循部门轮换的实质。 这就是长/短 2.0 相对方法的本质。

个体流程

一旦筛选完成,您可能希望查看列表中的一些股票。 因此,笔记本的其余部分是关于在个别股票水平上进行数据可视化的。 输入一个股票代码,例如,ticker = 'FMC'

bm_ticker= '^GSPC'
bm_df = pd.DataFrame()
bm_df[bm_col] = round(yf.download(tickers= bm_ticker,start= start, end = end,interval = "1d",
                 group_by = 'column',auto_adjust = True, prepost = True, 
                 treads = True, proxy = None)['Close'],dgt)
bm_df[ccy_col] = 1
ticker = 'FMC'
lvl = 2 # Try different levels to see

df = round(yf.download(tickers= ticker,start= start, end = end,    interval = "1d", group_by = 'column',auto_adjust = True,     prepost = True, treads = True, proxy = None),dgt)

df = swings(df,rel = False)
df = regime(df,lvl = 2,rel = False) # Try different lvl values (1-3) to vary absolute sensitivity
df = swings(df,rel = True) # Try different lvl values (1-3) to vary relative sensitivity
df = regime(df,lvl = 2,rel= True)
_o,_h,_l,_c = lower_upper_OHLC(df,relative = False)

for a in range(2):    
    df['sma'+str(_c)[:1]+str(st)+str(lt)] = regime_sma(df,_c,st,lt)
    df['bo'+str(_h)[:1]+str(_l)[:1]+ str(slow)] = regime_breakout(df,_h,_l,window)
    df['tt'+str(_h)[:1]+str(fast)+str(_l)[:1]+ str(slow)] = turtle_trader(df, _h, _l, slow, fast)
    _o,_h,_l,_c = lower_upper_OHLC(df,relative = True)
df[['Close','rClose']].plot(figsize=(20,5),style=['k','grey'],
                           title = str.upper(ticker)+ ' Relative &                              Absolute') 

这将打印出类似以下图表的内容:

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_Appendix_04.png

图 4:FMC 收盘价的绝对和相对系列

以下部分将数据绘制成三个图表:

# CHAPTER 5: Regime Definition 
plot_abs_cols = ['Close','Hi'+str(lvl), 'clg','flr','rg_ch','rg']
# plot_abs_cols = ['Close','Hi2', 'Lo2','clg','flr','rg_ch','rg']
plot_abs_style = ['k', 'ro', 'go', 'kv', 'k^','b:','b--']
y2_abs = ['rg']
plot_rel_cols = ['rClose','rH'+str(lvl), 'rL'+str(lvl), 'rclg','rflr','rrg_ch','rrg']
# plot_rel_cols = ['rClose','rH2', 'rL2','rclg','rflr','rrg_ch','rrg']
plot_rel_style = ['grey', 'ro', 'go', 'kv', 'k^','m:','m--']
y2_rel = ['rrg']
df[plot_abs_cols].plot(secondary_y= y2_abs,figsize=(20,8),
            title = str.upper(ticker)+ ' Absolute',# grid=True,
            style=plot_abs_style)
df[plot_rel_cols].plot(secondary_y=y2_rel,figsize=(20,8),
            title = str.upper(ticker)+ ' Relative',# grid=True,
            style=plot_rel_style)
df[plot_rel_cols + plot_abs_cols].plot(secondary_y=y2_rel + y2_abs,    figsize=(20,8), title = str.upper(ticker)+ ' Relative & Absolute',    # grid=True,
    style=plot_rel_style + plot_abs_style) 

这将创建三个图表:绝对、相对和绝对与相对结合。 红/绿色的点是波动。 水平线是制度变革波动。 请注意,以下图表是在绝对和相对系列的 lvl 设置为 2 的情况下生成的。 您可以通过在绝对系列的 df = regime(df,lvl = 2,rel = False) 行和相对系列的 df = regime(df,lvl = 2,rel = True) 行中更改此值来增加或减少任一系列的灵敏度。

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_Appendix_05.png

图 5:绝对图表,显示有虚线的底部/顶部制度

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_Appendix_06.png

图 6:相对图表,显示有虚线的底部/顶部制度。 红/绿色的点是波动

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_Appendix_07.png

图 7:具有底部/顶部制度的绝对和相对图表

下一段代码块使用 graph_regime_combo() 进行美观的视觉呈现。 首先,这是绝对系列,然后是相对系列:

# CHAPTER 5: Regime Definition 
ohlc = ['Open','High','Low','Close']
_o,_h,_l,_c = [ohlc[h] for h in range(len(ohlc))]
# ma_st = ma_mt = ma_lt = lt_lo = lt_hi = st_lo = st_hi = 0
mav = [fast, slow, 200]
ma_st,ma_mt,ma_lt = [df[_c].rolling(mav[t]).mean() for t in range(len(mav))]
bo = [fast, slow]
st_lo,lt_lo = [df[_l].rolling(bo[t]).min() for t in range(len(bo))]
st_hi,lt_hi = [df[_h].rolling(bo[t]).max() for t in range(len(bo))]
rg_combo = ['Close','rg','Lo3','Hi3','Lo3','Hi3','clg','flr','rg_ch']
_c,rg,lo,hi,slo,shi,clg,flr,rg_ch =[rg_combo[r] for r in range(len(rg_combo)) ]
graph_regime_combo(ticker,df,_c,rg,lo,hi,slo,shi,clg,flr,rg_ch,ma_st,ma_mt,ma_lt,lt_lo,lt_hi,st_lo,st_hi)
rohlc = ['rOpen','rHigh','rLow','rClose']
_o,_h,_l,_c = [rohlc[h] for h in range(len(rohlc)) ]

mav = [fast, slow, 200]
ma_st,ma_mt,ma_lt = [df[_c].rolling(mav[t]).mean() for t in range(len(mav))]
bo = [fast, slow]
st_lo,lt_lo = [df[_l].rolling(bo[t]).min() for t in range(len(bo))]
st_hi,lt_hi = [df[_h].rolling(bo[t]).max() for t in range(len(bo))]
rrg_combo = ['rClose','rrg','rL3','rH3','rL3','rH3','rclg','rflr','rrg_ch']
_c,rg,lo,hi,slo,shi,clg,flr,rg_ch =[rrg_combo[r] for r in range(len(rrg_combo)) ]
graph_regime_combo(ticker,df,_c,rg,lo,hi,slo,shi,clg,flr,rg_ch,ma_st,ma_mt,ma_lt,lt_lo,lt_hi,st_lo,st_hi) 

这将生成以下两个图表。

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_Appendix_08.png

图 8:具有多种制度方法的绝对图表

https://github.com/OpenDocCN/freelearn-quant-zh/raw/master/docs/algo-stsl-py/img/B17704_Appendix_09.png

图 9:具有多种制度方法的相对图表

这是本书的结尾,也是你踏上空头交易广阔荒野的征程的开始。你现在拥有一张地图,将帮助你在空头市场的惊涛骇浪中航行。

作为告别的话,我们无情的行业忠实地遵循一条法则:供需关系。在长/空头交易中,长期股票选择者充足,而熟练的空头卖家却严重短缺。

当市场走低时,那些挺身而出的人,才会脱颖而出。

内容概要:本文档主要介绍了Intel Edge Peak (EP) 解决方案,涵盖从零到边缘高峰的软件配置和服务管理。EP解决方案旨在简化客户的入门门槛,提供一系列工具和服务,包括Edge Software Provisioner (ESP),用于构建和缓存操作系统镜像和软件栈;Device Management System (DMS),用于远程集群或本地集群管理;以及Autonomous Clustering for the Edge (ACE),用于自动化边缘集群的创建和管理。文档详细描述了从软件发布、设备制造、运输、安装到最终设备激活的全过程,并强调了在不同应用场景(如公共设施、工业厂房、海上油井和移动医院)下的具体部署步骤和技术细节。此外,文档还探讨了安全设备注册(FDO)、集群管理、密钥轮换和备份等关键操作。 适合人群:具备一定IT基础设施和边缘计算基础知识的技术人员,特别是负责边缘设备部署和管理的系统集成商和运维人员。 使用场景及目标:①帮助系统集成商和客户简化边缘设备的初始配置和后续管理;②确保设备在不同网络环境下的安全启动和注册;③支持大规模边缘设备的自动化集群管理和应用程序编排;④提供详细的密钥管理和集群维护指南,确保系统的长期稳定运行。 其他说明:本文档是详细描述了Edge Peak技术及其应用案例。文档不仅提供了技术实现的指导,还涵盖了策略配置、安全性和扩展性的考虑,帮助用户全面理解和实施Intel的边缘计算解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值