交易系统开发技能及面试之TechCoding
Posted BBinChina
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了交易系统开发技能及面试之TechCoding相关的知识,希望对你有一定的参考价值。
文章目录
- 概要
- Q1设计股票订单撮合系统,支持5000以上不同品种,单品种每天上百万订单
- Q2 实践:设计订单簿,用于支持下单、撤单、撮合功能,撮合采用价优方式
- Q3 设计一个网关用于接收用户订单并将其转发给交易撮合服务,该网关需要实现限流功能,给定 N,T两个值,表示在T微妙时间内只处理N个消息,相当于滑动窗口控制。要求设计订单控制类:根据N、T、Input,处理接收的订单流,返回pass 或者 fail。
- Q4 实现前缀树的插入跟搜索
- Q5 给定一个存储股票价格的数组,代表某股票每天的价格,采用t+1方式,当天买隔天卖。计算出最优的盈利价格,比如:
- Q6 位运算:反转一个uint32整数的位值
- Q7 实现pow 幂等计算
- Q8 转换excel的列名如 1->A, 2->B , 26->Z, 28->AB...
概要
编程面试是整体面试中最核心的部分,毕竟开发者都是手艺者,需要动手跟思维都达到公司候选人的要求,而这部分通常包括以下几个知识要点:
1、链表、二叉树、字典树等数据结构
2、使用哈希表、栈、队列、优先队列来解决问题
3、数组、字符串的搜索及排序
4、位运算在低时延开发过程中是优化利器
5、动态规划虽然是算法中的经典也是最难的,但通常用的比较少,可以查看大厂是否有相关面试题
6、路径算法通常也较少出现,但推荐掌握BFS/DFS算法
关于面试题,建议多刷leetcode
Q1设计股票订单撮合系统,支持5000以上不同品种,单品种每天上百万订单
因为每个品种的订单簿是隔离的,相互不影响,所以采用独立线程或者独立进程处理某个品种的交易订单,这样做的目的可以让品种的处理进程水平扩展,可以将不同品种的订单处理部署在不同的机器,再通过交易网关进行路由。可以根据对品种的交易量进行预测,对交易活跃的品种升级CPU或者内存完成垂直扩展。
Q2 实践:设计订单簿,用于支持下单、撤单、撮合功能,撮合采用价优方式
//订单簿由买方、卖方两个二叉树构成
struct OrderBook
Limit* buyTree;
Limit* sellTree;
Limit* lowestSell;
Limit* highestBuy;
;
//二叉树节点记录当前价格档位以及订单数量
//节点上记录当前价位的订单队列(时间优先)
struct Limit
int limitPrice;
int size;
Limit* parent;
Limit* leftChild;
Limit* rightChild;
Order* headOrder;
Order* tailOrder;
;
//订单只需唯一标识id、交易方向、数量、价格
//采用列表方式存储订单
//
struct Order
int orderId;
bool buyOrSell;
int num;
int limitPrice;
Order* nextOrder;
Order* prevOrder;
Limit* parentLimit;
;
//采用订单id为key, 订单为value,用于撤单
map<int, Order*> orderMap;
//采用limitPrice为key, Limit节点为value, 用于
map<int, Limit*> sellLimitPriceMap;
map<int, Limit*> buyLimitPriceMap;
采用订单id为key,通过map存储所有的订单
1、下单
当下单时,如果二叉树不存在该价位的订单,那么添加二叉树节点,时间复杂度为O(log m)。
如果limitPriceMap存在该价位,那么时间复杂度:O(1)
2、撤单
通过orderMap进行索引,时间复杂度 : O(1)
3、成交
通过map找到对手价的节点,遍历节点的订单列表进行撮合
4、获取最高最低价
通过OrderBook的lowestSell、lowestBuy指针进行查找
Q3 设计一个网关用于接收用户订单并将其转发给交易撮合服务,该网关需要实现限流功能,给定 N,T两个值,表示在T微妙时间内只处理N个消息,相当于滑动窗口控制。要求设计订单控制类:根据N、T、Input,处理接收的订单流,返回pass 或者 fail。
N:5 T:20
消息流:
1640277839000
1640277839000
1640277839012
1640277839014
1640277839014
1640277839014
1640277839015
1640277839020
1640277839037
1640277839039
输出:
1640277839000 pass
1640277839000 pass
1640277839012 pass
1640277839014 pass
1640277839014 pass
1640277839014 fail
1640277839015 fail
1640277839020 fail
1640277839037 pass
1640277839039 pass
要点:
采用queue队列来接收消息,在限制时间T内,队列的大小不能超过N个。
/*
实现订单网关的限流
给定 n,t两个参数
表示在t毫秒内,只处理n个order
*/
#include <deque>
class rateLimiter
private:
//缓冲窗口队列,接收订单的时间戳
std::deque<uint64_t> _dq;
int _numMsgs;
int _timeWindow;
public:
rateLimiter(int n, int t) : _numMsgs(n), _timeWindow(t)
/// <summary>
/// 判断订单是否可被处理
/// </summary>
/// <param name="ts">订单的时间戳</param>
/// <returns></returns>
bool IsRateLimiterOK(uint64_t ts)
if (_dq.empty())
_dq.push_front(ts);
return true;
// 最新的订单时间戳在对头,队尾的订单如果与新订单的时间戳超过了时间窗口,那么可以弹出,剩余的表示在时间窗口内已处理的订单
while (!_dq.empty() && (ts - _dq.back() >= _timeWindow))
_dq.pop_back();
// 已出来的数量小于被允许的数量,说明新订单可被处理,记录其时间戳并返回true
if (_dq.size() < _numMsgs)
_dq.push_front(ts);
return true;
return false;
int GetCountInWindow()
return _dq.size();
;
Q4 实现前缀树的插入跟搜索
/*
前缀树 也叫字典树,常用于压缩字典内容
*/
#include <string>
using namespace std;
class PreFixTree
struct Node
//需要确认字符内容不超过26个字母
Node* child[26];
bool isWord;
//初始化子节点为nullptr
Node()
for (int i = 0; i < 26; i++)
child[i] = nullptr;
isWord = false;
;
Node* root;
public:
PreFixTree()
root = new Node();
void Insert(string word)
Node* curr = root;
for (auto ch : word)
int index = ch - 'a';
//已存在相同字符的节点,继续遍历
if (curr->child[index])
curr = curr->child[index];
continue;
//新建节点
curr->child[index] = new Node;
curr = curr->child[index];
curr->isWord = true;
bool Search(string word)
Node* curr = root;
for (auto ch : word)
int index = ch - 'a';
//不存在节点
if (!curr->child[index])
return false;
curr = curr->child[index];
return curr->isWord ? true : false;
;
Q5 给定一个存储股票价格的数组,代表某股票每天的价格,采用t+1方式,当天买隔天卖。计算出最优的盈利价格,比如:
输入:[7,1,5,3,6,4]
输出:5
即买入价格 1 ,到 卖出价格为6时,收益最大为5.
#include <vector>
#include <math.h>
using namespace std;
//给出股票的价格数组,找出最佳买卖点,得出收益
class FindStockBuySell
public:
static int FindResult(vector<int> prices)
if (prices.empty())
return 0;
int result = 0;
int minPrice = prices[0];
for (int i = 1; i < prices.size(); ++i)
result = max(result, prices[i] - minPrice);
minPrice = min(minPrice, prices[i]);
return result;
//动态规划
static int SumBuySell(vector<int> prices)
if (prices.empty())
return 0;
int n = prices.size();
int** dp = new int*[n];
for (int i = 0; i < n; ++i)
dp[i] = new int[2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < n; ++i)
//不持有时的收益
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
//持有时的收益
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
//不持有时的收益
return dp[n-1][0];
//贪心算法
static int SumBuySell2(vector<int> prices)
if (prices.empty())
return 0;
int result = 0;
for (int i = 1; i < prices.size(); ++i)
if (prices[i] > prices[i - 1])
result += (prices[i] - prices[i - 1]);
return result;
;
Q6 位运算:反转一个uint32整数的位值
class ReverseBits
public:
static uint32_t Reverse(uint32_t num)
for (int i = 31, j = 0; i > j; --i, ++j)
//确认i j 位的bit值是否有设置
bool iset = (num & (1 << i)) != 0;
bool jset = (num & (1 << j)) != 0;
//排除i\\j位均为0的情况
if ((iset && !jset) || (jset && !iset))
//异或:两个位相同为0,相异为1, 实现反转
num = (num ^ (1 << i));
num = (num ^ (1 << j));
return num;
;
Q7 实现pow 幂等计算
class Pow
public:
static double CalculatePow(double x, int n)
long long pow = n;
//如果n小于0,采用1.0除与pow(x,abs(n))的结果值
bool isNeg = n < 0;
//abs(n)
pow = isNeg ? -pow : pow;
double res = 1.0;
double xpow = x;
while (pow != 0)
if (pow & 1)
res *= xpow;
xpow *= xpow;
//采用位运算
pow >>= 1;
return isNeg ? 1.0 / res : res;
;
Q8 转换excel的列名如 1->A, 2->B , 26->Z, 28->AB…
#include <string>
class ConvertToTitle
public:
static std::string Convert(int i)
std::string col;
while (i > 26)
col += 'A' + (i / 26 - 1);
i /= 26;
if (i > 0)
col += 'A' + i - 1;
return col;
;
以上是关于交易系统开发技能及面试之TechCoding的主要内容,如果未能解决你的问题,请参考以下文章