时间加权平均价格算法(TWAP)和成交量平均算法(VWAP)在量化回测的应用

本文介绍了在量化回测中,时间加权平均价格(TWAP)和成交量加权平均价格(VWAP)算法的应用。这两种算法用于减少大额订单对市场价格的影响,通过分批执行交易降低交易成本。通过回测,表明使用WAP价格能有效评估策略的资金容量,并且对策略的收益率影响较小。文章还详细说明了TWAP和VWAP的计算方法、成交逻辑以及在回测平台上的设置和使用方法。
部署运行你感兴趣的模型镜像

为什么要引入TWAP和 VWAP?

为了评估策略的资金容量,我们对M.trade模块里买入点和卖出点这两个参数进行了更丰富的扩展,支持了策略能够按更丰富的算法交易价格(WAP)进行撮合。

如果资金是10万的话,那么在开盘买入基本上没有什么问题,如果资金量是300万、或者1000万呢?开盘如果只买入几只股票的话,本身的交易行为就会改变市场状态,冲击成本巨大。因此我们支持了算法交易里TWAP和VWAP。

TWAP (Time Weighted Average Price),时间加权平均价格算法,是一种最简单的传统算法交易策略。TWAP模型设计的目的是使交易对市场影响减小的同时提供一个较低的平均成交价格,从而达到减小交易成本的目的。在分时成交量无法准确估计的情况下,该模型可以较好地实现算法交易的基本目的。

VWAP (Volume Weighted Average Price), 成交量加权平均价格。VWAP策略即是一种拆分大额委托单,在约定时间段内分批执行,以期使得最终买入或卖出成交均价尽量接近该段时间内整个市场成交均价的算法交易策略。

上述两种方式是最常用的算法交易执行方式。其实在我们实际交易的过程中也会发现,如果是一笔较大的订单,我们肯定不会直接全部下单,而是把订单进行拆分,逐渐成交。因此如果我们在回测的过程中也可以使用TWAP和VWAP的价格进行撮合,最直接的目的就是能够检验我们策略的资金容量,如果按照TWAP和VWAP进行撮合以后,收益率变动不大,那我们对策略的资金容量会有很大信心,即策略不再是只能管理几十万,管理上百万、上千万是没有问题的。

我们在stockranker模板策略的基础上,用多组WAP价格进行回测,并和默认回测价格做了一个对比,回测结果如下表所示:

买点卖点夏普比率年化收益总收益最大回撤
openclose1.91108.29%314.1%47.39%
twap_1twap_81.87110.21%321.51%46.48%
twap_2twap_71.797.49%273.53%45.07%
twap_3twap_61.78101.26%287.48%43.16%
twap_4twap_51.8398.75%278.14%43.11%
twap_9twap_101.7286.66%234.89%40.13%
vwap_1vwap_81.87110.77%323.69%45.46%
vwap_2vwap_71.74100.74%285.53%44.63%
vwap_3vwap_61.78103.06%294.2%43.63%
vwap_4vwap_51.7895.54%266.4%43.55%
vwap_9vwap_101.8191.45%251.72%37.73%

可以看到使用WAP价格回测和使用默认的open买入close卖出回测相比,各项指标变化不大,这也一定程度上表明单一策略的资金容量其实比我们想象地要大很多。

字段查看

最近我们升级了数据文档,WAP相关的文档见:WAP算法交易字段,截图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PqOvr9os-1606876933758)(upload://8KAWOhZNmaS1MfaNGY2oeZ3L7cG.png)]

设置方法

在回测模块右侧属性栏中,可以看到有买入点和卖出点的参数设置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ziyctSpn-1606876933763)(upload://rMcSsoaEo4Vv7G4z4nTzFm3upQS.png)]

下拉即可选择想要的WAP价格。

字段介绍

  • 我们支持了TWAP,VWAP 两种WAP价格

  • 每个字段对应了1-11 共11种时间规则指标,具体时间规则如下:

    字段时间规则明细
    twap_1/vwap_1早盘前5分钟9:30到9:35
    twap_2/vwap_2早盘前15分钟9:30到9:45
    twap_3/vwap_3早盘前30分钟9:30到10点
    twap_4/vwap_4早盘前60分钟9:30到10:30
    twap_5/vwap_5尾盘前60分钟14:00到15:00
    twap_6/vwap_6尾盘前30分钟14:30到15:00
    twap_7/vwap_7尾盘前15分钟14:45到15:00
    twap_8/vwap_8尾盘前5分钟14:55到15:00
    twap_9/vwap_9上午9:30到11:30
    twap_10/vwap_10下午13:00到15:00
    twap_11/vwap_11全天上午+下午
  • 每一个字段对应了买卖两个方向

  • 每一个字段又对应了两种数据:价格和成交量

综上,字段示例如下:

字段解释
wap_1_twap_buy早盘前5分钟买单的twap价格
wap_1_twap_sell早盘前5分钟卖单的twap价格
wap_1_vwap_buy早盘前5分钟买单的vwap价格
wap_1_vwap_sell早盘前5分钟卖单的vwap价格
wap_1_buy_volume早盘前5分钟买单的成交量(经调整后)
wap_1_sell_volume早盘前5分钟卖单的成交量(经调整后)

以上字段均储存在 bar1d_wap_CN_STOCK_A表 和 bar1d_wap_CN_STOCK_A_adj表 中,如果要手动核查下撮合价格的话,可以访问这两个表来获取股票的TWAP,VWAP价格以及成交量。(其中第一个表是真实的加权价格数据,后一个表是后复权的价格加权数据,两表中的字段相同)。

DataSource('bar1d_wap_CN_STOCK_A_adj').read(start_date='2017-12-28',end_date='2017-12-28',instruments=['000002.SZA'],fields=['date','instrument','wap_1_buy_volume', 'wap_1_sell_volume', 'wap_1_twap_buy','wap_1_twap_sell', 'wap_1_vwap_buy', 'wap_1_vwap_sell'])

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRxKXTQq-1606876933765)(upload://9e3mKpp7aBFI5wBLegTzU30iwEK.png)]

也可以查询到表中所有字段如下:

DataSource('bar1d_wap_CN_STOCK_A_adj').read(start_date='2017-12-28',end_date='2017-12-28',instruments=['000002.SZA']).columns

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yL83KWws-1606876933767)(upload://mSObLRaktH31OSvDPiXD6YndxLs.png)]

我们选择买入点为twap_1,卖出点为twap_8,回测价格类型选择后复权,回测结果部分截图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kcBsR8ze-1606876933769)(upload://2O4TVUcb1yuNQYkO6WLiBsvXvgt.png)]

可以看到002801.SZA的买入价为128.17,000503.SZA的卖出价为319.99。

我们再从bar1d_wap_CN_STOCK_A_adj 表(如果回测价格类型选择的是真实价格,则用bar1d_wap_CN_STOCK_A 表)中读出这两只股票的wap_1_twap_buy和wap_8_twap_sell价格:

DataSource('bar1d_wap_CN_STOCK_A_adj').read(start_date='2016-12-30',end_date='2016-12-30',instruments=['002801.SZA','000503.SZA'],fields=['date','instrument','wap_1_twap_buy','wap_8_twap_sell'])

在这里插入图片描述

可以看到表中的价格和回测中的成交价是一致的,回测时确实使用了我们选择的WAP价格进行撮合成交。

计算方法和成交逻辑

  • 成交量:
    如果对应的是买,将涨停的分钟k线去除,不参与计算,即非涨停的量
    如果对应的是卖,将跌停的分钟k线去除,不参与计算,即非跌停的量
    例如:
    假设000002股票在上午【2h】成交100手,涨停;下午【1h】打开涨停(非跌停),成交200手,收盘【1h】跌停,成交50手。
    则,买入经调整的成交量是250 ,卖出经调整的成交量是 300

600339.SHA在2016-12-30这天盘中涨停,我们读这天的价量数据可以看到,当天的总成交量为15055283.0

DataSource('bar1d_CN_STOCK_A').read(start_date='2016-12-30',end_date='2016-12-30',instruments=['600339.SHA'])

在这里插入图片描述

而我们读取这个股票这一天的全天的buy_volume,可以看到交易量为8937100.0,去除了涨停的量:

DataSource('bar1d_wap_CN_STOCK_A_adj').read(start_date='2016-12-30',end_date='2016-12-30',instruments=['600339.SHA'],fields=['date','instrument','wap_11_buy_volume'])

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Giua37VL-1606876933771)(upload://kBJJb7bAxtiWygnw3OUD7k2fW3Y.png)]

  • 平均价格:
    如果对应的是买,将涨停的分钟k线去除
    如果对应的是卖,将跌停的分钟k线去除
    TWAP:
    Time Weighted Average Price, 是时间加权平均价格。
    计算公式为 mean(Pm,n) ,其中n为k线数量,Pm为典型价格(等于 (high+low+close)/3)。
    VWAP:
    Volume Weighted Average Price, 是交易量加权平均价格。
    计算公式为 (close_0 * volume_0 + close_1 * volume_1 +close_2 * volume_2 +…+close_59* volume_59) / (volume_0+volume_1+…+volume_59) 。

  • 成交逻辑:
    实际成交量会受到我们选择的买点卖点的价格类型所对应的的成交量的影响,例如:
    假设某天A股票成交1万手,而我们想买入2万手,假如买入点选择close,则实际可以成交1万手;假如买入点选择twap1,而当天wap_1_buy的成交量为500手,则实际只会成交500手。

更新后的WAP功能相比之前做了更进一步的细化和优化,考虑到了真实交易的涨跌和跌停情形,因此计算出来的算法交易平均价格更精准和符合实际,进一步提高了回测的准确性。欢迎大家使用和反馈意见。

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

### 关于A*算法在加权图中的Java实现 A*算法是一种启发式搜索算法,在路径规划领域应用广泛。尽管它并非总是能找到最短路径,但它通过结合实际代价估计代价来优化搜索过程[^1]。以下是基于加权图(如道路网络)的A*算法的Java实现: #### 实现思路 A*算法的核心在于维护一个优先队列,并利用`f(n) = g(n) + h(n)`作为节点评估函数: - `g(n)`表示从起点到当前节点的实际代价。 - `h(n)`是一个启发式函数,用于估算从当前节点到目标节点的成本。 对于加权图而言,通常可以采用欧几里得距离或曼哈顿距离作为`h(n)`的近似值。 #### Java代码示例 以下是一个简单的A*算法实现,适用于带权重的道路网络路径查找问题: ```java import java.util.*; public class AStarAlgorithm { static class Node implements Comparable<Node> { int id; double costFromStart; // g(n) double heuristicCostToGoal; // h(n) double totalCost; // f(n) = g(n) + h(n) public Node(int id, double costFromStart, double heuristicCostToGoal) { this.id = id; this.costFromStart = costFromStart; this.heuristicCostToGoal = heuristicCostToGoal; this.totalCost = costFromStart + heuristicCostToGoal; } @Override public int compareTo(Node other) { return Double.compare(this.totalCost, other.totalCost); } } public List<Integer> findPath(Map<Integer, Map<Integer, Double>> graph, int start, int goal, Map<Integer, Double> heuristicMap) { PriorityQueue<Node> openList = new PriorityQueue<>(); Set<Integer> closedSet = new HashSet<>(); Map<Integer, Integer> cameFrom = new HashMap<>(); openList.add(new Node(start, 0, heuristicMap.get(start))); while (!openList.isEmpty()) { Node current = openList.poll(); if (closedSet.contains(current.id)) continue; if (current.id == goal) { return reconstructPath(cameFrom, start, goal); } closedSet.add(current.id); for (Map.Entry<Integer, Double> neighbor : graph.getOrDefault(current.id, new HashMap<>()).entrySet()) { if (closedSet.contains(neighbor.getKey())) continue; double tentativeGScore = current.costFromStart + neighbor.getValue(); boolean isBetter = false; Optional<Node> existingNodeOpt = openList.stream() .filter(node -> node.id == neighbor.getKey()) .findFirst(); if (!existingNodeOpt.isPresent() || tentativeGScore < existingNodeOpt.get().costFromStart) { isBetter = true; } if (isBetter) { cameFrom.put(neighbor.getKey(), current.id); double estimatedHScore = heuristicMap.get(neighbor.getKey()); openList.add(new Node(neighbor.getKey(), tentativeGScore, estimatedHScore)); } } } return null; // No path found } private List<Integer> reconstructPath(Map<Integer, Integer> cameFrom, int start, int goal) { List<Integer> path = new ArrayList<>(); int currentNode = goal; while (currentNode != start) { path.add(currentNode); currentNode = cameFrom.get(currentNode); } path.add(start); Collections.reverse(path); return path; } public static void main(String[] args) { AStarAlgorithm astar = new AStarAlgorithm(); // Example Graph Representation Map<Integer, Map<Integer, Double>> graph = new HashMap<>(); graph.put(0, Map.of(1, 1.0, 2, 4.0)); graph.put(1, Map.of(2, 1.0, 3, 2.0)); graph.put(2, Map.of(3, 1.0)); // Heuristic Costs Map<Integer, Double> heuristicMap = new HashMap<>(); heuristicMap.put(0, 5.0); heuristicMap.put(1, 4.0); heuristicMap.put(2, 2.0); heuristicMap.put(3, 0.0); // Goal node System.out.println(astar.findPath(graph, 0, 3, heuristicMap)); // Output: [0, 1, 2, 3] } } ``` 上述代码实现了基本的A*算法逻辑,其中输入参数包括加权图、起始节点、目标节点以及启发式成本映射表。该实现在处理复杂路网时具有较高的效率。 #### 性能对比 相较于Dijkstra算法,A*算法的优势在于其能够借助启发式信息减少不必要的探索范围,从而降低时间复杂度[^2]。然而,这种性能提升依赖于合理的`h(n)`设计;如果`h(n)`过高,则可能导致次优解。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值