예제 #1
0
    def kernelTerminating(self):
        super().kernelTerminating()

        # If the oracle supports writing the fundamental value series for its
        # symbols, write them to disk.
        if hasattr(self.oracle, 'f_log'):
            for symbol in self.oracle.f_log:
                dfFund = pd.DataFrame(self.oracle.f_log[symbol])
                if not dfFund.empty:
                    dfFund.set_index('FundamentalTime', inplace=True)
                    self.writeLog(dfFund,
                                  filename='fundamental_{}'.format(symbol))
                    log_print("Fundamental archival complete.")
        if self.book_freq is None: return
        else:
            # Iterate over the order books controlled by this exchange.
            for symbol in self.order_books:
                start_time = dt.datetime.now()
                self.logOrderBookSnapshots(symbol)
                end_time = dt.datetime.now()
                print(
                    "Time taken to log the order book: {}".format(end_time -
                                                                  start_time))
                print("Order book archival complete.")
예제 #2
0
파일: Kernel.py 프로젝트: hogier/abides
    def __init__(self, kernel_name, random_state=None):
        # kernel_name is for human readers only.
        self.name = kernel_name
        self.random_state = random_state

        if not random_state:
            raise ValueError(
                "A valid, seeded np.random.RandomState object is required " +
                "for the Kernel", self.name)
            sys.exit()

        # A single message queue to keep everything organized by increasing
        # delivery timestamp.
        self.messages = queue.PriorityQueue()

        # currentTime is None until after kernelStarting() event completes
        # for all agents.  This is a pd.Timestamp that includes the date.
        self.currentTime = None

        # Timestamp at which the Kernel was created.  Primarily used to
        # create a unique log directory for this run.  Also used to
        # print some elapsed time and messages per second statistics.
        self.kernelWallClockStart = pd.Timestamp('now')

        # TODO: This is financial, and so probably should not be here...
        self.meanResultByAgentType = {}
        self.agentCountByType = {}

        # The Kernel maintains a summary log to which agents can write
        # information that should be centralized for very fast access
        # by separate statistical summary programs.  Detailed event
        # logging should go only to the agent's individual log.  This
        # is for things like "final position value" and such.
        self.summaryLog = []

        log_print("Kernel initialized: {}", self.name)
예제 #3
0
    def observePrice(self,
                     symbol,
                     currentTime,
                     sigma_n=1000,
                     random_state=None):
        # If the request is made after market close, return the close price.
        if currentTime >= self.mkt_close:
            r_t = self.advance_fundamental_value_series(
                self.mkt_close - pd.Timedelta('1ns'), symbol)
        else:
            r_t = self.advance_fundamental_value_series(currentTime, symbol)

        # Generate a noisy observation of fundamental value at the current time.
        if sigma_n == 0:
            obs = r_t
        else:
            obs = int(round(random_state.normal(loc=r_t, scale=sqrt(sigma_n))))

        log_print("Oracle: current fundamental value is {} at {}", r_t,
                  currentTime)
        log_print("Oracle: giving client value observation {}", obs)

        # Reminder: all simulator prices are specified in integer cents.
        return obs
예제 #4
0
 def modifyOrder(self, order, new_order):
     # Modifies the quantity of an existing limit order in the order book
     if not self.isSameOrder(order, new_order): return
     book = self.bids if order.is_buy_order else self.asks
     if not book: return
     for i, o in enumerate(book):
         if self.isEqualPrice(order, o[0]):
             for mi, mo in enumerate(book[i]):
                 if order.order_id == mo.order_id:
                     book[i][0] = new_order
                     for idx, orders in enumerate(self.history):
                         if new_order.order_id not in orders: continue
                         self.history[idx][new_order.order_id]['modifications'].append(
                             (self.owner.currentTime, new_order.quantity))
                         log_print("MODIFIED: order {}", order)
                         log_print("SENT: notifications of order modification to agent {} for order {}",
                                   new_order.agent_id, new_order.order_id)
                         self.owner.sendMessage(order.agent_id,
                                                Message({"msg": "ORDER_MODIFIED", "new_order": new_order}))
     if order.is_buy_order:
         self.bids = book
     else:
         self.asks = book
     self.last_update_ts = self.owner.currentTime
예제 #5
0
    def __init__(self, mkt_open, mkt_close, symbols):
        # Symbols must be a dictionary of dictionaries with outer keys as symbol names and
        # inner keys: r_bar, kappa, sigma_s.
        self.mkt_open = mkt_open
        self.mkt_close = mkt_close
        self.symbols = symbols
        self.f_log = {}

        # The dictionary r holds the most recent fundamental values for each symbol.
        self.r = {}

        # The dictionary megashocks holds the time series of megashocks for each symbol.
        # The last one will always be in the future (relative to the current simulation time).
        #
        # Without these, the OU process just makes a noisy return to the mean and then stays there
        # with relatively minor noise.  Here we want them to follow a Poisson process, so we sample
        # from an exponential distribution for the separation intervals.
        self.megashocks = {}

        then = dt.datetime.now()

        # Note that each value in the self.r dictionary is a 2-tuple of the timestamp at
        # which the series was computed and the true fundamental value at that time.
        for symbol in symbols:
            s = symbols[symbol]
            log_print(
                "SparseMeanRevertingOracle computing initial fundamental value for {}",
                symbol)
            self.r[symbol] = (mkt_open, s['r_bar'])
            self.f_log[symbol] = [{
                'FundamentalTime': mkt_open,
                'FundamentalValue': s['r_bar']
            }]

            # Compute the time and value of the first megashock.  Note that while the values are
            # mean-zero, they are intentionally bimodal (i.e. we always want to push the stock
            # some, but we will tend to cancel out via pushes in opposite directions).
            ms_time_delta = np.random.exponential(scale=1.0 /
                                                  s['megashock_lambda_a'])
            mst = self.mkt_open + pd.Timedelta(ms_time_delta, unit='ns')
            msv = s['random_state'].normal(loc=s['megashock_mean'],
                                           scale=sqrt(s['megashock_var']))
            msv = msv if s['random_state'].randint(2) == 0 else -msv

            self.megashocks[symbol] = [{
                'MegashockTime': mst,
                'MegashockValue': msv
            }]

        now = dt.datetime.now()

        log_print("SparseMeanRevertingOracle initialized for symbols {}",
                  symbols)
        log_print("SparseMeanRevertingOracle initialization took {}",
                  now - then)
예제 #6
0
    def placeOrder(self):
        # Called when it is time for the agent to determine a limit price and place an order.
        # updateEstimates() returns the agent's current total valuation for the share it
        # is considering to trade and whether it will buy or sell that share.
        v, buy = self.updateEstimates()

        # Select a requested surplus for this trade.
        R = self.random_state.randint(self.R_min, self.R_max + 1)

        # Determine the limit price.
        p = v  # - R if buy else v + R
        """
        if self.counter % 50 == 0:
            print("\nZI current limit price: ", p, "\n")

        self.counter += 1
        """

        # Either place the constructed order, or if the agent could secure (eta * R) surplus
        # immediately by taking the inside bid/ask, do that instead.
        bid, bid_vol, ask, ask_vol = self.getKnownBidAsk(self.symbol)
        if buy and ask_vol > 0:
            R_ask = v - ask
            if R_ask >= (self.eta * R):
                log_print(
                    "{} desired R = {}, but took R = {} at ask = {} due to eta",
                    self.name, R, R_ask, ask)
                p = ask
            else:
                log_print("{} demands R = {}, limit price {}", self.name, R, p)
        elif (not buy) and bid_vol > 0:
            R_bid = bid - v
            if R_bid >= (self.eta * R):
                log_print(
                    "{} desired R = {}, but took R = {} at bid = {} due to eta",
                    self.name, R, R_bid, bid)
                p = bid
            else:
                log_print("{} demands R = {}, limit price {}", self.name, R, p)

        # determine order size using the getOrderSize function
        self.getOrderSize()

        # Place the order.
        self.placeLimitOrder(self.symbol, self.order_size, buy, p)
예제 #7
0
 def handleOrderExecution(self, currentTime, msg):
     executed_order = msg.body['order']
     self.executed_orders.append(executed_order)
     executed_qty = sum(executed_order.quantity
                        for executed_order in self.executed_orders)
     self.rem_quantity = self.quantity - executed_qty
     log_print(
         f'[---- {self.name} - {currentTime} ----]: LIMIT ORDER EXECUTED - {executed_order.quantity} @ {executed_order.fill_price}'
     )
     log_print(
         f'[---- {self.name} - {currentTime} ----]: EXECUTED QUANTITY: {executed_qty}'
     )
     log_print(
         f'[---- {self.name} - {currentTime} ----]: REMAINING QUANTITY (NOT EXECUTED): {self.rem_quantity}'
     )
     log_print(
         f'[---- {self.name} - {currentTime} ----]: % EXECUTED: {round((1 - self.rem_quantity / self.quantity) * 100, 2)} \n'
     )
예제 #8
0
    def getPriceAtTime(self, symbol, query_time):
        """ Get the true price of a symbol at the requested time.
            :param symbol: which symbol to query
            :type symbol: str
            :param time: at this time
            :type time: pd.Timestamp
        """

        log_print("Oracle: client requested {} as of {}", symbol, query_time)

        fundamental_series = self.fundamentals[symbol]
        time_of_query = pd.Timestamp(query_time)

        series_open_time = fundamental_series.index[0]
        series_close_time = fundamental_series.index[-1]

        if time_of_query < series_open_time:  # time queried before open
            return fundamental_series[0]
        elif time_of_query > series_close_time:  # time queried after close
            return fundamental_series[-1]
        else:  # time queried during trading

            # find indices either side of requested time
            lower_idx = bisect_left(fundamental_series.index,
                                    time_of_query) - 1
            upper_idx = lower_idx + 1 if lower_idx < len(
                fundamental_series.index) - 1 else lower_idx

            # interpolate between values
            lower_val = fundamental_series[lower_idx]
            upper_val = fundamental_series[upper_idx]

            log_print(
                f"DEBUG: lower_idx: {lower_idx}, lower_val: {lower_val}, upper_idx: {upper_idx}, upper_val: {upper_val}"
            )

            interpolated_price = self.getInterpolatedPrice(
                query_time, fundamental_series.index[lower_idx],
                fundamental_series.index[upper_idx], lower_val, upper_val)
            log_print(
                "Oracle: latest historical trade was {} at {}. Next historical trade is {}. "
                "Interpolated price is {}", lower_val, query_time, upper_val,
                interpolated_price)

            self.f_log[symbol].append({
                'FundamentalTime': query_time,
                'FundamentalValue': interpolated_price
            })

            return interpolated_price
예제 #9
0
    def load_fundamentals(self):
        """ Method extracts fundamentals for each symbol into DataFrames. Note that input files must be of the form
            generated by util/formatting/mid_price_from_orderbook.py.
        """
        fundamentals = dict()
        log_print("Oracle: loading fundamental price series...")
        for symbol, params_dict in self.symbols.items():
            fundamental_file_path = params_dict['fundamental_file_path']
            log_print("Oracle: loading {}", fundamental_file_path)
            fundamental_df = pd.read_pickle(fundamental_file_path)
            fundamentals.update({symbol: fundamental_df})

        log_print("Oracle: loading fundamental price series complete!")
        return fundamentals
예제 #10
0
    def __init__(self, mkt_open, mkt_close, symbols):
        # Symbols must be a dictionary of dictionaries with outer keys as symbol names and
        # inner keys: r_bar, kappa, sigma_s.
        self.mkt_open = mkt_open
        self.mkt_close = mkt_close
        self.symbols = symbols

        # The dictionary r holds the fundamenal value series for each symbol.
        self.r = {}

        then = dt.datetime.now()

        # divide symbols upon their type
        stock_symbols = []
        etf_symbols = []
        for s in symbols:
            if symbols[s]["type"] == util.SymbolType.Stock:
                stock_symbols += [s]
            elif symbols[s]["type"] == util.SymbolType.ETF:
                etf_symbols += [s]
            else:
                raise NameError('Type  ' + str(symbols[s]["type"]) +
                                " is unkwonw")

        for symbol in stock_symbols:
            s = symbols[symbol]
            r_bar, kappa, sigma_s = s["r_bar"], s["kappa"], s["sigma_s"]
            log_print(
                "MeanRevertingOracle computing fundamental value series for {}",
                symbol)
            self.r[symbol] = self.generate_fundamental_value_series(
                symbol, r_bar, kappa, sigma_s)

        for etf_symbol in etf_symbols:
            underline_symbols = symbols[etf_symbol]["portfolio"]
            out_fvalue = None
            for un_sym in underline_symbols:
                out_fvalue = np.asarray(
                    self.r[un_sym]
                ) if out_fvalue is None else out_fvalue + self.r[un_sym]
            self.r[etf_symbol] = out_fvalue

        #for s in symbols:
        #  print("Fondamnetal ", s, self.r[s])

        now = dt.datetime.now()

        log_print("MeanRevertingOracle initialized for symbols {}", symbols)
        log_print("MeanRevertingOracle initialization took {}", now - then)
예제 #11
0
    def generate_schedule(self):

        if self.volume_profile_path is None:
            volume_profile = VWAPExecutionAgent.synthetic_volume_profile(self.start_time, self.freq)
        else:
            volume_profile = pd.read_pickle(self.volume_profile_path).to_dict()

        schedule = {}
        bins = pd.interval_range(start=self.start_time, end=self.end_time, freq=self.freq)
        for b in bins:
            schedule[b] = round(volume_profile[b.left] * self.quantity)
        log_print(f'[---- {self.name}  - Schedule ----]:')
        log_print(f'[---- {self.name}  - Total Number of Orders ----]: {len(schedule)}')
        for t, q in schedule.items():
            log_print(f"Start: {t.left.time()}, End: {t.right.time()}, Quantity: {q}")
        return schedule
예제 #12
0
    def generate_schedule(self):

        if self.volume_profile_path is None:
            volume_profile = VWAPExecutionAgent.synthetic_volume_profile(self.start_time, self.freq)
        else:
            volume_profile = pd.read_pickle(self.volume_profile_path).to_dict()

        schedule = {}
        bins = pd.interval_range(start=self.start_time, end=self.end_time, freq=self.freq)
        for b in bins:
            schedule[b] = round(volume_profile[b.left] * self.quantity)
        log_print('[---- {} {} - Schedule ----]:'.format(self.name, self.currentTime))
        log_print('[---- {} {} - Total Number of Orders ----]: {}'.format(self.name, self.currentTime, len(schedule)))
        for t, q in schedule.items():
            log_print("From: {}, To: {}, Quantity: {}".format(t.left.time(), t.right.time(), q))
        return schedule
예제 #13
0
    def generate_schedule(self):

        schedule = {}
        bins = pd.interval_range(start=self.start_time,
                                 end=self.end_time,
                                 freq=self.freq)
        child_quantity = int(self.quantity / len(self.execution_time_horizon))
        for b in bins:
            schedule[b] = child_quantity
        log_print('[---- {} {} - Schedule ----]:'.format(
            self.name, self.currentTime))
        log_print('[---- {} {} - Total Number of Orders ----]: {}'.format(
            self.name, self.currentTime, len(schedule)))
        for t, q in schedule.items():
            log_print("From: {}, To: {}, Quantity: {}".format(
                t.left.time(), t.right.time(), q))
        return schedule
예제 #14
0
    def generate_schedule(self):

        schedule = {}
        bins = pd.interval_range(start=self.start_time,
                                 end=self.end_time,
                                 freq=self.freq)
        child_quantity = int(self.quantity / len(self.execution_time_horizon))
        for b in bins:
            schedule[b] = child_quantity
        log_print(f'[---- {self.name}  - Schedule ----]:')
        log_print(
            f'[---- {self.name}  - Total Number of Orders ----]: {len(schedule)}'
        )
        for t, q in schedule.items():
            log_print(
                f"From: {t.left.time()}, To: {t.right.time()}, Quantity: {q}")
        return schedule
예제 #15
0
    def placeOrders(self, mid):
        """ Given a mid-price, compute new orders that need to be placed, then send the orders to the Exchange.

            :param mid: mid-price
            :type mid: int

        """

        bid_orders, ask_orders = self.computeOrdersToPlace(mid)

        if self.backstop_quantity is not None:
            bid_price = bid_orders[0]
            log_print('{}: Placing BUY limit order of size {} @ price {}',
                      self.name, self.backstop_quantity, bid_price)
            self.placeLimitOrder(self.symbol, self.backstop_quantity, True,
                                 bid_price)
            bid_orders = bid_orders[1:]

            ask_price = ask_orders[-1]
            log_print('{}: Placing SELL limit order of size {} @ price {}',
                      self.name, self.backstop_quantity, ask_price)
            self.placeLimitOrder(self.symbol, self.backstop_quantity, False,
                                 ask_price)
            ask_orders = ask_orders[:-1]

        for bid_price in bid_orders:
            log_print('{}: Placing BUY limit order of size {} @ price {}',
                      self.name, self.buy_order_size, bid_price)
            self.placeLimitOrder(self.symbol, self.buy_order_size, True,
                                 bid_price)

        for ask_price in ask_orders:
            log_print('{}: Placing SELL limit order of size {} @ price {}',
                      self.name, self.sell_order_size, ask_price)
            self.placeLimitOrder(self.symbol, self.sell_order_size, False,
                                 ask_price)
예제 #16
0
    def kernelStopping(self):
        # Always call parent method to be safe.
        super().kernelStopping()

        # Print end of day valuation.
        H = int(round(self.getHoldings(self.symbol), -2) / 100)
        # May request real fundamental value from oracle as part of final cleanup/stats.
        if self.symbol != 'ETF':
            rT = self.oracle.observePrice(self.symbol,
                                          self.currentTime,
                                          sigma_n=0,
                                          random_state=self.random_state)
        else:
            portfolio_rT, rT = self.oracle.observePortfolioPrice(
                self.symbol,
                self.portfolio,
                self.currentTime,
                sigma_n=0,
                random_state=self.random_state)

        # Start with surplus as private valuation of shares held.
        if H > 0:
            surplus = sum(
                [self.theta[x + self.q_max - 1] for x in range(1, H + 1)])
        elif H < 0:
            surplus = -sum(
                [self.theta[x + self.q_max - 1] for x in range(H + 1, 1)])
        else:
            surplus = 0

        log_print("surplus init: {}", surplus)

        # Add final (real) fundamental value times shares held.
        surplus += rT * H

        log_print("surplus after holdings: {}", surplus)

        # Add ending cash value and subtract starting cash value.
        surplus += self.holdings['CASH'] - self.starting_cash

        self.logEvent('FINAL_VALUATION', surplus, True)

        log_print(
            "{} final report.  Holdings {}, end cash {}, start cash {}, final fundamental {}, preferences {}, surplus {}",
            self.name, H, self.holdings['CASH'], self.starting_cash, rT,
            self.theta, surplus)
예제 #17
0
    def receiveMessage(self, currentTime, msg):
        super().receiveMessage(currentTime, msg)
        if msg.body['msg'] == 'ORDER_EXECUTED': self.handleOrderExecution(currentTime, msg)
        elif msg.body['msg'] == 'ORDER_ACCEPTED': self.handleOrderAcceptance(currentTime, msg)

        if currentTime > self.end_time:
            log_print(
                f'[---- {self.name} - {currentTime} ----]: current time {currentTime} is after specified end time of POV order '
                f'{self.end_time}. TRADING CONCLUDED. ')
            return

        if self.rem_quantity > 0 and \
                self.state == 'AWAITING_TRANSACTED_VOLUME' \
                and msg.body['msg'] == 'QUERY_TRANSACTED_VOLUME' \
                and self.transacted_volume[self.symbol] is not None\
                and currentTime > self.start_time:
            qty = round(self.pov * self.transacted_volume[self.symbol])
            self.cancelOrders()
            self.placeMarketOrder(self.symbol, qty, self.direction == 'BUY')
            log_print(f'[---- {self.name} - {currentTime} ----]: TOTAL TRANSACTED VOLUME IN THE LAST {self.look_back_period} = {self.transacted_volume[self.symbol]}')
            log_print(f'[---- {self.name} - {currentTime} ----]: MARKET ORDER PLACED - {qty}')
예제 #18
0
    def orderExecuted(self, order):
        log_print("Received notification of execution for: {}", order)

        # Log this activity.
        if self.log_orders:
            self.logEvent('ORDER_EXECUTED', js.dump(order,
                                                    strip_privates=True))

        # At the very least, we must update CASH and holdings at execution time.
        qty = order.quantity if order.is_buy_order else -1 * order.quantity
        sym = order.symbol

        if sym in self.holdings:
            self.holdings[sym] += qty
        else:
            self.holdings[sym] = qty

        if self.holdings[sym] == 0: del self.holdings[sym]

        # As with everything else, CASH holdings are in CENTS.
        self.holdings['CASH'] -= (qty * order.fill_price)

        # If this original order is now fully executed, remove it from the open orders list.
        # Otherwise, decrement by the quantity filled just now.  It is _possible_ that due
        # to timing issues, it might not be in the order list (i.e. we issued a cancellation
        # but it was executed first, or something).
        if order.order_id in self.orders:
            o = self.orders[order.order_id]

            if order.quantity >= o.quantity: del self.orders[order.order_id]
            else: o.quantity -= order.quantity

        else:
            log_print("Execution received for order not in orders list: {}",
                      order)

        log_print("After execution, agent open orders: {}", self.orders)

        # After execution, log holdings.
        self.logEvent('HOLDINGS_UPDATED', self.holdings)
예제 #19
0
    def load_fundamentals(self):
        """ Method extracts fundamentals for each symbol into DataFrames. Note that input files must be of the form
            generated by util/formatting/mid_price_from_orderbook.py.
        """
        fundamentals = dict()
        log_print("Oracle: loading fundamental price series...")
        """
        for symbol, params_dict in self.symbols.items():
            fundamental_file_path = params_dict["\\..\\..\\data\\IBM.bz2"]
            log_print("Oracle: loading {}", fundamental_file_path)
            fundamental_df = pd.read_pickle(fundamental_file_path)
            fundamentals.update({symbol: fundamental_df})
        """
        dirname = os.path.dirname(__file__)
        fundamental_file_path = os.path.join(dirname, "../../data//BTC.bz2")
        log_print("Oracle: loading {}", fundamental_file_path)
        fundamental_df = pd.read_pickle(fundamental_file_path)
        fundamentals.update({"BTC": fundamental_df})
        

        log_print("Oracle: loading fundamental price series complete!")
        return fundamentals
예제 #20
0
    def handleMarketOrder(self, order):

        if order.symbol != self.symbol:
            log_print(
                "{} order discarded.  Does not match OrderBook symbol: {}",
                order.symbol, self.symbol)
            return

        if (order.quantity <= 0) or (int(order.quantity) != order.quantity):
            log_print(
                "{} order discarded.  Quantity ({}) must be a positive integer.",
                order.symbol, order.quantity)
            return

        orderbook_side = self.getInsideAsks(
        ) if order.is_buy_order else self.getInsideBids()

        limit_orders = {
        }  # limit orders to be placed (key=price, value=quantity)
        order_quantity = order.quantity
        for price_level in orderbook_side:
            price, size = price_level[0], price_level[1]
            if order_quantity <= size:
                limit_orders[
                    price] = order_quantity  #i.e. the top of the book has enough volume for the full order
                break
            else:
                limit_orders[
                    price] = size  # i.e. not enough liquidity at the top of the book for the full order
                # therefore walk through the book until all the quantities are matched
                order_quantity -= size
                continue
        log_print("{} placing market order as multiple limit orders",
                  order.symbol, order.quantity)
        for lo in limit_orders.items():
            p, q = lo[0], lo[1]
            limit_order = LimitOrder(order.agent_id, order.time_placed,
                                     order.symbol, q, order.is_buy_order, p)
            self.handleLimitOrder(limit_order)
예제 #21
0
  def wakeup (self, currentTime):
    # Allow the base Agent to do whatever it needs to.
    super().wakeup(currentTime)

    # This agent only needs one wakeup call at simulation start.  At this time,
    # each client agent will send a number to each agent in its peer list.
    # Each number will be sampled independently.  That is, client agent 1 will
    # send n2 to agent 2, n3 to agent 3, and so forth.

    # Once a client agent has received these initial random numbers from all
    # agents in the peer list, it will make its first request from the sum
    # service.  Afterwards, it will simply request new sums when answers are
    # delivered to previous queries.

    # At the first wakeup, initiate peer exchange.
    if not self.peer_exchange_complete:
      n = [self.random_state.randint(low = 0, high = 100) for i in range(len(self.peer_list))]
      log_print ("agent {} peer list: {}", self.id, self.peer_list)
      log_print ("agent {} numbers to exchange: {}", self.id, n)

      for idx, peer in enumerate(self.peer_list):
        self.sendMessage(peer, Message({ "msg" : "PEER_EXCHANGE", "sender": self.id, "n" : n[idx] }))

    else:
      # For subsequent (self-induced) wakeups, place a sum query.
      n1, n2 = [self.random_state.randint(low = 0, high = 100) for i in range(2)]

      log_print ("agent {} transmitting numbers {} and {} with peer sum {}", self.id, n1, n2, self.peer_sum)

      # Add the sum of the peer exchange values to both numbers.
      n1 += self.peer_sum
      n2 += self.peer_sum

      self.sendMessage(self.serviceAgentID, Message({ "msg" : "SUM_QUERY", "sender": self.id,
                                                      "n1" : n1, "n2" : n2 })) 

    return
예제 #22
0
    def __init__(self, mkt_open, mkt_close, symbols):
        # Symbols must be a dictionary of dictionaries with outer keys as symbol names and
        # inner keys: r_bar, kappa, sigma_s.
        self.mkt_open = mkt_open
        self.mkt_close = mkt_close
        self.symbols = symbols

        # The dictionary r holds the fundamenal value series for each symbol.
        self.r = {}

        then = dt.datetime.now()

        for symbol in symbols:
            s = symbols[symbol]
            log_print(
                "MeanRevertingOracle computing fundamental value series for {}",
                symbol)
            self.r[symbol] = self.generate_fundamental_value_series(
                symbol=symbol, **s)

        now = dt.datetime.now()

        log_print("MeanRevertingOracle initialized for symbols {}", symbols)
        log_print("MeanRevertingOracle initialization took {}", now - then)
예제 #23
0
    def orderAccepted(self, order):
        log_print("Received notification of acceptance for: {}", order)

        # Log this activity.
        if self.log_orders: self.logEvent('ORDER_ACCEPTED', js.dump(order))
예제 #24
0
    def receiveMessage(self, currentTime, msg):
        super().receiveMessage(currentTime, msg)

        # Do we know the market hours?
        had_mkt_hours = self.mkt_open is not None and self.mkt_close is not None

        # Record market open or close times.
        if msg.body['msg'] == "WHEN_MKT_OPEN":
            self.mkt_open = msg.body['data']

            log_print("Recorded market open: {}",
                      self.kernel.fmtTime(self.mkt_open))

        elif msg.body['msg'] == "WHEN_MKT_CLOSE":
            self.mkt_close = msg.body['data']

            log_print("Recorded market close: {}",
                      self.kernel.fmtTime(self.mkt_close))

        elif msg.body['msg'] == "ORDER_EXECUTED":
            # Call the orderExecuted method, which subclasses should extend.  This parent
            # class could implement default "portfolio tracking" or "returns tracking"
            # behavior.
            order = msg.body['order']

            self.orderExecuted(order)

        elif msg.body['msg'] == "ORDER_ACCEPTED":
            # Call the orderAccepted method, which subclasses should extend.
            order = msg.body['order']

            self.orderAccepted(order)

        elif msg.body['msg'] == "ORDER_CANCELLED":
            # Call the orderCancelled method, which subclasses should extend.
            order = msg.body['order']

            self.orderCancelled(order)

        elif msg.body['msg'] == "MKT_CLOSED":
            # We've tried to ask the exchange for something after it closed.  Remember this
            # so we stop asking for things that can't happen.

            self.marketClosed()

        elif msg.body['msg'] == 'QUERY_LAST_TRADE':
            # Call the queryLastTrade method, which subclasses may extend.
            # Also note if the market is closed.
            if msg.body['mkt_closed']: self.mkt_closed = True

            self.queryLastTrade(msg.body['symbol'], msg.body['data'])

        elif msg.body['msg'] == 'QUERY_SPREAD':
            # Call the querySpread method, which subclasses may extend.
            # Also note if the market is closed.
            if msg.body['mkt_closed']: self.mkt_closed = True

            self.querySpread(msg.body['symbol'], msg.body['data'],
                             msg.body['bids'], msg.body['asks'],
                             msg.body['book'])

        elif msg.body['msg'] == 'QUERY_ORDER_STREAM':
            # Call the queryOrderStream method, which subclasses may extend.
            # Also note if the market is closed.
            if msg.body['mkt_closed']: self.mkt_closed = True

            self.queryOrderStream(msg.body['symbol'], msg.body['orders'])

        # Now do we know the market hours?
        have_mkt_hours = self.mkt_open is not None and self.mkt_close is not None

        # Once we know the market open and close times, schedule a wakeup call for market open.
        # Only do this once, when we first have both items.
        if have_mkt_hours and not had_mkt_hours:
            # Agents are asked to generate a wake offset from the market open time.  We structure
            # this as a subclass request so each agent can supply an appropriate offset relative
            # to its trading frequency.
            ns_offset = self.getWakeFrequency()

            self.setWakeup(self.mkt_open + ns_offset)
예제 #25
0
    def receiveMessage(self, currentTime, msg):
        super().receiveMessage(currentTime, msg)
        if msg.body['msg'] == 'MARKET_DATA':
            self.cancelOrders()

            self.last_market_data_update = currentTime
            bids, asks = msg.body['bids'], msg.body['asks']

            bid_liq = sum(x[1] for x in bids)
            ask_liq = sum(x[1] for x in asks)

            log_print("bid, ask levels: {}", len(bids), len(asks))
            log_print("bids: {}, asks: {}", bids, asks)

            # OBI strategy.
            target = 0

            if bid_liq == 0 or ask_liq == 0:
                log_print("OBI agent inactive: zero bid or ask liquidity")
                return
            else:
                # bid_pct encapsulates both sides of the question, as a normalized expression
                # representing what fraction of total visible volume is on the buy side.
                bid_pct = bid_liq / (bid_liq + ask_liq)

                # If we are short, we need to decide if we should hold or exit.
                if self.is_short:
                    # Update trailing stop.
                    if bid_pct - self.trail_dist > self.trailing_stop:
                        log_print(
                            "Trailing stop updated: new > old ({:2f} > {:2f})",
                            bid_pct - self.trail_dist, self.trailing_stop)
                        self.trailing_stop = bid_pct - self.trail_dist
                    else:
                        log_print(
                            "Trailing stop remains: potential < old ({:2f} < {:2f})",
                            bid_pct - self.trail_dist, self.trailing_stop)

                    # Check the trailing stop.
                    if bid_pct < self.trailing_stop:
                        log_print(
                            "OBI agent exiting short position: bid_pct < trailing_stop ({:2f} < {:2f})",
                            bid_pct, self.trailing_stop)
                        target = 0
                        self.is_short = False
                        self.trailing_stop = None
                    else:
                        log_print(
                            "OBI agent holding short position: bid_pct > trailing_stop ({:2f} > {:2f})",
                            bid_pct, self.trailing_stop)
                        target = -100
                # If we are long, we need to decide if we should hold or exit.
                elif self.is_long:
                    if bid_pct + self.trail_dist < self.trailing_stop:
                        log_print(
                            "Trailing stop updated: new < old ({:2f} < {:2f})",
                            bid_pct + self.trail_dist, self.trailing_stop)
                        self.trailing_stop = bid_pct + self.trail_dist
                    else:
                        log_print(
                            "Trailing stop remains: potential > old ({:2f} > {:2f})",
                            bid_pct + self.trail_dist, self.trailing_stop)

                    # Check the trailing stop.
                    if bid_pct > self.trailing_stop:
                        log_print(
                            "OBI agent exiting long position: bid_pct > trailing_stop ({:2f} > {:2f})",
                            bid_pct, self.trailing_stop)
                        target = 0
                        self.is_long = False
                        self.trailing_stop = None
                    else:
                        log_print(
                            "OBI agent holding long position: bid_pct < trailing_stop ({:2f} < {:2f})",
                            bid_pct, self.trailing_stop)
                        target = 100
                # If we are flat, we need to decide if we should enter (long or short).
                else:
                    if bid_pct < (0.5 - self.entry_threshold):
                        log_print(
                            "OBI agent entering long position: bid_pct < entry_threshold ({:2f} < {:2f})",
                            bid_pct, 0.5 - self.entry_threshold)
                        target = 100
                        self.is_long = True
                        self.trailing_stop = bid_pct + self.trail_dist
                        log_print("Initial trailing stop: {:2f}",
                                  self.trailing_stop)
                    elif bid_pct > (0.5 + self.entry_threshold):
                        log_print(
                            "OBI agent entering short position: bid_pct > entry_threshold ({:2f} > {:2f})",
                            bid_pct, 0.5 + self.entry_threshold)
                        target = -100
                        self.is_short = True
                        self.trailing_stop = bid_pct - self.trail_dist
                        log_print("Initial trailing stop: {:2f}",
                                  self.trailing_stop)
                    else:
                        log_print(
                            "OBI agent staying flat: long_entry < bid_pct < short_entry ({:2f} < {:2f} < {:2f})",
                            0.5 - self.entry_threshold, bid_pct,
                            0.5 + self.entry_threshold)
                        target = 0

                self.plotme.append({
                    'currentTime': self.currentTime,
                    'midpoint': (asks[0][0] + bids[0][0]) / 2,
                    'bid_pct': bid_pct
                })

            # Adjust holdings to target.
            holdings = self.holdings[
                self.symbol] if self.symbol in self.holdings else 0
            delta = target - holdings
            direction = True if delta > 0 else False
            price = self.computeRequiredPrice(direction, abs(delta), bids,
                                              asks)

            log_print("Current holdings: {}", self.holdings)

            if delta == 0:
                log_print("No adjustments to holdings needed.")
            else:
                log_print("Adjusting holdings by {}", delta)
                self.placeLimitOrder(self.symbol, abs(delta), direction, price)
예제 #26
0
    def runner(self,
               agents=[],
               startTime=None,
               stopTime=None,
               num_simulations=1,
               defaultComputationDelay=1,
               defaultLatency=1,
               agentLatency=None,
               latencyNoise=[1.0],
               agentLatencyModel=None,
               skip_log=False,
               seed=None,
               oracle=None,
               log_dir=None):

        # agents must be a list of agents for the simulation,
        #        based on class agent.Agent
        self.agents = agents

        # Simulation custom state in a freeform dictionary.  Allows config files
        # that drive multiple simulations, or require the ability to generate
        # special logs after simulation, to obtain needed output without special
        # case code in the Kernel.  Per-agent state should be handled using the
        # provided updateAgentState() method.
        self.custom_state = {}

        # The kernel start and stop time (first and last timestamp in
        # the simulation, separate from anything like exchange open/close).
        self.startTime = startTime
        self.stopTime = stopTime

        # The global seed, NOT used for anything agent-related.
        self.seed = seed

        # Should the Kernel skip writing agent logs?
        self.skip_log = skip_log

        # The data oracle for this simulation, if needed.
        self.oracle = oracle

        # If a log directory was not specified, use the initial wallclock.
        if log_dir:
            self.log_dir = log_dir
        else:
            self.log_dir = str(int(self.kernelWallClockStart.timestamp()))

        # The kernel maintains a current time for each agent to allow
        # simulation of per-agent computation delays.  The agent's time
        # is pushed forward (see below) each time it awakens, and it
        # cannot receive new messages/wakeups until the global time
        # reaches the agent's time.  (i.e. it cannot act again while
        # it is still "in the future")

        # This also nicely enforces agents being unable to act before
        # the simulation startTime.
        self.agentCurrentTimes = [self.startTime] * len(agents)

        # agentComputationDelays is in nanoseconds, starts with a default
        # value from config, and can be changed by any agent at any time
        # (for itself only).  It represents the time penalty applied to
        # an agent each time it is awakened  (wakeup or recvMsg).  The
        # penalty applies _after_ the agent acts, before it may act again.
        # TODO: this might someday change to pd.Timedelta objects.
        self.agentComputationDelays = [defaultComputationDelay] * len(agents)

        # If an agentLatencyModel is defined, it will be used instead of
        # the older, non-model-based attributes.
        self.agentLatencyModel = agentLatencyModel

        # If an agentLatencyModel is NOT defined, the older parameters:
        # agentLatency (or defaultLatency) and latencyNoise should be specified.
        # These should be considered deprecated and will be removed in the future.

        # If agentLatency is not defined, define it using the defaultLatency.
        # This matrix defines the communication delay between every pair of
        # agents.
        if agentLatency is None:
            self.agentLatency = [[defaultLatency] * len(agents)] * len(agents)
        else:
            self.agentLatency = agentLatency

        # There is a noise model for latency, intended to be a one-sided
        # distribution with the peak at zero.  By default there is no noise
        # (100% chance to add zero ns extra delay).  Format is a list with
        # list index = ns extra delay, value = probability of this delay.
        self.latencyNoise = latencyNoise

        # The kernel maintains an accumulating additional delay parameter
        # for the current agent.  This is applied to each message sent
        # and upon return from wakeup/receiveMessage, in addition to the
        # agent's standard computation delay.  However, it never carries
        # over to future wakeup/receiveMessage calls.  It is useful for
        # staggering of sent messages.
        self.currentAgentAdditionalDelay = 0

        log_print("Kernel started: {}", self.name)
        log_print("Simulation started!")

        # Note that num_simulations has not yet been really used or tested
        # for anything.  Instead we have been running multiple simulations
        # with coarse parallelization from a shell script.
        for sim in range(num_simulations):
            log_print("Starting sim {}", sim)

            # Event notification for kernel init (agents should not try to
            # communicate with other agents, as order is unknown).  Agents
            # should initialize any internal resources that may be needed
            # to communicate with other agents during agent.kernelStarting().
            # Kernel passes self-reference for agents to retain, so they can
            # communicate with the kernel in the future (as it does not have
            # an agentID).
            log_print("\n--- Agent.kernelInitializing() ---")
            for agent in self.agents:
                agent.kernelInitializing(self)

            # Event notification for kernel start (agents may set up
            # communications or references to other agents, as all agents
            # are guaranteed to exist now).  Agents should obtain references
            # to other agents they require for proper operation (exchanges,
            # brokers, subscription services...).  Note that we generally
            # don't (and shouldn't) permit agents to get direct references
            # to other agents (like the exchange) as they could then bypass
            # the Kernel, and therefore simulation "physics" to send messages
            # directly and instantly or to perform disallowed direct inspection
            # of the other agent's state.  Agents should instead obtain the
            # agent ID of other agents, and communicate with them only via
            # the Kernel.  Direct references to utility objects that are not
            # agents are acceptable (e.g. oracles).
            log_print("\n--- Agent.kernelStarting() ---")
            for agent in self.agents:
                agent.kernelStarting(self.startTime)

            # Set the kernel to its startTime.
            self.currentTime = self.startTime
            log_print("\n--- Kernel Clock started ---")
            log_print("Kernel.currentTime is now {}", self.currentTime)

            # Start processing the Event Queue.
            log_print("\n--- Kernel Event Queue begins ---")
            log_print(
                "Kernel will start processing messages.  Queue length: {}",
                len(self.messages.queue))

            # Track starting wall clock time and total message count for stats at the end.
            eventQueueWallClockStart = pd.Timestamp('now')
            ttl_messages = 0

            # Process messages until there aren't any (at which point there never can
            # be again, because agents only "wake" in response to messages), or until
            # the kernel stop time is reached.
            while not self.messages.empty() and self.currentTime and (
                    self.currentTime <= self.stopTime):
                # Get the next message in timestamp order (delivery time) and extract it.
                self.currentTime, event = self.messages.get()
                msg_recipient, msg_type, msg = event

                # Periodically print the simulation time and total messages, even if muted.
                if ttl_messages % 100000 == 0:
                    print(
                        "\n--- Simulation time: {}, messages processed: {}, wallclock elapsed: {} ---\n"
                        .format(self.fmtTime(self.currentTime), ttl_messages,
                                pd.Timestamp('now') -
                                eventQueueWallClockStart))

                log_print("\n--- Kernel Event Queue pop ---")
                log_print("Kernel handling {} message for agent {} at time {}",
                          msg_type, msg_recipient,
                          self.fmtTime(self.currentTime))

                ttl_messages += 1

                # In between messages, always reset the currentAgentAdditionalDelay.
                self.currentAgentAdditionalDelay = 0

                # Dispatch message to agent.
                if msg_type == MessageType.WAKEUP:

                    # Who requested this wakeup call?
                    agent = msg_recipient

                    # Test to see if the agent is already in the future.  If so,
                    # delay the wakeup until the agent can act again.
                    if self.agentCurrentTimes[agent] > self.currentTime:
                        # Push the wakeup call back into the PQ with a new time.
                        self.messages.put((self.agentCurrentTimes[agent],
                                           (msg_recipient, msg_type, msg)))
                        log_print("Agent in future: wakeup requeued for {}",
                                  self.fmtTime(self.agentCurrentTimes[agent]))
                        continue

                    # Set agent's current time to global current time for start
                    # of processing.
                    self.agentCurrentTimes[agent] = self.currentTime

                    # Wake the agent.
                    agents[agent].wakeup(self.currentTime)

                    # Delay the agent by its computation delay plus any transient additional delay requested.
                    self.agentCurrentTimes[agent] += pd.Timedelta(
                        self.agentComputationDelays[agent] +
                        self.currentAgentAdditionalDelay)

                    log_print(
                        "After wakeup return, agent {} delayed from {} to {}",
                        agent, self.fmtTime(self.currentTime),
                        self.fmtTime(self.agentCurrentTimes[agent]))

                elif msg_type == MessageType.MESSAGE:

                    # Who is receiving this message?
                    agent = msg_recipient

                    # Test to see if the agent is already in the future.  If so,
                    # delay the message until the agent can act again.
                    if self.agentCurrentTimes[agent] > self.currentTime:
                        # Push the message back into the PQ with a new time.
                        self.messages.put((self.agentCurrentTimes[agent],
                                           (msg_recipient, msg_type, msg)))
                        log_print("Agent in future: message requeued for {}",
                                  self.fmtTime(self.agentCurrentTimes[agent]))
                        continue

                    # Set agent's current time to global current time for start
                    # of processing.
                    self.agentCurrentTimes[agent] = self.currentTime

                    # Deliver the message.
                    agents[agent].receiveMessage(self.currentTime, msg)

                    # Delay the agent by its computation delay plus any transient additional delay requested.
                    self.agentCurrentTimes[agent] += pd.Timedelta(
                        self.agentComputationDelays[agent] +
                        self.currentAgentAdditionalDelay)

                    log_print(
                        "After receiveMessage return, agent {} delayed from {} to {}",
                        agent, self.fmtTime(self.currentTime),
                        self.fmtTime(self.agentCurrentTimes[agent]))

                else:
                    raise ValueError("Unknown message type found in queue",
                                     "currentTime:", self.currentTime,
                                     "messageType:", self.msg.type)

            if self.messages.empty():
                log_print("\n--- Kernel Event Queue empty ---")

            if self.currentTime and (self.currentTime > self.stopTime):
                log_print("\n--- Kernel Stop Time surpassed ---")

            # Record wall clock stop time and elapsed time for stats at the end.
            eventQueueWallClockStop = pd.Timestamp('now')

            eventQueueWallClockElapsed = eventQueueWallClockStop - eventQueueWallClockStart

            # Event notification for kernel end (agents may communicate with
            # other agents, as all agents are still guaranteed to exist).
            # Agents should not destroy resources they may need to respond
            # to final communications from other agents.
            log_print("\n--- Agent.kernelStopping() ---")
            for agent in agents:
                agent.kernelStopping()

            # Event notification for kernel termination (agents should not
            # attempt communication with other agents, as order of termination
            # is unknown).  Agents should clean up all used resources as the
            # simulation program may not actually terminate if num_simulations > 1.
            log_print("\n--- Agent.kernelTerminating() ---")
            for agent in agents:
                agent.kernelTerminating()

            print(
                "Event Queue elapsed: {}, messages: {}, messages per second: {:0.1f}"
                .format(
                    eventQueueWallClockElapsed, ttl_messages, ttl_messages /
                    (eventQueueWallClockElapsed / (np.timedelta64(1, 's')))))
            log_print("Ending sim {}", sim)

        # The Kernel adds a handful of custom state results for all simulations,
        # which configurations may use, print, log, or discard.
        self.custom_state[
            'kernel_event_queue_elapsed_wallclock'] = eventQueueWallClockElapsed
        self.custom_state['kernel_slowest_agent_finish_time'] = max(
            self.agentCurrentTimes)

        # Agents will request the Kernel to serialize their agent logs, usually
        # during kernelTerminating, but the Kernel must write out the summary
        # log itself.
        self.writeSummaryLog()

        # This should perhaps be elsewhere, as it is explicitly financial, but it
        # is convenient to have a quick summary of the results for now.
        print("Mean ending value by agent type:")
        for a in self.meanResultByAgentType:
            value = self.meanResultByAgentType[a]
            count = self.agentCountByType[a]
            print("{}: {:d}".format(a, int(round(value / count))))

        print("Simulation ending!")

        return self.custom_state
예제 #27
0
    def sendMessage(self, sender=None, recipient=None, msg=None, delay=0):
        # Called by an agent to send a message to another agent.  The kernel
        # supplies its own currentTime (i.e. "now") to prevent possible
        # abuse by agents.  The kernel will handle computational delay penalties
        # and/or network latency.  The message must derive from the message.Message class.
        # The optional delay parameter represents an agent's request for ADDITIONAL
        # delay (beyond the Kernel's mandatory computation + latency delays) to represent
        # parallel pipeline processing delays (that should delay the transmission of messages
        # but do not make the agent "busy" and unable to respond to new messages).

        if sender is None:
            raise ValueError("sendMessage() called without valid sender ID",
                             "sender:", sender, "recipient:", recipient,
                             "msg:", msg)

        if recipient is None:
            raise ValueError("sendMessage() called without valid recipient ID",
                             "sender:", sender, "recipient:", recipient,
                             "msg:", msg)

        if msg is None:
            raise ValueError("sendMessage() called with message == None",
                             "sender:", sender, "recipient:", recipient,
                             "msg:", msg)

        # Apply the agent's current computation delay to effectively "send" the message
        # at the END of the agent's current computation period when it is done "thinking".
        # NOTE: sending multiple messages on a single wake will transmit all at the same
        # time, at the end of computation.  To avoid this, use Agent.delay() to accumulate
        # a temporary delay (current cycle only) that will also stagger messages.

        # The optional pipeline delay parameter DOES push the send time forward, since it
        # represents "thinking" time before the message would be sent.  We don't use this
        # for much yet, but it could be important later.

        # This means message delay (before latency) is the agent's standard computation delay
        # PLUS any accumulated delay for this wake cycle PLUS any one-time requested delay
        # for this specific message only.
        sentTime = self.currentTime + pd.Timedelta(
            self.agentComputationDelays[sender] +
            self.currentAgentAdditionalDelay + delay)

        # Apply communication delay per the agentLatencyModel, if defined, or the
        # agentLatency matrix [sender][recipient] otherwise.
        if self.agentLatencyModel is not None:
            latency = self.agentLatencyModel.get_latency(
                sender_id=sender, recipient_id=recipient)
            deliverAt = sentTime + pd.Timedelta(latency)
            log_print(
                "Kernel applied latency {}, accumulated delay {}, one-time delay {} on sendMessage from: {} to {}, scheduled for {}",
                latency, self.currentAgentAdditionalDelay, delay,
                self.agents[sender].name, self.agents[recipient].name,
                self.fmtTime(deliverAt))
        else:
            latency = self.agentLatency[sender][recipient]
            noise = self.random_state.choice(len(self.latencyNoise), 1,
                                             self.latencyNoise)[0]
            deliverAt = sentTime + pd.Timedelta(latency + noise)
            log_print(
                "Kernel applied latency {}, noise {}, accumulated delay {}, one-time delay {} on sendMessage from: {} to {}, scheduled for {}",
                latency, noise, self.currentAgentAdditionalDelay, delay,
                self.agents[sender].name, self.agents[recipient].name,
                self.fmtTime(deliverAt))

        # Finally drop the message in the queue with priority == delivery time.
        self.messages.put((deliverAt, (recipient, MessageType.MESSAGE, msg)))

        log_print("Sent time: {}, current time {}, computation delay {}",
                  sentTime, self.currentTime,
                  self.agentComputationDelays[sender])
        log_print("Message queued: {}", msg)
예제 #28
0
    def placeOrder(self):
        # Called when it is time for the agent to determine a limit price and place an order.
        limit_price = self.limit_price
        history = self.getKnownStreamHistory(self.symbol)

        # volume should be adjusted on the go until a particular transaction at a given process
        # is completed
        if self.buy:
            current_holding = self.getHoldings(self.symbol)

        else:
            current_holding = -1 * self.getHoldings(self.symbol)

        #prev_order_size = copy.deepcopy(self.order_size)

        # if all designated units have been traded
        if current_holding == self.total_order:
            """
            print("\nCurrent holding is: ", current_holding,
                "\nOrders so far adds up to: ", self.total_order,
                "\nPrevious order size was: ", prev_order_size)
            """

            # reset profit margin
            self.profit_margin = self.initial_profit_margin

            # choose new order size
            self.getOrderSize()

            # order is fully transacted, thus add new order size to the total
            self.total_order += self.order_size
            """
            print("Order has been fully executed. New order size is: ", self.order_size)
            """

            # receive new price
            if self.buy:
                self.limit_price = self.oracle.observePrice(
                    self.symbol,
                    self.currentTime,
                    sigma_n=self.sigma_n,
                    random_state=self.random_state)

            else:
                self.limit_price = self.oracle.observePrice(
                    self.symbol,
                    self.currentTime,
                    sigma_n=self.sigma_n,
                    random_state=self.random_state)

        # if transaction has not yet completed at this price
        else:

            self.order_size = self.total_order - current_holding
            """
            print("\nCurrent holding is: ", current_holding,
                "\nOrders so far adds up to: ", self.total_order,
                "\nOrder has not been fully executed. New order size is: ", self.order_size,
                "\nPrevious order size was: ", prev_order_size)
            """

        # Determine the limit price.
        shout_price = limit_price * (
            1 - self.profit_margin) if self.buy else limit_price * (
                1 + self.profit_margin)

        def upwards_adjustment(last_shout):

            # set target price
            self.target_price = self.rel_change_up * last_shout + self.abs_change_up

            # using target price and limit price, calculate widrow WH delta
            self.hoff_delta = self.learning_rate * (self.target_price -
                                                    shout_price)

            # Add momentum based update
            self.momentum_target_price = self.momentum * self.momentum_target_price + (
                1 - self.momentum) * self.hoff_delta

            # update margin based on this information
            self.profit_margin = (shout_price +
                                  self.momentum_target_price) / limit_price - 1

            return None

        def downwards_adjustment(last_shout):

            # set target price
            self.target_price = self.rel_change_down * last_shout + self.abs_change_down

            # using target price and limit price, calculate widrow WH delta
            self.hoff_delta = self.learning_rate * (self.target_price -
                                                    shout_price)

            # Add momentum based update
            self.momentum_target_price = self.momentum * self.momentum_target_price + (
                1 - self.momentum) * self.hoff_delta

            # update margin based on this information
            self.profit_margin = (shout_price +
                                  self.momentum_target_price) / limit_price - 1

            return None

        # Either place the constructed order, or if the agent could secure (eta * R) surplus
        # immediately by taking the inside bid/ask, do that instead.
        bid, bid_vol, ask, ask_vol = self.getKnownBidAsk(self.symbol)

        if self.buy and ask_vol > 0:
            R_ask = limit_price - ask
            if R_ask >= (limit_price * self.profit_margin):
                log_print(
                    "{} desired R = {}, but took R = {} at ask = {} due to eta",
                    self.name, self.profit_margin, R_ask, ask)
                shout_price = ask
            else:
                log_print("{} demands R = {}, limit price {}", self.name,
                          self.profit_margin, shout_price)
        elif (not self.buy) and bid_vol > 0:
            R_bid = bid - limit_price
            if R_bid >= (limit_price * self.profit_margin):
                log_print(
                    "{} desired R = {}, but took R = {} at bid = {} due to eta",
                    self.name, self.profit_margin, R_bid, bid)
                shout_price = bid
            else:
                log_print("{} demands R = {}, limit price {}", self.name,
                          self.profit_margin, shout_price)

        # Place the order.
        self.placeLimitOrder(self.symbol, self.order_size, self.buy,
                             int(shout_price))

        # Update margin
        if history.empty == False:

            last_shout = history.loc[
                0,
                'limit_price']  # note that "limit_price" here refers to the column name. Therefore the "last_shout" is the first entry of this column

            # for a seller
            if self.buy == False:

                # if the transaction value is not empty - i.e. if the transaction occurred
                if history.loc[0, 'transactions'] != []:

                    # if the ask price is too low, seller raises the ask price
                    if shout_price < last_shout:

                        upwards_adjustment(last_shout)

                    # otherwise, if the last shout was a bid, seller lowers the ask price
                    elif history.loc[0, 'is_buy_order'] == True:

                        downwards_adjustment(last_shout)

                # if the last shout did not undergo transaction
                else:

                    # if the last shout was an ask
                    if history.loc[0, 'is_buy_order'] == False:

                        downwards_adjustment(last_shout)

            # for a buyer
            elif self.buy == True:

                # if the transaction value is not empty - i.e. if the transaction occurred
                if history.loc[0, 'transactions'] != []:

                    # if the bid price is too high, buyer lowers bid price
                    if shout_price > last_shout:

                        downwards_adjustment(last_shout)

                    # otherwise, if the last shout was an ask, buyer raises bid price
                    elif history.loc[0, 'is_buy_order'] == False:

                        upwards_adjustment(last_shout)

                # if the last shout did not undergo transaction
                else:

                    # if the last shout was a bid, buyer raises bid price
                    if history.loc[0, 'is_buy_order'] == True:

                        upwards_adjustment(last_shout)
예제 #29
0
    def wakeup(self, currentTime):
        # Parent class handles discovery of exchange times and market_open wakeup call.
        super().wakeup(currentTime)

        self.state = 'INACTIVE'

        if not self.mkt_open or not self.mkt_close:
            # TradingAgent handles discovery of exchange times.
            return
        else:
            if not self.trading:
                self.trading = True

                # Time to start trading!
                log_print("{} is ready to start trading now.", self.name)

        # Steady state wakeup behavior starts here.

        # If we've been told the market has closed for the day, we will only request
        # final price information, then stop.
        if self.mkt_closed and (self.symbol in self.daily_close_price):
            # Market is closed and we already got the daily close price.
            return

        # Schedule a wakeup for the next time this agent should arrive at the market
        # (following the conclusion of its current activity cycle).
        # We do this early in case some of our expected message responses don't arrive.

        # Agents should arrive according to a Poisson process.  This is equivalent to
        # each agent independently sampling its next arrival time from an exponential
        # distribution in alternate Beta formation with Beta = 1 / lambda, where lambda
        # is the mean arrival rate of the Poisson process.
        #delta_time = self.random_state.exponential(scale=1.0 / self.lambda_a)
        #self.setWakeup(currentTime + pd.Timedelta('{}ns'.format(int(round(delta_time)))))

        # If the market has closed and we haven't obtained the daily close price yet,
        # do that before we cease activity for the day.  Don't do any other behavior
        # after market close.
        if self.mkt_closed and (not self.symbol in self.daily_close_price):
            self.getCurrentSpread(self.symbol)
            self.state = 'AWAITING_SPREAD'
            return

        # Issue cancel requests for any open orders.  Don't wait for confirmation, as presently
        # the only reason it could fail is that the order already executed.  (But requests won't
        # be generated for those, anyway, unless something strange has happened.)
        self.cancelOrders()

        # 20200304 Chris Cho - data_dummy added to send out different messages at each wakeup
        if type(self) == ZeroIntelligencePlus:

            # this is the msssage to query spread
            if self.data_dummy == 0:

                self.getCurrentSpread(self.symbol)
                self.state = 'AWAITING_SPREAD'

                # set wakeup to earliest time possible to query order stream
                self.setWakeup(currentTime + pd.Timedelta('{}ns'.format(1e9)))

                self.data_dummy = 1

            # this is the msssage to query order stream
            elif self.data_dummy == 1:

                self.getOrderStream(self.symbol, length=20)
                self.state = 'AWAITING_ORDER_STREAM'

                # Order arrival time can be fit into exponential distribution
                wake_time = self.modifyWakeFrequency(currentTime,
                                                     self.getWakeFrequency())

                self.setWakeup(currentTime + wake_time)

                self.data_dummy = 0

        else:
            self.state = 'ACTIVE'
예제 #30
0
    def __init__(self,
                 id,
                 name,
                 type,
                 mkt_open_time,
                 mkt_close_time,
                 symbol='IBM',
                 starting_cash=100000,
                 sigma_n=1000,
                 q_max=100,
                 R_min=0,
                 R_max=250,
                 eta=1.0,
                 lambda_a=0.05,
                 log_orders=False,
                 random_state=None):

        # Base class init.
        super().__init__(id,
                         name,
                         type,
                         starting_cash=starting_cash,
                         log_orders=log_orders,
                         random_state=random_state)

        # determine whether the agent is a "buy" agent or a "sell" agent
        self.buy = bool(self.random_state.randint(0, 2))
        log_print("Coin flip: this agent is a {} agent",
                  "BUY" if self.buy else "SELL")

        # Store important parameters particular to the ZI agent
        self.symbol = symbol  # symbol to trade
        self.sigma_n = sigma_n  # observation noise variance
        self.q_max = q_max  # max unit holdings
        self.R_min = R_min  # min requested surplus
        self.R_max = R_max  # max requested surplus
        self.eta = eta  # strategic threshold
        self.lambda_a = lambda_a  # mean arrival rate of ZI agents - this is the exp. distribution parameter to be tuned
        self.mkt_open_time = mkt_open_time
        self.mkt_close_time = mkt_close_time
        self.order_size = np.ceil(
            70 / np.random.power(3.5)
        )  #order size is fixed at 100 to start - the order size needs to be tuned
        self.total_order = self.order_size
        # std of 500 should be plenty

        self.limit_price = 0
        self.counter = 0
        """
        # we are not querying from an oracle right now
        self.limit_price = self.oracle.observePrice(self.symbol, startTime, sigma_n=self.sigma_n,random_state=self.random_state)
        """

        # ZIP update parameters
        self.target_price = 0  #target price just needs to exist
        self.momentum_target_price = 0  #initial condition
        self.hoff_delta = 0  #hoff delta needs to exist
        self.learning_rate = 0.1 + 0.4 * np.random.rand(
        )  #uniform [0.1,0.5] fixed per agent
        self.momentum = 0.2 + 0.6 * np.random.rand(
        )  #uniform [0.2,0.8] fixed per agent
        self.abs_change_up = 10 * np.random.rand(
        )  #uniform [0,0.05] fixed per agent - temporarily changed to 10 cents
        self.rel_change_up = 1 + 0.05 * np.random.rand(
        )  #uniform [1,1.05] fixed per agent
        self.abs_change_down = -10 * np.random.rand(
        )  #uniform [-0.05,0] fixed per agent - temporarily changed to 10 cents
        self.rel_change_down = 1 - 0.05 * np.random.rand(
        )  #uniform [0.95,1] fixed per agent
        self.initial_profit_margin = 0.3 + 0.05 * np.random.rand(
        )  #uniform [0.3,0.35] fixed per agent
        self.profit_margin = self.initial_profit_margin

        # the agent uses this to determine which data to query from the orderbook
        self.data_dummy = 0

        # The agent uses this to track whether it has begun its strategy or is still
        # handling pre-market tasks.
        self.trading = False

        # The agent begins in its "complete" state, not waiting for
        # any special event or condition.
        self.state = 'AWAITING_WAKEUP'

        # The agent must track its previous wake time, so it knows how many time
        # units have passed.
        self.prev_wake_time = None