在上一篇文章中,我们已经对Dresdon所提供的功效举行了简朴的先容。在这篇文章中,我们将先容若何基于Dresdon举行二次开发。
Dresdon的扩展点
就像上一篇文章所先容的那样,Dresdon主要是一个量化引擎。用户可以通过剧本或者Java编程的方式来形貌模子的生意条件,并进一步通过扫描该模子在所有股票中的所有匹配来评估该模子的具体显示。通过这种方式,用户可以很大水平地优化自己的买卖系统,从而实现稳固盈利。
通过剧原本形貌股票的买入卖出条件十分简朴:
// 当日和前日股价上涨 $isRaisingUp = growth(close()) > 0.01 && growth(close(), 1) > 0.01 // 5日前存在着一个长度至少为30,震荡幅度小于5%的平台 $isPlatform = platform_exists(0.05, 30, 5) // 在平台前存在长度至少为20日,最大上涨幅度为12%的缓慢增进 $isSlowRaiseBeforePlatform = is_slow_raise(0.12, 20, platform_start(0.05, 30, 5)) …… $buyCondition = $isRaisingUp && $isPlatform && $isSlowRaiseBeforePlatform && …… $sellCondition = close(0) < ma5(0) && ……
接下里用户就可以通过扫描2006年1月到2020年4月之间所有匹配来统计该模子的显示:
{ "averageBenefit" : 0.049035519980520418, // 平均单笔收益为4.9%左右 "maxBenefit" : 74.86122362293308, // 最高收益为74.9% "minBenefit" : -4.000000000000014, // 最大止损为4% "totalCount" : 313, // 2006.01 – 2020.04之间匹配313次 "averageDayCount" : 11.875656742556918, // 平均持股时间为11.9天 "successRatio" : 0.46059544658493873 // 成功率为46%左右 }
固然,若是用户会Java,那么他还可以将模子写成一个Java类,进而获得编译器的强类型支持:
// 当日和前日股价上涨 BooleanHolder isRaisingUp = and(greaterThan(growth(close()), 0), greaterThan(growth(close(), 1), 0)); // 5日前存在着一个长度至少为30,震荡幅度小于5%的平台 BooleanHolder platformExists = platformExists(0.05, 30, 5) // 在平台前存在长度至少为20日,最大上涨幅度为12%的缓慢增进 IntHolder platformStart = platformStart(0.05, 30, 5); BooleanHolder isSlowRaiseBeforePlatform = isSlowRaise(0.12, 20, platformStart); …… BooleanHolder condition = and(isRaisingUp, platformExists, isSlowRaiseBeforePlatform, ……);
除了添加自界说模子之外,用户还可以添加自界说函数。这些函数可以用来判断某日K线的特征,或者拟合特定K线形态。例如下面就是一个用来盘算指定K线震惊幅度的函数:
public static Value.Shrink shrink(int index) { return new Value.Shrink(index); } @Operation(key = KEY_SHRINK, resultType = DOUBLE, arguments = { @Arguments(paramTypes = { INT }) }) public static class Shrink extends HolderBase<Double> implements DoubleHolder { protected IntHolder index; protected Integer indexValue; public Shrink(int index) { this(new IntValue(index)); } public Shrink(IntHolder index) { super(KEY_SHRINK); this.index = index; } @Override public void preprocess(QuantContext context) { super.preprocess(context); preprocess(context, index); } @Override public boolean needRefresh(QuantContext context) { return !equals(indexValue, index, context); } @Override protected Double recalculate(QuantContext context) { indexValue = index.getValue(context); if (indexValue == null) { return null; } DailyTrading dailyTrading = context.getTradings().get(indexValue); double blockSize = Math.abs(dailyTrading.getClose() - dailyTrading.getOpen()); double totalVariation = dailyTrading.getHigh() - dailyTrading.getLow(); this.value = blockSize > SMALL_DOUBLE_VALUE ? totalVariation / blockSize : Double.MAX_VALUE; return value; } @Override public void persist(StringBuilder builder) { builder.append(getKey()); builder.append(INDEX_START); getIndex().persist(builder); builder.append(INDEX_END); } }
在后面的章节中,我们将详细解说上面模子中各个买入卖出条件的意义。
添加自界说模子
下面就让我们从添加自界说模子最先。抽取一个模子经常需要经由以下一系列步骤:
1. 确定模子形态。用户首先需要确定需要匹配的模子的大致形态有哪些,如起涨阶段的线形是什么样子的,整理期是以什么形态出现的,甚至之前筹码是若何网络的等等。
2. 初筛并网络目的匹配。用户需要为该模子界说一个大致的匹配条件,然后运行引擎。此时获得的效果可能存在着大量的噪音,因此统计数据经常并不悦目。但其中也会包罗大量的具有较高准确度的匹配。而这些匹配经常是模子的目的匹配。
3. 细化模子。添加其它条件逐渐祛除噪音,以提高模子准确匹配的比率。
4. 细化卖出条件。添加其它卖出提交,以提高模子的收益率及成功率。
固然,凡事都有一个从生疏到熟悉的历程。在添加了几个模子之后,用户可能就能摸到其中的诀窍,进而大大提高模子抽取的效率。在这里给人人列出来我在抽取模子历程中最常使用的一系列履历型计谋,制止人人重走我之前的弯路。
首先,模子的买入特征线型要显著,近端的辅助判断逻辑要严酷,而远端的辅助判断逻辑要具有较高的容错性。可以说,所谓的股票拉升现实上就是股票价格的异动,而该异动的阻力则很大水平上决议了股票行情到底能走多远。因此起涨阶段线形的略微差别都可能导致量化效果发生异常大的差异。好比都是上涨5%,一个有长上影的K线就远不如没有长上影的K线。反之离当前买卖日越远的买卖,其对当前股价的影响越小,因此远端的辅助判断逻辑不宜异常严酷。
其次,要对常见线形所代表的意义有准确的明白。同样的K线在差别的位置其意义经常并不相同。例如一般来说,低位揉搓线经常是一个好的K线组合,而高位揉搓线,尤其是放量揉搓线则很可能代表一段行情将要终结。
最后,筛选条件经常是可以通用的。就像第一条所说的那样,我们要将买入的特征线形严酷地区分。好比拉升是通过一根阳线完成的,和拉升是通过三根K线形成的组合K线完成的效果类似。然则它们的筛选逻辑则经常有一个为2的索引差:一根阳线完成的拉升,我们要从前一天的K线检查,而三根K线组成的拉升,则需要从三天前的买卖最先检查。只不过这些检查的参数有些不太相同而已。
添加自界说函数
在编写一段时间的模子之后,用户可能就会感觉到引擎内建的各个表达式很难显示一些特定的限制条件。例如他可能经常需要通过如下表达式来限制K线的颠簸情形:
$noBigShrink = abs(close(0) – open(0)) * 5 < high(0) – low(0)
甚至用Java编写出来的表达式的可读性更差:
BooleanHolder noBigShrink = lessThan(multiply(abs(minus(close(0), open(0))), 5), minus(high(0), low(0)));
而这部门的逻辑仅仅是在判断当日K线的实体是否过小,进而出现十字星或锤头线等形态。此时用户就可以在Plugin内里添加自界说的表达式:
public static Value.Shrink shrink(int index) { return new Value.Shrink(index); } @Operation(key = KEY_SHRINK, resultType = DOUBLE, arguments = { @Arguments(paramTypes = { INT }) }) public static class Shrink extends HolderBase<Double> implements DoubleHolder { protected IntHolder index; protected Integer indexValue; public Shrink(int index) { this(new IntValue(index)); } public Shrink(IntHolder index) { super(KEY_SHRINK); this.index = index; } @Override public void preprocess(QuantContext context) { super.preprocess(context); preprocess(context, index); } @Override public boolean needRefresh(QuantContext context) { return !equals(indexValue, index, context); } @Override protected Double recalculate(QuantContext context) { indexValue = index.getValue(context); if (indexValue == null) { return null; } DailyTrading dailyTrading = context.getTradings().get(indexValue); double blockSize = Math.abs(dailyTrading.getClose() - dailyTrading.getOpen()); double totalVariation = dailyTrading.getHigh() - dailyTrading.getLow(); this.value = blockSize > SMALL_DOUBLE_VALUE ? totalVariation / blockSize : Double.MAX_VALUE; return value; } @Override public void persist(StringBuilder builder) { builder.append(getKey()); builder.append(INDEX_START); getIndex().persist(builder); builder.append(INDEX_END); } }
下面就让我们一行行地解说这些代码的寄义。首先是一个静态函数:
public static Value.Shrink shrink(int index) { return new Value.Shrink(index); }
通过该静态函数,用户可以更直观地形貌模子逻辑,属于一种语法糖:
new lessThan(new Shrink(0), 5) vs. lessThan(shrink(0), 5)
接下来我们则通过@Operation来标明当前类中包罗的逻辑是一个引擎操作的界说。该操作的key为KEY_SHRINK,带有一个Integer类型的参数,返回值的类型为Double:
@Operation(key = KEY_SHRINK, resultType = DOUBLE, arguments = { @Arguments(paramTypes = { INT }) }) public static class Shrink extends HolderBase<Double> implements DoubleHolder {
这里有一个观点,那就是Holder。马上您就会看到,Shrink类实例上并没有纪录和买卖相关的数据,它仅仅用来承载盘算逻辑。也就是说,它相当于一个占位符。现实上,Dresdon支持的所有运算符都是一个Holder,内部只纪录算法,不纪录任何数据。
那么买卖相关的数据都纪录在那里呢?谜底是Context。用户可以通过各个holder的getValue()函数来获得各个holder的当前值。现在就让我们看看Shrink类的recalculate()函数的是若何使用它的:
@Override protected Double recalculate(QuantContext context) { indexValue = index.getValue(context); if (indexValue == null) { return null; } DailyTrading dailyTrading = context.getTradings().get(indexValue); double blockSize = Math.abs(dailyTrading.getClose() - dailyTrading.getOpen()); double totalVariation = dailyTrading.getHigh() - dailyTrading.getLow(); this.value = blockSize > SMALL_DOUBLE_VALUE ? totalVariation / blockSize : Double.MAX_VALUE; return value; }
可以看到,recalculate()函数传入了一个QuantContext类型的实例。接下来,该函数的实现通过挪用index的getValue()函数获得了index的现实值。接下来,我们就从context中取得了目的买卖数据(dailyTrading),并依次通过盘算目的K线的实体巨细(blockSize),当日最高价和最低价之差(totalVariation)来盘算当日的颠簸情形。这里需要注重的是,盘算效果将被首先纪录在value这个域中,然后才被该函数返回。
为了提高盘算的性能,我们引入了两个机制:refresh和preprocess。前者通过判断参数的值是否转变来确定是否需要运行recalculate()函数。究竟该函数所包罗的盘算逻辑可能相当庞大。在其它属性没有发生转变的时刻,我们可以通过直接返回value这个缓存域中纪录的值来提高运行性能:
@Override public boolean needRefresh(QuantContext context) { return !equals(indexValue, index, context); }
另一种情形则是对预处理的支持。其主要用来提高拟合功效的性能。让我们以一只股票在多年的买卖中存在着一系列盘整平台的情形为例。若是我们针对差别的日期都盘算一次拟合逻辑,那么引擎的性能将变得很差。究竟在平台内部的各个买卖日对应的是同一个平台。为了解决这个问题,我们添加了预处理步骤。该步骤允许引擎对所有买卖日举行一次扫描,并将其扫描效果存储在Context中。在需要的时刻从Context中取出响应的预处理效果即可:
@Override public void preprocess(QuantContext context) { …… PlatformExtractor extractor = new PlatformExtractor(symbol, ma5s, rangeValue, minLengthValue); List<PlatformInfo> platforms = extractor.extractPlatforms(); context.getVariableMap().setVariable(key, new ObjectWrapper(symbol, platforms)); } protected PlatformInfo getCurrentPlatform(QuantContext context) { …… ValueHolder<?> variable = context.getVariableMap().getVariable(key); List<PlatformInfo> platforms = (List<PlatformInfo>)(((ObjectWrapper)variable).getValue()); return platforms.stream().filter(platform -> platform.getStartDate().compareTo(seedDate) < 0 && platform.getEndDate().compareTo(seedDate) > 0).findFirst().orElse(null); }
通过这种方式,用户就可以自行建立更高级的函数,进而使得自己的模子变得更为简练。
转载请注明原文地址并标明转载:https://www.cnblogs.com/loveis715/p/13324937.html
商业转载请事先与我联系:silverfox715@sin ***
民众号一定协助别标成原创,由于协调起来太麻烦了。。。
,
欢迎进入欧博开户网址(Allbet Gaming):www.aLLbetgame.us,欧博网址开放会员注册、代理开户、电脑客户端下载、苹果安卓下载等业务。