我正在尝试为Poloniex Bitcoin exchange构建一个订单簿表示形式。我正在订阅Push-API,它通过Websocket发送订单的更新。问题是随着时间的推移,我的订单簿中的不一致,即应该删除的订单仍在我的书中。

下图的订单簿具有以下格式:

Exchange-Name - ASK - Amount - Price | Price - Amount - BID - Exchange-Name

c++ - 为比特币交易所建立订单簿表示-LMLPHP

左侧(ASK)是出售货币的人。右侧(BID)是购买货币的人。 BTCUSD ETHBTC ETHUSD 描述了不同的市场。 BTCUSD 表示将比特币换成美元, ETHBTC 表示以太坊换成比特币, ETHUSD 表示以太坊换成美元。

Poloniex通过Websocket以JSON格式发送更新。这是此类更新的示例:
[
   36,
   7597659581972377,
   8089731807973507,
   {},
   [
      {"data":{"rate":"609.00000029","type":"bid"},"type":"orderBookRemove"},{"data":{"amount":"0.09514285","rate":"609.00000031","type":"bid"},"type":"orderBookModify"}
   ],
   {
      "seq":19976127
   }
]
  • json [0] 对于此问题可以忽略。
  • json [1] 是市场标识符。这意味着我发送了一个诸如“订阅市场BTCUSD”的请求,他们回答“BTCUSD更新将以标识符号7597659581972377发送”。
  • json [2] 对于此问题可以忽略。
  • json [3] 对于这个问题可以忽略。
  • json [4] 包含实际的更新数据。以后再说。
  • json [5] 包含序列号。如果更新顺序不正确,它将用于正确执行更新。因此,如果我在1秒钟内按命令1-3-5-4-2收到5个更新,则它们必须像1-2-3-4-5那样执行。每个市场都有一个不同的“序列号序列”。

  • 如我所说, json [4] 包含一系列更新。 json[4][array-index]["type"]中有三种不同的类型:
  • orderBookModify :特定价格的可用金额已更改。
  • orderBookRemove :该订单不再可用,必须将其删除。
  • newTrade :可用于建立交易历史。我尝试执行的操作不是必需的,因此可以忽略它。

  • 如果是 orderBookRemove ,则json[4][array-index]["data"]包含两个值;如果是 orderBookModify ,则[36,8932491360003688,1315671639915103,{},[],{"seq":98045310}]包含三个值。
  • 汇率:价格。
  • 数量(仅当存在orderBookModify时才存在):新数量。
  • 类型:询问或出价。

  • 还有一种特殊的消息:
    m_mMarkets
    它仅包含一个序列号。这是一种心跳消息,不发送任何更新。

    编码

    我使用三个容器:
    std::map<std::uint64_t,CMarket> m_mMarkets;
    std::map<CMarket, long> m_mCurrentSeq;
    std::map<CMarket, std::map<long, web::json::value>> m_mStack;
    
    m_mCurrentSeq用于将市场标识符编号映射到存储在我的程序中的市场。
    m_mStack用于存储每个市场的当前序列号。
    long按市场和序列号(即ojit_code的目的)存储更新,直到可以执行。

    这是接收更新的部分:
    // ....
    
    // This method can be called asynchronously, so lock the containers.
    this->m_muMutex.lock();
    
    // Map the market-identifier to a CMarket object.
    CMarket market = this->m_mMarkets.at(json[1].as_number().to_uint64());
    
    // Check if it is a known market. This should never happen!
    if(this->m_mMarkets.find(json[1].as_number().to_uint64()) == this->m_mMarkets.end())
    {
        this->m_muMutex.unlock();
        throw std::runtime_error("Received Market update of unknown Market");
    }
    
    // Add the update to the execution-queue
    this->m_mStack[market][(long)json[5]["seq"].as_integer()] = json;
    
    // Execute the execution-queue
    this->executeStack();
    
    this->m_muMutex.unlock();
    
    // ....
    

    现在是执行队列。我认为这是我的错误所在。

    函数:“executeStack”:
    for(auto& market : this->m_mMarkets) // For all markets
    {
        if(this->m_mCurrentSeq.find(market.second) != this->m_mCurrentSeq.end()) // if market has a sequence number
        {
            long seqNum = this->m_mCurrentSeq.at(market.second);
    
            // erase old entries
            for(std::map<long, web::json::value>::iterator it = this->m_mStack.at(market.second).begin(); it != this->m_mStack.at(market.second).end(); )
            {
                if((*it).first < seqNum)
                it = this->m_mStack.at(market.second).erase(it);
                else
                ++it;
            }
    
            // This container is used to store the updates to the Orderbook temporarily.
            std::vector<Order> addOrderStack{};
    
            while(this->m_mStack.at(market.second).find(seqNum) != this->m_mStack.at(market.second).end())// has entry for seqNum
            {
                web::json::value json = this->m_mStack.at(market.second).at(seqNum);
    
                for(auto& v : json[4].as_array())
                {
                    if(v["type"].as_string().compare("orderBookModify") == 0)
                    {
                        Order::Type t = v["data"]["type"].as_string().compare("ask") == 0 ? Order::Type::Ask : Order::Type::Bid;
                        Order newOrder(std::stod(v["data"]["rate"].as_string()), std::stod(v["data"]["amount"].as_string()), t, market.second, this->m_pclParent, v.serialize());
    
                        addOrderStack.push_back(newOrder);
    
                    } else if(v["type"].as_string().compare("orderBookRemove") == 0)
                    {
                        Order::Type t = v["data"]["type"].as_string().compare("ask") == 0 ? Order::Type::Ask : Order::Type::Bid;
                        Order newOrder(std::stod(v["data"]["rate"].as_string()), 0, t, market.second, this->m_pclParent, v.serialize());
    
                        addOrderStack.push_back(newOrder);
    
                    } else if(v["type"].as_string().compare("newTrade") == 0)
                    {
                        //
                    } else
                    {
                        throw std::runtime_error("Unknown message format");
                    }
                }
    
                this->m_mStack.at(market.second).erase(seqNum);
                seqNum++;
            }
    
            // The actual OrderList gets modified here. The mistake CANNOT be inside OrderList::addOrderStack, because I am running Orderbooks for other exchanges too and they use the same method to modify the Orderbook, and they do not get inconsistent.
    
            if(addOrderStack.size() > 0)
            OrderList::addOrderStack(addOrderStack);
    
            this->m_mCurrentSeq.at(market.second) = seqNum;
    
        }
    }
    

    因此,如果运行时间较长,则订单簿将变得不一致。这意味着应该删除的订单仍然可用,并且书中有错误的条目。我不太确定为什么会这样。也许我对序列号做错了,因为似乎更新栈并不总是正确执行。我已经尝试了所有想到的方法,但是我无法使其正常工作,现在我不知道可能出什么问题了。如果您有任何疑问,请随时提问。

    最佳答案

    tl; dr:Poloniex API不完善,会丢弃消息。有些根本就没到。我发现,无论世界上什么位置,所有订阅的用户都会发生这种情况。

    希望有关使用Autobahn | cpp连接到Poloniex的Websocket API(here)的答案有用。我怀疑您已经知道了(否则这个问题/问题对您来说就不存在)。正如您可能聚集的那样,我也有一个用C++编写的Crypto Currency Bot。我一直在进行这项工作大约3.5年。

    您所面临的问题也是我必须克服的。在这种情况下,我不希望提供我的源代码,因为您的处理速度会对您的利润率产生巨大影响。但是,我将提供sudo代码,使我对如何处理Poloniex的Web Socket事件处理有一些非常粗略的了解。

    //Sudo Code
    void someClass::handle_poloniex_ws_event(ws_event event){
        if(event.seq_num == expected_seq_num){
           process_ws_event(event)
           update_expected_seq_num
        }
        else{
            if(in_cache(expected_seq_num){
                process_ws_event(from_cache(expected_seq_num))
                update_expected_seq_num
            }
            else{
                cache_event(event)
            }
        }
    }
    

    注意,上面我写的是我正在做的 super 简化版。我的实际解决方案是大约有500多行,并带有“goto xxx”和“goto yyy”。我建议采用时间戳/ cpu时钟周期计数,并与当前时间/周期计数进行比较,以帮助您在任何给定时刻做出决定(例如,我是否应该等待丢失的事件,应该继续处理并注意程序的其余部分)可能有误,我是否应该利用GET请求来填充表格等?)。我确定你知道,这里的游戏名称是速度。祝好运!希望听到你的消息。 :-)

    关于c++ - 为比特币交易所建立订单簿表示,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/39347965/

    10-11 22:40
    查看更多