股票学习网

股票行情网,股票入门,和讯股票,股票查询 - - 股票学习网!

德马克指标好用吗(德马克指标源码)

2023-05-08 17:39分类:看盘技巧 阅读:

 

首先,化验指标关系到什么是煤,煤是主要由植物遗体经煤化作用转化的富含碳的固体有机可燃沉积岩,区别煤和矸石的指标就是灰分,煤炭的灰分产率必须小于或等于50%(干基质量分数)。

一、工业分析和全硫(全部煤炭)

工业分析和全硫适用于全部煤炭的分析。工业分析又分为挥发分,水分,灰分和固定碳。

1.1 工业分析

(1)挥发分(V%),它是煤样在规定条件下隔绝空气加热,并进行水分校正后的质量损失。挥发分在运输过程中是唯一不变的指标,属于煤炭的身份ID挥发分常用的是Vdaf%(干燥无灰基)。

(2)水分(M%),指单位重量的煤中水的含量。工业分析中测定的水分有原煤样的全水分Mt (有时等于接受煤样的水分Mar )和分析煤样水分Mad(计算煤挥发分时用)两种。这里的全水分(Mt)是煤的外在水分和内在水分的总和。其中外在水分(Mf):在一定条件下煤样与周围空气湿度达到平衡时所失去的水分。内在水分(Minh):在一定条件下煤样与周围空气湿度达到平衡时所保持的水分。 水分常用的是Mad%(空气干燥基)和Mt(全水分)。

(3)灰分(A%),它指煤样在规定条件下完全燃烧后所得的残留物。灰分常用的是Ad%(干燥基)。

(4)固定碳(FC%),固定碳是计算出来的。测定煤的挥发分时,剩下的不挥发物称为焦渣。焦渣减去灰分称为固定碳。它是煤中不挥发的固体可燃物,可以用计算方法算出。

1.2 各种基及换算

(1)基是表示化验结果是以什么状态下的煤样为基础而得出的。煤质分析中常见的“基”有收到基、空气干燥基、干燥基、干燥无灰基、干燥无矿物质基。ar:收到基是以收到状态的煤为基准,符号为ar。ad:空气干燥基,也就是分析基是以与空气湿度达到平衡状态的煤为基准,符号为ad。d:干燥基是以假想无水状态的煤为基准,符号为d。daf:干燥无灰基是以假想无水无灰状态的煤为基准,符号为daf。

dmmf:干燥无矿物质基是以假想无水、无矿物质状态的煤为基准,符号为dmmf。

maf:恒湿无灰基,煤样的这种状态也是换算出来的。恒湿的含义是指温度在30c,相对湿度为 96%时测得煤样的水分(或叫最高内在水分)。

(2)常见3种基下的指标之和都是100%

Vad FCad Aad Mad=100 (空气干燥基)Vd FCd Ad=100 (干燥基)

Vdaf FCdaf=100 (干燥无灰基)

上述各种基可以互相换算,

(3)干基d的换算:

Xd=100*Xad/(100-Mad)%

式中: Xad——分析基的化验结果(注意只有挥发分,灰分,固定碳3种指标); Mad——分析基水分; Xd——换算干燥基的化验结果。为什么要除以(100-Mad)?因为干燥基d是没有水分的,所以对于空气干燥基里的水分必须减掉,而原先的挥发分,灰分,固定碳这3个指标在干燥基d下所占的比例纸盒就得是100%,这就得用ad下的数值去除以ad下这3个指标的和,这样总和才回是100%。简单说,就是没什么就减掉什么。

(4)干燥无灰基daf的换算:

Xdaf=100Xad/(100-Mad-Aad)%

式中: Aad——分析基灰分; Xdaf——换算为干燥无灰基的化验结果。同样,因为干燥无灰基下无灰分,无水分,所以得用100减去水分和灰分。

(5)收到基的换算:Xar=(100-Mar)/(100-Mad)%

式中: Mar——收到基水分; Xar——换算为收到基的化验结果。

 

1.3 全硫

所有的煤中都含有数量不等的硫。煤中硫通常可分为有机硫和无机硫两大类,分析中一般测定的是全硫(有机硫和无机硫之和,常用S t,d ;S t,ad ),有机硫是以全硫减去无机硫而得。无机硫可以洗掉,有机硫不行。

1.4 元素分析

元素分析就是煤中碳、氢、氧、氮、硫五个煤炭分析项目的分析。

二、粘结指数,胶质层厚度(焦煤)

2.1 粘结指数GR.I

炼焦用煤的重要指标之一就是粘结指数GR.I。它是在规定条件下以烟煤在加热后粘结专用无烟煤的能力,即反映烟煤粘结其本身或外加惰性物的能力。煤的粘结性是煤形成焦炭的前提和必要条件,炼焦煤中肥煤的粘结性最好。粘结指数和挥发分构成了煤炭编号的两个要素。

此外,煤炭的焦渣特性与粘结指数正相关。

2.2 胶质层厚度Y(mm)

胶质层最大厚度Y(mm) ,胶质层指数测定中,利用探针测出的胶质体上下层面差是胶质层最大厚度Y,另还能得到最终收缩度X,体积曲线。

2.3 结焦性

煤的结焦性是指煤在工业焦炉或模拟工业焦炉的炼焦条件下,结成具有一定块度和强度焦炭的能力。炼焦煤中焦煤的结焦性最好(不是最大)。

2.4 岩相分析。

煤炭的岩相分析是比较新的技术,可以将供应商配好的煤通过光谱分析直接判断出是由哪些煤种,各自多少比例而配煤得到的。煤的镜质组反射率直方图是鉴定煤类别、配煤炼焦的有效手段,该图由光谱分析可以得出。由于煤炭仅仅通过工业分析,粘结指数是无法表征煤炭的本来性质的,混煤就经常会在这些普通分析中蒙混过关,但是真正炼焦时其结焦性又往往较差。

2.5 热强度

焦炭热强度是反映焦炭热态性能的一项机械强度指标。它表征焦炭在使用环境的温度和气氛下,同时经受热应力和机械力时,抵抗破碎和磨损的能力。热强度不够的话,甚至会导致高炉塌炉。

三、发热量,煤灰熔融性,可磨性(电煤)

3.1 发热量(MJ/kg,大卡)

煤的发热量是煤质分析中的一个重要指标。主要是燃烧设备热工计算的基础:燃煤工艺过程中的热平衡、耗煤量及热效率等的计算都是以所用煤的热值为依据的。在设计电厂锅炉和蒸发量大的各种高压锅炉时,也需要根据煤的平均低位发热量来考虑锅炉种类、型号、燃烧方式。在国际煤炭分类标准中,挥发分大于33%的烟煤以及褐煤,发热量(恒湿无灰基)是一项确定类别的指标。工业和商务上多依收到基恒容低位发热量(Qnet,v,ar)进行计算和设计,因为这个数据最近实际燃烧能量消耗情况。低位发热量也即由高位发热量减去水的汽化热后得到的发热量。尤其是签订合同时,要注意是高位还是低位发热量,因为要减去水分,经验公式是1个水分就会掉60-70大卡的发热量。发热量国标的单位是MJ/kg,行业上常用的则是大卡,MJ/kg和大卡的划算:

1大卡=1000卡=1000*4.18焦耳=4180焦耳(1卡是4.18焦耳,就是让将1克水在1大气压下提升1℃所需要的热量)1MJ/kg=1百万焦耳/kg=1000000焦耳/kg=1000*1000/4180大卡/kg=239.234大卡/kg

举个例子,20.91MJ/公斤=5002大卡/公斤 ,这样的煤就是5000大卡发热量的。

3.2 煤灰熔融性(ST,摄氏度)

当在规定条件下加热煤灰试样时,随着温度的升高、煤灰试样会从局部熔融到全部熔融并伴随产生一定的特征物理状态——变形、软化、半球和流动。人们以这4个特征物理状态相对应的温度来表征煤灰熔融性。分别是变形温度(DT)、软化温度(ST)、半球温度(HT)和流动温度(FT)。灰熔融性是动力用煤和气化用煤的重要指标,主要用于固态排渣锅炉和气化炉的设计,并能指导实际生产操作。在固态排渣锅炉和气化炉中,原料煤的灰熔融温度越高越好,以免造成炉内结渣而难以排出。

3.3 可磨性(哈氏可磨性指数,符号:HGI)

煤的可磨性表示煤被磨碎的难易程度,煤的可磨性指数越大,则这种煤越易磨碎,反之则难。作为动力用煤,如电力、水泥厂等在设计与改进制粉系统并估计磨煤机的产量和耗电量时,可磨性指数是一个很重要的指标,一般规律是褐煤、年老无烟煤最低40~,长焰煤、不粘煤次之50 ~ 80,烟煤较高,其中焦煤最高,可达100以上也就是说煤化程度中等的可磨性最高,过高过低的煤都不易磨碎。

3.4 烟煤奥亚膨胀度b%和透光率PM%

奥亚膨胀b%计试验是直接测定烟煤粘结性的一种重要方法,它在区分中等以上粘结煤,特别是强粘结性煤方面具有优势。可测定软化点t1、始膨点t2、固化点t3,煤的最大收缩度a%、最大膨胀度b%,所以可反映胶质体的质和量。低价煤的透光率PM%就是指褐煤、长焰煤等低煤化度煤在99.5±5 ℃的温度下用稀的硝酸和磷酸 混合酸水溶液处理后所得有色溶液对一定波长的光的透过率。

南方财经全媒体集团 记者 孙诗卉 实习生 王亚琪 上海报道,本期南财保险测评选取市场上采用月交模式的传统终身成人重疾险产品——太平福禄御禧,使用“南财重疾险TDW(Ten Dimensions Weighted)模型”进行评测。

太平福禄御禧测评综合得分8.75,在南财重疾险TDW测评成人险总榜单中名列前茅,保障力度得分 29.9 分,星级指数✮✮✮✮+,目前榜单共收录重疾险产品 84 款。

图:南财TDW重疾险测评(成人版)总榜单(部分)

以下是具体的测评结果:

基本保障全面 可选责任满足多样需求

太平福禄御禧终身重大疾病保险,综合得分8.75,保障力度得分29.9,星级指数:✮✮✮✮+。

这款产品投保范围是28日到65周岁,自合同生效日零时起 90 日为等待期。

在保障责任方面,这款产品的保险责任分为基本部分和可选部分。被保人可以单独投保基本部分,也可以在投保基本部分的基础上同时投保一项或多项可选部分,但不能单独投保可选部分

基本保障方面,这款产品保障范围有120种重疾,按照100%基本保额赔付1次;20种中症疾病,按照50%基本保额赔付1次;40种轻症疾病最多可以赔付6次,每次按照25%基本保额赔付,每种轻症疾病仅限赔付一次

其他必选责任为“轻中重症保费豁免”和“身故”。其中交费期内,如被保人初次确诊罹患产品约定的轻症、中症后可豁免后续未交保费,较为常规;身故责任方面,如被保险人因意外伤害事故或等待期后因意外伤害事故以外的原因导致身故,未满18周岁,按已交保险费赔付;已满18周岁,按已交保险费、基本保额、现金价值三项金额中的较大者赔付。

除以上必选责任外,这款产品还有可选责任“定期重大疾病保险金”和“多次给付重大疾病保险金”。

定期重大疾病保险金方面,被保人未满71周岁时,初次确诊罹患产品约定的重疾,按照50%基本保额额外赔付;多次给付重大疾病保险金方面,120种重疾被分为6组,每组限赔付1次,除重大疾病保险金外,总计可再额外多赔付5次,每次赔付100%基本保额,每次间隔期为1年。

根据太平人寿最新披露的2022年第三季度偿付能力报告显示,太平人寿当前的风险评级为A级。

保费方面,以20岁女性,30万基本保额,20年交和所有可选责任中多次给付重大疾病保险金来计算保费,最终价格为6450元每年。

值得注意的是,目前太平福禄御禧创新开通了月交保费服务。

购买保险产品的缴费方式有多种,一般分为月交、年交、趸交,即每月缴费一次、每年缴费一次和一次性缴清。一般而言,月交、年交这种分期付款的方式适用于长期寿险、百万医疗险等保额较高的保险产品,趸交则适合期限短于1年的各种财产险和人寿险产品。

目前市面上已有的重疾险产品多为年交产品,即一年缴费一次,往期南财重疾险TDW测评时也均以年缴金额为测评标准。

月交重疾险的出现丰富了消费者的选择,也使消费者的保障杠杆更高。站在消费者角度,月交在一定程度上可以解决重疾健康保障需求增加和经济不确定性带来的支付压力矛盾,平滑客户交费,减轻消费者的压力。

南财保险测评栏目介绍:

“南财重疾险TDW(Ten Dimensions Weighted)模型”主要选取重疾险十个维度的数十个指标,以综合评分作为因变量,选取多因素作为自变量,运用多元加权分析得出结果。

其中,主要选取的维度包括基础保障、特色保障、投保年龄、身故责任、保险公司风险评级、保费、增值服务等。

保障力度作为消费者最为关心的维度之一,在该模型中配比权重最大,约为45%;保费次之,约为20%。

目前,南财保险测评已有综合评分、保障力度评分以及保险星级指数三项指标,共同构建南财保险三位一体的指标评价体系。

其中,综合评分满分10分,由“南财重疾险TDW模型”测评得出,主要选取的维度包括基础保障、特色保障、投保年龄、身故责任、保险公司风险评级、保费、增值服务等十个维度。运用十个维度的数十个指标得出每个维度的评分并赋予不同权重,得出综合评分,该评分综合各维度数据,是一项全面标准评分。

保障力度评分满分40分,是“南财重疾险TDW模型”中涉及保障力度的基础保障、特色保障、可选责任等维度,选取一定的权重,得出产品保障力度评分。保障力度作为消费者最为关心的维度,与综合得分具有同样的重要性。

此外,星级指数体系以 5 星为满分,以“南财重疾险TDW模型”综合评分9 分以上为必要不充分条件。4 星及 4 星半以8 分以上为必要条件,但若某一维度有重大缺陷,“偏科”现象严重,可能予以适当降级。3 星及 3 星半需要至少 7 分以上,为中等评级,可能说明产品有某项缺陷或整体表现平平无亮点。3 星以下级别,可能说明产品存在已知缺陷且对消费者影响明显。

更多内容请下载21财经APP

当前全球股市都在实行T+0制度,而唯独A股市场还在执行T+1交易制度,这让很多散户感到不满。他们现在强烈呼吁监管层,要松绑股指期货可以,但股指期货也必须要实行T+1制度,否则机构既可以有做空工具,又有T+0制度助力,那股民在股市中岂不是永远成为等待收割的韭菜了。

所谓T+1交易制度简单说就是,今天你买入的股票,今天无法卖出,必须在明天才能选择卖出。一直以来,A股实行T+1交易制度,主要是监管层为了保护股民的利益,防止市场出现过度投机化。但实际上,T+1规则并没有起到保护散户的效果,反而成为股民亏损的主要原因。

那主力都是怎么利用T+1规则来坑害股民的?

首先,这次股指期货松绑之后,意味着机构投资者有了对冲风险的工具,而且股指期货是T+0交易制度,以后即使股市下跌,机构投资者都能够做空获利。此外,在熊市的底部,机构投资者可以通过股指期货一边做空砸盘,一边在二级市场上吸收筹价筹码。最终亏损的还是广大股民。

再者,对于机构投资者来说,其拥有庞大的资金,即使在个股操作上,机构投资者完全可以摆脱T+1的限制,每天都可以不断买进卖出股票进行洗盘,甚至于机构可以利用对筹码的打压、拉升和出货,并且快速封住涨停板,完全不用受T+1的影响,而股民只能是次日追涨或割肉,成为了被机构投资者宰割的韭菜。

最后,T+1交易制度再配上涨停板制度,让很多股民成由轻微亏损变成了巨大亏损。假设股民老张在某天早上买了某只次新股,下午该股票突然暴出大量负面新闻。那么,股民老张即使想当天处理掉该股票,也是不可能的事情。

而往往是到第二天,被暴雷股票就直奔跌停板,而在大量跌停的封单中,股民就算想割肉也很难。而机构投资者则可利用场内的VIP通道优先交易,提前离场,甚至还可转手做空。而股民受这种规则的影响当天明明知道形势不妙,也不能及时脱身,次日又跑不掉,很容易出现无法弥补的损失。

有人说炒股就如做生意,你首先要选择自己熟悉的行业,了解它,用心做规划,然后看准时机,果断出手。因此,做T+0交易前,要做好规划,分析股性,然后静候时机。如果贸然操作,由于这种交易本身的风险较T+1交易风险高,所以引火烧身的可能性更高。

从目前市场情况来看,尤其是科创板即将推出,涨跌幅限制为20%,监管层对开始有所松动,但T+1交易制度并没有讨论,所以从这方面来看,A股市场制度改革道路还是任重而道远,股民散户在交易中还是要谨慎小心,诸如一些题材股根本不是中小股民所能玩的,如果选择坚决不参与,那么被坑的概率就会少一些。

总的来说T+1只是一种外力手段,并不能从根本上改变A股市场的投机力量。特别是在特殊情况下,T+1对市场还会造成伤害。比如在市场极其低迷甚至恐惶的情况下,T+1会导致市场缺少流动性,甚至达到崩盘的地步。

与T+1模式相比,T+0模式具有的特点,如图所示。

由此可知,T+0模式是活跃股票市场的有效手段,一笔资金可以在一个交易日内反复购买和卖出,多次交易,能在不增加股票市场资金总量的前提下,活跃股票市场,提高市场的流通性、交易量和活跃度,交易次数增多,使得佣金、过户费、印花税也随之增长,而投资者也能获得更多的短线交易机会,获利可能增高,提高盈利水平,因此,对政府、券商、投资者来说,T+0模式能够实现“多赢”。T+0模式对股票投资清淡的弱市格局能起到促进、刺激作用。

不同的交易制度对股市市场的影响是不同的,T+1模式虽然在某种程度上限制了股票市场的流通性、活跃度,但其保证了股票市场的稳定,对过度投机有一定的抑制作用,保护了广大投资者的利益。T+0模式虽好,也有其弊端,如造成虚假成交量,致使投资者受到迷惑,如果投资者判断失误,更易被套牢等。

如何变“T+1”交易为“T+0”交易

1.当你判断手中股票会涨时

你可以买入与所持个股同等数量的股票,待其涨升到一定高度,并判断调整随时可能发生后,在该股调整当日第二次上冲不能创新高时卖出,然后在一个适当的低点接回。由于在获取当日股票差价利润的同时,持股数前后没有变化,所以这等于是完成了一次盘中“T+0”操作。这种操作方式通过经常性地赚取短线利润,来达到快速盈利的目的。

2.当你判断手中股票会跌时

当你判断手中股票会跌时,那么你就应该利用手中已有的筹码,做“倒‘T+0’”操作,也就是先卖出,等到股价跌到一定程度时,你再考虑买进。这种方法一般用于在中高位出现重大利空的时候。因为,即使是套牢筹码或者是长线看好的品种,在大盘遭遇重大利空的时候,尤其是股指、股价处于中高位的时候,也往往可能会出现中阴线,甚至是长阴线的下跌机会。

这时,投资者可以即时逢高抛出,在股指、股价企稳之后逢低吸纳,如果是同一品种、高卖低买的话,那么就可以吃到“倒差价”,从而达到筹码数量不变、账上现金增加的效果,或者达到总成本不变、账上股票数量增加的目的,因为高位卖出之后,同样的钱在低位时可以买更多数量的同一股票。

“倒‘T+0’”,也叫“拔档子”,一般适用于上升途中的整理、洗盘的过程,不适用于大熊市。大熊市一般要空仓、长时间等待。

做T的前提三要素,是做T前需要重点思考的问题

(1)大盘环境:俗话说牛市持股不动,乱动必死,不适合做T;熊市不能持股太久,不动必死,适合做T。真正牛逼发财的人不是在牛市发财,而是在熊市,利用大盘的波动联动个股震荡,通过做T实现高收益,虽然很多人认为做T可以忽略大盘,但是本大侠认为做T虽然需要轻大盘重个股,但是大盘的环境不能完全忽略,特别是我天朝A股,天天千股跌停都弄得出来,什么事儿都弄得出来,试想千股跌停,再牛逼的做T技巧也是无用。

(2)个股的股性。什么样的股适合做T,什么趋势下适合那种做T方式,这是做T前的准备工作,也是至关重要的环节,正如行军打仗,提前了解战场的地形地理。股性活跃的个股适合做T,那么什么样的股是股性活跃的呢。

到这儿多说几句,股性要想活跃,首先盘子不能太大,必须是小盘股或者中盘股,最好是流通股在1亿股以下的小盘股,傻子都知道四大行、两桶油不可能让你做T挣钱。除了盘子小,还需要从时间上观察起涨跌情况,根据K线观察其日间震荡区间的大小,日内振幅最好是大于5%,否则做起来就没太大意义。

(3)个股趋势。根据多年经验,将个股的趋势用5日均线、60日均线作为界限分为三类,5日均线以上属于短期强势,60日均线以下属于中期弱势,不同的趋势下做T的选择也是不同的。5日、60日均线以上适合正T;5日均线以下,60日均线以上适合反T。

T+0最佳操作时间:

1、早盘9:50-10:10往往是对前一交易日热点个股顺势拉高的阶段,容易产生短期的高点。经验:在这个时间段高抛效果不错。

2、上午盘10:00-10:40是主力入场的时间,此时间段如果热点拉升清晰,大盘无忧。经验:这个时间如果个股出现拉升,且主力数据良好,那对这股可以放心。

3、上午11:10以后的急拉除非市场非常强势,否则不要跟风,容易中套,指数是拉给下午要买票的人看的。

4、14:00-14:30分是盘面最容易发现转向的阶段,很多游资如果看指数14:00后的走势相对稳健时往往会袭某只股票拉涨停。

5、14:30-15:00分,弱势行情短线诱多往往在这个时间段产生,当然强势行情不存在诱多,而是进一步拔高吸引人气。

“T+0”选股思路:

1、选取走势活跃的股票

“T+0”选股的首要条件是股票的盘面要交投积极,个股走势要活跃。有些股票被庄家控盘,或者人气极度低迷,这样的股票适合中长线投资者参与,“T+0”短线还是回避为好。这类股票因为交投清淡,交易分歧大,在盘面上常会出现买一价格和卖一价格相差较远,一分钟才成交两三次的情况。“T+0”的投资者进去了,不憋疯了才怪。而交投活跃的股票比较容易进出,像一些政策利好的概念股、题材股,都非常适合“T+0”的操作,获利程度甚至比全仓持股还要高。

2、选取单日振幅较大的股票

“T+0”选股另一个关键点是要求股票前期单日振幅较大。一般来说,一只股票的单日振幅至少要在4%以上才有“T+0”的操作价值。因为,对于一只股票一天中你能买在最低点和卖在最高点的可能性并不大。如果振幅低于4%,你除去买在次低和卖在次高的头尾,外加一次买卖周期的双向手续费和印花税,那你可能就根本赚不到差价。

3、选取股价波动有规律的股票

“T+0”选取的股票,股价的波动一定要有规律。有的投资者自己接触过很多股票,虽然成交也非常活跃,但主力操盘的手法太怪异,走势总是出乎意料,最后只好放弃,而有些股票主力做盘有明显的习惯倾向,在掌握这一倾向后,做“T+0”就比较容易成功。

史上最典型的分时图T+0实战技巧

在分时图T+0交易策略中,我们高抛低吸选择买卖点位非常重要。而在采取T+0交易策略前,我们需要关注日K线图中价格走势。通常,如果价格在日K线图中表现强势,并且分时图中股价波动空间较大,我们就可以完成T+0交易过程。

一、分时图T+0的形态特征

1、分时图股价波动空间较大:分时图中价格波动空间较大,我们有更低的价位建仓,可以在价格涨幅较大的时候高抛低吸。可以说,足够的价格波动空间是我们完成T+0交易策略的重要条件。

2、 日K线价格表现强势:日K线图中价格处于回升趋势,虽然股价出现了回调情况,但是价格表现依然强势,这是我们确认分时图中可以采取T+0交易的条件。如图所示:

二、分时图T+0的操作要领

1、 T+0策略实现:上午盘卖掉股票以后,午后我们可以半仓资金建仓该股,这样就完成了T+0交易过程。可以说,该股提供的交易机会还是比较典型的,这使得我们可以完成超短线交易过程。

2、从分时图交易买卖信号看:分时图中股价高开以后震荡回落,该股上午盘出现2%以上的涨幅,午后股价跌幅超过3%,整体波动空间达5%以上,我们可以顺利完成高抛和低吸的T+0交易过程。

3、从日K线价格表现看:该股明显处于回升趋势线上方,价格走势较强,即便出现回调走势,我们依然认为该股适合T+0交易策略。

分时图中价格震荡空间越大,我们高抛低吸的价格空间就越大,期间获利空间也会越大。可以说,分时图中我们可以在高强度价格波动中提高超短线交易的盈利,逐步扩大收益空间。

三、开盘买收盘卖T+0形态

分时图中,如果股价开盘即出现下跌的走势,也就是股价低开回落,我们就有机会在开盘后确认低位建仓的交易机会。随着盘中股价的企稳回升,尾盘股价表现强势,价格涨幅较大的时候我们的卖点就出现了。

(一)平淡开盘买形态

分时图中股价开盘波动空间不大,这是价格表现强势的信号。多空双方确认的开盘价格波动不大,说明价格可以延续前期的运行趋势。如果股价处于上行趋势中,那么我们认为股价平淡开盘以后还会继续表现强势。等待股价进一步上涨的时候,我们可以在尾盘确认卖点。

1、形态特征

A. 日K线价格处于回升趋势:价格处于回升趋势中,我们对分时图中的走势更加乐观。特别是如果开盘后股价表现强势,那么我们在价格调整之时确认买点,可以在T+0交易中获得不错的回报。

B. 筹码峰提供强支撑:筹码峰提供了较强支撑,这是股价短线走强的重要看点。股价很难在筹码高位集中的支撑位出现调整走势,那么在分时图中交易机会自然会出现。

C. 开盘后股价表现强势:开盘价波动空间不大,开盘以后股价强势,这是股价能够走强的信号。在日K线图和分时图里我们发现股价都可能走强,那么我们应该采取先买后卖的交易策略完成T+0交易。先买后卖的时候,我们能够在更低的价位买入股票,同时又可以在价格上涨的尾盘减仓获利。如图所示:

2、操作要领

A. 从价格表现来看:由于日K线图中Q位置的筹码规模较大,在该股回升趋势中很难出现明显下跌。分时图中股价开盘后运行稳定,我们在开盘后半小时内可以确认该股当日价格会走强。

B. 从交易机会来看:开盘后股价走势比较活跃,我们确认该股开盘价格波动很小,而开盘后股价已经表现出强势上涨的迹象。确认这一点非常重要,这能使得我们在价格强势横盘的时候抄底买入股票,在尾盘涨停价抛售前一个交易日持有的股票,先买后卖。这样,我们就能在高抛低吸中获得不错的回报。

C. 从买卖效果来看:我们尾盘卖掉股票,可以获利10%。与此同时,当日建仓持股也能够盈利8.5%。这样,T+0交易的总资金收益率将达到9.25%。

在实战当中,我们可以利用价格双向波动的机会确认高抛低吸交易机会。当股价走势较强时,我们可以率先买入股票,在低价持股以后尾盘抛售前一日买入的股票,获得超短线交易带来的回报。

(二)低开开盘买形态

从日K线图来看,如果价格走势平稳,我们则认为股价没有明显低开杀跌的可能。一旦确认分时图中股价低开杀跌,那么我们认为是短线介入的机会。随着建仓的完成,我们在股价低开运行期间可以获得廉价筹码。与此同时,当股价超跌反弹以后,我们可以在回升趋势中获得高收益。

1、形态特征

A. 日K线图中价格回升:日K线图中股价维持回升趋势,分时图中价格回调便是不错的建仓交易机会。分时图中股价调整空间越大,我们获得廉价筹码的机会就越多。调整结束以后,价格上涨时我们自然能够获得收益。

B. .股价低开下跌:分时图中股价低开,提示价格走势较弱。在股价低开以后我们有机会在更低的价位确认买点。如果量能没有放大,那么我们可以等到下午盘中股价超跌以后确认买点,这个时候的持仓成本更低,T+0交易中我们更容易盈利。

C. 价格下方筹码规模庞大:从日K线图来看,如果股价下方筹码规模较大,那么价格短线很难大跌收盘。筹码提供的支撑较强,股价更容易在分时图中回调以后触底回升。作为超短线T+0交易者,我们在这个时候确认买点可以获得较高的回报。如图所示:

2、操作要领

A. 从交易机会来看:低开以后该股上午盘中反弹回升,但是量能有限放大,我们确认股价午后还是会下跌。下午盘中股价杀跌后再创新低,图中跌幅达3%的低位股价横盘时间较长,我们能够将其确认为不错的买点。尾盘股价强势反弹,这与日K线图中价格回升趋势一致,我们在股价涨幅达2%附近确认T+0交易的卖点。

B. 从买卖效果来看:午后我们的建仓成本在股价跌幅达3%的位置,在收盘上涨2%的情况下,当日盈利空间达5%。在尾盘股价涨幅达2%的情况下,考虑到前一个交易日中我们已经盈利2%以上。

C. 从价格表现来看:该股在A位置低开运行,价格走势较弱。我们发现分时图中该股表现不强。考虑到日K线图中股价处于上行趋势,并且图中M和N两个位置的筹码规模都很大,对价格支撑较强,我们认为分时图中低开回调是不错的买点。

(三)高开运行先卖形态

日K线图中,在价格表现强势的情况下,我们可以考虑在股价高位震荡的过程中先卖出股票。因为在T+0交易策略中,我们总会在分时图中完成买和卖的动作,先卖掉股票不仅是适应T+0交易策略,同时也是按照分时图中价格走势操作股票。

1、形态特征

A. 股价高开后强势运行:股价高开后强势运行,如果成交量开始萎缩,那么价格可以出现高开回落的走势。确认高开后股价涨幅较大,我们可以在高位卖掉股票。减仓完成以后,我们可以在分时图的尾盘买入股票,提升盈利空间。

B. 日K线图中价格持续回升:日K线图中价格持续回升,短线回调并不会影响价格上行趋势。分时图中股价高开运行,我们高位卖掉股票以后,自然也能够在尾盘价格低位建仓后继续盈利。

C. 价格下方筹码规模较大:日K线图中,筹码密集分布在价格下方,这是支撑股价上行的重要因素。我们确认这种筹码集中分布的形态有助于我们完成T+0交易策略。特别是在分时图中我们抄底买入股票以后,可以在价格反弹的过程中轻松盈利。如图所示:

2、操作要领

A. 从交易机会来看:股价高开以后高位震荡,该股短线涨幅接近4%。不过图中W位置量能明显萎缩,这是股价难以高位运行的重要信号。因此,我们确认该股开盘一小时内的价格高位已经是盘中高点,我们首先卖掉半仓持有的股票。等待尾盘股价跌幅到位的时候,我们选择价格跌幅达7%以下的底位买入股票获利。

B. 从买卖效果来看:股价涨幅达3%以上减仓,我们获利达3%。同时,尾盘股价杀跌后我们的建仓成本低至价格跌幅达7%的位置。建仓成本较低,有助于在接下来的交易日中获利。

由于日K线图中C位置的筹码呈现出高位多的情况,因此对价格支撑较强。虽然股价在分时图中高开回落,但是继续下跌的空间已经很小。我们在尾盘抄底买入,有望获得高收益。

C.从价格表现来看:日K线图中D位置量能处于放大状态,在价格回升期间显然能够获得C位置筹码峰提供的支撑,此时我们确认股价上行趋势可以延续。分时图中股价高开后缩量运行,提示我们可以采取T+0交易策略先卖后买,以便适应价格高开回落走势。

高抛低吸T+0分时副图指标公式:

V1:=(C*2+H+L)/4*10; V2:=EMA(V1,13)-EMA(V1,34); V3:=EMA(V2,5); V4:=2*(V2-V3)*5.5;

主力撤: IF(V4

主力进: IF(V4>=0,V4,0),COLORFF00FF;

V5:=(HHV(INDEXH,8)-INDEXC)/(HHV(INDEXH,8)-LLV(INDEXL,8))*8;

V6:=EMA(3*V5-2*SMA(V5,18,1),5);

V7:=(INDEXC-LLV(INDEXL,8))/(HHV(INDEXH,8)-LLV(INDEXL,8))*10;

V8:=(INDEXC*2+INDEXH+INDEXL)/4; V9:=EMA(V8,13)-EMA(V8,34);

VA:=EMA(V9,3); VB:=(V9-VA)/2;

大盘资金进场:IF(VB>=0,VB,0),COLORRED;

大盘资金撤走:IF(VB

V11:=3*SMA((C-LLV(L,55))/(HHV(H,55)-LLV(L,55))*100,5,1)-2*SMA(SMA((C-LLV(L,55))/(HHV(H,55)-LLV(L,55))*100,5,1),3,1);

趋势线: EMA(V11,3);

V12:=(趋势线-REF(趋势线,1))/REF(趋势线,1)*100;

准备现金: STICKLINE(趋势线

AA:=(趋势线

DRAWTEXT (AA,20,'准备'),COLORCC9900;

买入股票: STICKLINE(趋势线<=13 AND V12>13,0,16,5,0),COLOR0099FF;

BB:= 趋势线<=13 AND V12>13 AND FILTER((趋势线<=13 AND V12>13),10);

DRAWTEXT (BB,5,'买入'),COLORYELLOW;

卖临界: STICKLINE(趋势线>90 AND 趋势线>REF(趋势线,1),100,95,15,1),COLORFFFF00;

见顶清仓:FILTER(趋势线>90 AND 趋势线

DRAWTEXT( 见顶清仓,90,'逃顶'),COLORYELLOW; CC:=(趋势线>=90 AND V12) AND FILTER((趋势线>=90 AND V12),10);

STICKLINE(大盘资金进场 AND 趋势线<13,0,30,10,0),COLORRED; STICKLINE(大盘资金撤走 AND 趋势线>90,0,30,10,0),COLORGREEN;

STICKLINE(主力进 AND 趋势线<13,0,40,10,0),COLORFF00FF; STICKLINE(主力撤 AND 趋势线>90,0,40,10,0),COLORBLUE;

公式代码复制过来难免造成部分格式错误,如果不能成功导入,可以找我领取源码!想了解更多目前A股阶段的操作技巧及公式代码,或有任何疑惑,可关注公众号越声攻略(yslc688),更多后市操作及股票技术分析方法等你来学习,干货源源不断!

交易心态的正确心态

1、刚开始交易的时候,担心的是这次买进来会跌。

对策:设定止损,每次交易损失达到总资金的一定百分比(例如2%)就撤,坚决执行。

2、有了止损,不用担心跌了,可担心的是不能获利,而且连续止损,止损个七八回,老本也就去了一大块了,只见止损不见盈利是这时最担心的。

对策:提高操作成功率,去寻找波动的临界转向点,即波动最大可能会按当前趋势运行一段时间的一些进入点。

(此为所有技术分析的核心所在,各人的方法都不同,能十发六七中找到临界转向点,已经算是比较成功了。)

3、提高了成功率,不用担心连续止损了,则开始担心赚时赚的少,特别是错过大行情。手续费加上止损的两头差价,止损一次的代价都比较高,所以赢时要是利润不够仍然不足以弥补损失,或者利润微小,无法肯定能否长久获利。

对策:试着在获利时让盈利扩大,如果能抓住大波动,那么小止损也伤不了几根毫发。

4、学会等待,开始成功抓住了一些大波动,不怕赚时赚的少了,但同时也出现了更多的由等待而造成的转赢为亏的交易。这些亏损交易不仅影响心情,而且浪费了时间和精力,对整体交易成效也有明显的负作用。

对策:使用“绝不让盈利转为亏损”的格言,当盈利头寸跌回进入价格附近时平仓。

5、损失的交易少了,但因为打平出局而错过的大行情也多了,综合一比较,似乎还不如将那些错过的大波动用止损去一搏。

对策:放弃“绝不将盈利转为亏损”的格言,改为用止损来搏大波动。进一步研究改善止损点位的摆放,同时找出由盈利转而跌破买入价而仍然可以继续等待的一些条件。

6、具体问题大都解决了,接下来担心的就是自己这套方法到底能管用多大程度,多久?

对策:将系统实施在过去十年的历史图上模拟实验来验证修改,通过以后用系统做50次以上的交易,达到能不受市场干扰稳定执行系统的地步。

7、系统可以稳定执行了,总的交易成果从统计上来说是正的,这时都不担心单一一次交易的成果如何,而是具有在整体上的必胜信心。但仍然有新的问题,担心的就是市场一旦某种性质发生了变化(比如周期变化,主力的操作风格变化)造成股性改变而使系统突然失效。

对策:到此就没有对策了,股市是个永远变化的地方,没有一种方法可以长胜不败,特别是技术分析是一种跟风的操作,唯一能做的就是保持对市场的敬畏心态,随时检查系统的运行,当多次交易不正常的结果出现,确认股票出现新的特殊的变化以后必须重新建立新的策略。

以上仅是建立系统交易方法所面临的技术问题造成的心理障碍,而实际操作中,越贴近股市受到各种各样其他因素的影响就越多,如市场气氛,主力的出入,消息的震荡,行情运行的拉锯,都可能使一个交易者无法承受心理压力而放弃使用经过研究的系统。

因此,如果操作时能够保持一种若即若离的心态,多关注系统而少关注市场,则将有利于心态的稳定。

(以上内容仅供参考,不构成操作建议。如自行操作,注意仓位控制和风险自负。)

声明:本内容由越声攻略提供,不代表投资快报认可其投资观点。

节点选择器:NodeSelectorSlot

NodeSelectorSlot 负责为资源的首次访问创建 DefaultNode,以及维护 Context.curNode 和调用树。NodeSelectorSlot 被放在 ProcessorSlotChain 链表的第一个位置,这是因为后续的 ProcessorSlot 都需要依赖这个 ProcessorSlot。NodeSelectorSlot 源码如下:

public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> { // Context 的 name -> 资源的 DefaultNode private volatile Map<String, DefaultNode> map = new HashMap<>(10); // 入口方法 @Override public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) throws Throwable { // 使用 Context 的名称作为 key 缓存资源的 DefaultNode DefaultNode node = map.get(context.getName()); if (node == null) { synchronized (this) { node = map.get(context.getName()); if (node == null) { // 为资源创建 DefaultNode node = new DefaultNode(resourceWrapper, null); // 替换 map HashMap<String, DefaultNode> cacheMap = new HashMap<>(map.size()); cacheMap.putAll(map); cacheMap.put(context.getName(), node); map = cacheMap; // 绑定调用树 ((DefaultNode) context.getLastNode()).addChild(node); } } } // 替换 Context 的 curNode 为当前 DefaultNode context.setCurNode(node); fireEntry(context, resourceWrapper, node, count, prioritized, args); } // 出口方法什么也不做 @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } }

如源码所示,map 字段是一个非静态字段,意味着每个 NodeSelectorSlot 都有一个 map。由于一个资源对应一个 ProcessorSlotChain,而一个 ProcessorSlotChain 只创建一个 NodeSelectorSlot,并且 map 缓存 DefaultNode 使用的 key 并非资源 ID,而是 Context.name,所以 map 的作用是缓存针对同一资源为不同调用链路入口创建的 DefaultNode。

在 entry 方法中,首先根据 Context.name 从 map 获取当前调用链路入口的资源 DefaultNode,如果资源第一次被访问,也就是资源的 ProcessorSlotChain 第一次被创建,那么这个 map 是空的,就会加锁为资源创建 DefaultNode,如果资源不是首次被访问,但却首次作为当前调用链路(Context)的入口资源,也需要加锁为资源创建一个 DefaultNode。可见,Sentinel 会为同一资源 ID 创建多少个 DefaultNode 取决于有多少个调用链使用其作为入口资源,直白点就是同一资源存在多少个 DefaultNode 取决于 Context.name 有多少种不同取值,这就是为什么说一个资源可能有多个 DefaultNode 的原因。

为什么这么设计呢?

举个例子,对同一支付接口,我们需要使用 Spring MVC 暴露给前端访问,同时也可能会使用 Dubbo 暴露给其它内部服务调用。Sentinel 的 Web MVC 适配器在调用链路入口创建名为“
sentinel_spring_web_context”的 Context,与 Sentinel 的 Dubbo 适配器调用 ContextUtil#enter 方法创建的 Context 名称不同。针对这种情况,我们可以实现只限制 Spring MVC 进来的流量,也就是限制前端发起接口调用的 QPS、并行占用的线程数等。

NodeSelectorSlot#entry 方法最难以理解的就是实现绑定调用树这行代码:

((DefaultNode) context.getLastNode()).addChild(node);

这行代码分两种情况分析更容易理解,我们就以 Sentinel 提供的 Demo 为例进行分析。

一般情况

Sentinel 的 sentinel-demo 模块下提供了多种使用场景的 Demo,我们选择
sentinel-demo-spring-webmvc 这个 Demo 为例,该 Demo 下有一个 hello 接口,其代码如下。

@RestController public class WebMvcTestController { @GetMapping("/hello") public String apiHello() throws BlockException { doBusiness(); return "Hello!"; } }

我们不需要添加任何规则,只是为了调试 Sentinel 的源码。将 demo 启动起来后,在浏览器访问“/hello”接口,在 NodeSelectorSlot#entry 方法的绑定调用树这一行代码下断点,观察此时 Context 的字段信息。正常情况下我们可以看到如下图所示的结果。

从上图中可以看出,此时的 Context.entranceNode 的子节点为空(childList 的大小为 0),并且当前 CtEntry 父、子节点都是 Null(curEntry 字段)。当绑定调用树这一行代码执行完成后,Context 的字段信息如下图所示:

从上图可以看出,NodeSelectorSlot 为当前资源创建的 DefaultNode 被添加到了 Context.entranceNode 的子节点。entranceNode 类型为 EntranceNode,在调用 ContextUtil#enter 方法时创建,在第一次创建名为“
sentinel_spring_web_context”的 Context 时创建,相同名称的 Context 都使用同一个 EntranceNode。并且该 EntranceNode 在创建时会被添加到 Constant.ROOT。

此时,Constant.ROOT、Context.entranceNode、当前访问资源的 DefaultNode 构造成的调用树如下:

ROOT (machine-root) / EntranceNode (context name: sentinel_spring_web_context) / DefaultNoderesource name: GET:/hello

如果我们现在再访问 Demo 的其他接口,例如访问“/err”接口,那么生成的调用树就会变成如下:

ROOT (machine-root) / EntranceNode (context name: sentinel_spring_web_context) / DefaultNoderesource name: GET:/helloDefaultNoderesource name: GET:/err

Context.entranceNode 将会存储 Web 项目的所有资源(接口)的 DefaultNode。

存在多次 SphU#entry 的情况

比如我们在一个服务中添加了 Sentinel 的 Web MVC 适配模块的依赖,也添加了 Sentinel 的 OpenFeign 适配模块的依赖,并且我们使用 OpenFeign 调用内部其他服务的接口,那么就会存在一次调用链路上出现多次调用 SphU#entry 方法的情况。

首先 webmvc 适配器在接收客户端请求时会调用一次 SphU#entry,在处理客户端请求时可能需要使用 OpenFeign 调用其它服务的接口,那么在发起接口调用时,Sentinel 的 OpenFeign 适配器也会调用一次 SphU#entry。

现在我们将 Demo 的 hello 接口修改一下,将 hello 接口调用的 doBusiness 方法也作为资源使用 Sentinel 保护起来,改造后的 hello 接口代码如下:

@RestController public class WebMvcTestController { @GetMapping("/hello") public String apiHello() throws BlockException { ContextUtil.enter("my_context"); Entry entry = null; try { entry = SphU.entry("POST:http://wujiuye.com/hello2", EntryType.OUT); // ==== 这里是被包装的代码 ===== doBusiness(); return "Hello!"; // ==== end =============== } catch (Exception e) { if (!(e instanceof BlockException)) { Tracer.trace(e); } throw e; } finally { if (entry != null) { entry.exit(1); } ContextUtil.exit(); } } }

我们可将 doBusiness 方法看成是远程调用,例如调用第三方的接口,接口名称为“http://wujiuye.com/hello2”,使用 POST 方式调用,那么我们可以使用“
POST:http://wujiuye.com/hello2”作为资源名称,并将流量类型设置为 OUT 类型。上下文名称取名为"my_context"。

现在启动 demo,使用浏览器访问“/hello”接口。当代码执行到 apiHello 方法时,在 NodeSelectorSlot#entry 方法的绑定调用树这一行代码下断点。当绑定调用树这行代码执行完成后,Context 的字段信息如下图所示。

如图所示,Sentinel 并没有创建名称为 my_context 的 Context,还是使用应用接收到请求时创建名为“
sentinel_spring_web_context”的 Context,所以处理浏览器发送过来的请求的“GET:/hello”资源是本次调用链路的入口资源,Sentinel 在调用链路入口处创建 Context 之后不再创建新的 Context。

由于之前并没有为名称为“
POST:http://wujiuye.com/hello2”的资源创建 ProcessorSlotChain,所以 SphU#entry 会为该资源创建一个 ProcessorSlotChain,也就会为该 ProcessorSlotChain 创建一个 NodeSelectorSlot。在执行到 NodeSelectorSlot#entry 方法时,就会为该资源创建一个 DefaultNode,而将该资源的 DefaultNode 绑定到节点树后,该资源的 DefaultNode 就会成为“GET:/hello”资源的 DefaultNode 的子节点,调用树如下。

ROOT (machine-root) / EntranceNode (name: sentinel_spring_web_context) / DefaultNodeGET:/hello) ......... / DefaultNode (POST:/hello2)

此时,当前调用链路上也已经存在两个 CtEntry,这两个 CtEntry 构造一个双向链表,如下图所示。

虽然存在两个 CtEntry,但此时 Context.curEntry 指向第二个 CtEntry,第二个 CtEntry 在 apiHello 方法中调用 SphU#entry 方法时创建,当执行完 doBusiness 方法后,调用当前 CtEntry#exit 方法,由该 CtEntry 将 Context.curEntry 还原为该 CtEntry 的父 CtEntry。这有点像入栈和出栈操作,例如栈帧在 Java 虚拟机栈的入栈和出栈,调用方法时方法的栈帧入栈,方法执行完成栈帧出栈。

NodeSelectorSlot#entry 方法我们还有一行代码没有分析,就是将当前创建的 DefaultNode 设置为 Context 的当前节点,代码如下:

// 替换 Context.curNode 为当前 DefaultNode context.setCurNode(node);

替换 Context.curNode 为当前资源 DefaultNode 这行代码就是将当前创建的 DefaultNode 赋值给当前 CtEntry.curNode。对着上图理解就是,将资源“GET:/hello”的 DefaultNode 赋值给第一个 CtEntry.curNode,将资源“
POST:http://wujiuye.com/hello2”的 DefaultNode 赋值给第二个 CtEntry.curNode。

要理解 Sentinel 构造 CtEntry 双向链表的目的,首先我们需要了解调用 Context#getCurNode 方法获取当前资源的 DefaultNode 可以做什么。

Tracer#tracer 方法用于记录异常。以异常指标数据统计为例,在发生非 Block 异常时,Tracer#tracer 需要从 Context 获取当前资源的 DefaultNode,通知 DefaultNode 记录异常,同时 DefaultNode 也会通知 ClusterNode 记录记录,如下代码所示。

public class DefaultNode extends StatisticNode { ...... @Override public void increaseExceptionQps(int count) { super.increaseExceptionQps(count); this.clusterNode.increaseExceptionQps(count); } }

这个例子虽然简单,但也足以说明 Sentinel 构造 CtEntry 双向链表的目的。

ClusterNode 构造器:ClusterBuilderSlot

ClusterNode 出现的背景

在一个资源的 ProcessorSlotChain 中,NodeSelectorSlot 负责为资源创建 DefaultNode,这个 DefaultNode 仅限同名的 Context 使用。所以一个资源可能会存在多个 DefaultNode,那么想要获取一个资源的总的 QPS 就必须要遍历这些 DefaultNode。为了性能考虑,Sentinel 会为每个资源创建一个全局唯一的 ClusterNode,用于统计资源的全局并行占用线程数、QPS、异常总数等指标数据。

ClusterBuilderSlot

与 NodeSelectorSlot 的职责相似,ClusterBuilderSlot 的职责是为资源创建全局唯一的 ClusterNode,仅在资源第一次被访问时创建。ClusterBuilderSlot 还会将 ClusterNode 赋值给 DefaultNode.clusterNode,由 DefaultNode 持有 ClusterNode,负责管理 ClusterNode 的指标数据统计。这点也是 ClusterBuilderSlot 在 ProcessorSlotChain 链表中必须排在 NodeSelectorSlot 之后的原因,即必须先有 DefaultNode,才能将 ClusterNode 交给 DefaultNode 管理。

ClusterBuilderSlot 的源码比较多,本篇只分析其实现 ProcessorSlot 接口的 entry 和 exit 方法。ClusterBuilderSlot 删减后的源码如下。

public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot<DefaultNode> { // 资源 -> ClusterNode private static volatile Map<ResourceWrapper, ClusterNode> clusterNodeMap = new HashMap<>(); private static final Object lock = new Object(); // 非静态,一个资源对应一个 ProcessorSlotChain,所以一个资源共用一个 ClusterNode private volatile ClusterNode clusterNode = null; @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { if (clusterNode == null) { synchronized (lock) { if (clusterNode == null) { // 创建 ClusterNode clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType()); // 添加到缓存 HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16)); newMap.putAll(clusterNodeMap); newMap.put(node.getId(), clusterNode); clusterNodeMap = newMap; } } } // node 为 NodeSelectorSlot 传递过来的 DefaultNode node.setClusterNode(clusterNode); // 如果 origin 不为空,则为远程创建一个 StatisticNode if (!"".equals(context.getOrigin())) { Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin()); context.getCurEntry().setOriginNode(originNode); } fireEntry(context, resourceWrapper, node, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } }

ClusterBuilderSlot 使用一个 Map 缓存资源的 ClusterNode,并且用一个非静态的字段维护当前资源的 ClusterNode。因为一个资源只会创建一个 ProcessorSlotChain,意味着 ClusterBuilderSlot 也只会创建一个,那么让 ClusterBuilderSlot 持有该资源的 ClusterNode 就可以省去每次都从 Map 中获取的步骤,这当然也是 Sentinel 为性能做出的努力。

ClusterBuilderSlot#entry 方法的 node 参数由前一个 ProcessorSlot 传递过来,也就是 NodeSelectorSlot 传递过来的 DefaultNode。ClusterBuilderSlot 将 ClusterNode 赋值给 DefaultNode.clusterNode,那么后续的 ProcessorSlot 就能从 node 参数中取得 ClusterNode。DefaultNode 与 ClusterNode 的关系如下图所示。

ClusterNode 有一个 Map 类型的字段用来缓存 origin 与 StatisticNode 的映射,代码如下:

public class ClusterNode extends StatisticNode { private final String name; private final int resourceType; private Map<String, StatisticNode> originCountMap = new HashMap<>(); }

如果上游服务在调用当前服务的接口传递 origin 字段过来,例如可在 http 请求头添加“S-user”参数,或者 Dubbo rpc 调用在请求参数列表加上“application”参数,那么 ClusterBuilderSlot 就会为 ClusterNode 创建一个 StatisticNode,用来统计当前资源被远程服务调用的指标数据。

例如,当 origin 表示来源应用的名称时,对应的 StatisticNode 统计的就是针对该调用来源的指标数据,可用来查看哪个服务访问这个接口最频繁,由此可实现按调用来源限流。

ClusterNode#getOrCreateOriginNode 方法源码如下:

public Node getOrCreateOriginNode(String origin) { StatisticNode statisticNode = originCountMap.get(origin); if (statisticNode == null) { try { lock.lock(); statisticNode = originCountMap.get(origin); if (statisticNode == null) { statisticNode = new StatisticNode(); // 这几行代码在 Sentinel 中随处可见 HashMap<String, StatisticNode> newMap = new HashMap<>(originCountMap.size() + 1); newMap.putAll(originCountMap); newMap.put(origin, statisticNode); originCountMap = newMap; } } finally { lock.unlock(); } } return statisticNode; }

为了便于使用,ClusterBuilderSlot 会将调用来源(origin)的 StatisticNode 赋值给
Context.curEntry.originNode,后续的 ProcessorSlot 可调用 Context#getCurEntry#getOriginNode 方法获取该 StatisticNode。这里我们可以得出一个结论,如果我们自定义的 ProcessorSlot 需要用到调用来源的 StatisticNode,那么在构建 ProcessorSlotChain 时,我们必须要将这个自定义 ProcessorSlot 放在 ClusterBuilderSlot 之后。

资源指标数据统计:StatisticSlot

StatisticSlot 才是实现资源各项指标数据统计的 ProcessorSlot,它与 NodeSelectorSlot、ClusterBuilderSlot 组成了资源指标数据统计流水线,分工明确。

首先 NodeSelectorSlot 为资源创建 DefaultNode,将 DefaultNode 向下传递,ClusterBuilderSlot 负责给资源的 DefaultNode 加工,添加 ClusterNode 这个零部件,再将 DefaultNode 向下传递给 StatisticSlot,如下图所示:

StatisticSlot 在统计指标数据之前会先调用后续的 ProcessorSlot,根据后续 ProcessorSlot 判断是否需要拒绝该请求的结果决定记录哪些指标数据,这也是为什么 Sentinel 设计的责任链需要由前一个 ProcessorSlot 在 entry 或者 exit 方法中调用 fireEntry 或者 fireExit 完成调用下一个 ProcessorSlot 的 entry 或 exit 方法,而不是使用 for 循环遍历调用 ProcessorSlot 的原因。每个 ProcessorSlot 都有权决定是先等后续的 ProcessorSlot 执行完成再做自己的事情,还是先完成自己的事情再让后续 ProcessorSlot 执行,与流水线有所区别。

StatisticSlot 源码框架如下:

public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> { @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { try { // Do some checking. fireEntry(context, resourceWrapper, node, count, prioritized, args); // ..... } catch (PriorityWaitException ex) { // ..... } catch (BlockException e) { // .... throw e; } catch (Throwable e) { // ..... throw e; } } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { DefaultNode node = (DefaultNode)context.getCurNode(); // .... fireExit(context, resourceWrapper, count); } }

  • entry:先调用 fireEntry 方法完成调用后续的 ProcessorSlot#entry 方法,根据后续的 ProcessorSlot 是否抛出 BlockException 决定记录哪些指标数据,并将资源并行占用的线程数加 1。
  • exit:若无任何异常,则记录响应成功、请求执行耗时,将资源并行占用的线程数减 1。

entry 方法

第一种情况:当后续的 ProcessorSlot 未抛出任何异常时,表示不需要拒绝该请求,放行当前请求。

当请求可正常通过时,需要将当前资源并行占用的线程数增加 1、当前时间窗口被放行的请求总数加 1,代码如下:

// Request passed, add thread count and pass count. node.increaseThreadNum(); node.addPassRequest(count);

如果调用来源不为空,也将调用来源的 StatisticNode 的当前并行占用线程数加 1、当前时间窗口被放行的请求数加 1,代码如下:

if (context.getCurEntry().getOriginNode() != null) { // Add count for origin node. context.getCurEntry().getOriginNode().increaseThreadNum(); context.getCurEntry().getOriginNode().addPassRequest(count); }

如果流量类型为 IN,则将资源全局唯一的 ClusterNode 的并行占用线程数、当前时间窗口被放行的请求数都增加 1,代码如下:

if (resourceWrapper.getEntryType() == EntryType.IN) { // Add count for global inbound entry node for global statistics. Constants.ENTRY_NODE.increaseThreadNum(); Constants.ENTRY_NODE.addPassRequest(count); }

回调所有
ProcessorSlotEntryCallback#onPass 方法,代码如下:

// Handle pass event with registered entry callback handlers. for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { handler.onPass(context, resourceWrapper, node, count, args); }

可调用
StatisticSlotCallbackRegistry#addEntryCallback 静态方法注册
ProcessorSlotEntryCallback,
ProcessorSlotEntryCallback 接口的定义如下:

public interface ProcessorSlotEntryCallback<T> { void onPass(Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args) throws Exception; void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args); }

  • onPass:该方法在请求被放行时被回调执行。
  • onBlocked:该方法在请求被拒绝时被回调执行。

第二种情况:捕获到类型为 PriorityWaitException 的异常。

这是特殊情况,在需要对请求限流时,只有使用默认流量效果控制器才可能会抛出 PriorityWaitException 异常,这部分内容将在分析 FlowSlot 的实现源码时再作分析。

当捕获到 PriorityWaitException 异常时,说明当前请求已经被休眠了一会了,但请求还是允许通过的,只是不需要为 DefaultNode 记录这个请求的指标数据了,只自增当前资源并行占用的线程数,同时,DefaultNode 也会为 ClusterNode 自增并行占用的线程数。最后也会回调所有
ProcessorSlotEntryCallback#onPass 方法。这部分源码如下。

node.increaseThreadNum(); if (context.getCurEntry().getOriginNode() != null) { // Add count for origin node. context.getCurEntry().getOriginNode().increaseThreadNum(); } if (resourceWrapper.getEntryType() == EntryType.IN) { // Add count for global inbound entry node for global statistics. Constants.ENTRY_NODE.increaseThreadNum(); } // Handle pass event with registered entry callback handlers. for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { handler.onPass(context, resourceWrapper, node, count, args); }

第三种情况:捕获到 BlockException 异常,BlockException 异常只在需要拒绝请求时抛出。

当捕获到 BlockException 异常时,将异常记录到调用链路上下文的当前 Entry(StatisticSlot 的 exit 方法会用到),然后调用 DefaultNode#increaseBlockQps 方法记录当前请求被拒绝,将当前时间窗口的 block qps 这项指标数据的值加 1。如果调用来源不为空,让调用来源的 StatisticsNode 也记录当前请求被拒绝;如果流量类型为 IN,则让用于统计所有资源指标数据的 ClusterNode 也记录当前请求被拒绝。这部分的源码如下:

// Blocked, set block exception to current entry. context.getCurEntry().setError(e); // Add block count. node.increaseBlockQps(count); if (context.getCurEntry().getOriginNode() != null) { context.getCurEntry().getOriginNode().increaseBlockQps(count); } if (resourceWrapper.getEntryType() == EntryType.IN) { // Add count for global inbound entry node for global statistics. Constants.ENTRY_NODE.increaseBlockQps(count); } // Handle block event with registered entry callback handlers. for (ProcessorSlotEntryCallback<DefaultNode> handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { handler.onBlocked(e, context, resourceWrapper, node, count, args); } throw e;

StatisticSlot 捕获 BlockException 异常只是为了收集被拒绝的请求,BlockException 异常还是会往上抛出。抛出异常的目的是为了拦住请求,让入口处能够执行到 catch 代码块完成请求被拒绝后的服务降级处理。

第四种情况:捕获到其它异常。

其它异常并非指业务异常,因为此时业务代码还未执行,而业务代码抛出的异常是通过调用 Tracer#trace 方法记录的。

当捕获到非 BlockException 异常时,除 PriorityWaitException 异常外,其它类型的异常都同样处理。让 DefaultNode 记录当前请求异常,将当前时间窗口的 exception qps 这项指标数据的值加 1。调用来源的 StatisticsNode、用于统计所有资源指标数据的 ClusterNode 也记录下这个异常。这部分源码如下:

// Unexpected error, set error to current entry. context.getCurEntry().setError(e); // This should not happen. node.increaseExceptionQps(count); if (context.getCurEntry().getOriginNode() != null) { context.getCurEntry().getOriginNode().increaseExceptionQps(count); } if (resourceWrapper.getEntryType() == EntryType.IN) { Constants.ENTRY_NODE.increaseExceptionQps(count); } throw e;

exit 方法

exit 方法被调用时,要么请求被拒绝,要么请求被放行并且已经执行完成,所以 exit 方法需要知道当前请求是否正常执行完成,这正是 StatisticSlot 在捕获异常时将异常记录到当前 Entry 的原因,exit 方法中通过 Context 可获取到当前 CtEntry,从当前 CtEntry 可获取 entry 方法中写入的异常。

exit 方法源码如下(有删减):

@Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { DefaultNode node = (DefaultNode)context.getCurNode(); if (context.getCurEntry().getError() == null) { // 计算耗时 long rt = TimeUtil.currentTimeMillis() - context.getCurEntry().getCreateTime(); // 记录执行耗时与成功总数 node.addRtAndSuccess(rt, count); if (context.getCurEntry().getOriginNode() != null) { context.getCurEntry().getOriginNode().addRtAndSuccess(rt, count); } // 自减当前资源占用的线程数 node.decreaseThreadNum(); // origin 不为空 if (context.getCurEntry().getOriginNode() != null) { context.getCurEntry().getOriginNode().decreaseThreadNum(); } // 流量类型为 in 时 if (resourceWrapper.getEntryType() == EntryType.IN) { Constants.ENTRY_NODE.addRtAndSuccess(rt, count); Constants.ENTRY_NODE.decreaseThreadNum(); } } // Handle exit event with registered exit callback handlers. Collection<ProcessorSlotExitCallback> exitCallbacks = StatisticSlotCallbackRegistry.getExitCallbacks(); for (ProcessorSlotExitCallback handler : exitCallbacks) { handler.onExit(context, resourceWrapper, count, args); } fireExit(context, resourceWrapper, count); }

exit 方法中通过 Context 可获取当前资源的 DefaultNode,如果 entry 方法中未出现异常,那么说明请求是正常完成的,在请求正常完成情况下需要记录请求的执行耗时以及响应是否成功,可将当前时间减去调用链路上当前 Entry 的创建时间作为请求的执行耗时。

资源指标数据的记录过程

ClusterNode 才是一个资源全局的指标数据统计节点,但我们并未在 StatisticSlot#entry 方法与 exit 方法中看到其被使用。因为 ClusterNode 被 ClusterBuilderSlot 交给了 DefaultNode 掌管,在 DefaultNode 的相关指标数据收集方法被调用时,ClusterNode 的对应方法也会被调用,如下代码所示:

public class DefaultNode extends StatisticNode { ...... private ClusterNode clusterNode; @Override public void addPassRequest(int count) { super.addPassRequest(count); this.clusterNode.addPassRequest(count); } }

记录某项指标数据指的是:针对当前请求,记录当前请求的某项指标数据,例如请求被放行、请求被拒绝、请求的执行耗时等。

假设当前请求被成功处理,StatisticSlot 会调用 DefaultNode#addRtAndSuccess 方法记录请求处理成功、并且记录处理请求的耗时,DefaultNode 先调用父类的 addRtAndSuccess 方法,然后 DefaultNode 会调用 ClusterNode#addRtAndSuccess 方法。ClusterNode 与 DefaultNode 都是 StatisticNode 的子类,StatisticNode#addRtAndSuccess 方法源码如下:

@Override public void addRtAndSuccess(long rt, int successCount) { // 秒级滑动窗口 rollingCounterInSecond.addSuccess(successCount); rollingCounterInSecond.addRT(rt); // 分钟级的滑动窗口 rollingCounterInMinute.addSuccess(successCount); rollingCounterInMinute.addRT(rt); }

rollingCounterInSecond 是一个秒级的滑动窗口,rollingCounterInMinute 是一个分钟级的滑动窗口,类型为 ArrayMetric。分钟级的滑动窗口一共有 60 个 MetricBucket,每个 MetricBucket 都被 WindowWrap 包装,每个 MetricBucket 统计一秒钟内的各项指标数据,如下图所示:

当调用 rollingCounterInMinute#addSuccess 方法时,由 ArrayMetric 根据当前时间戳获取当前时间窗口的 MetricBucket,再调用 MetricBucket#addSuccess 方法将 success 这项指标的值加上方法参数传递进来的值(一般是 1)。MetricBucket 使用 LongAdder 记录各项指标数据的值。

Sentinel 在 MetricEvent 枚举类中定义了 Sentinel 会收集哪些指标数据,MetricEvent 枚举类的源码如下:

public enum MetricEvent { PASS, BLOCK, EXCEPTION, SUCCESS, RT, OCCUPIED_PASS }

  • pass 指标:请求被放行的总数
  • block:请求被拒绝的总数
  • exception:请求处理异常的总数
  • success:请求被处理成功的总数
  • rt:被处理成功的请求的总耗时
  • occupied_pass:预通过总数(前一个时间窗口使用了当前时间窗口的 passQps)

其它的指标数据都可通过以上这些指标数据计算得出,例如,平均耗时可根据总耗时除以成功总数计算得出。

资源指标数据统计总结

  • 一个调用链路上只会创建一个 Context,在调用链路的入口创建(一个调用链路上第一个被 Sentinel 保护的资源)。
  • 一个 Context 名称只创建一个 EntranceNode,也是在调用链路的入口创建,调用 Context#enter 方法时创建。
  • 与方法调用的入栈出栈一样,一个线程上调用多少次 SphU#entry 方法就会创建多少个 CtEntry,前一个 CtEntry 作为当前 CtEntry 的父节点,当前 CtEntry 作为前一个 CtEntry 的子节点,构成一个双向链表。Context.curEntry 保存的是当前的 CtEntry,在调用当前的 CtEntry#exit 方法时,由当前 CtEntry 将 Context.curEntry 还原为当前 CtEntry 的父节点 CtEntry。
  • 一个调用链路上,如果多次调用 SphU#entry 方法传入的资源名称都相同,那么只会创建一个 DefaultNode,如果资源名称不同,会为每个资源名称创建一个 DefaultNode,当前 DefaultNode 会作为调用链路上的前一个 DefaultNode 的子节点。
  • 一个资源有且只有一个 ProcessorSlotChain,一个资源有且只有一个 ClusterNode。
  • 一个 ClusterNode 负责统计一个资源的全局指标数据。
  • StatisticSlot 负责记录请求是否被放行、请求是否被拒绝、请求是否处理异常、处理请求的耗时等指标数据,在 StatisticSlot 调用 DefaultNode 用于记录某项指标数据的方法时,DefaultNode 也会调用 ClusterNode 的相对应方法,完成两份指标数据的收集。
  • DefaultNode 统计当前资源的各项指标数据的维度是同一个 Context(名称相同),而 ClusterNode 统计当前资源各项指标数据的维度是全局。

https://www.xusbuy.com

上一篇:充电桩概念股排行(充电桩概念股有哪个)

下一篇:买入平仓与卖出平仓(买入平仓卖出平仓)

相关推荐

返回顶部