简体   繁体   English

如何修复 RuntimeError:在 Python 迭代期间 deque 发生变异

[英]How to fix RuntimeError: deque mutated during iteration in Python

I'm trying to implement market simulation for algorithmic trading and I've found this code on github https://github.com/DrAshBooth/PyLOB .我正在尝试为算法交易实现市场模拟,我在 github https://github.com/DrAshBooth/PyLOB上找到了此代码。 The problem is when I'm running my code for small window, for example, 2 days, everything is fine and I get my expected results.问题是当我为小窗口运行代码时,例如 2 天,一切都很好,我得到了预期的结果。 But when I increase window to 20 days or more, I get "RuntimeError: deque mutated during iteration".但是当我将窗口增加到 20 天或更长时间时,我得到“RuntimeError: deque mutated during iteration”。 I've checked my code but I've never found anything that could mutate deque during my runs.我已经检查了我的代码,但我从来没有发现任何可以在我的运行过程中改变 deque 的东西。 Below is the part of the code that produces the error:以下是产生错误的代码部分:

    self.tape = deque(maxlen=None)
    .
    .
    .
    def avg_volume_traded(self):
       if self.tape != None and len(self.tape) > 0:
          num = 0
          total_volume = 0
          for entry in self.tape:
             total_volume = entry['qty'] + total_volume
             num += 1
          if num != 0 and total_volume != None:
             return total_volume, num
       else:
          return None, None

This is the actual error message:这是实际的错误消息:

    Exception in thread Thread-10986:
    Traceback (most recent call last):
      File "/home/hamid/anaconda3/lib/python3.6/threading.py", line 916, in _bootstrap_inner
        self.run()
      File "/home/hamid/anaconda3/lib/python3.6/threading.py", line 864, in run
        self._target(*self._args, **self._kwargs)
      File "exchange_envirnment.py", line 60, in _doit
        self.func(self.args[0], self.args[1])
      File "/home/hamid/dukto/src2/src_new/traders/market_maker_trader.py", line 46, in trade
        self.type_three(lob_obj, reporter_obj)
      File "/home/hamid/dukto/src2/src_new/traders/market_maker_trader.py", line 285, in type_three
        max_volume = lob_obj.max_volume_traded()
      File "/home/hamid/dukto/src2/src_new/PyLOB/orderbook.py", line 395, in max_volume_traded
        for entry in self.tape:
    RuntimeError: deque mutated during iteration

This is the main part that use threading in two parts(class Periodic and day_period):这是在两个部分(类 Periodic 和 day_period)中使用线程的主要部分:

class Periodic(object):
    def __init__(self, object, compression_factor, args=[], kwargs={}):
        self.compression_factor = compression_factor
        self.object = object
        self.func = object.trade
        self.args = args
        self.kwargs = kwargs
        self.seppuku = Event()
    def start(self):
        self.seppuku.clear()
        self.proc = Thread(target=self._doit)
        self.proc.start()
    def stop(self):
        self.seppuku.set()
        self.proc.join()
    def _doit(self):
        while True:
            self.seppuku.wait(self.object.interval / self.compression_factor)
            if self.seppuku.is_set():
                break
            self.func(self.args[0], self.args[1])

class day_period(object):
    def __init__(self, object, compression_factor, args=[], kwargs={}):
        self.period = (3600 * 4) / compression_factor
        self.func = object.run
        self.args = args
        self.kwargs = kwargs
        self.seppuku = Event()
    def start(self):
        self.seppuku.clear()
        self.proc = Thread(target=self._doit)
        self.proc.start()
    def stop(self):
        self.seppuku.set()
        self.proc.join()
    def _doit(self):
        while True:
            self.seppuku.wait(self.period)
            if self.seppuku.is_set():
                break
            self.func(self.args)

class intra_day_traders_mng(object):
    def __init__(self, simulation_config):
        self.config = simulation_config
        self.agents_list = []
        self.agents_dict = {}
        self.p_list = []
        self.compression_factor = simulation_config['simulation_config']['compression_factor']
        self.trader_creator()
        self.first_time = True
        self.day_of_simulation  = simulation_config['simulation_config']['day_number']

    def trader_creator(self):
        for agent_name in self.config['agents']['intra_day']:
            for config in self.config['agents']['intra_day'][agent_name]:
                if agent_name == 'nonclassified_trader':
                    for k in range(config['n_traders']):
                        self.agents_list.append(NON_CLASSIFIED_TRADER_INTRADAY(config))
                        time.sleep(.1)
        for agent_name in self.config['agents']['daily']:
            for config in self.config['agents']['daily'][agent_name]:
                if agent_name == 'nonclassified_trader':
                    for k in range(config['n_traders']):
                        self.agents_list.append(NON_CLASSIFIED_TRADER_DAILY(config))
                        time.sleep(0.1)
                if agent_name == "market_maker_trader":
                    for k in range(config['n_traders']):
                        self.agents_list.append(MARKET_MAKER_TRADER_DAILY(config))
                        time.sleep(0.1)
        for agent in self.agents_list:
            self.agents_dict.update({agent.id: agent})
        for agent in self.agents_list:
            agent.set_trader_dict(self.agents_dict)

    def random_initial(self):
        agents_random_list = random.choices(self.agents_list, k=len(self.agents_list))
        return agents_random_list

    def run(self, args):
        lob = args[0]
        reporter_obj = args[1]
        # when the trader running for first time
        if self.first_time == True:
            lob.time_obj.reset()
            agents_random_list = self.random_initial()
            for agent in agents_random_list:
                self.p_list.append(Periodic(agent, self.compression_factor, args=(lob,reporter_obj)))
                self.p_list[-1].start()
                time.sleep(.1)
            self.first_time = False
        else:
            for proc in self.p_list:
                proc.stop()
            for agent in self.agents_list:
                agent.reset_trader(lob)
            time_series = lob.ohcl()
            if len(time_series) == self.day_of_simulation :
                out = {'out':time_series}
                with open('output.json', 'w') as outfile:
                    json.dump(out, outfile)
                reporter_obj.save_as_csv()
                trade_summary = lob.trade_summary()
                with open('trade_report.csv', 'w') as csvFile:
                    writer = csv.writer(csvFile)
                    writer.writerows(trade_summary)
                csvFile.close()
                sys.exit()
            print("***********************************************************************************")
            print("day is:",lob.time_obj.day)
            lob.time_obj.reset()
            for proc in self.p_list:
                proc.start()
                time.sleep(.1)

if __name__ == '__main__':
    with open('config.json', 'r') as f:
        simulation_config = json.load(f)
    intra_day_mng_obj = intra_day_traders_mng(simulation_config)
    reporter_obj = REPORTER()
    # for synchronization of time
    time_obj = TIME_MANAGE(compression_factor=simulation_config['simulation_config']['compression_factor'])
    lob = OrderBook(time_obj, tick_size=simulation_config['simulation_config']['tickSize'])
    day_period(intra_day_mng_obj, simulation_config['simulation_config']['compression_factor'], args=(lob,reporter_obj)).start()

And finally the "OrderBook" that defines "self.tape" in the below code:最后是在以下代码中定义“self.tape”的“OrderBook”:

class OrderBook():
    def __init__(self, time_obj, tick_size=0.0001):
        self.tape = deque(maxlen=None)  # Index [0] is most recent trade
        self.bids = OrderTree()
        self.asks = OrderTree()
        self.lastTick = None
        self.lastTimestamp = 0
        self.tickSize = tick_size
        self.time = 0
        self.nextQuoteID = 0
        self.time_series = []
        self.time_obj = time_obj

    def clipPrice(self, price):
        return round(price, int(math.log10(1 / self.tickSize)))

    def updateTime(self):
        self.time = int(self.time_obj.now()['time'])

    def processOrder(self, quote, fromData, verbose):
        orderType = quote['type']
        orderInBook = None
        if fromData:
            self.time = quote['timestamp']
        else:
            self.updateTime()
            quote['timestamp'] = self.time
        if quote['qty'] <= 0:
            sys.exit('processLimitOrder() given order of qty <= 0')
        if not fromData: self.nextQuoteID += 1
        if orderType == 'market':
            trades = self.processMarketOrder(quote, verbose)
        elif orderType == 'limit':
            quote['price'] = self.clipPrice(quote['price'])
            trades, orderInBook = self.processLimitOrder(quote, fromData, verbose)
        else:
            sys.exit("processOrder() given neither 'market' nor 'limit'")
        return trades, orderInBook

    def processOrderList(self, side, orderlist,
                         qtyStillToTrade, quote, verbose):
        trades = []
        qtyToTrade = qtyStillToTrade
        while len(orderlist) > 0 and qtyToTrade > 0:
            headOrder = orderlist.getHeadOrder()
            tradedPrice = headOrder.price
            counterparty = headOrder.tid
            if qtyToTrade < headOrder.qty:
                tradedQty = qtyToTrade
                newBookQty = headOrder.qty - qtyToTrade
                headOrder.updateQty(newBookQty, headOrder.timestamp)
                qtyToTrade = 0
            elif qtyToTrade == headOrder.qty:
                tradedQty = qtyToTrade
                if side == 'bid':
                    self.bids.removeOrderById(headOrder.idNum)
                else:
                    self.asks.removeOrderById(headOrder.idNum)
                qtyToTrade = 0
            else:
                tradedQty = headOrder.qty
                if side == 'bid':
                    self.bids.removeOrderById(headOrder.idNum)
                else:
                    self.asks.removeOrderById(headOrder.idNum)
                qtyToTrade -= tradedQty
            if verbose: print('>>> TRADE \nt=%d $%f n=%d p1=%d p2=%d' %
                              (self.time, tradedPrice, tradedQty,
                               counterparty, quote['tid']))

            transactionRecord = {'timestamp': self.time,
                                 'price': tradedPrice,
                                 'qty': tradedQty,
                                 'time': self.time,
                                 'day': self.time_obj.now()['day']}
            if side == 'bid':
                transactionRecord['party1'] = [counterparty,
                                               'bid',
                                               headOrder.idNum]
                transactionRecord['party2'] = [quote['tid'],
                                               'ask',
                                               None]
            else:
                transactionRecord['party1'] = [counterparty,
                                               'ask',
                                               headOrder.idNum]
                transactionRecord['party2'] = [quote['tid'],
                                               'bid',
                                               None]
            self.tape.append(transactionRecord)
            trades.append(transactionRecord)
        return qtyToTrade, trades

    def processMarketOrder(self, quote, verbose):
        trades = []
        qtyToTrade = quote['qty']
        side = quote['side']
        if side == 'bid':
            while qtyToTrade > 0 and self.asks:
                bestPriceAsks = self.asks.minPriceList()
                qtyToTrade, newTrades = self.processOrderList('ask',
                                                              bestPriceAsks,
                                                              qtyToTrade,
                                                              quote, verbose)
                trades += newTrades
        elif side == 'ask':
            while qtyToTrade > 0 and self.bids:
                bestPriceBids = self.bids.maxPriceList()
                qtyToTrade, newTrades = self.processOrderList('bid',
                                                              bestPriceBids,
                                                              qtyToTrade,
                                                              quote, verbose)
                trades += newTrades
        else:
            sys.exit('processMarketOrder() received neither "bid" nor "ask"')
        return trades

    def processLimitOrder(self, quote, fromData, verbose):
        orderInBook = None
        trades = []
        qtyToTrade = quote['qty']
        side = quote['side']
        price = quote['price']
        if side == 'bid':
            while (self.asks and
                   price >= self.asks.minPrice() and
                   qtyToTrade > 0):
                bestPriceAsks = self.asks.minPriceList()
                qtyToTrade, newTrades = self.processOrderList('ask',
                                                              bestPriceAsks,
                                                              qtyToTrade,
                                                              quote, verbose)

                trades += newTrades
            if qtyToTrade > 0:
                if not fromData:
                    quote['idNum'] = self.nextQuoteID
                quote['qty'] = qtyToTrade
                self.bids.insertOrder(quote)
                orderInBook = quote
        elif side == 'ask':
            while (self.bids and
                   price <= self.bids.maxPrice() and
                   qtyToTrade > 0):
                bestPriceBids = self.bids.maxPriceList()
                qtyToTrade, newTrades = self.processOrderList('bid',
                                                              bestPriceBids,
                                                              qtyToTrade,
                                                              quote, verbose)
                trades += newTrades
            if qtyToTrade > 0:
                if not fromData:
                    quote['idNum'] = self.nextQuoteID
                quote['qty'] = qtyToTrade
                self.asks.insertOrder(quote)
                orderInBook = quote
        else:
            sys.exit('processLimitOrder() given neither bid nor ask')
        return trades, orderInBook

    def avg_volume_traded(self):
        if self.tape != None and len(self.tape) > 0:
            num = 0
            total_volume = 0
            for entry in self.tape:
                total_volume = entry['qty'] + total_volume
                num += 1
            if num != 0 and total_volume != None:
                return total_volume, num
        else:
            return None, None

The problem is due to competing accesses between processOrderList() and avg_volume_traded() .问题是由于processOrderList()avg_volume_traded()之间的竞争访问。 The former modifies the deque while the latter is iterating over the deque.前者修改双端队列,而后者迭代双端队列。

An easy solution is to have list() atomically extract the data from the deque in avg_volume_traded() :一个简单的解决方案是让list()avg_volume_traded() 中的双端队列中自动提取数据:

def avg_volume_traded(self):
   if self.tape != None and len(self.tape) > 0:
      num = 0
      total_volume = 0
      for entry in list(self.tape):     # <-- atomic extraction step
         total_volume = entry['qty'] + total_volume
         num += 1
      if num != 0 and total_volume != None:
         return total_volume, num
   else:
      return None, None

It looks like you are using threads.看起来您正在使用线程。 And it is very likely that self.tape is changed by another thread.而且很可能 self.tape 被另一个线程改变了。 You should try and block that thread from mutating self.tape during execution of avg_volume_traded.您应该尝试在执行 avg_volume_traded 期间阻止该线程改变 self.tape。

As Tom Dalton noted, this exception can be thrown due to self.tape modification in different thread.正如Tom Dalton 所指出的,由于不同线程中的 self.tape 修改,可能会引发此异常。 I encountered this problem before, as for me, I fixed it by creating a Lock.我之前遇到过这个问题,至于我,我通过创建一个锁来解决它。 I can also suggest you just to ignore this exception, but this can lead to an undefiend behaviour我也可以建议您忽略此异常,但这可能会导致不正当行为

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 RuntimeError:OrderedDict在迭代期间发生变异(Python3) - RuntimeError: OrderedDict mutated during iteration (Python3) RuntimeError:OrderedDict在迭代过程中发生了变异 - RuntimeError: OrderedDict mutated during iteration Python 列表可以在迭代过程中发生变化,但不能在双端队列中发生变化。 为什么? - Python list can be mutated during iteration but not deque. Why? 如何解决这个python错误? RuntimeError:字典在迭代期间改变了大小 - How to fix this python error? RuntimeError: dictionary changed size during iteration 递归:如何在迭代RuntimeError期间避免Python集更改集 - Recursion: how to avoid Python set changed set during iteration RuntimeError RuntimeError:字典在python迭代期间更改了大小 - RuntimeError: dictionary changed size during iteration in python python RuntimeError:字典在迭代期间改变了大小 - python RuntimeError: dictionary changed size during iteration Python,RuntimeError:字典在迭代过程中更改了大小 - Python, RuntimeError: dictionary changed size during iteration Django Rest框架和python3.5 OrderedDict在迭代过程中发生了变异 - Django rest framework and python3.5 OrderedDict mutated during iteration 如何避免“RuntimeError: dictionary changed size during iteration”错误? - How to avoid "RuntimeError: dictionary changed size during iteration" error?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM