blankly-finance / blankly

🚀 💸 Easily build, backtest and deploy your algo in just a few lines of code. Trade stocks, cryptos, and forex across exchanges w/ one package.
https://package.blankly.finance
GNU Lesser General Public License v3.0
2.03k stars 261 forks source link

bar_event squashes price_event price #249

Open WhileE opened 8 months ago

WhileE commented 8 months ago

Description

If you add both a bar_event and price_event, the bar_event's resolution impacts the price_event's price. For example, if I have a bar_event that is triggered at a resolution of 15m, and a price_event with a resolution of 1m, then the price_event will be triggered every 1m, but the price will only change at 15m thresholds.

from blankly import *
from datetime import datetime
import pytz

est = pytz.timezone('US/Eastern')

def price_event(price, symbol, state: StrategyState):
    time = datetime.fromisoformat(utils.iso8601_from_epoch(state.time)).astimezone(est)
    print(f'[PRICE_EVENT][{time}] price:{price}')

def bar_event(bar, symbol, state):
    time = time = datetime.fromisoformat(utils.iso8601_from_epoch(bar['time'])).astimezone(est)
    price = bar['close']
    print(f'[BAR_EVENT] ({time}) price:{price}')

exchange = Oanda()
s = Strategy(exchange)

currency_pair = "AUD-JPY"
currency_base = currency_pair.split('-')[0]
currency_quoted = currency_pair.split('-')[1]

s.add_price_event(price_event, currency_pair, resolution="1m")
s.add_bar_event(bar_event, currency_pair, resolution="15m")

results = s.backtest(quote_account_value_in=currency_quoted, initial_values={currency_base: 1000000}, start_date="10/29/2023", end_date="10/31/2023", GUI_output=False)

Output:

[PRICE_EVENT][2023-10-30 19:09:00-04:00] price:94.967
[PRICE_EVENT][2023-10-30 19:10:00-04:00] price:94.967
[PRICE_EVENT][2023-10-30 19:11:00-04:00] price:94.967
[PRICE_EVENT][2023-10-30 19:12:00-04:00] price:94.967
[PRICE_EVENT][2023-10-30 19:13:00-04:00] price:94.967
[PRICE_EVENT][2023-10-30 19:14:00-04:00] price:94.967
[PRICE_EVENT][2023-10-30 19:15:00-04:00] price:94.967
[BAR_EVENT] (2023-10-30 19:00:00-04:00) price:94.958
[PRICE_EVENT][2023-10-30 19:16:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:17:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:18:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:19:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:20:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:21:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:22:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:23:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:24:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:25:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:26:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:27:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:28:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:29:00-04:00] price:94.948
[PRICE_EVENT][2023-10-30 19:30:00-04:00] price:94.948
[BAR_EVENT] (2023-10-30 19:15:00-04:00) price:94.967
[PRICE_EVENT][2023-10-30 19:31:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:32:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:33:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:34:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:35:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:36:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:37:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:38:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:39:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:40:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:41:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:42:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:43:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:44:00-04:00] price:94.946
[PRICE_EVENT][2023-10-30 19:45:00-04:00] price:94.946
[BAR_EVENT] (2023-10-30 19:30:00-04:00) price:94.948
[PRICE_EVENT][2023-10-30 19:46:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 19:47:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 19:48:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 19:49:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 19:50:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 19:51:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 19:52:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 19:53:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 19:54:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 19:55:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 19:56:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 19:57:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 19:58:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 19:59:00-04:00] price:95.049
[PRICE_EVENT][2023-10-30 20:00:00-04:00] price:95.049
[BAR_EVENT] (2023-10-30 19:45:00-04:00) price:94.946
[PRICE_EVENT][2023-10-30 20:01:00-04:00] price:95.049

If I comment the add_bar_event() out, or simply change the resolution to 1m (matches price_event) then it works fine:

[PRICE_EVENT][2023-10-30 19:09:00-04:00] price:94.976
[PRICE_EVENT][2023-10-30 19:10:00-04:00] price:94.964
[PRICE_EVENT][2023-10-30 19:11:00-04:00] price:94.962
[PRICE_EVENT][2023-10-30 19:12:00-04:00] price:94.965
[PRICE_EVENT][2023-10-30 19:13:00-04:00] price:94.96
[PRICE_EVENT][2023-10-30 19:14:00-04:00] price:94.958

[...]

[PRICE_EVENT][2023-10-30 19:55:00-04:00] price:94.962
[PRICE_EVENT][2023-10-30 19:56:00-04:00] price:94.966
[PRICE_EVENT][2023-10-30 19:57:00-04:00] price:94.95
[PRICE_EVENT][2023-10-30 19:58:00-04:00] price:94.938
[PRICE_EVENT][2023-10-30 19:59:00-04:00] price:94.938

So is this an issue, or is my implementation/assumption incorrect?

WhileE commented 8 months ago

It looks like if I swap the order add_price_event() and add_bar_event(), things work better.

Digging into the code a bit, it initially looks like the final_prices are getting squashed here:

(BacktestController.py, line 418)

if len(relevant_data) > 0:
    final_prices[symbol] = pd.concat(relevant_data)

My initial testing shows that the following code preserves all prices @ all resolutions:

if len(relevant_data) > 0:
    if symbol not in final_prices:
        final_prices[symbol] = pd.concat(relevant_data)
    else:
        new_data = pd.concat(relevant_data)
        final_prices[symbol] = pd.concat([final_prices[symbol], new_data])

I'm not super familiar with enough of the code to understand what impact this could cause, but if there are tests I can run against it just let me know. Going to keep testing to see if it breaks anything else that I can see.