blankly
blankly copied to clipboard
market_order_target
Hello,
According to doc https://docs.blankly.finance/orders/order/ a market_order
method exists but it could be a nice improvement to add market_order_target
with target
parameter (relative size of the position after the trade) being a float between -1 and 1.
target = 1 means buy with 100% of portfolio value
target = -1 means sell with 100% of portfolio value (ie sell shorting)
Kind regards
Could definitely be an upgrade. I would recommend writing this utility though on your own because it's just one extra API call to size that portfolio. If you're interested in contributing, I would be happy to accept a contribution to the spot base class to have this functionality.
I tried to monkey patch the code using:
import blankly
from blankly.data.data_reader import PriceReader
from blankly import Strategy, StrategyState
from blankly.enums import Side
from blankly.exchanges.orders.market_order import MarketOrder
def init(symbol, state: StrategyState):
print(symbol, state)
state.variables["run_once"] = True
def market_order_target(interface,
symbol: str,
target: float) -> MarketOrder:
"""
Used for buying or selling market orders
Args:
symbol: asset to buy or sell
target: relative size of the position after the trade
"""
pos_weight = interface.get_positions(symbol) / interface.cash
buy_pos = (target - pos_weight) * interface.cash
if buy_pos > 0:
return interface.market_order(symbol, Side.BUY, buy_pos)
elif buy_pos < 0:
return interface.market_order(symbol, Side.SELL, -buy_pos)
return None
def price_event(price, symbol, state: StrategyState):
#print(price, symbol, state)
if state.variables["run_once"]:
# state.interface.market_order(symbol, side='buy', size=1.0)
type(state.interface).market_order_target = market_order_target
#market_order_target(state.interface, symbol, 1)
state.interface.market_order_target(symbol, 1)
if state.variables["run_once"]:
state.variables["run_once"] = False
if __name__ == "__main__":
# Run on the keyless exchange, starting at 100k
exchange = blankly.KeylessExchange(price_reader=PriceReader('./XBTUSDT_1Min.csv', 'BTC-USD'))
# Use our strategy helper
strategy = Strategy(exchange)
# Make the price event function above run every minute (60s)
strategy.add_price_event(price_event, symbol='BTC-USD', resolution=60, init=init)
# Backtest the strategy
results = strategy.backtest(start_date=1576778778, end_date=1656633557, initial_values={'USD': 10000})
print(results)
but I'm getting the following error:
AttributeError: 'PaperTradeInterface' object has no attribute 'get_positions'
Any idea what is wrong with my code ?
Kind regards
it doesn't fix the AttributeError
mentioned previously but
pos_weight = interface.get_positions(symbol) / interface.cash
should be changed to
pos_weight = interface.get_positions(symbol) / portfolio_value(interface, quote_asset)
where portfolio_value
is defined as
def portfolio_value(interface, quote_asset):
portfolio_value = 0.0
for base_asset, values in interface.account.items():
if values["available"] != 0:
if base_asset == quote_asset:
portfolio_value += values["available"]
else:
price = interface.get_price(f"{base_asset}-{quote_asset}")
portfolio_value += price * values["available"]
return portfolio_value
maybe such a function which returns portfolio value should also be defined.
This looks correct, however the .get_positions
is only going to work for futures. I just add to the docs to clarify that. I would recommend running (for example) .get_account('BTC')
instead which should give you a result that works inside of your function.
I wonder if a buildin portfolio_value
function/method doesn't ever exist in blankly.
I wonder also if in portfolio_value I shouldn't also consider "hold"
Here is WIP
import requests
import pandas as pd
import blankly
from blankly.data.data_reader import PriceReader
from blankly import Strategy, StrategyState
from blankly.enums import Side
from blankly.exchanges.orders.market_order import MarketOrder
from blankly import utils
def portfolio_value(interface, quote_asset):
portfolio_value = 0.0
for base_asset, account_values in interface.account.items():
if account_values["available"] != 0:
if base_asset == quote_asset:
values = account_values["available"]
portfolio_value += values
else:
symbol = f"{base_asset}-{quote_asset}"
price = interface.get_price(symbol)
print(f"price: {price}")
values = price * account_values["available"]
portfolio_value += values
return portfolio_value
def market_order_target(interface,
symbol: str,
target: float) -> MarketOrder:
quote_asset = utils.get_quote_asset(symbol)
base_asset = utils.get_base_asset(symbol)
pv = portfolio_value(interface, quote_asset)
pos_weight = (interface.get_account(base_asset)["available"]* interface.get_price(symbol)) / pv
buy_pos = (target - pos_weight) * pv
price = interface.get_price(symbol)
filter = interface.get_order_filter(symbol)
increment = filter["market_order"]["base_increment"]
precision = utils.increment_to_precision(increment)
size = blankly.trunc(abs(buy_pos) / price, precision)
if buy_pos > 0:
if size >= filter["market_order"]["base_min_size"]:
return interface.market_order(symbol, Side.BUY, size)
elif buy_pos < 0:
if size >= filter["market_order"]["base_min_size"]:
return interface.market_order(symbol, Side.SELL, size)
return None
def init(symbol, state: StrategyState):
print(symbol, state)
state.variables["run_once"] = True
state.variables["runs"] = 0
def price_event(price, symbol, state: StrategyState):
state.variables["runs"] += 1
#print(price, symbol, state)
if state.variables["run_once"]:
print(state.base_asset)
print(state.quote_asset)
print(symbol)
#state.interface.market_order(symbol, side='buy', size=1.0)
type(state.interface).market_order_target = market_order_target
##market_order_target(state.interface, symbol, 1.0)
state.variables["run_once"] = False
if state.variables["runs"] == 100_000:
target = 0.25
print(f"target: {target}")
state.interface.market_order_target(symbol, target)
elif state.variables["runs"] == 200_000:
target = 0.5
print(f"target: {target}")
state.interface.market_order_target(symbol, target)
elif state.variables["runs"] == 300_000:
target = 0.75
print(f"target: {target}")
state.interface.market_order_target(symbol, target)
elif state.variables["runs"] == 400_000:
target = 1.0
print(f"target: {target}")
state.interface.market_order_target(symbol, target)
if __name__ == "__main__":
# This downloads an example CSV
#data = requests.get(
# 'https://firebasestorage.googleapis.com/v0/b/blankly-6ada5.appspot.com/o/demo_data.csv?alt=media&token=acfa5c39-8f08-45dc-8be3-2033dc2b7b28').text
#with open('./price_examples.csv', 'w') as file:
# file.write(data)
# Run on the keyless exchange, starting at 100k
#exchange = blankly.KeylessExchange(price_reader=PriceReader('./price_examples.csv', 'BTC-USD'))
"""
df = pd.read_csv("XBTUSDT.csv", names=["time", "price", "volume"])
df["time"] = pd.to_datetime(df["time"], unit="s")
df = df.set_index("time")
prices = df.resample("1Min")["price"].ohlc().fillna(method="ffill")
volume = df.resample("1Min")["volume"].sum().fillna(value=0)
volume.name = "volume"
df_1Min = pd.concat([prices, volume], axis=1)
df_1Min = df_1Min.reset_index()
df_1Min["time"] = df_1Min["time"].map(pd.Timestamp.timestamp)
df_1Min.to_csv("XBTUSDT_1Min.csv")
"""
exchange = blankly.KeylessExchange(price_reader=PriceReader('./XBTUSDT_1Min.csv', 'BTC-USD'))
# Use our strategy helper
strategy = Strategy(exchange)
# Make the price event function above run every minute (60s)
strategy.add_price_event(price_event, symbol='BTC-USD', resolution=60, init=init)
# Backtest the strategy
#results = strategy.backtest(start_date=1588377600, end_date=1650067200, initial_values={'USD': 10000})
results = strategy.backtest(start_date=1576778778, end_date=1656633557, initial_values={'USD': 10000})
print(results)