All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.co.real_logic.artio.example_exchange.ExchangeSessionHandler Maven / Gradle / Ivy

The newest version!
package uk.co.real_logic.artio.example_exchange;

import io.aeron.logbuffer.ControlledFragmentHandler.Action;
import org.agrona.DirectBuffer;
import org.agrona.concurrent.UnsafeBuffer;
import uk.co.real_logic.artio.*;
import uk.co.real_logic.artio.builder.ExecutionReportEncoder;
import uk.co.real_logic.artio.decoder.NewOrderSingleDecoder;
import uk.co.real_logic.artio.dictionary.generation.CodecUtil;
import uk.co.real_logic.artio.fields.DecimalFloat;
import uk.co.real_logic.artio.library.OnMessageInfo;
import uk.co.real_logic.artio.library.SessionHandler;
import uk.co.real_logic.artio.messages.DisconnectReason;
import uk.co.real_logic.artio.session.Session;
import uk.co.real_logic.artio.util.MutableAsciiBuffer;

import static io.aeron.logbuffer.ControlledFragmentHandler.Action.ABORT;
import static io.aeron.logbuffer.ControlledFragmentHandler.Action.CONTINUE;
import static java.nio.charset.StandardCharsets.US_ASCII;

public class ExchangeSessionHandler implements SessionHandler
{
    private static final DecimalFloat MINIMUM_PRICE = new DecimalFloat().fromLong(100);
    private static final char[] VALID_SYMBOL = "MSFT".toCharArray();
    private static final byte[] SYMBOL_BYTES = "MSFT".getBytes(US_ASCII);

    private static final int SIZE_OF_ASCII_LONG = String.valueOf(Long.MAX_VALUE).length();

    // static fields below safe due to single threaded nature of the logic.

    // execIds unique to venue.
    private static final byte[] EXEC_ID_BUFFER = new byte[SIZE_OF_ASCII_LONG];
    private static int execIdEncodedLength;
    private static final UnsafeBuffer EXEC_ID_ENCODER = new UnsafeBuffer(EXEC_ID_BUFFER);
    private static long execId = 0;

    private static final byte[] ORDER_ID_BUFFER = new byte[SIZE_OF_ASCII_LONG];
    private static int orderIdEncodedLength;
    private static final UnsafeBuffer ORDER_ID_ENCODER = new UnsafeBuffer(ORDER_ID_BUFFER);

    // orderIds unique per session
    private long orderId = 0;

    private final NewOrderSingleDecoder newOrderSingle = new NewOrderSingleDecoder();
    private final ExecutionReportEncoder executionReport = new ExecutionReportEncoder();
    private final MutableAsciiBuffer asciiBuffer = new MutableAsciiBuffer();

    ExchangeSessionHandler(final Session session)
    {
    }

    public Action onMessage(
        final DirectBuffer buffer,
        final int offset,
        final int length,
        final int libraryId,
        final Session session,
        final int sequenceIndex,
        final long messageType,
        final long timestampInNs,
        final long position,
        final OnMessageInfo messageInfo)
    {
        asciiBuffer.wrap(buffer, offset, length);

        if (messageType == NewOrderSingleDecoder.MESSAGE_TYPE)
        {
            newOrderSingle.decode(asciiBuffer, 0, length);

            if (!validOrder())
            {
                return cancelOrder(session);
            }

            return fillOrder(session);
        }

        return CONTINUE;
    }

    private Action cancelOrder(final Session session)
    {
        final Side side = newOrderSingle.sideAsEnum();

        newOrderId();

        newExecId();

        executionReport
            .execType(ExecType.CANCELED)
            .ordStatus(OrdStatus.CANCELED)
            .orderID(ORDER_ID_BUFFER, orderIdEncodedLength)
            .execID(EXEC_ID_BUFFER, execIdEncodedLength)
            .side(side);

        executionReport.instrument().symbol(newOrderSingle.symbol(), newOrderSingle.symbolLength());

        return Pressure.apply(session.trySend(executionReport));
    }

    private boolean validOrder()
    {
        if (!CodecUtil.equals(newOrderSingle.symbol(), VALID_SYMBOL, newOrderSingle.symbolLength()))
        {
            return false;
        }

        final OrdType ordType = newOrderSingle.ordTypeAsEnum();
        if (ordType != OrdType.MARKET)
        {
            return false;
        }

        final DecimalFloat price = newOrderSingle.price();
        return MINIMUM_PRICE.compareTo(price) > 0;
    }

    private Action fillOrder(final Session session)
    {
        final Side side = newOrderSingle.sideAsEnum();

        newOrderId();

        newExecId();

        executionReport
            .orderID(ORDER_ID_BUFFER, orderIdEncodedLength)
            .execID(EXEC_ID_BUFFER, execIdEncodedLength)
            .execType(ExecType.FILL)
            .ordStatus(OrdStatus.FILLED)
            .side(side);

        executionReport.instrument().symbol(SYMBOL_BYTES);

        final long sendPosition = session.trySend(executionReport);
        if (Pressure.isBackPressured(sendPosition))
        {
            // Roll back transactional state and indicate that you need to retry.
            orderId--;
            execId--;

            return ABORT;
        }
        else
        {
            return CONTINUE;
        }
    }

    private void newExecId()
    {
        execId++;
        execIdEncodedLength = EXEC_ID_ENCODER.putLongAscii(0, execId);
    }

    private void newOrderId()
    {
        // Generate ids GC free, underlying buffer is associated with the execution report in the constructor
        orderId++;
        orderIdEncodedLength = ORDER_ID_ENCODER.putLongAscii(0, orderId);
    }

    @Override
    public void onSessionStart(final Session session)
    {
    }

    @Override
    public void onTimeout(final int libraryId, final Session session)
    {

    }

    @Override
    public void onSlowStatus(final int libraryId, final Session session, final boolean hasBecomeSlow)
    {

    }

    @Override
    public Action onDisconnect(
        final int libraryId, final Session session, final DisconnectReason reason)
    {
        return CONTINUE;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy