Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

market_order_target #224

Open
femtotrader opened this issue Feb 11, 2023 · 5 comments
Open

market_order_target #224

femtotrader opened this issue Feb 11, 2023 · 5 comments

Comments

@femtotrader
Copy link

femtotrader commented Feb 11, 2023

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

@EmersonDove
Copy link
Member

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.

@femtotrader
Copy link
Author

femtotrader commented Feb 13, 2023

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

@femtotrader
Copy link
Author

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.

@EmersonDove
Copy link
Member

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.

@femtotrader
Copy link
Author

femtotrader commented Feb 13, 2023

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants