Skip to content

Instantly share code, notes, and snippets.

@linanqiu
Last active July 14, 2020 04:28
Show Gist options
  • Save linanqiu/71601277006743b6ba7850d689e6cfba to your computer and use it in GitHub Desktop.
Save linanqiu/71601277006743b6ba7850d689e6cfba to your computer and use it in GitHub Desktop.

Broker Algo Game

Overview

You're quantitative developers of the renowned hedge fund Principles Capital Management LLC. You're responsible for building systems and algorithms to execute the fund's trades everyday. In order to execute your trades, you engage brokers to do trading on behalf of you. The brokers differ from one another in cost and your job is to incur the lower total trading cost within a given trading session.

Here are the rules:

  • Trading sessions:
    • Each trading session lasts 9 minutes
    • It starts every 10th minute (e.g. 6pm, 6.10pm, 6.20pm etc.) and lasts for 9 minutes i.e. a 1 minute interval between each trading session
  • Brokers
    • You have between 5 to 10 brokers to choose from each session
    • During each trading session, some brokers will consistently give you better costs while some will give you worse costs. However, the market is volatile and there is a lot of randomness / noise in the costs as well.
    • Brokers "reset" between sessions, so cost patterns don't carry over between sessions. Broker costs are independent between successive sessions.
  • Trading
    • In each session, you have to execute 1000 units of orders.
    • You put out orders to brokers a single order at a time for 1 unit of asset. i.e. you can only trade 1 unit with 1 broker at a time. You trade 2 units by doing a 1 unit trade twice.
    • You have to complete trading 1000 units by the end of 9 minutes otherwise you incur a large penalty.
    • You only know the cost that an order incurred after you've traded that unit i.e. the broker will not tell you the cost of your trading BEFORE you've traded. That won't make sense right?

Example:

  • At 6.10pm, a new trading session starts. You have 1000 units to trade. There are 5 brokers in the market: BrokerA, BrokerB, BrokerC, BrokerD, BrokerE.
  • You decide to trade with BrokerA, so you send an order to BrokerA. BrokerA responds with cost=5. Having traded 1 unit, you now have 999 units left to trade.
  • Then, you decide to trade with BrokerB, so you send an order to BrokerB. BrokerB responds with cost=4. Having traded 2 units (1 with BrokerA, 1 with BrokerB), you now have 998 units left to trade. You total cost is total_cost = 5 + 4 = 9.
  • ...you trade 98 more units... At this point, you decide that on average, BrokerC is your best broker since it has given you the lowest costs so far. So you decide to trade with BrokerC for all of your remaining orders (900 orders).
  • You complete trading at 6.15pm. The market ends at 6.19pm.

API

API Key

Each team will be assigned several API keys. You can use this to simultaneously test your code. However, during the final competition, you'll only be allowed to use one API key. (Using multiple will just map all those requests to the same user anyway, so there's really no difference).

Keep your API key secret unless you want other teams impersonating as you and sabotaging your trades.

Specification

{url_root} = https://broker-algo-intern-functions.azurewebsites.net

(for this who are curious, yes I used Azure Functions because I'm lazy as hell!)

For each of these requests, make the requests without the { and } brackets and placeholders filled in.

  • GET {url_root}/api/Users/GetActiveSession
    • Gets the details (including session id) of the current active session
    • Return sample:
{
    "sessionId": "5f2af895-9fd0-4633-9c4f-ddb18c1817ba", // the id of the current session. Use this in places where the API asks for {session_id}
    "sessionStartTime": "2020-07-14T03:50:00.0222514+00:00", // start time of the session in UTC
    "sessionEndTime": "2020-07-14T03:59:00.0222514+00:00", // end time of the session in UTC (note that it's 9 minutes after the start time)
    "unitsToBuy": 1000, // units you should attempt to buy during this session. for all intents and purposes this will always be at 1000
    "brokerIds": [
        "mrsdownjones",
        "arbitrage.andy",
        "overheardonwallstreet",
        "litquidity",
        "notyourfathersbroker",
        "ebitdad",
        "justthequant"
    ] // a list of BrokerIds you can trade with. these are just finance meme insta influencers haha
}
  • GET {url_root}/api/Users/Order?ApiKey={api_key}&SessionId={session_id}&BrokerId={broker_id}
    • Trades a single unit with {broker_id} during {session_id} for the team with the corresponding {api_key}
    • Returns the cost of the order, id identifying the order, and broker id
    • When the session has expired, no more orders will be allowed and you'll get an error
    • If you trade past 1000, the API will continue to allow you trade (for a bit) but only the first 1000 orders will be counted. The rest will be rejected in the final tally.
    • Return sample:
{
    "TransactionTime": "2020-07-14T03:45:47.5337748+00:00", // transaction tiem in UTC time
    "Cost": 130.66883099246755, // this is the cost you should be concerned with
    "BrokerId": "litquidity", // an echo of the BrokerId of the broker you traded with
    "SessionId": "4ecd5de2-e674-438c-a8f7-b6781bb804da", // an echo of the SessionId you traded in
    "UserId": "linanqiu", // an echo of your UserId
    "id": "8bf5268b-475e-40df-97a5-68a842f4cd6e", // id of this Fill
    "EventType": "FilledEvent"
}
  • GET {url_root}/api/Users/GetFillsBySession?ApiKey={api_key}&SessionId={session_id}
    • Retrieves the orders that the team corresponding to {api_key} has executed so far in the specified {session_id} session.
    • This works for analyzing historical past session performance as well.
    • Return sample:
{
    "sessionId": "4ecd5de2-e674-438c-a8f7-b6781bb804da", // an echo of the session_id you provided
    "userId": "linanqiu", // your user id (this return message will be specific to your team)
    "stats": { // stats of your performance so far.
        "UserId": "linanqiu",
        "TotalCost": 7723.540659688935, // sum of costs of your orders so far
        "UnitsBought": 75 // number of orders you've purchased so far
    },
    "fills": [ // details of each individual fill you have. this is the same message as what you'll get from the Order api
        {
            "TransactionTime": "2020-07-14T03:44:15.0983501+00:00",
            "Cost": 90.56267282575399,
            "BrokerId": "litquidity",
            "SessionId": "4ecd5de2-e674-438c-a8f7-b6781bb804da",
            "UserId": "linanqiu",
            "id": "30adcc06-78ae-49aa-9b8c-dc8b4cb0e7ee",
            "EventType": "FilledEvent"
        },
        // redacted for brevity. there's 73 more fills here
        {
            "TransactionTime": "2020-07-14T03:45:45.5523862+00:00",
            "Cost": 126.42931617879975,
            "BrokerId": "litquidity",
            "SessionId": "4ecd5de2-e674-438c-a8f7-b6781bb804da",
            "UserId": "linanqiu",
            "id": "9a9389a2-e6ca-43e5-b78f-8c8d26343838",
            "EventType": "FilledEvent"
        }
    ]
}

The time zone of this API is UTC. Since we have everyone working around the globe, we will not impose American supremacy / homogeneity. eagle sqwawk

Service Level Agreements (SLAs)

  • For now, I can't guarantee that the service is up and database / app limit quotas won't be hit. I accidentally deployed this to an Azure subscription that's my "student" plan. That has a $100 free quota and once it's hit it will get locked. I will transition over the app to a proper subscription that allows me to handle all sorts of DOS-ing you throw at me. That means an hour or two downtime here and there (especially this week).
  • That also means you should not hard code the {url_root}. It is subject to change.
  • For performance reasons, the API is eventually consistent. That means it does not do insanely strict checking for each of your orders (e.g. if the session has elapsed, if you've reached the max order count). It may do so a second or two later. However, the final tally of scores will only use the first 1000 orders and only the orders that have arrived before the end of the session.

Judging

  • Just like real devs, your app will be judged holistically. That means everything from the algorithm itself, the architecture of the application, the user experience etc. This is important because if you release a super awesome algorithm but no one understands how / why it works, compliance will rain hell on you and no one will dare to assume responsibility for the trading activities of your app. You may even cause a Knight Capital Group incident. So build a robust app through and through.
  • Use your mentors as a resource to think through what factors are important and how to distribute the resources of your team across the various features.

Questions for you to think about:

  • What is the worst possible thing that can happen if your algorithm goes crazy? How do you stop that behavior? What sorts of outcomes are unacceptably bad? Would overtrading be one of them? Should you build a manual override? A circuit breaker? Read this: https://www.reuters.com/article/us-knightcapital-loss-dealerresponse/wall-street-sees-knight-as-software-risk-wake-up-call-idUSBRE87300H20120804 Ask your mentor about the concept of risk control.
  • What's the user experience of someone interacting / monitoring your application? What will the concerns of that person be? How can someone supervising these trades be comfortable with what the algorithm is doing? How can you answer the question "what is the algorithm thinking of right now?"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment