diff --git a/README.md b/README.md index 3738752a1..ed9cc3334 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ :warning: **Use at own risk** :warning: -v5.9.7 +v5.9.8 ## Overview diff --git a/auto_profit_transfer.py b/auto_profit_transfer.py index dc6bd66ad..921b008cc 100644 --- a/auto_profit_transfer.py +++ b/auto_profit_transfer.py @@ -89,7 +89,7 @@ async def main(): transferred = await bot.transfer_from_derivatives_to_spot(args.quote, to_transfer) logging.info(f"income: {profit} transferred {to_transfer} {args.quote}") if exchange == "bybit": - if "ret_msg" not in transferred or transferred["ret_msg"] != "OK": + if "ret_msg" not in transferred or transferred["ret_msg"] not in ["OK", "success"]: print(f"error with transfer {transferred}") continue logging.info(f"{transferred}") diff --git a/configs/forager/example_config.hjson b/configs/forager/example_config.hjson index e33d518ac..1fdef7f34 100644 --- a/configs/forager/example_config.hjson +++ b/configs/forager/example_config.hjson @@ -27,22 +27,32 @@ // if symbol is missing from live_configs_map, default to this config default_config_path: configs/live/clock_mode.example.json - // if true, allow only symbols present in live_configs_map + // if true, allow only symbols present in live_configs_map or live_configs_map_{long/short} // if false, will use default_config_path when symbol is missing from live_configs_map approved_symbols_only: false + // if symbol is present in live_configs_map_{long/short}, use that config for {long/short} + // elif symbol is present in live_configs_map, use that config for {long/short} + // else use default_config_path for {long/short} + live_configs_map: { BTCUSDT: configs/live/clock_mode.example.json ETHUSDT: configs/live/neat_grid_mode.example.json DOGEUSDT: configs/live/recursive_grid_mode.example.json } - // min markup when -gs - gs_mm: null - // markup range when -gs - gs_mr: null - // long WE_limit when -gs - gs_lw: null - // short WE_limit when -gs - gs_sw: null + // will override long configs from live_configs_map + live_configs_map_long: { + BTCUSDT: configs/live/clock_mode.example.json + XMRUSDT: configs/live/neat_grid_mode.example.json + XRPUSDT: configs/live/recursive_grid_mode.example.json + } + + // will override short configs from live_configs_map + live_configs_map_short: { + BTCUSDT: configs/live/clock_mode.example.json + ETHUSDT: configs/live/neat_grid_mode.example.json + DOGEUSDT: configs/live/recursive_grid_mode.example.json + } + } diff --git a/forager.py b/forager.py index 1d8d946a2..d14e2561c 100644 --- a/forager.py +++ b/forager.py @@ -14,6 +14,7 @@ import traceback from procedures import load_exchange_key_secret_passphrase, utc_ms from njit_funcs import calc_emas +from pure_funcs import determine_pos_side_ccxt def score_func_old(ohlcv): @@ -101,8 +102,14 @@ def generate_yaml( sorted_syms = [x[1] for x in sorted_syms] current_positions_long = sorted(set(current_positions_long + current_open_orders_long)) current_positions_short = sorted(set(current_positions_short + current_open_orders_short)) - ideal_longs = sorted_syms[:n_longs] - ideal_shorts = sorted_syms[:n_shorts] + if config["approved_symbols_only"]: + approved_longs = set(config["live_configs_map_long"]) | set(config["live_configs_map"]) + approved_shorts = set(config["live_configs_map_short"]) | set(config["live_configs_map"]) + else: + approved_longs = set(sorted_syms) + approved_shorts = set(sorted_syms) + ideal_longs = [x for x in sorted_syms if x in approved_longs][:n_longs] + ideal_shorts = [x for x in sorted_syms if x in approved_shorts][:n_shorts] free_slots_long = max(0, n_longs - len(current_positions_long)) active_longs = [sym for sym in ideal_longs if sym in current_positions_long] @@ -128,42 +135,66 @@ def generate_yaml( active_bots.append(elm) else: bots_on_gs.append(elm) - for z in range(0, len(active_bots), config["max_n_panes"]): - active_bots_slice = active_bots[z : z + config["max_n_panes"]] - yaml += f"- window_name: {config['user']}_normal_{z}\n layout: " - yaml += f"even-vertical\n shell_command_before:\n - cd ~/passivbot\n panes:\n" - for sym, long_enabled, short_enabled in active_bots_slice: - lm = "n" if long_enabled and lw > 0.0 else "gs" - sm = "n" if short_enabled and sw > 0.0 else "gs" - conf_path = ( - config["live_configs_map"][sym] if sym in config["live_configs_map"] else config["default_config_path"] - ) - pane = f" - shell_command:\n - python3 passivbot.py {user} {sym} {conf_path} " + + bot_instances = [] + for sym, long_enabled, short_enabled in active_bots + bots_on_gs: + lm = "n" if long_enabled and lw > 0.0 else "gs" + sm = "n" if short_enabled and sw > 0.0 else "gs" + if sym in config["live_configs_map_long"]: + conf_path_long = config["live_configs_map_long"][sym] + elif sym in config["live_configs_map"]: + conf_path_long = config["live_configs_map"][sym] + else: + conf_path_long = config["default_config_path"] + + if sym not in (shorts_on_gs + active_shorts): + conf_path_short = conf_path_long + elif sym in config["live_configs_map_short"]: + conf_path_short = config["live_configs_map_short"][sym] + elif sym in config["live_configs_map"]: + conf_path_short = config["live_configs_map"][sym] + else: + conf_path_short = config["default_config_path"] + + if sym not in (longs_on_gs + active_longs): + conf_path_long = conf_path_short + + if conf_path_long == conf_path_short: + pane = f" - shell_command:\n - python3 passivbot.py {user} {sym} {conf_path_long} " pane += f"-lw {lw} -sw {sw} -lm {lm} -sm {sm} -lev {config['leverage']} -cd -pt {config['price_distance_threshold']}" - yaml += pane + "\n" - if bots_on_gs: - for z in range(0, len(bots_on_gs), config["max_n_panes"]): - bots_on_gs_slice = bots_on_gs[z : z + config["max_n_panes"]] - yaml += ( - f"- window_name: {config['user']}_gs_{z}\n layout: even-vertical\n shell_command_before:\n - cd ~/passivbot\n panes:" - + "\n" - ) - gs_lw = lw if config["gs_lw"] is None else config["gs_lw"] - gs_sw = lw if config["gs_sw"] is None else config["gs_sw"] - for sym, _, _ in bots_on_gs_slice: - conf_path = ( - config["live_configs_map"][sym] - if sym in config["live_configs_map"] - else config["default_config_path"] - ) - pane = f" - shell_command:\n - python3 passivbot.py {user} {sym} {conf_path} -lw {gs_lw} " - pane += ( - f"-sw {gs_sw} -lm gs -sm gs -lev {config['leverage']} -cd -pt {config['price_distance_threshold']}" - ) - for k0, k1 in [("lmm", "gs_mm"), ("lmr", "gs_mr")]: - if config[k1] is not None: - pane += f" -{k0} {config[k1]}" - yaml += pane + "\n" + bot_instances.append((sym, pane)) + else: + # long and short use different configs + long_active = sym in active_longs or sym in current_positions_long + short_active = sym in active_shorts or sym in current_positions_short + if long_active and short_active: + # two separate bot instances for long & short + pane = f" - shell_command:\n - python3 passivbot.py {user} {sym} {conf_path_long} " + pane += f"-lw {lw} -sw {sw} -lm {lm} -sm m -lev {config['leverage']} -cd -pt {config['price_distance_threshold']}" + bot_instances.append((sym, pane)) + pane = f" - shell_command:\n - python3 passivbot.py {user} {sym} {conf_path_short} " + pane += f"-lw {lw} -sw {sw} -lm m -sm {sm} -lev {config['leverage']} -cd -pt {config['price_distance_threshold']}" + bot_instances.append((sym, pane)) + elif long_active: + pane = f" - shell_command:\n - python3 passivbot.py {user} {sym} {conf_path_long} " + pane += f"-lw {lw} -sw {sw} -lm {lm} -sm {sm} -lev {config['leverage']} -cd -pt {config['price_distance_threshold']}" + bot_instances.append((sym, pane)) + elif short_active: + pane = f" - shell_command:\n - python3 passivbot.py {user} {sym} {conf_path_short} " + pane += f"-lw {lw} -sw {sw} -lm {lm} -sm {sm} -lev {config['leverage']} -cd -pt {config['price_distance_threshold']}" + bot_instances.append((sym, pane)) + + both_on_gs = False + z = 0 + for sym, pane in bot_instances: + if not both_on_gs and sym not in active_longs and sym not in active_shorts: + z = 0 + both_on_gs = True + if z % config["max_n_panes"] == 0: + yaml += f"- window_name: {config['user']}_{'gs' if both_on_gs else 'normal'}_{z}\n layout: " + yaml += f"even-vertical\n shell_command_before:\n - cd ~/passivbot\n panes:\n" + yaml += pane + "\n" + z += 1 return yaml @@ -228,7 +259,8 @@ async def get_current_symbols(cc): oos = await cc.fetch_open_orders() current_open_orders_long, current_open_orders_short = [], [] for elm in oos: - if elm["side"] == "short": + pos_side = determine_pos_side_ccxt(elm) + if pos_side == "short": current_open_orders_short.append(elm["symbol"]) else: current_open_orders_long.append(elm["symbol"]) @@ -236,7 +268,12 @@ async def get_current_symbols(cc): current_positions_short = sorted(set(current_positions_short)) current_open_orders_long = sorted(set(current_open_orders_long)) current_open_orders_short = sorted(set(current_open_orders_short)) - return current_positions_long, current_positions_short, current_open_orders_long, current_open_orders_short + return ( + current_positions_long, + current_positions_short, + current_open_orders_long, + current_open_orders_short, + ) async def get_min_costs(cc): @@ -284,7 +321,15 @@ async def dump_yaml(cc, config): approved = sorted(set(approved) - set(config["symbols_to_ignore"])) if config["approved_symbols_only"]: # only use approved symbols - approved = sorted(set(approved) & set(config["live_configs_map"])) + approved = sorted( + set(approved) + & ( + set(config["live_configs_map"]) + | set(config["live_configs_map_long"]) + | set(config["live_configs_map_short"]) + ) + ) + print("getting current bots...") ( current_positions_long, @@ -353,6 +398,8 @@ async def main(): ("ohlcv_interval", "15m"), ("leverage", 10), ("symbols_to_ignore", []), + ("live_configs_map_long", {}), + ("live_configs_map_short", {}), ]: if key not in config: config[key] = value diff --git a/passivbot.py b/passivbot.py index 3f7716a51..efe37c501 100644 --- a/passivbot.py +++ b/passivbot.py @@ -1418,6 +1418,7 @@ async def start_ohlcv_mode(self): res = self.calc_minutes_until_next_orders() if do_long: line += f"entry long: {res['entry_long']:.1f}, close long: {res['close_long']:.1f}" + line += " | " if do_short else "" if do_short: line += f"entry short: {res['entry_short']:.1f}, close short: {res['close_short']:.1f}" except Exception as e: diff --git a/pure_funcs.py b/pure_funcs.py index 23bec381f..1c9cd8e2e 100644 --- a/pure_funcs.py +++ b/pure_funcs.py @@ -1563,3 +1563,40 @@ def shorten_custom_id(id_: str) -> str: ]: id0 = id0.replace(k_, r_) return id0 + + +def determine_pos_side_ccxt(open_order: dict) -> str: + oo = open_order["info"] + keys_map = {key.lower().replace("_", ""): key for key in oo} + for poskey in ["posside", "positionside"]: + if poskey in keys_map: + return oo[keys_map[poskey]].lower() + if oo["side"].lower() == "buy": + if "reduceonly" in keys_map: + if oo[keys_map["reduceonly"]]: + return "short" + else: + return "long" + if "closedsize" in keys_map: + if float(oo[keys_map["closedsize"]]) != 0.0: + return "short" + else: + return "long" + if oo["side"].lower() == "sell": + if "reduceonly" in keys_map: + if oo[keys_map["reduceonly"]]: + return "long" + else: + return "short" + if "closedsize" in keys_map: + if float(oo[keys_map["closedsize"]]) != 0.0: + return "long" + else: + return "short" + for key in ["order_link_id", "clOrdId", "clientOid"]: + if key in oo: + if "long" in oo[key] or "lng" in oo[key]: + return "long" + if "short" in oo[key] or "shrt" in oo[key]: + return "short" + return "both"