如何建立回测系统(二)

如何建立回测系统(二)

一个简单的回放市场实现

public class MarketPlace {

    private Map<String, List<Order>> orderChannel;
    private Map<String, Long> lastComplete;

    public MarketPlace() {
        orderChannel = new ConcurrentHashMap<>();
        lastComplete = new ConcurrentHashMap<>();
    }

    public void order(Order order) {
        List<Order> list = orderChannel.get(order.getTradeCode());
        if (Objects.isNull(list)) {
            list = new ArrayList<>();
            orderChannel.put(order.getTradeCode(), list);
        }
        synchronized (list) {
            List<Order> targetPriceOrder;
            List<Order> sellOrder = list.stream()
                .filter(order1 -> !order1.isComplete())
                .filter(Order::sell)
                .sorted(Comparator.comparing(Order::getPrice)
                    .thenComparing(Order::getTimestamp))
                .collect(Collectors.toList());
            List<Order> buyOrder = list.stream()
                .filter(order1 -> !order1.isComplete())
                .filter(order1 -> !order1.sell())
                .sorted(Comparator.comparing(Order::getPrice, Comparator.reverseOrder())
                    .thenComparing(Order::getTimestamp))
                .collect(Collectors.toList());
            //进来的是买单
            if (!order.sell()) {
                if (!buyOrder.isEmpty() && buyOrder.get(0).getPrice() > order.getPrice()) {
                    list.add(order);
                    return;
                }
                //处理好后,是按照价格小,时间先排列
                targetPriceOrder = sellOrder.stream()
                    //需要找到挂单中比当前订单要价低或者相等的
                    .filter(order1 -> order.getPrice() <= order1.getPrice())
                    .collect(Collectors.toList());
            } else {
                if (!sellOrder.isEmpty() && sellOrder.get(0).getPrice() < order.getPrice()) {
                    list.add(order);
                    return;
                }
                //进来的是卖单,找买单 , 卖单进来,先开始撮合最高价的买单
                targetPriceOrder = buyOrder.stream()
                    //需要找到买单中比当前订单要价高的
                    .filter(order1 -> order.getPrice() <= order1.getPrice())
                    .collect(Collectors.toList());
                //新入订单需要撮合的数量
            }
            //新入订单需要撮合的数量
            long count = order.getRemainCount();
            //连续竞价,买单进来,开始先撮合最低价的卖单
            for (Order order1 : targetPriceOrder) {
                count = count - order1.getRemainCount();
                if (count < 0) {
                    //说明当前订单已经满足,order不需要进入队列排队,order1更新成交数,中断循环
                    order1.addCompleteCount(order1.getCount() - Math.abs(count));
                    order.addCompleteCount(order.getCount());
                    order.complete();
                    this.lastComplete.put(order.getTradeCode(), order.getPrice());
                    break;
                } else if (count == 0) {
                    //说明订单恰好满足,中断循环,将order1添加移除队列
                    order.addCompleteCount(order.getCount());
                    order1.addCompleteCount(order1.getCount());
                    order1.complete();
                    order.complete();
                    this.lastComplete.put(order.getTradeCode(), order.getPrice());
                } else {
                    //说明订单未满足,将order1添加移除队列,并继续循环
                    order1.complete();
                    order.addCompleteCount(order1.getRemainCount());
                }
                count = order.getRemainCount();
            }
            if (!order.isComplete()) {
                list.add(order);
            }
        }
    }

    public Long getLastPrice(String security) {
        return this.lastComplete.get(security);
    }

性能分析

最高对单个标的物单线程,且订单越多,处理速度越慢

T = f(m*n)

可能的优化点

  1. 减少每次对全部订单进行筛选排序,分出买卖单
  2. 针对国内涨跌幅限制,对价格进行分级,每个价格中订单按照实践排序
  3. 尽可能用基础类型

如何建立回测系统

如何建立回测系统

什么是回测

就是对市场的变化进行回放,加入我们的策略得出,如果我们参与其中会有怎么样的结果

如何回放

首先要了解平时我们说的股票当日价格是如何形成的

股票一旦出售后本身是没有价格的,在每个交易日开始前,交易所会进行集中竞价,最后取买卖集合中的笔数最多的价格作为当日的开盘价

紧接着开始连续竞价

连续竞价中,股票价格为股票的成交价,成交价如下定义:

1. 最高买进申报与最低卖出申报相同,则该价格即为成交价格;

2. 买入申报价格高于即时揭示的最低卖出申报价格时,以即时揭示的最低卖出申报价格为成交价

3. 卖出申报价格低于即时揭示的最高买入申报价格时,以即时揭示的最高买入申报价格为成交价

总结

若需要对股市状况进行回测,首先应该保存当日所有的订单情况,然后对价格进行撮合,若撮合成功,则此时可以算出当前价格

平时我们接受的数据成为股市交易的快照

其中记录着一段时间内的成交量,成交均价,成交总价格等的信息

网络路径是如何被发现的

网络路径是如何被发现的

最近突然好奇到,我们熟悉的IP网络是如何运作的,查了半天资料都是说TCP/IP那几层协议,根本没有说网络是如何运行的。那我们试试头脑风暴,再发明一次网络吧

一个理想的模型

网络是由无数个可以相互连接的点组成

一些限制

每个点是没有先验知识的,也就是说,点与点之间如果不是直接连接在一开始是不知道能否由通路的,但点能知道直接连通的道路

发现通信路径

在没有点与点之间没有先验知识的前提下,我们就只能通过BFS/DFS算法来发现路径
所以当第一次通信的时候,点与点之间的时间最优情况T(n^n) (假设每个点直接连接n个点,在第二跳就能找到通路)

问题

这个情况下,我们会发现性能是和我们现在的网络不同,应该是会很慢的

优化

如果需要提升性能,就需要增加先验知识

增加假设

点的直接通路中由特定的点具有网络通路的解释权,我们称之为高优先级点,高优先级点的位置应该预先内置在访问点处,高优先级点数量m << n

优化结果

性能会提升至T(m^m)

再增加假设

假设我们可以广播,亦即是发出一个信号,如果点能到达,则返回路径表,此时的实际访问次数是T(h) h是网络中的最先达成通路的深度

问题

在未达成通路之前,整个网络相当于开启了m^m^h个半连接,会产生信息风暴

优化

划分网络,减少同时建立的半连接数量

对应到我们真实的IP网络

这个时候是不是就有点像我们的现有的IP网络了

硬件层就是能直接连接的路径

需要中间转发的,就在IP协议层完成

比如ICMP就是我们的广播信号

在一些核心交换机上还存在手工配置的路由表,就是高优先级点的先验信息

网关就是我们的高优先级点

划分就是我们的掩码

当然这都是我瞎几把想的东西,和实际出入应该大了去了,IP网发展了这么多年,基本模型都有可能变了

效率优先

效率优先

开场白真难,好,开场结束

什么是低效实践

我是以java语言为主力开发语言的代码编写人员,同时还负责我们所有的服务器的部署及维护, 实践中遇到最痛苦的事情莫过于, 精细化管理一切代码行为

为什么MyBatis在我的项目中很糟糕

我们团队一共就五个人,两个做后端,一个前端,一个原型设计,外加一个对外沟通的部门负责人.在一开始的选型中, 选中的框架是使用mybatis+springboot+vue来完成功能,我们主要是采集工厂生产过程中关键点温度的变化,以及保温性能的数据。

对于工业化设备监控来说,开发过程中最大的问题是,我们的模型的字段会在开发过程中不断的更新,于是我们实践中就会将mybatis当成一个Orm框架来使用,基本来说,就是只是用CRUD,每个对象是一张表,操作顺序都是查出对象-》修改对象-》回写数据库,这种半自动的工具在这样的实践中就会带来需要管理的代码实在太多,而且基本为样板代码,更严重的是,对象直接的关系维护必须手工写代码(这种代码基本就是样板代码,其实Hibernate这种框架都已经解决好了),对于一个小团队来说这是一个不可取的行为。没那么多精力,也不应该花那么多精力来维护这些样板代码。

举个栗子

我们对于采集点管理进行建模,首先有区域,然后有管道,接着有测温点信息,最后测温点有测温点数据

然后我们需要维护区域与管道之间的关系,管道与测温点之间的关系,区域与区域之间的关系,测温点数据与测温点之间的关系

一顿操作,需要有四个Dao,对应四个Entity,然后关系(就是Repository的职责)有四个Repository。

以上全是样板代码

提升效率

问题的关键在于如何将我们的开发人员从无趣且繁琐的样板代码实施中解放出来呢,答案在EJB中已经被实践,就是全自动的ORM框架

全自动的ORM带来的是开发人员不用再去做大量重复性工作以维护数据之间的关系和正确性

对于一个我们进行中的项目,采用MyBatis plus方案需要实现四个Dao,四个Repository,还有实现四个Entity,而改用Jpa/Hibernate之后,代码数量减少到四个Repository,无需实现Entity,Dao,而且SpringDataJpa已经可以通过动态代理方式生成代码,Repository是无需实现,只需要声明接口

原先使用了接近5(人/日)的工作量才完成的方案,改用Jpa后只需要0.5(人/日)

需要回避的点

全自动ORM是否有缺陷?

有,一是对象与数据库之间的关系需要靠一套不可变的规则维持,但是代码总是有人维护的,而且Hibernate的实现人并不是我们,若规则变动,我们的数据全部报废

二是全自动的ORM会有解析对象的性能开销,具体到我们项目是,启动时间增加两秒到五秒

三是需要实现一层控制事务的Service

解决方法也很简单,版本别动,启动时间其实无所谓,然后多采用懒加载,事务无非就是多一个Transactional的注解

结语

  1. mybatis不适用于人少的项目,开发人员少的情况下,要优先考虑开发效率
  2. 绝大部分开发人员真的不需要考虑从持久化方案中榨取更多性能,而榨取的通道多数会变成你的维护成本
  3. 性能有绝对要求下应该考虑其他方案而不采用数据库
  4. 真心建议国内的风气变一下,我在调研中发现mybatis最后演化出了mybatisplus的方案,一个四不像的方案,既没有减轻开发人员维护数据库的负担,也没有减少样板代码(mybatisplus依旧要手工维护关系)

一点小感悟

从JPA和MyBatis的开发过程来看其设计思路区别就有点大了

MyBatis的工具主要都是面向现有表结构,然后才有对象的设计方式,其最佳实践环境应是在有专门的DBA帮助完成表设计,然后依据表设计来构建业务代码

而JPA的思路是,我先有对象,然后仅仅是分析对象之间的关系,并持久化对象,其两者的思路不一致

二者都可以应用DDD做设计,但因为分工原因,会导致其DBA的设计思路是按着最少冗余设计,而开发时候只能迁就其设计思路

而对象设计者很可能是对象的实现者,这时候,将持久化的过程交给一套方法或者规则来完成,能最大化匹配其实现业务代码的思路

JPA+Springboot小限制

JPA+Springboot小限制

限制一

  • 通过JPA(Hibernate)在MySQL环境下自动建表要注意,JPA的默认字段长度为255,而如果数据库中存在索引不能超过768bytes的限制,则需要考虑自己手工维护表

  • 限制来源于JPA或默认新建一个记录序号的表,此表的主键为Varchar(255),且程序员无法修改,只要编码长度是超过三字节编码,则建表失败

  • 可以通过SpringData/Hibernate自动调用classpath下特定的sql文件特性绕过

  • 但如果抛弃了由hibernate做表维护而转为程序员手工维护表关系则不如采用mybatis方案更为直接

限制二

  • hibernate自动建表时候,是没法设置在哪个schame下或者哪个catalog下建表,如果DB的其他schame有同名表,则建表会失败

  • 此限制来源于Hibernate做数据库元数据查询时候只使用了table字段,且我无法找到可以让查询元数据时候指定schame+table联合确定table是否存在的办法

  • 此限制的场景会在Junit测试时候如果想做schame隔离是无法做到的

  • 一并发现此问题的还有activiti的自动建表也是

什么是锁(粗版)

什么是锁

  • 锁是一种特殊的数据结构+调度器,这个数据结构的维护通常是由操作系统维护的,当然如果语言自身也实现了线程管理的话,一般也会自己实现锁结构

  • 本质上锁是如下的一个数据结构

struct waitList{
    *ExecuteNode root;
    *ExecuteNode next;
}

每当一个进程/线程开始执行的时候,cpu会读取要操作的内存结构,其head中有一个字段是CPU保证当前过程不能发生软中断(基于语言虚拟机有相似的过程),当CPU发现这个字段置为1/true时候,CPU应要忽略其他请求,直到把该位置恢复为false,然后随机唤醒一个进程/线程(ExecuteNode)

基于以上事实在调度过程中,就能发现和我们在Java中使用锁的表现一致,但CPU不能保证其他进程/线程不能修改该标志位(系统或者语言调度器保证)

从业务到代码

从业务到代码

目的

所以我写这篇文章的目的很简单,为了给各位写代码的新手带来一点不同的视角

前言

代码不过是业务的表示,当你把代码看作一门语言(这里语言不是指java/C这些编码语言的意思,而是普适意义上的language)的时候,我们,作为编码人员,更多的是像是陈述事实一样,将业务陈述,然后通过代码落地。

问题

在新手中,经常有这么几个疑问
1. 这么写有什么用
2. 明明我一个函数就可以把所有工作做完,为什么要拆开来
3. 这个工具屌不屌
4. 这个语言如何如何

看法

结论

  1. 如果不能跳出只是代码堆砌工的眼界,那么永远不知道有什么用
  2. 拆分是为了将函数目的单纯化
  3. 工具只是生产力工具的一部分,不能说锤子一定比机床好
  4. 同样,都是锤子,不能说A类型的锤子一定好与B类型锤子

理由

不知道各位呆过几个公司,每家公司给人的感觉都是不一样的。提几个点让大家想一想
1. 有问题能不能找到确定的一个人能解决确定的一类问题
2. 出了事情,背锅是平摊锅还是说加权锅,还是看老板心情分
3. 简单点,如果电脑坏了要换,上报后多久能搞定
4. 工作的成果是否能够量化
让我们来换到代码的视角来看这个问题
1. 出了bug能否容易精确定位在什么类
2. 出错了,整个系统的其他部分是否依旧稳定
3. 某个部件如果因为市场因素全部重做,重做的成本是低是高
可能换到编码视角,各位一定能够简单的提出一些熟悉的概念,比如鲁棒性,比如重构,etc。但是不知道各位有没有做过思考,亦即这些概念套用到非编码视角的时候也是通用的,更或者说,这概念都是从其他行业或者领域移植到软件开发领域,毕竟软件开发到今天真正普及也就四十年?

所以

  1. 向业务学习,好的代码应该是和业务一致的
  2. 因为你的代码就是业务时候,业务的快速变动就能变成了代码变动
  3. 而一个公司的业务变动不可能连根本逻辑都改变了
  4. 加班就会少了
  5. 与业务人员沟通也会顺畅了

论目标

论目标

信息安全中经常需要验证一条信息,其做法是,定义一个函数f,f作用于信息得到一条简短的信息,即f(g(x),c) == f(h(x),c) == F 其中,g(x)是发送端的信息,h(x)是接收端信息,c是双方约定的一个信息,F是计算的结果,如果经过f作用后,得到的结果是相同,那么就可以认为,这条信息是真实的。

原理上这东西很简单,也非常实用,那么对应到不是信息安全领域中呢。

举个例子:

  1. 领导说,我们的当前目标是,在合法的范围内挣得一百万
  2. 所有人开始提方案
  3. 每个人根据目标,代入自己的方案,是否满足条件
  4. 如果所有方案都不能满足,和领导讨论是否tradeoff一部分要求

现在我经常性面对的问题是:

  1. 负责的人,提不出一个目标
  2. 执行的人,总能找到各种似是而非的理由,在一定条件下这又是成立的

举个例子:

  1. 提出做一个系统
  2. a提出A方案,方案A优点是1,2,3,4,5
  3. 但是大家都不同意,觉得这些操作相当冗余

那么这时候谁对谁错,作为一个负责人,这时候就要想到这么一个事情,这个事情的目标是什么:

  • A1,A2,A3,A4

那么上面的对错就容易判断了,如果a提出的1,2,3,4,5不满足A1,A2,A3,A4,一律是要砍掉,只保留能够满足A1,A2,A3,A4的设计

对应出来我们就得到了类似的和信息安全领域的结果即
f(1,2,3,4,5) == A1,A2,A3,A4
如此我们总能够简单的验证我们的工作成果,而不是陷入无止境的扯皮中

软件开发的三个层级

软件开发的三个层级

  1. 核心领域模型:该层级与实际业务应该一致,举个例子:我们在财务系统中有依据收款信息开票的选项,那么我们的领域模型就应该有个类叫做收款信息,它有一个动作叫做开票
  2. 领域模型对外的交互:到了这个层级,会有很多开源代码库帮助我们,例如我们需要发票对象需要对外暴露,那么通过mvc的形式,可以对外暴露发票的信息,或者是发票需要做持久化,有各种各样的Persistence的工具帮助我们
  3. 底层:例如数据库字段长度,报文的payload等等,到了这一层,应该是一个很具体的问题,具体来说,在这一层级不应该有任何业务相关的知识,而完全是计算机领域的知识

通过对软件开发进行划分层级,可以帮助我们开发过程中渐进式开发
比如我要关注业务代码的时候,就纯粹的业务思维,如果我是底层的开发,我只需要关注计算机领域的知识

为什么要这么划分

我在开发中观察到,大部分新手或者开发了很久的人,在写代码的时候,过早的关注到细节问题,比如写开发票的时候就开始关注到发票数据库字段长度,就直接陷进了细节中,具体对外表现出来就是业务实现统统一个函数打包,甚至还有全局变量等等
渐进式的分层开发有助于我们专注于一件事件,只有专注了,那么开发中错误才能减少