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.05k stars 261 forks source link

Size resolution is too high error, can't buy fractional? #62

Closed gaborvecsei closed 2 years ago

gaborvecsei commented 2 years ago

Description

Some problem with the order size. Seems like we can't buy fractional coins?

Of course it can easily happen that I misunderstood something, but then a more verbose error would be better

settings.json

{
  "settings": {
    "account_update_time": 5000,
    "use_sandbox": false,
    "use_sandbox_websockets": false,
    "websocket_buffer_size": 10000,
    "test_connectivity_on_auth": true,
    "coinbase_pro": {
      "cash": "EUR"
    }
  }
}

backtest.json (if applicable)

{
  "price_data": {
    "assets": []
  },
  "settings": {
    "use_price": "close",
    "smooth_prices": false,
    "GUI_output": false,
    "show_tickers_with_zero_delta": false,
    "save_initial_account_value": true,
    "show_progress_during_backtest": true,
    "cache_location": "./price_caches",
    "continuous_caching": true,
    "resample_account_value_for_metrics": "1d",
    "quote_account_value_in": "EUR",
    "ignore_user_exceptions": false
  }
}

Error (if applicable)

blankly.utils.exceptions.InvalidOrder: Size resolution is too high, the highest resolution allowed for this symbol is: 1e-08. You specified 0.008585614459548448.

Traceback (most recent call last):
  File "bot.py", line 36, in <module>
    results = strategy.backtest(to="1y", initial_values={"EUR": 100})
  File "/Users/gabor.vecsei/anaconda3/lib/python3.8/site-packages/blankly/frameworks/strategy/strategy_base.py", line 493, in backtest
    results = self.backtesting_controller.run()
  File "/Users/gabor.vecsei/anaconda3/lib/python3.8/site-packages/blankly/exchanges/interfaces/paper_trade/backtest_controller.py", line 878, in run
    metrics_indicators['Compound Annual Growth Rate (%)'] = metrics.cagr(dataframes)
  File "/Users/gabor.vecsei/anaconda3/lib/python3.8/site-packages/blankly/exchanges/interfaces/paper_trade/metrics.py", line 26, in cagr
    return round(metrics.cagr(account_values['value'].iloc[0], account_values['value'].iloc[-1], years), 2) * 100
  File "/Users/gabor.vecsei/anaconda3/lib/python3.8/site-packages/blankly/metrics/portfolio.py", line 31, in cagr
    return (end_value / start_value) ** (1 / years) - 1
ZeroDivisionError: division by zero

Platform Info

This was the quick test code which produced the error:

import blankly

def price_event(price, symbol, state: blankly.StrategyState):
    state.variables["step_count"] += 1

    if state.variables["step_count"] == 1:
        buy = state.interface.cash / price
        state.interface.market_order(symbol, side="buy", size=buy)

    if state.variables["step_count"] == 30:
        curr_value = state.interface.account[symbol].available
        state.interface.market_order(symbol, side='sell', size=curr_value)

def init(symbol, state: blankly.StrategyState):
    # Download price data to give context to the algo
    state.variables["history"] = state.interface.history(symbol, to=150, return_as="deque")["close"]
    state.variables["owns_position"] = False
    state.variables["step_count"] = 0

if __name__ == "__main__":
    # Authenticate coinbase pro strategy
    exchange = blankly.CoinbasePro(portfolio_name="algotrade_test_portfolio")

    # Use our strategy helper on coinbase pro
    strategy = blankly.Strategy(exchange)

    # Run the price event function every time we check for a new price - by default that is 15 seconds
    strategy.add_price_event(price_event, symbol="BTC-EUR", resolution="1d", init=init)

    # Start the strategy. This will begin each of the price event ticks
    # strategy.start()
    # Or backtest using this
    results = strategy.backtest(to="1y", initial_values={"EUR": 100})
    print(results)
EmersonDove commented 2 years ago

Thanks for opening an issue!

In this case this should be expected behavior. Notice that when placing an order on Coinbase in the web interface, there is only a certain number of decimals you can put in your order size. In this case Coinbase enforces a maximum resolution of 1e-8 of your base currency.

You created an order for 0.008585614459548448 base, which has a higher resolution than coinbase allows. We enforce the resolutions in the backtest so that you can be confident your live model will also be able to accurately submit orders.

Solution:

This is why we built blankly.trunc().

Try wrapping your market orders to look like this:

state.interface.market_order(symbol, side="buy", size=blankly.trunc(buy, 8))
state.interface.market_order(symbol, side='sell', size=blankly.trunc(curr_value, 8))

This will enforce an 8 decimal accuracy and truncate higher accuracy decimals.

I recommend viewing the order filter we're checking with by doing:

print(state.interface.get_order_filter(symbol))

I will add a recommendation to use blankly.trunc to the error!

gaborvecsei commented 2 years ago

@EmersonDove Thank you for the explanation and for extending the documentation!