Example #1
0
def algo_loop(trading_day, tick_coef, peg_to_bbo):
    log_message('Beginning Market-Making Strategy run')

    round_lot = 100
    avg_spread = (trading_day.ask_px - trading_day.bid_px).mean()
    half_spread = avg_spread / 2
    print("Average stock spread for sample: {:.4f}".format(avg_spread))

    # this should be a higher hard constraint but for now let's just leave it at a fixed amount
    live_order_quantity = 200

    # init our price and volume variables
    [last_price, last_size, bid_price, bid_size, ask_price, ask_size,
     volume] = np.zeros(7)

    # init our counters
    [trade_count, quote_count, cumulative_volume] = [0, 0, 0]

    # init some time series objects for collection of telemetry
    fair_values = pd.Series(index=trading_day.index)
    midpoints = pd.Series(index=trading_day.index)
    tick_factors = pd.Series(index=trading_day.index)

    # let's set up a container to hold trades. preinitialize with the index
    buys = pd.DataFrame(columns=['price', 'shares', 'bar', 'trade_type'],
                        index=trading_day.index)
    sells = pd.DataFrame(columns=['price', 'shares', 'bar', 'trade_type'],
                         index=trading_day.index)
    # leaving this as a DataFrame since I may want to add other attributes
    net_positions = pd.DataFrame(columns=['position'], index=trading_day.index)
    current_net_pos = 0

    # MAIN EVENT LOOP
    current_bar = 0

    # other order and market variables
    total_quantity_bought = 0
    total_quantity_sold = 0
    vwap_numerator = 0.0

    total_trade_count = 0
    total_agg_count = 0
    total_pass_count = 0

    # fair value pricing variables
    midpoint = 0.0
    fair_value = 0.0

    # define our accumulator for the tick EMA
    message_type = 0
    tick_window = 20
    tick_factor = 0
    tick_ema_alpha = 2 / (tick_window + 1)
    prev_tick = 0
    prev_price = 0

    # quoting related
    our_bid = 0.0
    our_ask = 0.0
    half_spread_coef = 0.5

    # risk factor for part 2
    risk_factor = 0.0
    risk_coef = 0.0

    log_message('starting main loop')
    for index, row in trading_day.iterrows():
        # get the time of this message
        time_from_open = (index - pd.Timedelta(hours=9, minutes=30))
        minutes_from_open = (time_from_open.hour * 60) + time_from_open.minute

        # MARKET DATA HANDLING
        if pd.isna(row.trade_px):  # it's a quote
            # skip if not NBBO
            if not ((row.qu_source == 'N') and (row.natbbo_ind == 4)):
                continue
            # set our local NBBO variables
            if (row.bid_px > 0 and row.bid_size > 0):
                bid_price = row.bid_px
                bid_size = row.bid_size * round_lot
            if (row.ask_px > 0 and row.ask_size > 0):
                ask_price = row.ask_px
                ask_size = row.ask_size * round_lot
            quote_count += 1
            message_type = 'q'
        else:  # it's a trade
            # store the last trade price
            prev_price = last_price
            # now get the new data
            last_price = row.trade_px
            last_size = row.trade_size
            trade_count += 1
            cumulative_volume += row.trade_size
            vwap_numerator += last_size * last_price
            message_type = 't'

            # check the trade against our quotes
            # for now assume we always have a quote out there
            # has our quote been hit or lifted?

            # check bid
            if (last_price <= our_bid) & (bid_price > 0):  # our bid was hit
                fill_size = min(live_order_quantity, last_size)
                record_trade(buys, index, our_bid, fill_size, current_bar, 'p',
                             'b')
                current_net_pos += fill_size
                net_positions.loc[index] = [current_net_pos]
                #print("buy: NBO: {} trade size: {} net_pos: {}".format( our_bid, fill_size, current_net_pos ) )
            # check offer
            if (last_price >= our_ask) & (ask_price > 0):  # our ask was lifted
                fill_size = min(live_order_quantity, last_size)
                record_trade(sells, index, our_ask, fill_size, current_bar,
                             'p', 's')
                current_net_pos -= fill_size
                net_positions.loc[index] = [current_net_pos]
                #print("sell: NBO: {} trade size: {} net_pos: {}".format( our_ask, fill_size, current_net_pos ) )

        # TICK FACTOR
        # only update if it's a trade
        if message_type == 't':
            # calc the tick
            this_tick = np.sign(last_price - prev_price)
            if this_tick == 0:
                this_tick = prev_tick

            # now calc the tick
            if tick_factor == 0:
                tick_factor = this_tick
            else:
                tick_factor = (tick_ema_alpha *
                               this_tick) + (1 - tick_ema_alpha) * tick_factor

            # store the last tick
            prev_tick = this_tick

        # RISK FACTOR
        # TODO

        # PRICING LOGIC
        new_midpoint = bid_price + (ask_price - bid_price) / 2
        if new_midpoint > 0:
            midpoint = new_midpoint

        # FAIR VALUE CALCULATION
        # check inputs, skip of the midpoint is zero, we've got bogus data (or we're at start of day)
        if midpoint == 0:
            #print( "{} no midpoint. b:{} a:{}".format( index, bid_price, ask_price ) )
            continue
        fair_value = midpoint + half_spread * ((tick_coef * tick_factor) +
                                               (risk_coef * risk_factor))

        # collect our data
        fair_values[index] = fair_value
        midpoints[index] = midpoint
        tick_factors[index] = tick_factor

        # QUOTE PLACEMENT
        # for now we're going to assume no hedging or aggressive trading at all.

        # in this version just ignore fair value and always set our price to the BBO
        if peg_to_bbo:
            #print("Pegging to NBBO")
            our_bid = bid_price
            our_ask = ask_price

        # use our fair value
        else:
            # in this version, bid and offer are placed around the fair value but constrained by nbbo
            our_bid = round(
                min(fair_value - (half_spread_coef * half_spread), bid_price),
                2)
            our_ask = round(
                max(fair_value + (half_spread_coef * half_spread), ask_price),
                2)
            #print("bid: {} ask: {}".format(our_bid, our_ask))

    # looping done
    log_message('end simulation loop')
    log_message('order analytics')

    # TODO: add a final trade for net position at m2m to do simple P&L closeout
    if current_net_pos > 0:  # we need to sell
        record_trade(sells, index, bid_price, current_net_pos, current_bar,
                     'p', 's')
        print("selling to close residual of {} shares".format(current_net_pos))
        current_net_pos -= current_net_pos
    else:  # we need to buy
        record_trade(buys, index, ask_price, current_net_pos, current_bar, 'p',
                     'b')
        print("buying to close residual of {} shares".format(current_net_pos))
        current_net_pos += current_net_pos

    # Now, let's look at some stats
    # clean up all the ticks in which we didn't do trades
    buys = buys.dropna()
    sells = sells.dropna()

    log_message('Algo run complete.')

    # assemble results and return
    # TODO: add P&L
    return {
        'midpoints': midpoints,
        'fair_values': fair_values,
        'tick_factors': tick_factors,
        'buys': buys,
        'sells': sells,
        'quote_count': quote_count,
        'net_positions': net_positions,
        'residual_position': current_net_pos
    }
Example #2
0
def algo_loop(trading_day, order_side, original_order_quantity,
              vwap_coefficients):
    log_message('Beginning VWAP run: {:s} {:d} shares'.format(
        order_side, original_order_quantity))

    round_lot = 100
    avg_spread = (trading_day.ask_px - trading_day.bid_px).mean()
    print("Average stock spread for sample: {:.4f}".format(avg_spread))

    # generate target schedule - use bins 1 - 390 giving an automatic 1 minute "look ahead"
    # note that targets have been converted to shares from percent
    order_targets = vwap_target(np.arange(0, 390, dtype='int64'),
                                vwap_coefficients) * original_order_quantity

    # let's force the last bin to complete
    order_targets[-1] = original_order_quantity

    # init our price and volume variables
    [last_price, last_size, bid_price, bid_size, ask_price, ask_size,
     volume] = np.zeros(7)

    # init our counters
    [trade_count, quote_count, cumulative_volume] = [0, 0, 0]

    # init some time series objects for collection of telemetry
    fair_values = pd.Series(index=trading_day.index)
    midpoints = pd.Series(index=trading_day.index)
    schedule_factors = pd.Series(index=trading_day.index)

    # let's set up a container to hold trades. preinitialize with the index
    trades = pd.DataFrame(columns=['price', 'shares', 'bar', 'trade_type'],
                          index=trading_day.index)

    max_behind = 2000

    # MAIN EVENT LOOP
    current_bar = 0
    current_target_shares = 0

    quantity_behind = 0

    # track state and values for a current working order
    live_order = False
    live_order_price = 0.0
    live_order_quantity = 0.0

    # other order and market variables
    total_quantity_filled = 0
    quantity_remaining = original_order_quantity - total_quantity_filled
    vwap_numerator = 0.0

    total_trade_count = 0
    total_agg_count = 0
    total_pass_count = 0

    # fair value pricing variables
    midpoint = 0.0
    fair_value = 0.0
    schedule_factor = 0.0
    schedule_coef = 1.0

    log_message('starting main loop')
    for index, row in trading_day.iterrows():
        # get the time of this message
        time_from_open = (index - pd.Timedelta(hours=9, minutes=30))
        minutes_from_open = (time_from_open.hour * 60) + time_from_open.minute

        # MARKET DATA HANDLING
        if pd.isna(row.trade_px):  # it's a quote
            # skip if not NBBO
            if not ((row.qu_source == 'N') and (row.natbbo_ind == 4)):
                continue
            # set our local NBBO variables
            if (row.bid_px > 0 and row.bid_size > 0):
                bid_price = row.bid_px
                bid_size = row.bid_size * round_lot
            if (row.ask_px > 0 and row.ask_size > 0):
                ask_price = row.ask_px
                ask_size = row.ask_size * round_lot
            quote_count += 1
        else:  # it's a trade
            last_price = row.trade_px
            last_size = row.trade_size
            trade_count += 1
            cumulative_volume += row.trade_size
            vwap_numerator += last_size * last_price

            # CHECK OPEN ORDER(S) if we have a live order,
            # has it been filled by the trade that just happened?
            if live_order:
                if (order_side == 'b') and (last_price <= live_order_price):
                    fill_size = min(live_order_quantity, last_size)
                    record_trade(trades, index, live_order_price, fill_size,
                                 current_bar, 'p')
                    total_quantity_filled += fill_size
                    total_pass_count += 1

                    # even if we only got partially filled, let's assume we're cancelling the entire quantity.
                    # If we're still behind we'll replace later in the loop
                    live_order = False
                    live_order_price = 0.0
                    live_order_quantity = 0.0
                    quantity_behind = current_target_shares - total_quantity_filled

                if (order_side == 's') and (last_price >= live_order_price):
                    fill_size = min(live_order_quantity, last_size)
                    record_trade(trades, index, live_order_price, fill_size,
                                 current_bar, 'p')
                    total_quantity_filled += fill_size
                    total_pass_count += 1

                    # even if we only got partially filled, let's assume we're cancelling the entire quantity.
                    # If we're still behind we'll replace later in the loop
                    live_order = False
                    live_order_price = 0.0
                    live_order_quantity = 0.0
                    quantity_behind = current_target_shares - total_quantity_filled

        # SCHEDULE FACTOR
        if minutes_from_open > current_bar:
            # we're in a new bar do new bar things here
            current_bar = minutes_from_open
            current_target_shares = order_targets[current_bar]
            quantity_behind = current_target_shares - total_quantity_filled
            # uncomment for debug
            #print( "{} bar: {:d} target:{:.2f}".format(index, current_bar, current_target_shares) )

        # PRICING LOGIC
        new_midpoint = bid_price + (ask_price - bid_price) / 2
        if new_midpoint > 0:
            midpoint = new_midpoint

        schedule_factor = calc_schedule_factor(max_behind,
                                               current_target_shares,
                                               total_quantity_filled,
                                               original_order_quantity)

        # FAIR VALUE CALCULATION
        # check inputs, skip of the midpoint is zero, we've got bogus data (or we're at start of day)
        if midpoint == 0:
            #print( "{} no midpoint. b:{} a:{}".format( index, bid_price, ask_price ) )
            continue
        fair_value = midpoint + (schedule_coef * schedule_factor * avg_spread /
                                 2)

        # collect our data
        fair_values[index] = fair_value
        midpoints[index] = midpoint
        schedule_factors[index] = schedule_factor

        # TRADING LOGIC
        # check where our FV is versus the BBO and constrain
        if order_side == 'b':
            if fair_value >= ask_price and quantity_behind > round_lot:
                total_agg_count += 1

                new_trade_price = ask_price

                # now place our aggressive order: assume you can execute the full size across spread
                new_order_quantity = calc_order_quantity(
                    quantity_behind, round_lot, quantity_remaining)

                record_trade(trades, index, new_trade_price,
                             new_order_quantity, current_bar, 'a')

                # update quantity remaining
                quantity_remaining = min(
                    0, quantity_remaining - new_order_quantity)
                total_quantity_filled += new_order_quantity

                live_order_quantity = 0.0
                live_order_price = 0.0
                live_order = False

            else:  # we're not yet willing to cross the spread, stay passive
                if quantity_behind > round_lot:
                    live_order_price = bid_price
                    live_order_quantity = calc_order_quantity(
                        quantity_behind, round_lot, quantity_remaining)
                    #live_order_quantity = quantity_behind
                    live_order = True

        elif order_side == 's':
            if fair_value <= bid_price and quantity_behind > round_lot:
                total_agg_count += 1

                new_trade_price = bid_price

                # now place our aggressive order: assume you can execute the full size across spread
                new_order_quantity = calc_order_quantity(
                    quantity_behind, round_lot, quantity_remaining)
                #new_order_quantity = quantity_behind
                record_trade(trades, index, new_trade_price,
                             new_order_quantity, current_bar, 'a')

                # update quantity remaining
                quantity_remaining = min(
                    0, quantity_remaining - new_order_quantity)
                total_quantity_filled += new_order_quantity

                live_order_quantity = 0.0
                live_order_price = 0.0
                live_order = False

            else:  # not yet willing to cross spread
                if quantity_behind > round_lot:
                    live_order_price = ask_price
                    live_order_quantity = calc_order_quantity(
                        quantity_behind, round_lot, quantity_remaining)
                    #live_order_quantity = quantity_behind
                    live_order = True
        else:
            # we shouldn't have got here, something is wrong with our order type
            print('Got an unexpected order_side value: ' + str(order_side))

    # looping done
    log_message('end simulation loop')
    log_message('order analytics')

    # Now, let's look at some stats
    trades = trades.dropna()
    day_vwap = vwap_numerator / cumulative_volume

    # prep our text output
    avg_price = (trades['price'] *
                 trades['shares']).sum() / trades['shares'].sum()

    log_message('VWAP run complete.')

    # assemble results and return

    return {
        'midpoints': midpoints,
        'fair_values': fair_values,
        'schedule_factors': schedule_factors,
        'trades': trades,
        'quote_count': quote_count,
        'day_vwap': day_vwap,
        'avg_price': avg_price,
        'order_targets': order_targets
    }
Example #3
0
def algo_loop( trading_day, risk_adj = 0, risk_denominator=1, tick_coef = 1, tick_window = 20, spread_factor = 2,
               risk_factor=0):
    log_message( 'Beginning Tick Strategy run' )
    #log_message( 'TODO: remove this message. Simply a test to see how closely you are reading this code' )


    round_lot = 100
    avg_spread = (trading_day.ask_px - trading_day.bid_px).mean()
    print("Average stock spread for sample: {:.4f}".format(avg_spread))

    # init our price and volume variables
    [last_price, last_size, bid_price, bid_size, ask_price, ask_size, volume] = np.zeros(7)

    # init some time series objects for collection of telemetry
    fair_values = pd.Series(index=trading_day.index)
    midpoints = pd.Series(index=trading_day.index)
    # tick_factors = pd.Series( index=trading_day.index )
    # risk_factors = pd.Series( index=trading_day.index )

    # let's set up a container to hold trades. preinitialize with the index
    trades = pd.DataFrame(
        columns=['tick', 'risk', 'fair_value', 'market_price', 'trade_price', 'avg_price', 'position', 'unrealized_pnl',
                 'realized_pnl', 'trade_shares', 'trade_type', 'trade_side'], index=trading_day.index)

    # MAIN EVENT LOOP
    trade_count = 0
    order_type = '-'
    order_side = '-'

    avg_price = 0.0

    previous_pos = 0
    current_pos = 0
    trade_size = 1

    unrealized_pnl = 0.0
    realized_pnl = 0.0

    # track state and values for a current working order
    live_order = False
    live_order_price = 0.0
    live_order_quantity = 0.0

    # other order and market variables

    # fair value pricing variables
    midpoint = 0.0
    fair_value = 0.0

    # define our accumulator for the tick EMA
    message_type = 0

    tick_factor = 0
    tick_ema_alpha = 2 / (tick_window + 1)
    prev_tick = 0
    this_tick = 0

    # risk factor for part 2
    # TODO: implement and evaluate

    risk_coef = -1.0
    prev_risk = 0
    this_risk = 0
    

    # signals
    signal: int = 0

    log_message('starting main loop')
    for index, row in trading_day.iterrows():

        # MARKET DATA HANDLING

        # When it's quote data
        if pd.isna(row.trade_px):  # it's a quote
            # skip if not NBBO
            if not ((row.qu_source == 'N') and (row.natbbo_ind == 4)):
                continue
            # set our local NBBO variables
            if (row.bid_px > 0 and row.bid_size > 0):
                bid_price = row.bid_px
                # bid_size = row.bid_size * round_lot
            if (row.ask_px > 0 and row.ask_size > 0):
                ask_price = row.ask_px
                # ask_size = row.ask_size * round_lot

            message_type = 'q'


        # When it's trade data
        else:  # it's a trade
            # store the last trade price
            prev_price = last_price

            # now get the new data
            last_price = row.trade_px
            last_size = row.trade_size

            message_type = 't'

            # TICK FACTOR
            # only update if it's a trade
            if message_type == 't':
                # calc the tick
                this_tick = np.sign(last_price - prev_price)
                if this_tick == 0:
                    this_tick = prev_tick

                # now calc the tick
                if tick_factor == 0:
                    tick_factor = this_tick
                else:
                    tick_factor = (tick_ema_alpha * this_tick) + (1 - tick_ema_alpha) * tick_factor

                # store the last tick
                prev_tick = this_tick

            # TODO: For Part 2 Incorporate the Risk Factor
            # RISK FACTOR
            # use sigmoid function to map any real number to the range(-1,1)
            if message_type == 't' and risk_adj == 1:
                # call the risk
                this_risk = (1/spread_factor)*sigmoid(current_pos * avg_price / risk_denominator)
                if this_risk == 0:
                    this_risk = prev_risk

                # now calculate the risk factor
                if risk_factor == 0:
                    risk_factor = this_risk
                else:
                    risk_factor = this_risk

                #store the last risk
                prev_risk = this_risk

                
            # PRICING LOGIC
            new_midpoint = bid_price + (ask_price - bid_price) / 2
            if new_midpoint > 0:
                midpoint = new_midpoint

            # FAIR VALUE CALCULATION
            # check inputs, skip of the midpoint is zero, we've got bogus data (or we're at start of day)
            if midpoint == 0:
                continue
            fair_value = midpoint + (ask_price - bid_price)/spread_factor * ((tick_coef * tick_factor) + (risk_coef * risk_factor))

            # collect our data
            # fair_values[ index ] = fair_value
            # midpoints[ index ] = midpoint
            # tick_factors[ index ] = tick_factor
            # TODO: add collectors for new factors

            # TRADING LOGIC

            # update signal
            signal = np.sign(last_price - prev_price)

            # LONG POSITION
            if current_pos > 0:  # long position

                # CHECK for live limit order first
                if live_order:
                    if (order_side == 'b') and (last_price <= live_order_price):
                        order_type = 'Pas' #passive trade
                        # update P&L
                        unrealized_pnl = current_pos * (last_price - avg_price)
                        # realized_pnl unchanged

                        # update position
                        previous_pos = current_pos
                        current_pos = current_pos + live_order_quantity

                        # update avg(buy) price
                        avg_price = (previous_pos * avg_price + live_order_quantity * live_order_price) / current_pos

                        # now place our aggressive order and record trade information
                        record_trade(trade_df=trades, idx=index, tick=signal, risk=risk_factor, fair_value=fair_value,
                                     market_price=last_price, trade_price=live_order_price, avg_price=avg_price,
                                     position=current_pos, unrealized_pnl=unrealized_pnl, realized_pnl=realized_pnl,
                                     trade_shares=trade_size,
                                     trade_type=order_type, trade_side=order_side)

                        # deal with live order
                        live_order_quantity = 0.0
                        live_order_price = 0.0
                        live_order = False

                        continue

                    elif (order_side == 's') and (last_price >= live_order_price):
                        order_type = 'Pas'

                        # update P&L
                        unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price,
                                                                  avg_price=avg_price)
                        realized_pnl = calculate_realized_pnl(realized_pnl=realized_pnl, trade_size=trade_size,
                                                              order_price=live_order_price, avg_price=avg_price)

                        # update position
                        current_pos = current_pos - live_order_quantity

                        # avg(buy) price unchanged

                        # now place our order and record trade information
                        record_trade(trade_df=trades, idx=index, tick=signal, risk=risk_factor, fair_value=fair_value,
                                     market_price=last_price, trade_price=live_order_price, avg_price=avg_price,
                                     position=current_pos, unrealized_pnl=unrealized_pnl, realized_pnl=realized_pnl,
                                     trade_shares=trade_size,
                                     trade_type=order_type, trade_side=order_side)

                        # deal with live order
                        live_order = False
                        live_order_price = 0.0
                        live_order_quantity = 0.0

                        continue

                # TODO: determine if we want to buy or sell
                if signal == 1:  # if signal appears and we hold a short position or zero position, buy!
                    order_side = 'b'

                    # if fair price is > ask, buy agg
                    if fair_value >= ask_price:
                        order_type = 'Agg'
                        # trade_price = ask_price ;  trade_size = +100

                        # update P&L
                        unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price,
                                                                  avg_price=avg_price)
                        # realized_pnl unchanged

                        # update position
                        previous_pos = current_pos
                        current_pos = current_pos + trade_size

                        # update avg(buy) price
                        avg_price = (previous_pos * avg_price + trade_size * ask_price) / current_pos

                        # now place our aggressive order and record trade information
                        record_trade(trade_df=trades, idx=index, tick=signal, risk=risk_factor, fair_value=fair_value,
                                     market_price=last_price, trade_price=ask_price, avg_price=avg_price,
                                     position=current_pos, unrealized_pnl=unrealized_pnl, realized_pnl=realized_pnl,
                                     trade_shares=trade_size,
                                     trade_type=order_type, trade_side=order_side)

                        # deal with live order
                        live_order_quantity = 0.0
                        live_order_price = 0.0
                        live_order = False

                    # if fair price is > bid, buy passive
                    else:
                        # order_type = 'Pas'
                        # position unchanged

                        # avg(buy) price unchanged

                        # update P&L
                        # unrealized_pnl = current_pos * (last_price - avg_price)
                        # realized_pnl unchanged

                        # don't need to record trade information

                        # send limit order
                        live_order_quantity = 1 * trade_size  # = +100
                        live_order_price = bid_price
                        live_order = True

                elif signal == -1:
                    order_side = 's'

                    # if fair price is < bid, sell agg
                    if fair_value <= bid_price:
                        order_type = 'Agg'
                        # trade_price = bid_price ; trade_size = -100

                        # update P&L
                        unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price,
                                                                  avg_price=avg_price)
                        realized_pnl = calculate_realized_pnl(realized_pnl=realized_pnl, trade_size=trade_size,
                                                              order_price=bid_price, avg_price=avg_price)

                        # update position
                        current_pos = current_pos - trade_size

                        # avg(buy) price unchanged

                        # now place our aggressive order and record trade information
                        record_trade(trade_df=trades, idx=index, tick=signal, risk=risk_factor, fair_value=fair_value,
                                     market_price=last_price, trade_price=bid_price, avg_price=avg_price,
                                     position=current_pos, unrealized_pnl=unrealized_pnl, realized_pnl=realized_pnl,
                                     trade_shares=trade_size,
                                     trade_type=order_type, trade_side=order_side)

                        # deal with live order
                        live_order_quantity = 0.0
                        live_order_price = 0.0
                        live_order = False

                    # if fair price is < ask, sell passive
                    else:
                        # order_type = 'Pas'
                        # position unchanged

                        # avg(buy) price unchanged

                        # update P&L
                        # unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price, avg_price=avg_price)
                        # realized_pnl unchanged

                        # don't need to record trade information

                        # send limit order
                        live_order_quantity = 1 * trade_size  # = 100
                        live_order_price = ask_price
                        live_order = True

                else:  # signal = 0
                    continue

            # SHORT POSITION
            elif current_pos < 0:  # short position

                # CHECK for live limit order first
                if live_order:
                    if (order_side == 'b') and (last_price <= live_order_price):
                        order_type = 'Pas'

                        # update P&L
                        unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price,
                                                                  avg_price=avg_price)
                        realized_pnl = calculate_realized_pnl(realized_pnl=realized_pnl, trade_size=trade_size,
                                                              order_price=live_order_price, avg_price=avg_price)

                        # update position
                        current_pos = current_pos + live_order_quantity

                        # avg(sell) price unchanged

                        # now place our aggressive order and record trade information
                        record_trade(trade_df=trades, idx=index, tick=signal, risk=risk_factor, fair_value=fair_value,
                                     market_price=last_price, trade_price=live_order_price, avg_price=avg_price,
                                     position=current_pos, unrealized_pnl=unrealized_pnl, realized_pnl=realized_pnl,
                                     trade_shares=trade_size,
                                     trade_type=order_type, trade_side=order_side)

                        # deal with live order
                        live_order_quantity = 0.0
                        live_order_price = 0.0
                        live_order = False

                        continue

                    elif (order_side == 's') and (last_price >= live_order_price):
                        order_type = 'Pas'

                        # update P&L
                        unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price,
                                                                  avg_price=avg_price)
                        # realized_pnl unchanged

                        # update position
                        previous_pos = current_pos
                        current_pos = current_pos - live_order_quantity

                        # update avg(buy) price
                        avg_price = (previous_pos * avg_price - live_order_quantity * live_order_price) / current_pos

                        # now place our aggressive order and record trade information
                        record_trade(trade_df=trades, idx=index, tick=signal, risk=risk_factor, fair_value=fair_value,
                                     market_price=last_price, trade_price=live_order_price, avg_price=avg_price,
                                     position=current_pos, unrealized_pnl=unrealized_pnl, realized_pnl=realized_pnl,
                                     trade_shares=trade_size,
                                     trade_type=order_type, trade_side=order_side)

                        # deal with live order
                        live_order = False
                        live_order_price = 0.0
                        live_order_quantity = 0.0

                        continue

                # TODO: determine if we want to buy or sell
                if signal == 1:  # if signal appears and we hold a short position or zero position, buy!
                    order_side = 'b'

                    # if fair price is > ask, buy agg
                    if fair_value >= ask_price:
                        order_type = 'Agg'
                        # trade_price = ask_price ; trade_size = +100

                        # update P&L
                        unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price,
                                                                  avg_price=avg_price)
                        realized_pnl = calculate_realized_pnl(realized_pnl=realized_pnl, trade_size=trade_size,
                                                              order_price=ask_price, avg_price=avg_price)

                        # update position
                        current_pos = current_pos + trade_size  # long shares to close previous short position or to open new position

                        # avg(sell) price unchanged

                        # now place our aggressive order and record trade information
                        record_trade(trade_df=trades, idx=index, tick=signal, risk=risk_factor, fair_value=fair_value,
                                     market_price=last_price, trade_price=ask_price, avg_price=avg_price,
                                     position=current_pos, unrealized_pnl=unrealized_pnl, realized_pnl=realized_pnl,
                                     trade_shares=trade_size,
                                     trade_type=order_type, trade_side=order_side)

                        # deal with live order
                        live_order_quantity = 0.0
                        live_order_price = 0.0
                        live_order = False

                    # if fair price is > bid, buy passive
                    else:
                        # order_type = 'Pas'
                        # position unchanged

                        # avg(buy) price unchanged

                        # update P&L
                        # unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price, avg_price=avg_price)
                        # realized_pnl unchanged

                        # don't need to record trade information

                        # send limit order
                        live_order_quantity = 1 * trade_size  # = +100
                        live_order_price = bid_price
                        live_order = True

                elif signal == -1:
                    order_side = 's'

                    # if fair price is < bid, sell agg
                    if fair_value <= bid_price:
                        order_type = 'Agg'
                        # trade_price = bid_price ; trade_size = -100

                        # update P&L
                        unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price,
                                                                  avg_price=avg_price)
                        # realized_pnl unchanged

                        # update position
                        previous_pos = current_pos
                        current_pos = current_pos - trade_size

                        # update avg(buy) price
                        trade_count += 1
                        avg_price = (previous_pos * avg_price - trade_size * bid_price) / current_pos

                        # now place our aggressive order and record trade information
                        record_trade(trade_df=trades, idx=index, tick=signal, risk=risk_factor, fair_value=fair_value,
                                     market_price=last_price, trade_price=bid_price, avg_price=avg_price,
                                     position=current_pos, unrealized_pnl=unrealized_pnl, realized_pnl=realized_pnl,
                                     trade_shares=trade_size,
                                     trade_type=order_type, trade_side=order_side)

                        # deal with live order
                        live_order_quantity = 0.0
                        live_order_price = 0.0
                        live_order = False

                    # if fair price is < ask, sell passive
                    else:
                        # order_type = 'Pas'
                        # position unchanged

                        # avg(buy) price unchanged

                        # update P&L
                        # unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price, avg_price=avg_price)
                        # realized_pnl unchanged

                        # don't need to record trade information

                        # send limit order
                        live_order_quantity = 1 * trade_size  # = 100
                        live_order_price = ask_price
                        live_order = True

                else:  # signal = 0
                    continue

            # ZERO POSITION
            else:  # position = 0

                # clear the avg_price and trade_count so next transaction can reset avg price of position
                avg_price = 0
                trade_count = 0

                # CHECK for live limit order first
                if live_order:
                    if (order_side == 'b') and (last_price <= live_order_price):
                        order_type = 'Pas'

                        # update P&L
                        unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price,
                                                                  avg_price=avg_price)
                        # realized_pnl unchanged

                        # update position
                        previous_pos = current_pos
                        current_pos = current_pos + live_order_quantity

                        # update avg(buy) price
                        avg_price = (previous_pos * avg_price + live_order_quantity * live_order_price) / current_pos

                        # now place our aggressive order and record trade information
                        record_trade(trade_df=trades, idx=index, tick=signal, risk=risk_factor, fair_value=fair_value,
                                     market_price=last_price, trade_price=live_order_price, avg_price=avg_price,
                                     position=current_pos, unrealized_pnl=unrealized_pnl, realized_pnl=realized_pnl,
                                     trade_shares=trade_size,
                                     trade_type=order_type, trade_side=order_side)

                        # deal with live order
                        live_order_quantity = 0.0
                        live_order_price = 0.0
                        live_order = False

                        continue

                    elif (order_side == 's') and (last_price >= live_order_price):
                        order_type = 'Pas'

                        # update P&L
                        unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price,
                                                                  avg_price=avg_price)
                        # realized_pnl unchanged

                        # update position
                        previous_pos = current_pos
                        current_pos = current_pos - live_order_quantity

                        # update avg(buy) price
                        avg_price = (previous_pos * avg_price - live_order_quantity * live_order_price) / current_pos

                        # now place our aggressive order and record trade information
                        record_trade(trade_df=trades, idx=index, tick=signal, risk=risk_factor, fair_value=fair_value,
                                     market_price=last_price, trade_price=live_order_price, avg_price=avg_price,
                                     position=current_pos, unrealized_pnl=unrealized_pnl, realized_pnl=realized_pnl,
                                     trade_shares=trade_size,
                                     trade_type=order_type, trade_side=order_side)

                        # deal with live order
                        live_order = False
                        live_order_price = 0.0
                        live_order_quantity = 0.0

                        continue

                # TODO: determine if we want to buy or sell
                if signal == 1:  # if signal appears and we hold a short position or zero position, buy!
                    order_side = 'b'

                    # if fair price is > ask, buy agg
                    if fair_value >= ask_price:
                        order_type = 'Agg'
                        # trade_price = ask_price ;  trade_size = +100

                        # update P&L
                        unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price,
                                                                  avg_price=avg_price)
                        # realized_pnl unchanged

                        # update position
                        previous_pos = current_pos
                        current_pos = current_pos + trade_size

                        # update avg(buy) price
                        avg_price = (previous_pos * avg_price + trade_size * ask_price) / current_pos

                        # now place our aggressive order and record trade information
                        record_trade(trade_df=trades, idx=index, tick=signal, risk=risk_factor, fair_value=fair_value,
                                     market_price=last_price, trade_price=ask_price, avg_price=avg_price,
                                     position=current_pos, unrealized_pnl=unrealized_pnl, realized_pnl=realized_pnl,
                                     trade_shares=trade_size,
                                     trade_type=order_type, trade_side=order_side)

                        # deal with live order
                        live_order_quantity = 0.0
                        live_order_price = 0.0
                        live_order = False

                    # if fair price is > bid, buy passive
                    else:
                        # order_type = 'Pas'
                        # position unchanged

                        # avg(buy) price unchanged

                        # update P&L
                        # unrealized_pnl = current_pos * (last_price - avg_price)
                        # realized_pnl unchanged

                        # don't need to record trade information

                        # send limit order
                        live_order_quantity = 1 * trade_size  # = +100
                        live_order_price = bid_price
                        live_order = True

                elif signal == -1:
                    order_side = 's'

                    # if fair price is < bid, sell agg
                    if fair_value <= bid_price:
                        order_type = 'Agg'
                        # trade_price = bid_price ; trade_size = -100

                        # update P&L
                        unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price,
                                                                  avg_price=avg_price)
                        # realized_pnl unchanged

                        # update position
                        previous_pos = current_pos
                        current_pos = current_pos - trade_size

                        # update avg(buy) price
                        avg_price = (previous_pos * avg_price - trade_size * bid_price) / current_pos

                        # now place our aggressive order and record trade information
                        record_trade(trade_df=trades, idx=index, tick=signal, risk=risk_factor, fair_value=fair_value,
                                     market_price=last_price, trade_price=bid_price, avg_price=avg_price,
                                     position=current_pos, unrealized_pnl=unrealized_pnl, realized_pnl=realized_pnl,
                                     trade_shares=trade_size,
                                     trade_type=order_type, trade_side=order_side)

                        # deal with live order
                        live_order_quantity = 0.0
                        live_order_price = 0.0
                        live_order = False

                    else:
                        # order_type = 'Pas'
                        # position unchanged

                        # avg(buy) price unchanged

                        # update P&L
                        # unrealized_pnl = calculate_unrealized_pnl(position=current_pos, last_price=last_price, avg_price=avg_price)
                        # realized_pnl unchanged

                        # don't need to record trade information

                        # send limit order
                        live_order_quantity = 1 * trade_size  # = -100
                        live_order_price = ask_price
                        live_order = True

                else:  # signal = 0
                    continue

    # looping done
    log_message('end simulation loop')
    log_message('order analytics')

    # Now, let's look at some stats
    trades = trades.dropna()

    log_message('Algo run complete.')

    # assemble results and return
    # TODO: add P&L
    return trades
Example #4
0
def algo_loop(trading_day):
    log_message('Beginning Tick Strategy run')
    #log_message( 'TODO: remove this message. Simply a test to see how closely you are reading this code' )

    round_lot = 100
    avg_spread = (trading_day.ask_px - trading_day.bid_px).mean()
    half_spread = avg_spread / 2
    print("Average stock spread for sample: {:.4f}".format(avg_spread))

    # init our price and volume variables
    [last_price, last_size, bid_price, bid_size, ask_price, ask_size,
     volume] = np.zeros(7)

    # init some time series objects for collection of telemetry
    fair_values = pd.Series(index=trading_day.index)
    midpoints = pd.Series(index=trading_day.index)
    tick_factors = pd.Series(index=trading_day.index)
    risk_factors = pd.Series(index=trading_day.index)

    # let's set up a container to hold trades. preinitialize with the index
    trades = pd.DataFrame(columns=[
        'current_tick', 'stock_price', 'current_position', 'current_pnl',
        'trade_price', 'trade_shares', 'trade_type', 'trade_side'
    ],
                          index=trading_day.index)

    # MAIN EVENT LOOP
    live_order_index = trading_day.index[0]

    start_price = 0.0  # price when open the position
    prev_price = 0

    current_pos = 0.0
    trade_size = 100

    current_pnl = 0.0
    total_pnl = 0.0

    # track state and values for a current working order
    live_order = False
    live_order_price = 0.0
    live_order_quantity = 0.0
    order_side = '-'

    # other order and market variables

    # fair value pricing variables
    midpoint = 0.0
    fair_value = 0.0

    # define our accumulator for the tick EMA
    message_type = 0
    tick_coef = 1
    tick_window = 20
    tick_factor = 0
    tick_ema_alpha = 2 / (tick_window + 1)
    prev_tick = 0
    this_tick = 0

    # risk factor for part 2
    # TODO: implement and evaluate
    risk_factor = 0.0
    risk_coef = 0.0

    # signals
    tick_signal = 0

    log_message('starting main loop')
    for index, row in trading_day.iterrows():
        # get the time of this message
        time_from_open = (index - pd.Timedelta(hours=9, minutes=30))
        minutes_from_open = (time_from_open.hour * 60) + time_from_open.minute

        # MARKET DATA HANDLING
        # When it's quote data
        if pd.isna(row.trade_px):  # it's a quote
            # skip if not NBBO
            if not ((row.qu_source == 'N') and (row.natbbo_ind == 4)):
                continue
            # set our local NBBO variables
            if (row.bid_px > 0 and row.bid_size > 0):
                bid_price = row.bid_px
                #bid_size = row.bid_size * round_lot
            if (row.ask_px > 0 and row.ask_size > 0):
                ask_price = row.ask_px
                #ask_size = row.ask_size * round_lot

            message_type = 'q'

        #When it's trade data
        else:  # it's a trade
            # store the last trade price
            prev_price = last_price

            # now get the new data
            last_price = row.trade_px
            #last_size = row.trade_size

            message_type = 't'

            # CHECK OPEN ORDER(S) if we have a live order,
            # has it been filled by the trade that just happened?
            if live_order:
                if (order_side == 'b') and (last_price <= live_order_price):  #

                    # even if we only got partially filled, let's assume the entire live order quantity can be filled.
                    fill_size = live_order_quantity  # = +100

                    # update current position
                    current_pnl = current_pos * (live_order_price -
                                                 start_price)
                    current_pos = current_pos + fill_size

                    # record trading data (previous index)
                    record_trade(trades, live_order_index, prev_tick,
                                 last_price, current_pos, current_pnl,
                                 live_order_price, fill_size, 'p', order_side)
                    total_pnl += current_pnl

                    # update start price
                    if current_pos > 0:
                        start_price = live_order_price
                    elif current_pos == 0:
                        start_price = 0.0

                    # deal with live order
                    live_order = False
                    live_order_price = 0.0
                    live_order_quantity = 0.0

                if (order_side == 's') and (last_price >= live_order_price):

                    # even if we only got partially filled, let's assume the entire live order quantity can be filled.
                    fill_size = live_order_quantity  # = -100

                    # update current position
                    current_pnl = current_pos * (live_order_price -
                                                 start_price)
                    current_pos = current_pos + fill_size

                    # record trading data
                    record_trade(trades, live_order_index, prev_tick,
                                 last_price, current_pos, current_pnl,
                                 live_order_price, fill_size, 'p', order_side)
                    total_pnl += current_pnl

                    # update start price
                    if current_pos < 0:
                        start_price = live_order_price
                    elif current_pos == 0:
                        start_price = 0.0

                    # deal with live order
                    live_order = False
                    live_order_price = 0.0
                    live_order_quantity = 0.0

            # TICK FACTOR
            # only update if it's a trade
            if message_type == 't':
                # calc the tick
                this_tick = np.sign(last_price - prev_price)
                if this_tick == 0:
                    this_tick = prev_tick

                # now calc the tick
                if tick_factor == 0:
                    tick_factor = this_tick
                else:
                    tick_factor = (tick_ema_alpha * this_tick
                                   ) + (1 - tick_ema_alpha) * tick_factor

                # store the last tick
                prev_tick = this_tick

            # RISK FACTOR
            # TODO: For Part 2 Incorporate the Risk Factor

            # PRICING LOGIC
            new_midpoint = bid_price + (ask_price - bid_price) / 2
            if new_midpoint > 0:
                midpoint = new_midpoint

            # FAIR VALUE CALCULATION
            # check inputs, skip of the midpoint is zero, we've got bogus data (or we're at start of day)
            if midpoint == 0:
                #print( "{} no midpoint. b:{} a:{}".format( index, bid_price, ask_price ) )
                continue
            fair_value = midpoint + half_spread * (
                (tick_coef * tick_factor) +
                (risk_coef * risk_factor))  #tick_coef = 1

            # collect our data
            fair_values[index] = fair_value
            midpoints[index] = midpoint
            #tick_factors[ index ] = tick_factor
            # TODO: add collectors for new factors
            # risk_factors[ index ] =

            # TRADING LOGIC

            # update signal
            tick_signal = this_tick

            # TODO: determine if we want to buy or sell
            if (tick_signal == 1) and (
                    current_pos <= 0
            ):  # if signal appears and we hold a short position or zero position, buy!
                order_side = 'b'

                # if fair price is > ask, buy agg
                if fair_value >= ask_price:

                    new_trade_price = ask_price
                    new_trade_size = 1 * trade_size  # = +100

                    # update P&L and position
                    current_pnl = current_pos * (new_trade_price - start_price)
                    current_pos = current_pos + new_trade_size  # long shares to close previous short position or to open new position

                    # now place our aggressive order and record trade information
                    record_trade(trades, index, tick_signal, last_price,
                                 current_pos, current_pnl, new_trade_price,
                                 new_trade_size, 'a', order_side)
                    total_pnl += current_pnl

                    # update start price
                    if current_pos > 0:
                        start_price = new_trade_price
                    elif current_pos == 0:
                        start_price = 0

                    # deal with live order
                    live_order_quantity = 0.0
                    live_order_price = 0.0
                    live_order = False

                else:  # if fair price is > bid, buy passive
                    # update P&L
                    current_pnl = current_pos * (last_price - start_price)
                    record_trade(trades, index, tick_signal, last_price,
                                 current_pos, current_pnl, 0, 0)

                    # send limit order
                    live_order_index = index
                    live_order_price = fair_value
                    live_order_quantity = tick_signal * trade_size  # = +100
                    live_order = True

            elif (tick_signal == -1) and (current_pos >= 0):
                order_side = 's'

                # if fair price is < bid, sell agg
                if fair_value <= bid_price:

                    new_trade_price = bid_price
                    new_trade_size = -1 * trade_size  # = -100

                    # update P&L and position
                    current_pnl = current_pos * (new_trade_price - start_price)
                    current_pos = current_pos + new_trade_size  # short shares to close previous long position or to open new position

                    # now place our aggressive order and record trade information
                    record_trade(trades, index, tick_signal, last_price,
                                 current_pos, current_pnl, new_trade_price,
                                 new_trade_size, 'a', order_side)
                    total_pnl += current_pnl

                    # update start price
                    if current_pos < 0:
                        start_price = new_trade_price
                    elif current_pos == 0:
                        start_price = 0

                    # deal with live order
                    live_order_quantity = 0.0
                    live_order_price = 0.0
                    live_order = False

                else:  # if fair price is < ask, sell passive
                    # update P&L
                    current_pnl = current_pos * (last_price - start_price)
                    record_trade(trades, index, tick_signal, last_price,
                                 current_pos, current_pnl, 0, 0)

                    # send limit order
                    live_order_index = index
                    live_order_price = fair_value
                    live_order_quantity = tick_signal * trade_size  # = -100
                    live_order = True

            else:
                # no order here. for now just continue
                current_pnl = current_pos * (last_price - start_price)
                record_trade(trades, index, tick_signal, last_price,
                             current_pos, current_pnl, 0, 0)
                continue

        prev_index = index

    # looping done
    log_message('end simulation loop')
    log_message('order analytics')

    # Now, let's look at some stats
    trades = trades.dropna()

    log_message('Algo run complete.')

    # assemble results and return
    # TODO: add P&L
    return {'trades': trades, 'total_pnl': total_pnl}
Example #5
0
def algo_loop(trading_day,
              factors_included=[1, 3, 4, 5],
              coef_included=[2, 8e-4, 10, 10],
              limit_orders_included=True,
              quantity_coef_=4000000,
              display_factors=False):

    factors_included = np.array(factors_included)
    if len(factors_included) != len(coef_included):
        print(
            'Factors included need to be the same length as coefficients included'
        )
        return 0

    log_message('Beginning Tick Strategy run')

    # Average spread is calculated with the NBBO prices
    avg_spread = (
        trading_day[((trading_day.qu_source == 'N') &
                     (trading_day.natbbo_ind == 4)) |
                    ((trading_day.qu_source == 'C') &
                     (trading_day.natbbo_ind == 'G'))].ask_px -
        trading_day[((trading_day.qu_source == 'N') &
                     (trading_day.natbbo_ind == 4)) |
                    ((trading_day.qu_source == 'C') &
                     (trading_day.natbbo_ind == 'G'))].bid_px).mean()
    # Calculating half the spread (used in factors)
    half_spread = avg_spread / 2
    print("Average stock spread for sample: {:.4f}".format(avg_spread))

    # init our price and volume variables
    [last_price, last_size, bid_price, bid_size, ask_price, ask_size,
     volume] = np.zeros(7)

    # init our counters
    [trade_count, quote_count, cumulative_volume] = [0, 0, 0]

    # init some time series objects for collection of telemetry
    fair_values = pd.Series(index=trading_day.index)
    midpoints = pd.Series(index=trading_day.index)
    bid_values = pd.Series(index=trading_day.index)
    ask_values = pd.Series(index=trading_day.index)
    tick_factors = pd.Series(index=trading_day.index)
    risk_factors = pd.Series(
        index=trading_day.index)  #-Added risk factor series - Matt
    c1_factors = pd.Series(index=trading_day.index)
    c2_factors = pd.Series(index=trading_day.index)
    c3_factors = pd.Series(index=trading_day.index)
    c4_factors = pd.Series(index=trading_day.index)
    c5_factors = pd.Series(index=trading_day.index)
    ma_factors = pd.Series(index=trading_day.index)
    indicator_list = np.zeros(5)

    # let's set up a container to hold trades. preinitialize with the index
    trades = pd.DataFrame(
        columns=['price', 'shares', 'bar', 'trade_type', 'side'],
        index=trading_day.index)

    ma_df = pd.DataFrame(columns=['MA_LT', 'MA_ST', 'MA_LT_m', 'MA_ST_m'],
                         index=trading_day.index)
    # Try to make this more general for many days
    trades_candle = pd.DataFrame(
        columns=['shares', 'side', 'filled'],
        index=[
            datetime.strptime(
                str(trading_day.index[0])[:10] + ' 09:30:00',
                '%Y-%m-%d %H:%M:%S') + timedelta(minutes=x) for x in range(391)
        ])
    candlesticks_df = pd.DataFrame(
        columns=['open', 'close', 'high', 'low'],
        index=[
            datetime.strptime(
                str(trading_day.index[0])[:10] + ' 09:30:00',
                '%Y-%m-%d %H:%M:%S') + timedelta(minutes=x) for x in range(391)
        ])
    candlestick_indicators = pd.DataFrame(
        columns=[4, 5, 6, 7, 8] + ['trades'],
        index=[
            datetime.strptime(
                str(trading_day.index[0])[:10] + ' 09:30:00',
                '%Y-%m-%d %H:%M:%S') + timedelta(minutes=x) for x in range(391)
        ])

    # MAIN EVENT LOOP
    current_bar = 0
    net_position = 0

    # track state and values for a current working order
    live_order = False
    live_order_price = 0.0
    live_order_quantity = 0.0
    order_side = '-'
    bid_price = 0
    ask_price = 0

    # other order and market variables
    total_quantity_filled = 0
    vwap_numerator = 0.0
    total_trade_count = 0
    total_agg_count = 0
    total_pass_count = 0

    # fair value pricing variables
    midpoint = 0.0
    fair_value = 0.0
    message_type = 0

    # Moving average
    ma_factor = 0.0
    if 1 in factors_included:
        ma_coef = coef_included[np.where(factors_included == 1)[0][0]]  #2#0.1
        temp = trading_day[trading_day.trade_px.isna() == False].shape[0]
        ma_interval = math.floor(temp * 5 / 390)
        ma_interval_long = math.floor(temp * 20 / 390)
        ma_vector = np.zeros(ma_interval)
        ma_vector_long = np.zeros(ma_interval_long)
        ma_vector_plot = np.zeros(ma_interval)
        ma_vector_long_plot = np.zeros(ma_interval_long)
        ma_i = 0
        ma_i_long = 0
        ma_bool = True
    else:
        ma_coef = 0

    # define our accumulator for the tick EMA
    tick_factor = 0.0
    if 2 in factors_included:
        tick_coef = coef_included[np.where(factors_included == 2)[0][0]]  #1.3
        tick_window = 30
        tick_ema_alpha = 2 / (tick_window + 1)
        prev_tick = 0
        prev_price = 0
    else:
        tick_coef = 0.0

    # risk factor for part 2
    risk_factor = 0.0
    if 3 in factors_included:
        risk_coef = coef_included[np.where(factors_included == 3)[0][0]]  #8e-4
    else:
        risk_coef = 0

    # Candlestick factor 1
    c1_factor = 0.0
    if 4 in factors_included:
        c1_coef = coef_included[np.where(factors_included == 4)[0][0]]  #10 #1
        c1_shift = 5
    else:
        c1_coef = 0
        c1_shift = 0

    # Candlestick factor 2
    c2_factor = 0.0
    if 5 in factors_included:
        c2_coef = coef_included[np.where(factors_included == 5)[0][0]]  #10
        c2_shift = 10
    else:
        c2_coef = 0
        c2_shift = 0

    # Candlestick factor 3
    c3_factor = 0.0
    if 6 in factors_included:
        c3_coef = coef_included[np.where(factors_included == 6)[0][0]]  # 10
        c3_shift = 60
    else:
        c3_coef = 0.0
        c3_shift = 0.0

    # Candlestick factor 4
    c4_factor = 0.0
    if 7 in factors_included:
        c4_coef = coef_included[np.where(factors_included == 7)[0][0]]
        c4_shift = 30
    else:
        c4_coef = 0.0
        c4_shift = 0

    # Candlestick factor 5
    c5_factor = 0.0
    if 8 in factors_included:
        c5_coef = coef_included[np.where(factors_included == 8)[0][0]]
        c5_shift = 30
    else:
        c5_coef = 0.0
        c5_shift = 0

    current_minute = 0
    last_minute = 0
    last_high = 0
    last_low = 0
    last_open = 0
    last_close = 0
    current_high = 0
    current_low = 0
    current_open = 0
    current_close = 0
    first_minute = True

    # factors calculating the quantity to buy and sell
    quantity_coef = quantity_coef_  #4000000
    # Floor on the minimum amount we need to place a limit order
    min_lim_order = 50

    # Variable that takes care of the fact that a passive order and aggressive order can be completed at the same time.
    # Instead of overwriting the trade in trades_df, we only allow one trade each iteration
    live_order_executed = False
    candlestick_calculation = False

    log_message('starting main loop')
    for index, row in trading_day.iterrows():

        last_minute = current_minute
        current_minute = index.minute
        # Change the if sentence if we have more than 1 day of data # IT WORKS FOR MANY DAYS
        if current_minute > last_minute or (current_minute == 0 and last_minute
                                            == 59) or first_minute:
            if first_minute:
                first_minute = not first_minute
            else:
                candlestick_calculation = True
                last_high = current_high
                last_low = current_low
                last_open = current_open
                last_close = current_close

                if current_minute == 0:
                    last_hour = index.hour - 1
                else:
                    last_hour = index.hour

                current_minute_data = trading_day[
                    (trading_day.index.hour == last_hour)
                    & (trading_day.index.minute == last_minute) &
                    (trading_day.trade_px.isna() == False)]

                temp = current_minute_data['trade_px']
                if len(temp) == 0:
                    current_high = last_close
                    current_low = last_close
                    current_open = last_close
                    current_close = last_close
                else:
                    current_high = temp.max()
                    current_low = temp.min()
                    current_open = temp[0]
                    current_close = temp[-1]

        # get the time of this message
        time_from_open = (index - pd.Timedelta(hours=9, minutes=30))
        minutes_from_open = (time_from_open.hour * 60) + time_from_open.minute

        ## Have to fix this because if a trade comes in, it has to fill the limit order as well
        ## but not just perform the candlestick trade and nothing more
        if candlestick_calculation:
            new_index = str(index)[:10] + ' ' + str(index.hour).zfill(2) + ':' + \
                        str(index.minute).zfill(2) + ':00'

            record_candlesticks(candlesticks_df, new_index, current_open,
                                current_close, current_high, current_low)
            if trades_candle['shares'][(trades_candle.index <= new_index) & (
                    trades_candle['filled'] == 0)].sum() != 0:
                live_order_executed = True
                if trades_candle['shares'][
                    (trades_candle.index <= new_index)
                        & (trades_candle['filled'] == 0)].sum() > 0:
                    print('Closing Position')
                    temp_side = 's'
                    record_trade(
                        trades, index, bid_price, trades_candle['shares']
                        [(trades_candle.index <= new_index)
                         & (trades_candle['filled'] == 0)].sum(), current_bar,
                        'a', temp_side)
                    net_position = net_position - trades_candle['shares'][
                        (trades_candle.index <= new_index)
                        & (trades_candle['filled'] == 0)].sum()
                    total_quantity_filled += trades_candle['shares'][
                        (trades_candle.index <= new_index)
                        & (trades_candle['filled'] == 0)].sum()
                    total_agg_count += 1
                    trades_candle['filled'][(trades_candle.index <= new_index)
                                            &
                                            (trades_candle['filled'] == 0)] = 1
                else:
                    print('Closing Position')
                    temp_side = 'b'
                    record_trade(
                        trades, index, ask_price, -trades_candle['shares'][
                            (trades_candle.index <= new_index)
                            & (trades_candle['filled'] == 0)].sum(),
                        current_bar, 'a', temp_side)
                    net_position = net_position - trades_candle['shares'][
                        (trades_candle.index <= new_index)
                        & (trades_candle['filled'] == 0)].sum()
                    total_quantity_filled -= trades_candle['shares'][
                        (trades_candle.index <= new_index)
                        & (trades_candle['filled'] == 0)].sum()
                    total_agg_count += 1
                    trades_candle['filled'][(trades_candle.index <= new_index)
                                            &
                                            (trades_candle['filled'] == 0)] = 1

        # MARKET DATA HANDLING
        if pd.isna(row.trade_px):  # it's a quote
            # skip if not NBBO
            if not (((row.qu_source == 'N') and (row.natbbo_ind == 4)) or
                    ((row.qu_source == 'C') and (row.natbbo_ind == 'G'))):
                candlestick_calculation = False
                bid_values[index] = bid_price
                ask_values[index] = ask_price
                continue
            # set our local NBBO variables
            if (row.bid_px > 0 and row.bid_size > 0):
                bid_price = row.bid_px
                bid_size = row.bid_size
            if (row.ask_px > 0 and row.ask_size > 0):
                ask_price = row.ask_px
                ask_size = row.ask_size
            quote_count += 1
            message_type = 'q'
        else:  # it's a trade
            # store the last trade price
            prev_price = last_price
            # now get the new data
            last_price = row.trade_px
            last_size = row.trade_size
            trade_count += 1
            cumulative_volume += row.trade_size
            vwap_numerator += last_size * last_price
            message_type = 't'

            # CHECK OPEN ORDER(S) if we have a live order,
            # has it been filled by the trade that just happened?
            if live_order and not live_order_executed and limit_orders_included:
                if (order_side == 'b') and (last_price <= live_order_price):
                    #Our side hasn't updated because once it does, the live order is cancelled
                    print('Limit buy @ %.3f' % (live_order_price))
                    fill_size = min(live_order_quantity, last_size)
                    record_trade(trades, index, live_order_price, fill_size,
                                 current_bar, 'p', order_side)
                    net_position = net_position + fill_size
                    total_quantity_filled += fill_size
                    total_pass_count += 1

                    # We chose to not cancel the limit order once a part of it was filled.
                    # We update the quantity
                    if fill_size >= live_order_quantity:
                        live_order = False
                        live_order_price = 0.0
                        live_order_quantity = 0.0
                    else:
                        live_order_quantity -= fill_size

                    live_order_executed = True

                if (order_side == 's') and (last_price >= live_order_price):
                    print('Limit sell @ %.3f' % (live_order_price))
                    fill_size = min(live_order_quantity, last_size)
                    record_trade(trades, index, live_order_price, fill_size,
                                 current_bar, 'p', order_side)
                    net_position = net_position - fill_size
                    total_quantity_filled += fill_size
                    total_pass_count += 1

                    # We chose to not cancel the limit order once a part of it was filled.
                    # We update the quantity
                    if fill_size >= live_order_quantity:
                        live_order = False
                        live_order_price = 0.0
                        live_order_quantity = 0.0
                    else:
                        live_order_quantity -= fill_size

                    live_order_executed = True

        # MOVING AVERAGE
        # calc moving average factor
        if 1 in factors_included:
            if message_type == 't' and midpoint > 0:
                #ma_spread = last_spread/2
                #if order_side == 'b':
                ma_vector[ma_i] = last_price  #- midpoint#ask_price - ma_spread
                ma_vector_long[ma_i_long] = last_price  #- midpoint
                ma_vector_plot[ma_i] = last_price
                ma_vector_long_plot[ma_i_long] = last_price
                #else:
                #    ma_vector[ma_i] = bid_price + ma_spread
                if ma_i == ma_interval - 1:
                    ma_i = 0
                else:
                    ma_i += 1

                if ma_i_long == ma_interval_long - 1:
                    ma_i_long = 0
                else:
                    ma_i_long += 1

                #i = (i+1)*((ma_interval - 1) != i)
                if ma_vector[-1] != 0 and ma_vector_long[-1] != 0:
                    st_mean = np.mean(ma_vector)
                    lt_mean = np.mean(ma_vector_long)
                    ma_df['MA_ST'][index] = np.mean(ma_vector_plot)
                    ma_df['MA_LT'][index] = np.mean(ma_vector_long_plot)
                    ma_df['MA_ST_m'][index] = st_mean
                    ma_df['MA_LT_m'][index] = lt_mean
                    if st_mean > lt_mean and not ma_bool:
                        ma_factor = 1
                        ma_bool = not ma_bool
                    elif st_mean < lt_mean and ma_bool:
                        ma_factor = -1
                        ma_bool = not ma_bool
                    else:
                        ma_factor = 0
                    #ma_factor = st_mean - lt_mean
                else:
                    ma_factor = 0

        # TICK FACTOR
        # only update if it's a trade
        if 2 in factors_included:
            if message_type == 't':
                # calc the tick
                this_tick = np.sign(last_price - prev_price)
                if this_tick == 0:
                    this_tick = prev_tick

                # now calc the tick
                if tick_factor == 0:
                    tick_factor = this_tick
                else:
                    tick_factor = (tick_ema_alpha * this_tick
                                   ) + (1 - tick_ema_alpha) * tick_factor

                # store the last tick
                prev_tick = this_tick

        # RISK FACTOR
        # calc the risk factor
        if 3 in factors_included:
            if net_position < 0:  #Need to buy back shares
                # Risk factor is dependent on how short we are
                risk_factor = -net_position
            elif net_position > 0:
                # Risk factor is dependent on how long we are
                risk_factor = -net_position
            else:
                # if the net position is 0, we cancel the risk factor
                risk_factor = 0

        # CANDLESTICK 1 FACTOR
        # DARKCLOUD COVER
        if 4 in factors_included:
            c1_factor = 0
            if candlestick_calculation:
                if ((last_close > last_open)
                        and (((last_close + last_open) / 2) > current_close)
                        and (current_open > current_close)
                        and (current_open > last_close)
                        and (current_close > last_open)
                        and ((current_open - current_close) /
                             (.001 + (current_high - current_low)) > 0.6)):
                    c1_factor = 1  #(abs(last_close - current_close) + abs(current_close - current_open))/2
                    indicator_list[0] = c1_factor

        # CANDLESTICK 2 FACTOR
        # DOJI
        if 5 in factors_included:
            c2_factor = 0
            if candlestick_calculation:
                if abs(current_close - current_open) / (current_high - current_low) < 0.1 and \
               (current_high - max(current_close, current_open)) > (3 * abs(current_close - current_open)) and \
               (min(current_close, current_open) - current_low) > (3 * abs(current_close - current_open)):
                    c2_factor = 1  #(abs(last_close - current_close) + abs(current_close - current_open))/2
                    indicator_list[1] = c2_factor

        # CANDLESTICK 3 FACTOR
        # BEARISH ENGULFING
        if 6 in factors_included:
            c3_factor = 0
            if candlestick_calculation:
                if (current_open >= last_close > last_open
                        and current_open > current_close
                        and last_open >= current_close and
                        current_open - current_close > last_close - last_open):
                    c3_factor = 1
                    indicator_list[2] = c3_factor

        # CANDLESTICK 4 FACTOR
        # HAMMER
        if 7 in factors_included:
            c4_factor = 0
            if candlestick_calculation:
                if (((current_high - current_low) > 3 *
                     (current_open - current_close))
                        and ((current_close - current_low) /
                             (.001 + current_high - current_low) > 0.6)
                        and ((current_open - current_low) /
                             (.001 + current_high - current_low) > 0.6)):
                    c4_factor = -1
                    indicator_list[3] = c4_factor

        # CANDLESTICK 5 FACTOR
        # INVERTED HAMMER
        if 8 in factors_included:
            c5_factor = 0
            if candlestick_calculation:
                if (((current_high - current_low) > 3 *
                     (current_open - current_close))
                        and ((current_high - current_close) /
                             (.001 + current_high - current_low) > 0.6)
                        and ((current_high - current_open) /
                             (.001 + current_high - current_low) > 0.6)):
                    c5_factor = -1
                    indicator_list[4] = c5_factor

        # PRICING LOGIC
        new_midpoint = bid_price + (ask_price - bid_price) / 2
        if new_midpoint > 0:
            midpoint = new_midpoint

        # FAIR VALUE CALCULATION
        # check inputs, skip of the midpoint is zero, we've got bogus data (or we're at start of day)
        if midpoint == 0:
            print("{} no midpoint. b:{} a:{}".format(index, bid_price,
                                                     ask_price))
            continue
        fair_value = midpoint + half_spread * ((risk_coef * risk_factor) +
                                               (ma_coef * ma_factor) +
                                               (c1_coef * c1_factor) +
                                               (c2_coef * c2_factor) +
                                               (c3_coef * c3_factor) +
                                               (c4_coef * c4_factor) +
                                               (c5_coef * c5_factor))

        # This prints out all important information. Good to see how the factors are functioning
        if display_factors:
            print('fair bid ask mid tick risk')
            print('%.3f %.3f %.3f %.3f %.3f %.3f' %
                  ((fair_value), (bid_price), (ask_price), (midpoint),
                   (half_spread * (tick_coef * tick_factor)),
                   (half_spread * (risk_coef * risk_factor))))

        # collect our data
        fair_values[index] = fair_value
        midpoints[index] = midpoint
        bid_values[index] = bid_price
        ask_values[index] = ask_price
        tick_factors[index] = half_spread * ((tick_coef * tick_factor))
        risk_factors[index] = half_spread * (risk_coef * risk_factor
                                             )  #Added risk_factor to series
        c1_factors[index] = c1_factor * c1_coef * half_spread
        c2_factors[index] = c2_factor * c2_coef * half_spread
        c3_factors[index] = c3_factor * c3_coef * half_spread
        c4_factors[index] = c4_factor * c4_coef * half_spread
        c5_factors[index] = c5_factor * c5_coef * half_spread
        ma_factors[index] = half_spread * ma_coef * ma_factor

        if candlestick_calculation:
            record_candlestick_indicators(candlestick_indicators,
                                          new_index,
                                          np.abs(indicator_list),
                                          trade=0)

        # TRADE DECISION
        # We want to sell if the fair value is below the midpoint and buy if the fair value is above
        if fair_value < midpoint:
            order_side = 's'
        elif fair_value > midpoint:
            order_side = 'b'
        else:
            order_side = '-'

        # TRADING LOGIC
        # check where our FV is versus the BBO and constrain
        if order_side == 'b':
            # Don't trade if a limit order was executed in the same iteration
            if (fair_value >= ask_price) and (live_order_executed == False):
                new_trade_price = ask_price

                # now place our aggressive order: assume you can execute the full size across spread
                new_order_quantity = calc_order_quantity(
                    net_position, midpoint, fair_value, order_side,
                    quantity_coef)
                new_order_quantity = np.minimum(new_order_quantity, 2000)

                # If the new order quantity is above 0, we do a trade
                if new_order_quantity != 0:
                    total_agg_count += 1
                    hour_index = index.hour
                    if np.any(
                            np.array([
                                c1_factor, c2_factor, c3_factor, c4_factor,
                                c5_factor
                            ]) > 0):
                        # sum([abs(x) for x in [c1_factor,c2_factor,c3_factor,c4_factor,c5_factor]]) != 0:
                        if c1_factor > 0:
                            time_shift = c1_shift
                        elif c2_factor > 0:
                            time_shift = c2_shift
                        elif c3_factor > 0:
                            time_shift = c3_shift
                        elif c4_factor > 0:
                            time_shift = c4_shift
                        else:
                            time_shift = c5_shift

                        if index.minute + time_shift > 59:  #Only works for small time shifts
                            hour_index = index.hour + 1
                            minute_index = index.minute + time_shift - 60
                        else:
                            hour_index = index.hour
                            minute_index = index.minute + time_shift
                        if hour_index <= 15:
                            record_candlestick_indicators(
                                candlestick_indicators,
                                new_index,
                                indicator_list,
                                trade=1)
                            new_index = str(trading_day.index[0])[:10] + \
                                        ' ' + str(hour_index).zfill(2) + \
                                        ':' + str(minute_index).zfill(2) + ':00'
                            record_candle_trades(trades_candle, new_index,
                                                 new_order_quantity,
                                                 order_side)
                    if hour_index <= 15:
                        record_trade(trades, index, new_trade_price,
                                     new_order_quantity, current_bar, 'a',
                                     order_side)
                        net_position = net_position + new_order_quantity

                        # Good to visualize the factor values by printing out during a trade
                        if display_factors:
                            print('Fair value: ', fair_value)
                            print('Tick: ',
                                  half_spread * ((tick_coef * tick_factor)))
                            print('Risk: ',
                                  half_spread * (risk_coef * risk_factor))

                        # update quantity remaining
                        total_quantity_filled += new_order_quantity

                        live_order_quantity = 0.0
                        live_order_price = 0.0
                        live_order = False

            elif limit_orders_included:  # we're not yet willing to cross the spread, stay passive
                order_quantity = calc_order_quantity(net_position, midpoint,
                                                     fair_value, order_side,
                                                     quantity_coef)

                # Cap our orders to 2000 shares.
                order_quantity = np.minimum(order_quantity, 2000)

                # Don't send a limit order if it's too small (to minimize transaction costs)
                if order_quantity >= min_lim_order:
                    live_order_price = bid_price
                    live_order_quantity = order_quantity
                    live_order = True

        elif order_side == 's':
            # Don't trade if a limit order was executed in the same iteration
            if (fair_value <= bid_price) and (live_order_executed == False):
                new_trade_price = bid_price
                # now place our aggressive order: assume you can execute the full size across spread
                new_order_quantity = calc_order_quantity(
                    net_position, midpoint, fair_value, order_side,
                    quantity_coef)
                new_order_quantity = np.minimum(new_order_quantity, 2000)

                if new_order_quantity != 0:
                    total_agg_count += 1
                    hour_index = index.hour
                    hour_index = index.hour
                    if np.any(
                            np.array([
                                c1_factor, c2_factor, c3_factor, c4_factor,
                                c5_factor
                            ]) < 0):
                        if c1_factor < 0:
                            time_shift = c1_shift
                        elif c2_factor < 0:
                            time_shift = c2_shift
                        elif c3_factor < 0:
                            time_shift = c3_shift
                        elif c4_factor < 0:
                            time_shift = c4_shift
                        else:
                            time_shift = c5_shift

                        if index.minute + time_shift > 59:  #Only works for small time shifts
                            hour_index = index.hour + 1
                            minute_index = index.minute + time_shift - 60
                        else:
                            hour_index = index.hour
                            minute_index = index.minute + time_shift
                        if hour_index <= 15:
                            record_candlestick_indicators(
                                candlestick_indicators,
                                new_index,
                                np.abs(indicator_list),
                                trade=-1)
                            new_index = str(trading_day.index[0])[:10] + \
                                        ' ' + str(hour_index).zfill(2) + \
                                        ':' + str(minute_index).zfill(2) + ':00'
                            record_candle_trades(trades_candle, new_index,
                                                 -new_order_quantity,
                                                 order_side)
                if hour_index <= 15:
                    record_trade(trades, index, new_trade_price,
                                 new_order_quantity, current_bar, 'a',
                                 order_side)
                    net_position = net_position - new_order_quantity

                    # update quantity remaining
                    total_quantity_filled += new_order_quantity

                    # Print factors for convenience
                    if display_factors:
                        print('Fair value: ', fair_value)
                        print('Tick: ',
                              half_spread * ((tick_coef * tick_factor)))
                        print('Risk: ',
                              half_spread * (risk_coef * risk_factor))

                    live_order_quantity = 0.0
                    live_order_price = 0.0
                    live_order = False

            elif limit_orders_included:  # not yet willing to cross spread
                order_quantity = calc_order_quantity(net_position, midpoint,
                                                     fair_value, order_side,
                                                     quantity_coef)

                # Cap or order quantity to 2000 shares each time
                order_quantity = np.minimum(order_quantity, 2000)

                # Don't send a limit order if it's too small (to minimize transaction costs)
                if order_quantity >= min_lim_order:
                    live_order_price = ask_price
                    live_order_quantity = order_quantity
                    live_order = True

        else:
            pass
            # no order here. for now just continue

        live_order_executed = False
        candlestick_calculation = False
        indicator_list = np.zeros(5)

    # looping done
    log_message('end simulation loop')
    log_message('order analytics')

    # Now, let's look at some stats
    trades = trades.dropna()
    day_vwap = vwap_numerator / cumulative_volume

    # prep our text output
    avg_price = 0

    if trades['shares'].sum() != 0:
        avg_price = (trades['price'] *
                     trades['shares']).sum() / trades['shares'].sum()

    log_message('Algo run complete.')

    #print(midpoints.loc['2019-10-10 15:57:00.020201161'])
    [
        total_pnl, intra_pnl_df, worst_intra, best_intra, max_long, max_short,
        min_pnl, max_pnl, min_shares_long, max_shares_long, min_shares_short,
        max_shares_short
    ] = trade_statistics(trades, net_position, bid_price, ask_price, midpoints,
                         ask_values, bid_values)

    statistics = [
        total_pnl, intra_pnl_df, worst_intra, best_intra, max_long, max_short,
        min_pnl, max_pnl, min_shares_long, max_shares_long, min_shares_short,
        max_shares_short
    ]

    # assemble results and return
    return {
        'midpoints': midpoints,
        'fair_values': fair_values,
        'tick_factors': tick_factors,
        'risk_factors': risk_factors,
        'c1_factors': c1_factors,
        'c2_factors': c2_factors,
        'c3_factors': c3_factors,
        'c4_factors': c4_factors,
        'c5_factors': c5_factors,
        'ma_factors': ma_factors,
        'trades': trades,
        'quote_count': quote_count,
        'day_vwap': day_vwap,
        'avg_price': avg_price,
        'net_position': net_position,
        'last_bid': bid_price,
        'last_ask': ask_price,
        'midpoints': midpoints,
        'ask_values': ask_values,
        'bid_values': bid_values,
        'statistics': statistics,
        'candlesticks': candlesticks_df,
        'candlestick_indicators': candlestick_indicators,
        'ma_df': ma_df
    }
def algo_loop(trading_day,
              order_side,
              original_order_quantity,
              vwap_coefficients,
              max_behind,
              schedule_coef,
              tick_window,
              tick_coef,
              limit_price=None,
              limit_quantity=np.inf):
    log_message('Beginning VWAP run: {:s} {:d} shares'.format(
        order_side, original_order_quantity))

    round_lot = 100
    avg_spread = (trading_day.ask_px - trading_day.bid_px).mean()
    half_spread = avg_spread / 2
    print("Average stock spread for sample: {:.4f}".format(avg_spread))

    # if limit price is not determined, convert it to numerical type
    if limit_price == None:
        if order_side == 'b':
            limit_price = np.inf
        elif order_side == 's':
            limit_price = 0
        else:
            print(f'order_side not understood in tick {trading_day.index}')
            return None

    # caclulate last_minutes_from_open in a specific trading day
    last_time_from_open = (trading_day.index[-1] -
                           pd.Timedelta(hours=9, minutes=30))
    last_minute_from_open = (last_time_from_open.hour *
                             60) + last_time_from_open.minute

    # generate target schedule - use bins 1 - 390 giving an automatic 1 minute "look ahead"
    # note that targets have been converted to shares from percent
    order_targets = vwap_target(np.arange(0, 390, dtype='int64'),
                                vwap_coefficients) * original_order_quantity
    # order_targets = np.round(order_targets)

    # let's force the last bin to complete
    order_targets[-1] = original_order_quantity

    # init our price and volume variables
    [last_price, last_size, bid_price, bid_size, ask_price, ask_size,
     volume] = np.zeros(7)

    # init our counters
    [trade_count, quote_count, cumulative_volume] = [0, 0, 0]

    # init some time series objects for collection of telemetry
    fair_values = pd.Series(index=trading_day.index)
    midpoints = pd.Series(index=trading_day.index)
    schedule_factors = pd.Series(index=trading_day.index)
    tick_factors = pd.Series(index=trading_day.index)

    # let's set up a container to hold trades. preinitialize with the index
    trades = pd.DataFrame(columns=['price', 'shares', 'bar', 'trade_type'],
                          index=trading_day.index)

    #max_behind = 2000

    # MAIN EVENT LOOP
    current_bar = 0
    current_target_shares = 0

    quantity_behind = 0

    # track state and values for a current working order
    live_order = False
    live_order_price = 0.0
    live_order_quantity = 0.0

    # other order and market variables
    total_quantity_filled = 0
    quantity_remaining = original_order_quantity - total_quantity_filled
    vwap_numerator = 0.0

    total_trade_count = 0
    total_agg_count = 0
    total_pass_count = 0

    # fair value pricing variables
    midpoint = 0.0
    fair_value = 0.0
    schedule_factor = 0.0
    if (order_side == 's') & (schedule_coef >= 0):
        schedule_coef = -schedule_coef  # change the schedule factor sign for sell orders

    # define our accumulator for the tick EMA
    message_type = 0
    #tick_coef = 1.
    #tick_window = 20
    tick_factor = 0
    tick_ema_alpha = 2 / (tick_window + 1)
    prev_tick = 0
    prev_price = 0

    log_message('starting main loop')
    for index, row in trading_day.iterrows():
        # get the time of this message
        time_from_open = (index - pd.Timedelta(hours=9, minutes=30))
        minutes_from_open = (time_from_open.hour * 60) + time_from_open.minute

        # MARKET DATA HANDLING
        if pd.isna(row.trade_px):  # it's a quote
            # skip if not NBBO
            #if not ( ( row.qu_source == 'N' ) and ( row.natbbo_ind == 4 ) ):
            #    continue
            # set our local NBBO variables
            if (row.bid_px > 0 and row.bid_size > 0):
                bid_price = row.bid_px
                bid_size = row.bid_size * round_lot
            if (row.ask_px > 0 and row.ask_size > 0):
                ask_price = row.ask_px
                ask_size = row.ask_size * round_lot
            quote_count += 1
            message_type = 'q'
        else:  # it's a trade
            # store the last trade price
            prev_price = last_price
            # now get the new data
            last_price = row.trade_px
            last_size = row.trade_size
            trade_count += 1
            cumulative_volume += row.trade_size
            vwap_numerator += last_size * last_price
            message_type = 't'

            # CHECK OPEN ORDER(S) if we have a live order,
            # has it been filled by the trade that just happened?
            if live_order:
                if (order_side == 'b') and (last_price <= live_order_price):
                    fill_size = min(live_order_quantity, last_size)

                    total_quantity_filled += fill_size
                    total_pass_count += 1

                    # even if we only got partially filled, let's assume we're cancelling the entire quantity.
                    # If we're still behind we'll replace later in the loop
                    quantity_behind = current_target_shares - total_quantity_filled
                    record_trade(trades, index, live_order_price, fill_size,
                                 current_bar, 'p')
                    live_order = False
                    live_order_price = 0.0
                    live_order_quantity = 0.0

                if (order_side == 's') and (last_price >= live_order_price):
                    fill_size = min(live_order_quantity, last_size)
                    total_quantity_filled += fill_size
                    # print("{} passive. quantity_behind:{} new_order_quantity:{}".format(index, quantity_behind,
                    #                                                                     fill_size))
                    total_pass_count += 1

                    # even if we only got partially filled, let's assume we're cancelling the entire quantity.
                    # If we're still behind we'll replace later in the loop
                    quantity_behind = current_target_shares - total_quantity_filled
                    record_trade(trades, index, live_order_price, fill_size,
                                 current_bar, 'p')
                    live_order = False
                    live_order_price = 0.0
                    live_order_quantity = 0.0

        # SCHEDULE FACTOR
        if minutes_from_open > current_bar:
            # we're in a new bar do new bar things here
            current_bar = minutes_from_open
            current_target_shares = order_targets[current_bar]
            quantity_behind = current_target_shares - total_quantity_filled
            # uncomment for debug
            # print("{} bar: {:d} target:{:.2f} behind:{:.2f}".format(index, current_bar, current_target_shares,
            #                                                         quantity_behind))

        # TICK FACTOR
        # only update if it's a trade
        if message_type == 't':
            # calc the tick
            this_tick = np.sign(last_price - prev_price)
            if this_tick == 0:
                this_tick = prev_tick

            # now calc the tick
            if tick_factor == 0:
                tick_factor = this_tick
            else:
                tick_factor = (tick_ema_alpha *
                               this_tick) + (1 - tick_ema_alpha) * tick_factor

                # store the last tick
            prev_tick = this_tick

        # PRICING LOGIC
        new_midpoint = bid_price + (ask_price - bid_price) / 2
        if new_midpoint > 0:
            midpoint = new_midpoint

        schedule_factor = calc_schedule_factor(max_behind,
                                               current_target_shares,
                                               total_quantity_filled,
                                               original_order_quantity)

        # FAIR VALUE CALCULATION
        # check inputs, skip of the midpoint is zero, we've got bogus data (or we're at start of day)
        if midpoint == 0:
            # print("{} no midpoint. b:{} a:{}".format(index, bid_price, ask_price))
            continue
        fair_value = midpoint + (schedule_coef * schedule_factor * half_spread
                                 ) + (tick_coef * tick_factor * half_spread)

        # collect our data
        fair_values[index] = fair_value
        midpoints[index] = midpoint
        schedule_factors[index] = schedule_factor
        tick_factors[index] = tick_factor

        # TRADING LOGIC
        # check where our FV is versus the BBO and constrain
        # check if the current tick is passively traded

        if order_side == 'b':
            if (fair_value >= ask_price and quantity_behind > round_lot) or (
                    minutes_from_open == last_minute_from_open
                    and quantity_behind > 0):

                # if passive trading happened in this tick,
                # or if ask price is larger than limit price, do not execute aggresive traiding
                if trades.loc[
                        index].trade_type == 'p' or ask_price > limit_price:
                    continue

                total_agg_count += 1

                new_trade_price = ask_price

                # now place our aggressive order: assume you can execute the full size across spread
                if minutes_from_open == last_minute_from_open:
                    new_order_quantity = min(quantity_behind, limit_quanitity)
                else:
                    new_order_quantity = min(
                        calc_order_quantity(quantity_behind, round_lot),
                        limit_quantity)

                #update total quantity filled
                total_quantity_filled += new_order_quantity

                # update quantity_behind
                quantity_behind = current_target_shares - total_quantity_filled

                record_trade(trades, index, new_trade_price,
                             new_order_quantity, current_bar, 'a')
                live_order_quantity = 0.0
                live_order_price = 0.0
                live_order = False

            else:  # we're not yet willing to cross the spread, stay passive
                if quantity_behind > round_lot and bid_price < limit_price:
                    live_order_price = bid_price
                    live_order_quantity = calc_order_quantity(
                        quantity_behind, round_lot)
                    # live_order_quantity = quantity_behind
                    live_order = True

        elif order_side == 's':
            if (fair_value <= bid_price and quantity_behind > round_lot) or (
                    minutes_from_open == last_minute_from_open
                    and quantity_behind > 0):

                # if passive trading happened in this tick, do not execute aggresive traiding
                if trades.loc[
                        index].trade_type == 'p' or bid_price < limit_price:
                    continue

                total_agg_count += 1

                new_trade_price = bid_price

                # now place our aggressive order: assume you can execute the full size across spread
                if minutes_from_open == last_minute_from_open:
                    new_order_quantity = min(quantity_behind, limit_quantity)
                else:
                    new_order_quantity = min(
                        calc_order_quantity(quantity_behind, round_lot),
                        limit_quantity)

                # new_order_quantity = quantity_behind

                # update total quantity filled
                total_quantity_filled += new_order_quantity

                # update quantity_behind
                quantity_behind = current_target_shares - total_quantity_filled

                record_trade(trades, index, new_trade_price,
                             new_order_quantity, current_bar, 'a')

                live_order_quantity = 0.0
                live_order_price = 0.0
                live_order = False

            else:  # not yet willing to cross spread
                if quantity_behind > round_lot and ask_price > limit_price:
                    live_order_price = ask_price
                    live_order_quantity = calc_order_quantity(
                        quantity_behind, round_lot)
                    live_order = True
        else:
            # we shouldn't have got here, something is wrong with our order type
            print('Got an unexpected order_side value: ' + str(order_side))

    # looping done
    log_message('end simulation loop')
    log_message('order analytics')

    # Now, let's look at some stats
    trades = trades.dropna()
    day_vwap = vwap_numerator / cumulative_volume

    # prep our text output
    avg_price = (trades['price'] *
                 trades['shares']).sum() / trades['shares'].sum()

    log_message('VWAP run complete.')

    # assemble results and return

    return {
        'midpoints': midpoints,
        'fair_values': fair_values,
        'schedule_factors': schedule_factors,
        'tick_factors': tick_factors,
        'trades': trades,
        'quote_count': quote_count,
        'day_vwap': day_vwap,
        'avg_price': avg_price
    }
def algo_loop(total_data, fx_list, period_list, leverage=2.0, jpy=0):

    log_message('Beginning Carry-Trade Strategy run')

    # equity initialization
    equity = 10000

    # position info initialization
    current_pos = 0
    rate_open = 0

    # pnl initialization
    unreal_pnl = 0
    real_pnl = 0
    temp_pnl = 0

    # perform analysis initialization
    max_return = 0
    max_drawdown = 0

    # portfolio info initialziation
    trade_fx = '-'
    trade_period = 0
    trade_period_name = '-'

    # set up the trade book
    trades = pd.DataFrame(columns=[
        'Signal', 'FX_name', 'Period', 'Foreign_IR', 'Domestic_IR', 'FX_Rate',
        'Equity', 'Asset Pos', 'Unreal_Return', 'Real_Return', 'Drawdown'
    ],
                          index=total_data.index)

    for index, row in total_data.iterrows():

        if equity < 0:  # We've gone insolvent
            break

        if current_pos == 0:

            # find max signal
            [max_signal, max_period,
             max_fx] = find_max_signal(row_row=row,
                                       period_list=period_list,
                                       fx_list=fx_list)
            max_period_name = cal_period_name(trading_day=max_period)

            if max_signal > 0:

                # record trading fx name and period
                trade_fx = max_fx  # e.g. AUD
                trade_period = max_period  # e.g. 60
                trade_period_name = max_period_name  # e.g. 2M

                # record trading day
                start_day = index
                end_day = holiday_adjust(start_day,
                                         dt.timedelta(days=trade_period))

                # Interest rates idx
                [
                    fx_libor_idx, dstc_libor_idx, spot_fx_rate_idx,
                    forward_fx_rate_idx, ask_fx_rate_idx, bid_fx_rate_idx
                ] = cal_rates_name(fx_name=trade_fx,
                                   period_name=trade_period_name)

                # Interest rates
                trade_r_f = row[fx_libor_idx] / 100
                trade_r_d = row[dstc_libor_idx] / 100

                if trade_fx == 'JPY':
                    trade_fx_rate = 1
                    trade_ask_fx_rate = 1
                    trade_bid_fx_rate = 1
                else:
                    trade_fx_rate = row[spot_fx_rate_idx]
                    trade_ask_fx_rate = row[ask_fx_rate_idx]
                    trade_bid_fx_rate = row[bid_fx_rate_idx]

                # update trading info
                rate_open = trade_ask_fx_rate  # use ask price to buy -> Transaction Cost
                current_pos = equity * leverage

                # calculate unrealized pnl, use bid price to sell -> Transaction Cost
                unreal_pnl = calculate_pnl(leverage=leverage,
                                           r_foreign=trade_r_f,
                                           r_domestic=trade_r_d,
                                           rate_open=rate_open,
                                           rate_close=trade_bid_fx_rate,
                                           trade_period=trade_period)

                # record trading info
                record_trade(trade_df=trades,
                             idx=index,
                             signal=max_signal,
                             fx_name=trade_fx,
                             period_name=trade_period_name,
                             foreign_ir=trade_r_f,
                             domestic_ir=trade_r_d,
                             fx_rate=trade_fx_rate,
                             equity=equity,
                             position=current_pos,
                             unreal_r=unreal_pnl,
                             real_r=real_pnl,
                             drawdown=max_drawdown)

            else:

                if jpy == 1:  # invest in local interest market if no fx signal appears
                    # record trading fx name and period
                    trade_fx = 'JPY'
                    trade_period = 7
                    trade_period_name = '1W'

                    # record trading day
                    start_day = index
                    end_day = holiday_adjust(start_day,
                                             dt.timedelta(days=trade_period))

                    # Interest rates idx
                    [
                        fx_libor_idx, dstc_libor_idx, spot_fx_rate_idx,
                        forward_fx_rate_idx, ask_fx_rate_idx, bid_fx_rate_idx
                    ] = cal_rates_name(fx_name=trade_fx,
                                       period_name=trade_period_name)
                    # Interest rates
                    trade_r_f = row[fx_libor_idx] / 100
                    trade_r_d = row[dstc_libor_idx] / 100

                    if trade_fx == 'JPY':
                        trade_fx_rate = 1
                        trade_ask_fx_rate = 1
                        trade_bid_fx_rate = 1

                    else:
                        trade_fx_rate = row[spot_fx_rate_idx]
                        trade_ask_fx_rate = row[ask_fx_rate_idx]
                        trade_bid_fx_rate = row[bid_fx_rate_idx]

                    if trade_r_d > 0:  # invest only when the local interest rate > 0

                        # update trading info
                        rate_open = trade_ask_fx_rate  # use ask price to buy -> Transaction Cost
                        current_pos = equity

                        # calculate unrealized pnl, use bid price to sell -> Transaction Cost
                        unreal_pnl = calculate_pnl(
                            leverage=1,
                            r_foreign=trade_r_f,
                            r_domestic=trade_r_d,
                            rate_open=rate_open,
                            rate_close=trade_bid_fx_rate,
                            trade_period=trade_period)

                        # record trading info
                        record_trade(trade_df=trades,
                                     idx=index,
                                     signal=max_signal,
                                     fx_name=trade_fx,
                                     period_name=trade_period_name,
                                     foreign_ir=trade_r_f,
                                     domestic_ir=trade_r_d,
                                     fx_rate=trade_fx_rate,
                                     equity=equity,
                                     position=current_pos,
                                     unreal_r=unreal_pnl,
                                     real_r=real_pnl,
                                     drawdown=max_drawdown)
                    else:
                        # record trading info
                        record_trade(trade_df=trades,
                                     idx=index,
                                     signal=max_signal,
                                     fx_name='-',
                                     period_name='-',
                                     foreign_ir=0,
                                     domestic_ir=0,
                                     fx_rate=1,
                                     equity=equity,
                                     position=0,
                                     unreal_r=unreal_pnl,
                                     real_r=real_pnl,
                                     drawdown=max_drawdown)
                else:
                    # record trading info
                    record_trade(trade_df=trades,
                                 idx=index,
                                 signal=max_signal,
                                 fx_name='-',
                                 period_name='-',
                                 foreign_ir=0,
                                 domestic_ir=0,
                                 fx_rate=1,
                                 equity=equity,
                                 position=0,
                                 unreal_r=unreal_pnl,
                                 real_r=real_pnl,
                                 drawdown=max_drawdown)

        else:  # position != 0

            # Interest rates idx
            [
                fx_libor_idx, dstc_libor_idx, spot_fx_rate_idx,
                forward_fx_rate_idx, ask_fx_rate_idx, bid_fx_rate_idx
            ] = cal_rates_name(fx_name=trade_fx, period_name=trade_period_name)

            # Interest rates
            trade_r_f = row[fx_libor_idx] / 100
            trade_r_d = row[dstc_libor_idx] / 100

            if trade_fx == 'JPY':
                trade_fx_rate = 1
                trade_ask_fx_rate = 1
                trade_bid_fx_rate = 1
            else:
                trade_fx_rate = row[spot_fx_rate_idx]
                trade_ask_fx_rate = row[ask_fx_rate_idx]
                trade_bid_fx_rate = row[bid_fx_rate_idx]

            # close the position, return the money we borrowed
            if index >= end_day:

                # calculate pnl, use bid price to sell -> Transaction Cost
                unreal_pnl = 0
                temp_pnl = calculate_pnl(leverage=leverage,
                                         r_foreign=trade_r_f,
                                         r_domestic=trade_r_d,
                                         rate_open=rate_open,
                                         rate_close=trade_bid_fx_rate,
                                         trade_period=trade_period)
                real_pnl = (1 + real_pnl) * (1 + temp_pnl) - 1
                equity *= (1 + temp_pnl)

                # record drawdown
                max_drawdown = real_pnl - max_return

                # update max return
                if real_pnl > max_return:
                    max_return = real_pnl

                # record trading info
                record_trade(trade_df=trades,
                             idx=index,
                             signal=max_signal,
                             fx_name=trade_fx,
                             period_name=trade_period_name,
                             foreign_ir=trade_r_f,
                             domestic_ir=trade_r_d,
                             fx_rate=trade_fx_rate,
                             equity=equity,
                             position=current_pos,
                             unreal_r=unreal_pnl,
                             real_r=real_pnl,
                             drawdown=max_drawdown)

                # refresh the variables
                current_pos = 0
                rate_open = 0

                # refresh trade info
                trade_fx = '-'
                trade_period = 0
                trade_period_name = '-'

            else:
                # calculate unrealized pnl, use bid price to sell -> Transaction Cost
                unreal_pnl = calculate_pnl(leverage=leverage,
                                           r_foreign=trade_r_f,
                                           r_domestic=trade_r_d,
                                           rate_open=rate_open,
                                           rate_close=trade_bid_fx_rate,
                                           trade_period=trade_period)

                # record trading info
                record_trade(trade_df=trades,
                             idx=index,
                             signal=max_signal,
                             fx_name=trade_fx,
                             period_name=trade_period_name,
                             foreign_ir=trade_r_f,
                             domestic_ir=trade_r_d,
                             fx_rate=trade_fx_rate,
                             equity=equity,
                             position=current_pos,
                             unreal_r=unreal_pnl,
                             real_r=real_pnl,
                             drawdown=max_drawdown)

    log_message('Algo run complete.')

    return trades
Example #8
0
def algo_loop(trading_day, last_day):

    log_message('Beginning Tick Strategy run')

    round_lot = 100

    #NBBO
    NBBO_last_day = last_day[(last_day.qu_source == 'N')
                             & (last_day.natbbo_ind == 4)]

    avg_spread = (NBBO_last_day.ask_px - NBBO_last_day.bid_px).mean()
    half_spread = avg_spread / 2
    print("Average stock spread for sample: {:.4f}".format(avg_spread))

    # init our price and volume variables
    [last_price, last_size, bid_price, bid_size, ask_price, ask_size,
     volume] = np.zeros(7)

    # init our counters
    [trade_count, quote_count, cumulative_volume] = [0, 0, 0]

    # init some time series objects for collection of telemetry
    fair_values = pd.Series(index=trading_day.index)
    midpoints = pd.Series(index=trading_day.index)
    tick_factors = pd.Series(index=trading_day.index)

    # let's set up a container to hold trades. preinitialize with the index
    trades = pd.DataFrame(
        columns=['price', 'shares', 'bar', 'trade_type', 'side'],
        index=trading_day.index)

    # MAIN EVENT LOOP
    current_bar = 0

    # track state and values for a current working order
    live_order = False
    live_order_price = 0.0
    live_order_quantity = 0.0
    order_side = '-'

    # other order and market variables
    total_quantity_filled = 0
    vwap_numerator = 0.0

    total_trade_count = 0
    total_agg_count = 0
    total_pass_count = 0

    # fair value pricing variables
    midpoint = 0.0
    fair_value = 0.0

    # define our accumulator for the tick EMA
    message_type = 0
    tick_coef = 1
    tick_window = 20
    tick_factor = 0
    tick_ema_alpha = 2 / (tick_window + 1)
    prev_tick = 0
    prev_price = 0

    # risk factor for part 2
    # TODO: implement and evaluate
    risk_factor = 0.0
    risk_coef = -0.5

    n = 2
    new_midpoint = 0.0
    cache = [[0 for i in range(4)] for i in range(n)]
    trade_lag = [0 for i in range(3)]

    log_message('starting main loop')
    for index, row in trading_day.iterrows():

        # Update risk factor
        # TODO: implement and evaluate
        if total_quantity_filled > 5000:
            risk_factor = 1

        elif total_quantity_filled <= -5000:
            risk_factor = -1

        else:
            risk_factor = 0

        # get the time of this message
        time_from_open = (index - pd.Timedelta(hours=9, minutes=30))
        minutes_from_open = (time_from_open.hour * 60) + time_from_open.minute

        # MARKET DATA HANDLING
        if pd.isna(row.trade_px):  # it's a quote
            # skip if not NBBO
            if not ((row.qu_source == 'N') and (row.natbbo_ind == 4)):
                continue
            # set our local NBBO variables
            if (row.bid_px > 0 and row.bid_size > 0):
                bid_price = row.bid_px
                bid_size = row.bid_size
            if (row.ask_px > 0 and row.ask_size > 0):
                ask_price = row.ask_px
                ask_size = row.ask_size
            quote_count += 1
            message_type = 'q'
            cache.pop(0)
            cache.append([message_type, row.bid_px, row.ask_px, row.trade_px])
        else:  # it's a trade
            # store the last trade price
            prev_price = last_price
            # now get the new data
            last_price = row.trade_px
            last_size = row.trade_size
            trade_count += 1
            cumulative_volume += row.trade_size
            vwap_numerator += last_size * last_price
            message_type = 't'
            cache.pop(0)
            cache.append([message_type, row.bid_px, row.ask_px, row.trade_px])
            trade_lag.pop(0)
            trade_lag.append(row.trade_px)
            # CHECK OPEN ORDER(S) if we have a live order,
            # has it been filled by the trade that just happened?
            if live_order:
                if (order_side == 'b') and (last_price <= live_order_price):
                    fill_size = min(live_order_quantity, last_size)
                    record_trade(trades, index, live_order_price, fill_size,
                                 current_bar, 'p', order_side)
                    total_quantity_filled += fill_size
                    total_pass_count += 1

                    # even if we only got partially filled, let's assume we're cancelling the entire quantity.
                    # If we're still behind we'll replace later in the loop
                    live_order = False
                    live_order_price = 0.0
                    live_order_quantity = 0.0

                if (order_side == 's') and (last_price >= live_order_price):
                    fill_size = min(live_order_quantity, last_size)
                    record_trade(trades, index, live_order_price, fill_size,
                                 current_bar, 'p', order_side)
                    total_quantity_filled -= fill_size
                    total_pass_count += 1

                    # even if we only got partially filled, let's assume we're cancelling the entire quantity.
                    # If we're still behind we'll replace later in the loop
                    live_order = False
                    live_order_price = 0.0
                    live_order_quantity = 0.0

        # TICK FACTOR
        # only update if it's a trade
        if message_type == 't':
            # calc the tick
            this_tick = np.sign(last_price - prev_price)
            if this_tick == 0:
                this_tick = prev_tick

            # now calc the tick
            if tick_factor == 0:
                tick_factor = this_tick
            else:
                tick_factor = (tick_ema_alpha *
                               this_tick) + (1 - tick_ema_alpha) * tick_factor

                # store the last tick
            prev_tick = this_tick

        # PRICING LOGIC
        new_midpoint = bid_price + (ask_price - bid_price) / 2
        if new_midpoint > 0:
            midpoint = new_midpoint

        # Determine the trade's trend using tick test
        if message_type == 't':
            flag = 0
            temp = cache.copy()
            temp.reverse()
            temp.pop(0)
            while (len(temp) != 1):
                lag = temp.pop(0)
                if lag[0] == 't':
                    if last_price > lag[3]:
                        order_side = 'b'
                    if last_price < lag[3]:
                        order_side = 's'
                    if last_price == lag[3]:
                        if trade_lag[0] < lag[3]:
                            order_side = 'b'
                        if trade_lag[0] > lag[3]:
                            order_side = 's'
                        else:
                            order_side = '-'
                    flag = 1
                    break

            # Determine the trade's trend using quote (if available, flag = 0)
            if flag == 0 and temp[0][0] == 'q':
                midpoint = temp[0][1] + (temp[0][2] - temp[0][1]) / 2

                if last_price > midpoint:
                    order_side = 'b'
                if last_price < midpoint:
                    order_side = 's'
                if last_price == midpoint:
                    if last_price > trade_lag[1]:
                        order_side = 'b'
                    if last_price < trade_lag[1]:
                        order_side = 's'
                    if last_price == trade_lag[1]:
                        if trade_lag[0] < trade_lag[1]:
                            order_side = 'b'
                        if trade_lag[0] > trade_lag[1]:
                            order_side = 's'
                        else:
                            order_side = '-'
            # FAIR VALUE CALCULATION(when me meet the trade message)
            fair_value = midpoint + half_spread * ((tick_coef * tick_factor) +
                                                   (risk_coef * risk_factor))

        # collect our data
        fair_values[index] = fair_value
        midpoints[index] = midpoint
        tick_factors[index] = tick_factor

        # TRADE DECISION
        # TODO: determine if we want to buy or sell
        # if fair price is < bid, sell agg for all bid price and amount
        # if fair price is < ask, sell passive
        # if fair price is > ask, buy agg for all ask price and amount
        # if fair price is > bid, buy passive

        # TRADING LOGIC
        # check where our FV is versus the BBO and constrain
        if message_type == 'q':
            if order_side == 'b':
                if fair_value >= ask_price:
                    total_agg_count += 1

                    new_trade_price = ask_price

                    # now place our aggressive order: assume you can execute the full size across spread
                    new_order_quantity = ask_size

                    record_trade(trades, index, new_trade_price,
                                 new_order_quantity, current_bar, 'a',
                                 order_side)

                    # update quantity remaining
                    total_quantity_filled += new_order_quantity

                    live_order_quantity = 0.0
                    live_order_price = 0.0
                    live_order = False

                else:  # we're not yet willing to cross the spread, stay passive
                    live_order_price = bid_price
                    live_order_quantity = ask_size
                    live_order = True

            elif order_side == 's':
                if fair_value <= bid_price:
                    total_agg_count += 1

                    new_trade_price = bid_price

                    # now place our aggressive order: assume you can execute the full size across spread
                    new_order_quantity = bid_size

                    # new_order_quantity = quantity_behind
                    record_trade(trades, index, new_trade_price,
                                 new_order_quantity, current_bar, 'a',
                                 order_side)

                    # update quantity remaining
                    total_quantity_filled -= new_order_quantity

                    live_order_quantity = 0.0
                    live_order_price = 0.0
                    live_order = False

                else:  # not yet willing to cross spread
                    live_order_price = ask_price
                    live_order_quantity = bid_size
                    live_order = True
            else:
                # no order here. for now just continue
                continue

    # looping done
    log_message('end simulation loop')
    log_message('order analytics')

    # Now, let's look at some stats
    trades = trades.dropna()
    day_vwap = vwap_numerator / cumulative_volume

    # prep our text output
    avg_price = 0
    if trades['shares'].sum() != 0:
        avg_price = (trades['price'] *
                     trades['shares']).sum() / trades['shares'].sum()

    log_message('Algo run complete.')

    # assemble results and return
    # TODO: add P&L
    return {
        'midpoints': midpoints,
        'fair_values': fair_values,
        'tick_factors': tick_factors,
        'trades': trades,
        'quote_count': quote_count,
        'day_vwap': day_vwap,
        'avg_price': avg_price
    }