Несколько экземпляров класса обновляются, когда отдельный экземпляр должен быть - PullRequest
0 голосов
/ 17 марта 2020

Я строю агент-ориентированную модель, которая имитирует фондовый рынок, в котором разные участники ведут себя полурандомизированным образом. По какой-то причине несколько агентов одного класса ведут одинаковое торговое поведение.

Основа c Вопрос: почему все экземпляры моего класса обновляют свое состояние, а не только один экземпляр?

Ниже я публикую код, который я использую. После этого некоторые пояснительные примечания, чтобы было более понятно, что происходит.


Вот код самой модели, в которой используются некоторые элементы библиотеки моделирования на основе агента Mesa:

    '''
    Simulate a market. Create company and trader agents, simulate trades,
    track macro variables and returns.
    '''
    def __init__(self,
                 companies=10, # TK All of these are temp values
                 noisetraders=2,
                 marketmakers=5,
                 fundamentaltraders=0,
                 systematictraders=0,
                 passivetraders=0,
                 rfr = 0.02,
                 days = 5000):
        super().__init__()
        self.time = 0 
        self.days = days # length of simulation
        # Macro variables
        self.widget_price = 10.00
        self.rfr = rfr

        self.companies_list = [Company(str(x), self) for x in range(companies)]
        self.orderbook = []
        # pre-populating trades list so market-makers start somewhere
        self.tape = [(x.ticker, 100, 10.00, 0) for x in self.companies_list]

        # Update traders list
        self.mm_list = [MarketMaker(x, self) for x in range(marketmakers)]
        self.traders_list = [NoiseTrader(x, self) for x in range(noisetraders)]
        self.traders_list += [FundamentalTrader(x, self) for x in range(fundamentaltraders)]
        self.traders_list += [SystematicTrader(x, self) for x in range(systematictraders)]
        self.traders_list += [PassiveTrader(x, self) for x in range(passivetraders)]

    def step(self):
        self.time += 1
        self.orderbook = [] # all orders GTC for now
        if self.widget_price >= 1.00:
            self.widget_price *= (1.0+ random.normalvariate(.0001, 0.01)) 
        else:
            self.widget_price += 0.01
        for trader in self.traders_list:
            for mm in self.mm_list:
                mm.step()
            trader.step()

    def run_model(self):
        for i in range(self.days):
            self.time = i
            self.step()

Вот как выглядит объект Order:

class Order:
    '''
    An order. All orders are limit orders, but for now traders submit 
    limit orders at the bid/ask.

    Orders can be submitted at arbitrary precision, but must be rounded.

    Orders below 0 converted to 0.01
    '''
    def __init__(self, otype, trader, ticker, px, qt, model):
        self.otype = otype
        self.trader = trader
        self.ticker = ticker
        self.px = max(round(px, 2), 0.01)
        self.qt = qt
        self.model = model

    def __repr__(self):
        return self.otype+' '+str(self.qt)+' '+self.ticker+' @ '+str(self.px)

    def execute(self, counterparty):
        if self.otype == 'buy':
            self.trader.buy(self.model, counterparty, self.ticker, self.qt, self.px)
        else:
            self.trader.sell(self.model, counterparty, self.ticker, self.qt, self.px)
        self.model.orderbook.remove(self)

Код для базового c класса трейдера:

class Trader(Agent):
    '''
    A high-level class from which all specific market actors inherit.
    '''
    def __init__(self,
                 unique_id,
                 model,
                 cash = 10000.00,
                 positions = defaultdict(int),
                 gross_range = (0,2.0),
                 net_range = (-1.0,1.0),
                 confidence = random.random()):
        self.unique_id = unique_id
        self.model = model
        self.cash = cash
        self.positions = positions
        self.gross_range = gross_range
        self.net_range =  net_range
        self.confidence = confidence # a general confidence indicator
        self.wealth = self.cash + sum([self.positions[ticker] * self.model.last_px(ticker) for ticker in list(self.positions.keys())])
        self.wealth_hist = [(0, cash)]

    def buy(self, model, seller, ticker, shares, price):
        """Execute a buy trade."""
        if ticker not in [x.ticker for x in self.model.companies_list]:
            raise Exception("Invalid ticker")
        value = shares * price
        self.cash -= value
        self.positions[ticker] += shares
        seller.cash += value
        seller.positions[ticker] -= shares
        if self.positions[ticker] == 0:
            del self.positions[ticker]
        if seller.positions[ticker] == 0:
            del seller.positions[ticker]
        self.model.tape.append((ticker, shares, price, self.model.time))

    def sell(self, model, buyer, ticker, shares, price):
        """Execute a sell trade."""
        if ticker not in [x.ticker for x in self.model.companies_list]:
            raise Exception("Invalid ticker")
        value = shares * price
        self.cash += value
        self.positions[ticker] -= shares
        buyer.cash -= value
        buyer.positions[ticker] += shares
        if self.positions[ticker] == 0:
            del self.positions[ticker]
        if buyer.positions[ticker] == 0:
            del buyer.positions[ticker]
        self.model.tape.append((ticker, shares, price, self.model.time))

    def net_exposure(self):
        '''Returns % net long position'''
        net = sum([self.positions[x] * m.last_px(x) for x in self.positions])
        return net / self.wealth

    def gross_exposure(self):
        '''Returns % gross position value'''
        gross = sum([abs(self.positions[x] * m.last_px(x)) for x in self.positions])
        return gross / self.wealth

    def check_margin(self):
        '''Return True if margin is below maintenance margin.'''
        gross_long = sum([self.positions[x] * m.last_px(x) for x in self.postitions if self.positions[x] >= 0])
        gross_short = sum([self.positions[x] * m.last_px(x) for x in self.postitions if self.positions[x] < 0])
        if self.cash >= self.wealth:
            self.gross_short / self.wealth  < 3
        else:
            self.gross_long / self.wealth < 3

    def buying_power(self):
        '''Total amount investor can go long.'''
        max_gross = self.wealth * self.gross_range[1] - self.gross_exposure()
        max_net = self.wealth * self.net_range[1] - self.max_exposure()
        return min(max_gross, max_net)

    def selling_power(self):
        '''Total amount trader call sell (or short).'''
        min_net = self.net_exposure() - self.wealth * self.net_range[0]

    def margin_liquidation(self, model):
        '''
        If trade is below maintenance margin, liquididate all positions.

        Note: this function is overridden for market-makers.
        '''
        for p in self.positions:
            if self.positions[p] > 0:
                model.top_of_book(p, 'sell').execute(self)
            if self.positions[p] < 0:
                model.top_of_book(p, 'buy').execute(self)

    def step(self):
        '''
        We allow time to start negative so companies can operate for a while
        and create fundamental data before traders trade.
        '''
        if self.model.time < 0:
            pass
        else:
            self.cash = self.cash*(1 + self.model.rfr) ** (1/252)
            self.wealth_hist.append((self.model.time, self.wealth))

Код для шумового трейдера:

class NoiseTrader(Trader):
    '''
    A trader who makes trades mostly at random.

    These will be our main source of noise, so we'll vary their number to 
    calibrate the model.    
    '''

    def __init__(self,
                 unique_id,
                 model,
                 cash = 10000.00,
                 positions = defaultdict(int),
                 gross_range = (0,2.0),
                 net_range = (-1.0,1.0),
                 confidence = random.random()):
        super().__init__(unique_id,
                         model,
                         cash,
                         positions,
                         gross_range,
                         net_range,
                         confidence)

    def random_buy(self):
        ticker = random.choice([c.ticker for c in self.model.companies_list])
        try:
            m.top_of_book(ticker, 'buy').execute(self)
        except:
            print("Buy failed! Ticker: ", ticker)

    def random_sell(self):
        ticker = random.choice([c.ticker for c in self.model.companies_list])
        try:
            m.top_of_book(ticker, 'sell').execute(self)
        except:
            print("Sell failed! Ticker: ", ticker)

    def step(self):
        self.cash = self.cash*(1 + self.model.rfr) ** (1/252)
        if self.gross_exposure() > self.gross_range[1] or self.net_exposure() > self.net_range[1]:
            self.random_sell()
            if self.net_exposure() < self.net_range[0]:
                self.random_buy()
            else:
                random.choice([self.random_buy(), self.random_sell()])
        else:
            random.choice([self.random_buy(), self.random_sell()])
        self.wealth = self.cash + sum([self.positions[ticker] * self.model.last_px(ticker) for ticker in list(self.positions.keys())])
        self.wealth_hist.append((self.model.time, self.wealth))

А для маркет-мейкеров:

class MarketMaker(Trader):
    '''Class representing a market-maker or specialist

    In this model, market-makers are responsible for posting all limit orders, 
    i.e. they provide 100% of liquidity. Market-makers have additional risk 
    criteria:

    - Overnight risk represents their preference for holding a position vs
    going to flat.
    - Net risk represents their willingness to be net long/short.

    Both preferences affect the width/direction of spreads they quote.
    '''
    def __init__(self,
                 unique_id,
                 model,
                 cash = 10000.00,
                 positions = defaultdict(int),
                 gross_range = (0,2.0),
                 net_range = (-1.0,1.0),
                 confidence = random.random(),
                 overnight_risk = random.random(),
                 spread = 0.1): # quoting a 10% wide spread initially
        super().__init__(unique_id,
                         model,
                         cash,
                         positions,
                         gross_range,
                         net_range,
                         confidence)
        self.confidence = random.random() 
        self.overnight_risk = overnight_risk
        # have to run these again to get new value
        # self.owned = [(c[0], self.model.last_px(c[0])) for c in self.positions]
        self.spread = spread 

    def update_risks(self):
        '''
        Update risk tolerance.

        Risk tolerance approaches 1 as wealth rises, or 0 as it falls.
        '''
        if self.wealth >= sum([x[1] for x in self.wealth_hist[:5]])/len(self.wealth_hist[:5]):
            self.confidence += 0.5 * (1-self.confidence)
            self.overnight_risk += 0.5 * (1-self.overnight_risk)
        else:
            self.confidence -= 0.5 * self.confidence
            self.overnight_risk -= 0.5 * self.overnight_risk

    def update_quotes(self):
        '''
        Market maker updates order book, quoting a spread in each name based on 
        their position and risk-tolerance.
        '''
        tape = self.model.tape
        lasts = [(c.ticker, self.model.last_px(c.ticker)) for c in self.model.companies_list]
        owned = [(c, self.model.last_px(c)) for c in self.positions]
        unowned = [c for c in lasts if c not in owned]
        if (not unowned):
            pass
        else:
            for co in unowned:
                self.model.orderbook.append(Order('buy',
                                                  self,
                                                  co[0],
                                                  co[1]-(self.spread/(2 * self.confidence)),
                                                  100,
                                                  self.model))
                self.model.orderbook.append(Order('sell',
                                                  self,
                                                  co[0],
                                                  co[1]+(self.spread/(2 * self.confidence)),
                                                  100,
                                                  self.model))
        if (not owned):
            pass
        else:
            for co in owned:
                if self.positions[co[0]] > 0:
                    adj_midpoint = co[1] - (self.spread/2 * self.confidence * self.overnight_risk)
                    self.model.orderbook.append(Order('buy',
                                                      self,
                                                      co[0],
                                                      adj_midpoint - (self.spread/2 * self.confidence),
                                                      100,
                                                      self.model))
                    self.model.orderbook.append(Order('sell',
                                                      self,
                                                      co[0],
                                                      adj_midpoint + (self.spread/2 * self.confidence),
                                                      100,
                                                      self.model))
                else:
                    adj_midpoint = co[1] + (self.spread/2 * self.confidence * self.overnight_risk)
                    self.model.orderbook.append(Order('buy',
                                                      self,
                                                      co[0],
                                                      adj_midpoint - (self.spread/2 * self.confidence),
                                                      100,
                                                      self.model))
                    self.model.orderbook.append(Order('sell',
                                                      self,
                                                      co[0],
                                                      adj_midpoint + (self.spread/2 * self.confidence),
                                                      100,
                                                      self.model))

    def margin_liquidation(self):
        '''
        If a market-maker runs out of maintenance margin, they liquidate by
        offering extremely wide quotes on anything not in their book, and
        and quoting midpoint-vs-extreme on anything still in their book.
        '''
        pass        

    def step(self):
        self.cash = self.cash*(1 + self.model.rfr) ** (1/252)
        self.update_risks()
        self.update_quotes()
        self.wealth = self.cash + sum([self.positions[ticker] * self.model.last_px(ticker) for ticker in list(self.positions.keys())])
        self.wealth_hist.append((self.model.time, self.wealth))

Так как же это предполагается работать?

Чтобы упростить вещи, у меня есть два вида трейдеры: маркет-мейкеры делают то, что делают традиционные маркет-мейкеры и биржевые специалисты; для каждой акции, которую они торгуют, они указывают цену, по которой они будут покупать, и цену, по которой они будут продавать. Они зарабатывают деньги, ожидая, что заказы в значительной степени случайны. В этой модели они ведут себя с упрощенной версией реального поведения на рынке: когда они оптимистично c (т.е. зарабатывают деньги), они предлагают торговать близко к последней цене; если они пессимисты c, они все еще торгуют, но они указывают цены дальше от центра рынка.

Реализация этого заключается в том, что маркет-мейкеры создают несколько экземпляров ордеров, каждый из которых добавляется в книгу заказов модели.

Сложная структура ветвления для маркет-мейкеров предназначена для сохранения их от накопления больших позиций в отдельных акциях; по мере того, как размер их позиции увеличивается, они пытаются более агрессивно довести ее до нуля.

Шумовые трейдеры становятся более прямыми. Пока у них есть сила покупки / продажи, они торгуют. то есть они выбирают открытые ордера в книге заказов и выполняют эти ордера.

Что происходит, когда я на самом деле запускаю, так это то, что каждый раз, когда трейдер с шумом выполняет ордер, у всех трейдеров с шумом есть позиции, и все маркет-мейкеры имеют смещенную позицию

...