
Trading System Development and Research Notes (1)
It is known that when an automated trading system (=bot) earns a little money every day, it soothes the mind and improves health [citation needed].
Currently, I have one bot that earns small change fairly consistently, but there is much room for improvement and it's clear that it will eventually plateau. Therefore, to strengthen the theory behind the bot and scale up profits, I decided to create bots that operate in other environments and exchanges.
I've only been working on this for about two weeks and have only created the core part, so many improvements are needed to generate profits. In this article, I will describe what I've worked on to make improvements and aim to convey the research results and the joy of creating bots.
Ultimately, this is just a memo for myself.
Premise
The exchange is Bitflyer, and the trading pair is FX_BTC_JPY. This is considered suitable for running bots because it has high trading volume and thus high liquidity. On the other hand, it's not straightforward as there are already bots running rampant, and the system becomes unstable quickly, showing its unruly side.
By the way, the spread is about 0.02% when stable (if we trust the display). If one unit is 500,000 yen, that's 100 yen, and for 0.01 lot, it means a loss of 1 yen accumulates (I will measure the actual spread and slippage for market orders if necessary).
The method is simple: monitor the market, take a single position when conditions are met, and close it in about 1 minute for ultra-short-term trading (=Scalping). I'll keep the trigger for taking positions a secret.
Goal
The short-term goal is to achieve a positive total over 3 days with almost the minimum lot size. Setting a profit target like N yen in a week is postponed for now, focusing first on reducing losses. It would be fine to aim for daily profits, but I'm allowing some flexibility as it largely depends on market conditions.
Current State of the Bot
The current bot has the following basic functions. However, as the verification period is yet to come, there is a high possibility of hidden bugs.
- Market environment monitoring
- Order placement
- Position monitoring and closing
- Exchange monitoring (not trading if it seems unstable)
By the way, this is the 3rd generation bot. The 1st generation is working hard elsewhere, and the 2nd generation is not running due to various circumstances. The 1st and 2nd were made with Python, while this one is made with Rust.
Currently, it takes positions with IFD orders (market order, Take Profit at +900, Stop Loss at -600) and closes them after 30 seconds.
Current Profit Status
In this state, I ran it for about 11 hours from around midnight with 0.02 lots. The downward trend is also mood-dampening.
Since this graph display alone doesn't show the details well, I created a tool in Python to roughly output the trading history, allowing for the following display. It's pulled from the API's /v1/me/getchildorders
(as mentioned later, this was causing problems).
The profit and loss don't quite match, but I'll ignore the slight difference (the app's display may not be correct).
...
2018-12-02 00:27:14, 0:01:12s, prof: -3.9, start: SELL, diff: 195.0, amount: 0.02,0.02
2018-12-02 01:31:36, 0:00:09s, prof: 15.48, start: BUY, diff: 774.0, amount: 0.02,0.02
2018-12-02 01:32:15, 0:00:17s, prof: 1.56, start: BUY, diff: 78.0, amount: 0.02,0.02
2018-12-02 01:33:04, 0:00:30s, prof: -18.26, start: SELL, diff: 913.0, amount: 0.02,0.02
2018-12-02 01:35:34, 0:00:18s, prof: -13.56, start: BUY, diff: -678.0, amount: 0.02,0.02
2018-12-02 01:36:04, 0:01:22s, prof: 9.8, start: SELL, diff: -490.0, amount: 0.02,0.02
2018-12-02 01:39:27, 0:00:02s, prof: 3.42, start: BUY, diff: 171.0, amount: 0.02,0.02
profit: -165.18000000000006, 120 trade
If further processing becomes necessary, I can also make it output in JSON or CSV format.
Hypothesis 1: Making the opposite trades would be profitable
This is a hypothesis everyone thinks of. If the time is fixed, it can only go up or down, so wouldn't making the opposite trades lead to explosive profits? That's the idea.
Looking at the history, there were 120 trades. Assuming a spread of 0.02% (about 100 yen range), with a lot size of 0.02, we're paying 2 * 120 = 240 yen. In reality, it's -165 yen, so in a world without spread, it would be +75 yen. Therefore, making the opposite trades would result in -315 yen, further expanding the loss.
However, this assumes that the spread is 0.02% and that individual trades have no specific characteristics. It's entirely possible that making opposite trades could be good under certain conditions, so it's worth revisiting this idea from time to time.
In fact, the result of making opposite trades for a few hours was a downward trend, so it seems I wasn't wrong.
Conclusion 1: The current trading direction is correct overall, so I'll continue running it as is for now.
Hypothesis 2: Abnormal positions are generating losses
Looking at the history, trades like the following can be seen:
2018-12-01 21:17:04, 0:00:01s, prof: -4.5600000000000005, start: SELL, diff: 228.0, amount: 0.02,0.02
2018-12-01 21:17:05, 0:00:01s, prof: -1.06, start: SELL, diff: 53.0, amount: 0.02,0.02
With the current settings, it should close after 30 seconds at this price range. Closing instantly would only result in paying the spread, so it's likely contributing to losses.
If it's an issue on the exchange's side, it's possible that the spread momentarily widened abnormally and triggered a market order at Stop Loss, or it's simply closing arbitrarily. However, I'd like to believe that such things don't happen in a system handling financial products.
Also, if it's a bug on the bot's side (which is most likely), it could be placing orders with strange parameters, making new orders at the time of closing, or the local state at the time of ordering could be incorrect. Moreover, it could just be a display issue, so we can't let our guard down.
There are several issues, but I'll start by investigating the following two:
- Hypothesis 2-1: Positions closing in a few seconds are due to the bot placing abnormal orders
- Hypothesis 2-2: Positions closing in a few seconds are caused by execution at prices that have diverged due to time lag
Motivating Example
To investigate the cause, I searched through the logs at the time of order placement. Fortunately, the last order was strange, so I'll focus on this.
2018-12-02 01:39:27, 0:00:02s, prof: 3.42, start: BUY, diff: 171.0, amount: 0.02,0.02, open:482126.0-close:482297.0
The parameters for this order were as follows. There were no logs confirming any new orders from our side after this.
{"minute_to_expire":10000,"order_method":"IFDOCO","parameters":[{"condition_type":"MARKET","product_code":"FX_BTC_JPY","side":"BUY","size":"0.02"},{"condition_type":"LIMIT","price":477918.0,"product_code":"FX_BTC_JPY","side":"SELL","size":"0.02"},{"condition_type":"STOP","product_code":"FX_BTC_JPY","side":"SELL","size":"0.02","trigger_price":476234.0}],"time_in_force":"GTC"}
Looking at this, it's ordering with TP: 477918.0, SL: 476234.0. Now, let's look at the actual execution price...
open:482126.0, close:482297.0
It's completely different! Since we use local price data at the time of order, the following causes can be considered:
- The price data held locally is incorrect
- Different prices are coming in
- There's a significant lag in price data
- The price data held locally is correct
- The price has moved significantly in the time difference until order placement
- The market order is slipping terribly
- The order book is incredibly thin
- The spread is abnormally wide only at the time of order
- The exchange is executing at an alternate dimension price
While some causes seem unlikely, I'll investigate them anyway. I added information to the logs and ran it for a while again.
Investigation Log
I added logs and ran it, then observed the logs for the order at 2018-12-02 14:32:20
which seemed strange. This appears to close in 4 seconds.
2018-12-02 14:32:20, 0:00:04s, prof: 5.8, start: BUY, diff: 580.0, amount: 0.01,0.01, open:466470.0-close:467050.0
Here's the price just before. 2018-12-02 14:32:20.393550181Z
before order price: {"bid":466056.0,"ask":466150.0}
bid: 2018-12-02T14:32:19.071246200Z
ask: 2018-12-02T14:32:19.118118700Z
Here are the parameters for the new order (2018-12-02 14:32:20.405687224 UTC
).
{"minute_to_expire":10000,"order_method":"IFDOCO","parameters":[{"condition_type":"MARKET","product_code":"FX_BTC_JPY","side":"BUY","size":"0.01"},{"condition_type":"LIMIT","price":467050.0,"product_code":"FX_BTC_JPY","side":"SELL","size":"0.01"},{"condition_type":"STOP","product_code":"FX_BTC_JPY","side":"SELL","size":"0.01","trigger_price":465456.0}],"time_in_force":"GTC"}
Open order details
{
"id": 735990320,
"child_order_id": "JFX20181202-143224-489678F",
"product_code": "FX_BTC_JPY",
"side": "BUY",
"child_order_type": "MARKET",
"price": 0,
"average_price": 466470,
"size": 0.01,
"child_order_state": "COMPLETED",
"expire_date": "2018-12-09T13:12:20",
"child_order_date": "2018-12-02T14:32:20",
"child_order_acceptance_id": "JRF20181202-143220-015334",
"outstanding_size": 0,
"cancel_size": 0,
"executed_size": 0.01,
"total_commission": 0
}
Open order execution details
{
"id": 615318665,
"side": "BUY",
"price": 466470,
"size": 0.01,
"exec_date": "2018-12-02T14:32:24.32",
"child_order_id": "JFX20181202-143224-489678F",
"commission": 0,
"child_order_acceptance_id": "JRF20181202-143220-015334"
}
Close order details
{
"id": 735991752,
"child_order_id": "JFX20181202-143229-498110F",
"product_code": "FX_BTC_JPY",
"side": "SELL",
"child_order_type": "LIMIT",
"price": 467050,
"average_price": 467050,
"size": 0.01,
"child_order_state": "COMPLETED",
"expire_date": "2018-12-09T13:12:20",
"child_order_date": "2018-12-02T14:32:24",
"child_order_acceptance_id": "JRF20181202-143220-015343",
"outstanding_size": 0,
"cancel_size": 0,
"executed_size": 0.01,
"total_commission": 0
}
Close order execution details
{
"id": 615320061,
"side": "SELL",
"price": 467050,
"size": 0.01,
"exec_date": "2018-12-02T14:32:45.743",
"child_order_id": "JFX20181202-143229-498110F",
"commission": 0,
"child_order_acceptance_id": "JRF20181202-143220-015343"
}
Consideration of Causes
Although I'm getting it via Websocket, it's price data delayed by about 1,200ms. It might just be that there was no recent trade, but it's better to consider it as a delay. I'll verify this additionally.
The bid/ask of the price data is 466,056/466,150, and it's executed at 466,470. It seems to have slipped by about 300, but considering the delay, it looks reasonable.
It took less than 600ms from placing the order to it being accepted, 4 seconds from order to execution, and a whopping 21 seconds to close by hitting the TP.
...It's not 4 seconds!
Conclusion 2-1
Hypothesis 2-1: Positions closing in a few seconds are due to the bot placing abnormal orders
The time difference I was outputting with the Python tool was between child_order_date
s. In the case of If-Done-OCO orders where Stop Loss or Take Profit is triggered, the child_order_date
of the close order becomes the time when the parent order was executed, so this time it just appeared to close in 4 seconds!
I modified the tool to look at the order details (now fetching from /v1/me/getexecutions
), and normal logs were obtained. All's well that ends well.
Conclusion 2-1: The tool was incorrect, making some trades appear to close in a few seconds.
Conclusion 2-2
Hypothesis 2-2: Positions closing in a few seconds are caused by execution at prices that have diverged due to time lag
While some trades were resolved with 2-1, the one shown in the Motivating Example doesn't fall under 2-1.
What can be considered here is the phenomenon where if there's a time lag from order placement, the execution price diverges and immediately hits TP/SL.
Regarding this, several concerning pieces of information came up during the investigation process.
- Is the information from WS significantly delayed?
- It takes abnormally long from placing a market order to execution (this time it took 4 seconds)
- Rumors say there's a mysterious specification where only orders via API are delayed
The time lag line seems strong, but to know what actually happened, we need detailed price data down to the millisecond. 1-minute data is easy to obtain, but anything else needs to be taken from execution history. It will take time to prepare, so I'll cover it in the next article...
In the next article, I'll verify 2-2, confirm the existence of this delay, and test the hypothesis that it's contributing to losses.
Conclusion 2-2: There is a time lag of a few seconds. However, detailed price data is needed to determine if this is the cause. I'll prepare it and verify by the next time.
Summary of This Time
As with any product, right after release, you're often chasing bug fixes and can't take measures for essential improvements. This time was exactly that.
Next time, I hope to gather data while investigating the surroundings and make improvements that can reduce losses.