diff --git a/README-Chinese.md b/README-Chinese.md index 068d25e..11bfad0 100644 --- a/README-Chinese.md +++ b/README-Chinese.md @@ -44,10 +44,23 @@ A muti pairs martingle trading bot for Binance exchange. 11. max_increase_pos_count: 最大的加仓次数. +12. turnover_threshold: + 这个是过滤值,就是要求一小时的最低成交量不能低于多少,默认是值 100,000 USDT. +13. blocked_lists: + 这个是禁止交易的交易对,如果你想过滤某写不想交易的山寨币,你可以把他们放在这个列表上如: + ['XMLUSDT', 'XRPUSDT'], + +14. allowed_lists: 如果你只想交易某一些交易对,那么放这里: + ['BTCUSDT', 'ETHUSDT', 'ADAUSDT', 'BNBUSDT'] + +15. proxy_host: 代理主机ip地址: 如'132.148.123.22' + +16. proxy_port: 代理主机的端口号如: 8888, 9999 ect. + ## 如何使用 -1. 把代码下载下来,然后配置你的交易所的api key 和 secret, - 然后修改你相应的配置选项,选项值的配置值如上面描述 +1. 把代码下载下来,然后编辑config.json文件,它会读取你这个配置文件,记得填写你的交易所的api + key 和 secret, 然后保存该配置文件,配置文件选项的说明如上面描述。 2. 直接运行main.py文件或者通过shell脚本运行, 执行 sh start.sh 就可以运行。 diff --git a/README.md b/README.md index 2fc9aa6..a808d4f 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,26 @@ A muti pairs martingle trading bot for Binance exchange. "api_key": "xxxx", "api_secret": "xxxxx", "max_pairs": 4, - "pump_pct": 0.03, - "initial_trade_value": 500, - "trade_value_multiplier": 1.3, + "pump_pct": 0.026, + "pump_pct_4h": 0.045, + "initial_trade_value": 200, + "trade_value_multiplier": 1.5, "increase_pos_when_drop_down": 0.05, "exit_profit_pct": 0.01, "profit_pull_back_pct": 0.01, "trading_fee": 0.0004, "max_increase_pos_count": 5, + "turnover_threshold": 100000, + "blocked_lists": [ + "BTCUSDT", + "ADAUSDT" + ], + "allowed_lists": [], "proxy_host": "", "proxy_port": 0 } + ``` 1. platform: binance_future for Binance Future Exchange, binance_spot @@ -42,7 +50,18 @@ A muti pairs martingle trading bot for Binance exchange. 11. max_increase_pos_count: how many times you want to increase your positions + +12. turnover_threshold: the pair's trading value should be over this + value, the default value is 100,000 USDT. +13. blocked_lists: if you don't want to trade the symbols/pairs, put it + here likes ['XMLUSDT', 'XRPUSDT'], +14. allowed_lists: if you only want to trade some specific pairs, put it + here, like : ['BTCUSDT', 'ETHUSDT', 'ADAUSDT', 'BNBUSDT'] + +15. proxy_host: proxy host ip location like '132.148.123.22' + +16. proxy_port: proxy port like : 8888, 9999 ect. ## how-to use 1. just config your config.json file, past your api key and secret from diff --git a/config-example.json b/config-example.json index e2318fc..e4f99bc 100644 --- a/config-example.json +++ b/config-example.json @@ -12,6 +12,12 @@ "profit_pull_back_pct": 0.01, "trading_fee": 0.0004, "max_increase_pos_count": 5, + "turnover_threshold": 100000, + "blocked_lists": [ + "BTCUSDT", + "ADAUSDT" + ], + "allowed_lists": [], "proxy_host": "", "proxy_port": 0 } diff --git a/config.json b/config.json index e2318fc..0de40f6 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,5 @@ { - "platform": "binance_future", + "platform": "binance_spot", "api_key": "xxxx", "api_secret": "xxxxx", "max_pairs": 4, @@ -12,6 +12,9 @@ "profit_pull_back_pct": 0.01, "trading_fee": 0.0004, "max_increase_pos_count": 5, + "turnover_threshold": 100000, + "blocked_lists": [], + "allowed_lists": [], "proxy_host": "", "proxy_port": 0 } diff --git a/main.py b/main.py index 8a3161e..bf7353d 100644 --- a/main.py +++ b/main.py @@ -47,9 +47,9 @@ def get_data(trader: Union[BinanceFutureTrader, BinanceSpotTrader]): klines = trader.get_klines(symbol=symbol, interval=Interval.HOUR_1, limit=100) if len(klines) > 0: df = pd.DataFrame(klines, dtype=np.float64, - columns=['open_time', 'open', 'high', 'low', 'close', 'volume', 'close_time', 'a1', 'a2', + columns=['open_time', 'open', 'high', 'low', 'close', 'volume', 'close_time', 'turnover', 'a2', 'a3', 'a4', 'a5']) - df = df[['open_time', 'open', 'high', 'low', 'close', 'volume']] + df = df[['open_time', 'open', 'high', 'low', 'close', 'volume', 'turnover']] df.set_index('open_time', inplace=True) df.index = pd.to_datetime(df.index, unit='ms') + pd.Timedelta(hours=8) @@ -57,7 +57,8 @@ def get_data(trader: Union[BinanceFutureTrader, BinanceSpotTrader]): 'high': 'max', 'low': 'min', 'close': 'last', - 'volume': 'sum' + 'volume': 'sum', + 'turnover': 'sum' }) # print(df) @@ -66,7 +67,7 @@ def get_data(trader: Union[BinanceFutureTrader, BinanceSpotTrader]): pct = df['close'] / df['open'] - 1 pct_4h = df_4hour['close']/df_4hour['open'] - 1 - value = {'pct': pct[-1], 'pct_4h':pct_4h[-1] , 'symbol': symbol} + value = {'pct': pct[-1], 'pct_4h':pct_4h[-1] , 'symbol': symbol, 'hour_turnover': df['turnover'][-1]} # calculate your signal here. @@ -89,12 +90,17 @@ def get_data(trader: Union[BinanceFutureTrader, BinanceSpotTrader]): if __name__ == '__main__': config.loads('./config.json') + print(config.blocked_lists) if config.platform == 'binance_spot': trader = BinanceSpotTrader() else: trader = BinanceFutureTrader() + + + exit() + trader.get_exchange_info() get_data(trader) # for testing diff --git a/trader/binance_future_trader.py b/trader/binance_future_trader.py index 14b03dc..7bc31a0 100644 --- a/trader/binance_future_trader.py +++ b/trader/binance_future_trader.py @@ -27,20 +27,22 @@ class BinanceFutureTrader(object): def __init__(self): """ - the binance future trader, 币安合约交易的网格交易, - the grid trading in Future will endure a lot of risk, use it before you understand the risk and grid strategy. - 网格交易在合约上会有很大的风险,请注意风险 + 免责声明: + the binance future trader, 币安合约马丁格尔策略. + the Martingle strategy in Future will endure a lot of risk, use it before you understand the risk and martingle strategy, and the code may have bugs, + Use it at your own risk. We won't ensure you will earn money from this code. + 马丁策略在合约上会有很大的风险,请注意风险, 使用前请熟知该代码,可能会有bugs或者其他未知的风险。 """ self.http_client = BinanceFutureHttp(api_key=config.api_key, secret=config.api_secret, proxy_host=config.proxy_host, proxy_port=config.proxy_port) - self.symbols_dict = {} # 全市场的交易对. + self.symbols_dict = {} # 全市场的交易对. all symbols dicts {'BTCUSDT': value} self.tickers_dict = {} # 全市场的tickers数据. self.buy_orders_dict = {} # 买单字典 buy orders {'symbol': [], 'symbol1': []} self.sell_orders_dict = {} # 卖单字典. sell orders {'symbol': [], 'symbol1': []} - self.positions = Positions() + self.positions = Positions('future_positions.json') self.initial_id = 0 def get_exchange_info(self): @@ -184,10 +186,10 @@ def start(self): elif check_order.get('status') == OrderStatus.NEW.value: - print(f"sell order status is: New, 时间: {datetime.now()}") + print(f"sell order status is: New, time: {datetime.now()}") else: print( - f"sell order status is not in above options: {check_order.get('status')}, 时间: {datetime.now()}") + f"sell order status is not in above options: {check_order.get('status')}, time: {datetime.now()}") # the expired\canceled\delete orders for delete_order in delete_sell_orders: @@ -218,12 +220,12 @@ def start(self): if bid_price > 0 and ask_price > 0: value = pos * bid_price if value < self.symbols_dict.get(s, {}).get('min_notional', 0): - print(f"{s} 的仓位价值小于最小的仓位价值, 所以删除了该交易对的仓位.") - del self.positions.positions[s] # 删除仓位价值比较小的交易对. + print(f"{s} notional value is small, delete the position data.") + del self.positions.positions[s] # delete the position data if the position notional is very small. else: avg_price = pos_data.get('avg_price') self.positions.update_profit_max_price(s, bid_price) - # 计算利润. + # calculate the profit here. profit_pct = bid_price / avg_price - 1 pull_back_pct = self.positions.positions.get(s, {}).get('profit_max_price', 0) / bid_price - 1 @@ -231,7 +233,7 @@ def start(self): current_increase_pos_count = self.positions.positions.get(s, {}).get('current_increase_pos_count', 1) - # 判断是否是有利润,然后考虑出场. + # there is profit here, consider whether exit this position. if profit_pct >= config.exit_profit_pct and pull_back_pct >= config.profit_pull_back_pct and len( self.sell_orders_dict.get(s, [])) <= 0: """ @@ -244,7 +246,7 @@ def start(self): print( "cancel the buy orders. when we want to place sell orders, we need to cancel the buy orders.") self.http_client.cancel_order(s, buy_order.get('clientOrderId')) - # 处理价格和精度. + # price tick and quantity precision qty = round_to(abs(pos), min_qty) sell_order = self.http_client.place_order(symbol=s, order_side=OrderSide.SELL, @@ -286,8 +288,8 @@ def start(self): else: print(f"{s}: bid_price: {bid_price}, ask_price: {bid_price}") - pos_symbols = self.positions.positions.keys() # 有仓位的交易对信息. - pos_count = len(pos_symbols) # 仓位的个数. + pos_symbols = self.positions.positions.keys() # the position's symbols, if there is {"symbol": postiondata}, you get the symbols here. + pos_count = len(pos_symbols) # position count left_times = config.max_pairs - pos_count @@ -300,32 +302,42 @@ def start(self): index = 0 for signal in signal_data.get('signals', []): - if signal['signal'] == 1 and index < left_times and signal['symbol'] not in pos_symbols: + s = signal['symbol'] + + if signal['signal'] == 1 and index < left_times and signal['symbol'] not in pos_symbols and signal[ + 'hour_turnover'] >= config.turnover_threshold: - index += 1 - s = signal['symbol'] # the last one hour's the symbol jump over some percent. + if len(config.allowed_lists) > 0 and s in config.allowed_lists: - buy_value = config.initial_trade_value - min_qty = self.symbols_dict.get(s, {}).get('min_qty') - bid_price = self.tickers_dict.get(s, {}).get('bid_price', 0) # bid price - if bid_price <= 0: - print(f"error -> future {s} bid_price is :{bid_price}") - return + index += 1 + # the last one hour's the symbol jump over some percent. + self.place_order(s, signal['pct'], signal['pct_4h']) - qty = round_to(buy_value / bid_price, min_qty) + elif s not in config.blocked_lists: + index += 1 + self.place_order(s, signal['pct'], signal['pct_4h']) - buy_order = self.http_client.place_order(symbol=s, order_side=OrderSide.BUY, - order_type=OrderType.LIMIT, quantity=qty, - price=bid_price) - print( - f"{s} hour change: {signal['pct']}, 4hour change: {signal['pct_4h']}, place buy order: {buy_order}") - if buy_order: - # resolve buy orders - orders = self.buy_orders_dict.get(s, []) - orders.append(buy_order) - self.buy_orders_dict[s] = orders + self.positions.save_data() + def place_order(self, symbol: str, hour_change: float, four_hour_change: float): - else: - pass + buy_value = config.initial_trade_value + min_qty = self.symbols_dict.get(symbol, {}).get('min_qty') + bid_price = self.tickers_dict.get(symbol, {}).get('bid_price', 0) # bid price + if bid_price <= 0: + print(f"error -> future {symbol} bid_price is :{bid_price}") + return + + qty = round_to(buy_value / bid_price, min_qty) + + buy_order = self.http_client.place_order(symbol=symbol, order_side=OrderSide.BUY, + order_type=OrderType.LIMIT, quantity=qty, + price=bid_price) + print( + f"{symbol} hour change: {hour_change}, 4hour change: {four_hour_change}, place buy order: {buy_order}") + if buy_order: + # resolve buy orders + orders = self.buy_orders_dict.get(symbol, []) + orders.append(buy_order) + self.buy_orders_dict[symbol] = orders diff --git a/trader/binance_spot_trader.py b/trader/binance_spot_trader.py index f06fe4a..a304a8e 100644 --- a/trader/binance_spot_trader.py +++ b/trader/binance_spot_trader.py @@ -25,6 +25,14 @@ class BinanceSpotTrader(object): + """ + 免责声明: + the binance spot trader, 币安现货马丁格尔策略. + the Martingle strategy in Crypto Market will endure a lot of risk, use it before you understand the risk and martingle strategy, and the code may have bugs, + Use it at your own risk. We won't ensure you will earn money from this code. + 马丁策略在合约上会有很大的风险,请注意风险, 使用前请熟知该代码,可能会有bugs或者其他未知的风险。 + + """ def __init__(self): """ @@ -40,7 +48,7 @@ def __init__(self): self.buy_orders_dict = {} # 买单字典 buy orders {'symbol': [], 'symbol1': []} self.sell_orders_dict = {} # 卖单字典. sell orders {'symbol': [], 'symbol1': []} - self.positions = Positions() + self.positions = Positions('spot_positions.json') self.initial_id = 0 def get_exchange_info(self): @@ -187,10 +195,10 @@ def start(self): elif check_order.get('status') == OrderStatus.NEW.value: - print(f"sell order status is: New, 时间: {datetime.now()}") + print(f"sell order status is: New, time: {datetime.now()}") else: print( - f"sell order status is not in above options: {check_order.get('status')}, 时间: {datetime.now()}") + f"sell order status is not in above options: {check_order.get('status')}, time: {datetime.now()}") # the expired\canceled\delete orders for delete_order in delete_sell_orders: @@ -221,8 +229,8 @@ def start(self): if bid_price > 0 and ask_price > 0: value = pos * bid_price if value < self.symbols_dict.get(s, {}).get('min_notional', 0): - print(f"{s} 的仓位价值小于最小的仓位价值, 所以删除了该交易对的仓位.") - del self.positions.positions[s] # 删除仓位价值比较小的交易对. + print(f"{s} notional value is small, delete the position data.") + del self.positions.positions[s] # delete the position data if the position notional is very small. else: avg_price = pos_data.get('avg_price') self.positions.update_profit_max_price(s, bid_price) @@ -234,7 +242,7 @@ def start(self): current_increase_pos_count = self.positions.positions.get(s, {}).get('current_increase_pos_count', 1) - # 判断是否是有利润,然后考虑出场. + # there is profit here, consider whether exit this position. if profit_pct >= config.exit_profit_pct and pull_back_pct >= config.profit_pull_back_pct and len( self.sell_orders_dict.get(s, [])) <= 0: """ @@ -247,7 +255,7 @@ def start(self): print( "cancel the buy orders. when we want to place sell orders, we need to cancel the buy orders.") self.http_client.cancel_order(s, buy_order.get('clientOrderId')) - # 处理价格和精度. + # the price tick and quantity precision. qty = round_to(abs(pos), min_qty) sell_order = self.http_client.place_order(symbol=s, order_side=OrderSide.SELL, @@ -303,35 +311,40 @@ def start(self): index = 0 for signal in signal_data.get('signals', []): - if signal['signal'] == 1 and index < left_times and signal['symbol'] not in pos_symbols: - - index += 1 + s = signal['symbol'] + if signal['signal'] == 1 and index < left_times and s not in pos_symbols and signal[ + 'hour_turnover'] >= config.turnover_threshold: + if len(config.allowed_lists) > 0 and s in config.allowed_lists: - s = signal['symbol'] + index += 1 + # the last one hour's the symbol jump over some percent. + self.place_order(s, signal['pct'], signal['pct_4h']) - # the last one hour's the symbol jump over some percent. + elif s not in config.blocked_lists: + index += 1 + self.place_order(s, signal['pct'], signal['pct_4h']) - buy_value = config.initial_trade_value - min_qty = self.symbols_dict.get(s, {}).get('min_qty') - bid_price = self.tickers_dict.get(s, {}).get('bid_price', 0) # bid price - if bid_price <= 0: - print(f"error -> spot {s} bid_price is :{bid_price}") - return + self.positions.save_data() - qty = round_to(buy_value / bid_price, min_qty) + def place_order(self, symbol: str, hour_change: float, four_hour_change: float): - buy_order = self.http_client.place_order(symbol=s, order_side=OrderSide.BUY, - order_type=OrderType.LIMIT, quantity=qty, - price=bid_price) + buy_value = config.initial_trade_value + min_qty = self.symbols_dict.get(symbol, {}).get('min_qty') + bid_price = self.tickers_dict.get(symbol, {}).get('bid_price', 0) # bid price + if bid_price <= 0: + print(f"error -> spot {symbol} bid_price is :{bid_price}") + return - print( - f"{s} hour change: {signal['pct']}, 4hour change: {signal['pct_4h']}, place buy order: {buy_order}") - if buy_order: - # resolve buy orders - orders = self.buy_orders_dict.get(s, []) - orders.append(buy_order) - self.buy_orders_dict[s] = orders + qty = round_to(buy_value / bid_price, min_qty) + buy_order = self.http_client.place_order(symbol=symbol, order_side=OrderSide.BUY, + order_type=OrderType.LIMIT, quantity=qty, + price=bid_price) - else: - pass + print( + f"{symbol} hour change: {hour_change}, 4hour change: {four_hour_change}, place buy order: {buy_order}") + if buy_order: + # resolve buy orders + orders = self.buy_orders_dict.get(symbol, []) + orders.append(buy_order) + self.buy_orders_dict[symbol] = orders diff --git a/trader/future_positions.json b/trader/future_positions.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/trader/future_positions.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/trader/spot_positions.json b/trader/spot_positions.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/trader/spot_positions.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/utils/config.py b/utils/config.py index 7da009b..25087ab 100644 --- a/utils/config.py +++ b/utils/config.py @@ -29,18 +29,20 @@ def __init__(self): self.api_key: str = None self.api_secret: str = None self.max_pairs = 4 - self.pump_pct = 0.026 # 需要涨多少以上才能进入交易池里面. - self.pump_pct_4h = 0.045 # 4小时的上涨. + self.pump_pct = 0.026 # the price need to go up over 2.6% in 1 hour, then you may consider to enter a position + self.pump_pct_4h = 0.045 # the price need to go up over 2.6% in 4 hour, then you may consider to enter a position self.initial_trade_value = 500 self.trade_value_multiplier = 1.3 self.increase_pos_when_drop_down = 0.05 - self.exit_profit_pct = 0.01 # 出场的利润. - self.profit_pull_back_pct = 0.01 # 回调百分比. + self.exit_profit_pct = 0.01 # profit percent + self.profit_pull_back_pct = 0.01 # pull back percent. self.trading_fee = 0.0004 # self.max_increase_pos_count = 5 self.proxy_host = "" # proxy host self.proxy_port = 0 # proxy port - + self.blocked_lists = [] # symbols ['BTCUSDT', 'ETHUSDT', ... ], the symbols in here will not trade. + self.allowed_lists = [] # symbols ['BTCUSDT', 'ETHUSDT', ... ], if the list contains value(not empty), it will only trade the symbol in this lists + self.turnover_threshold = 100,000 # 100k usdt, the trading value should be higher than 100k usdt in an hour. def loads(self, config_file=None): """ Load config file. diff --git a/utils/positions.py b/utils/positions.py index 7cdebf8..93fafb5 100644 --- a/utils/positions.py +++ b/utils/positions.py @@ -18,12 +18,30 @@ 服务器购买地址: https://www.ucloud.cn/site/global.html?invitation_code=C1x2EA81CD79B8C#dongjing """ from utils.config import config +from utils.utility import get_file_path, load_json, save_json + class Positions: - def __init__(self): + def __init__(self, file_name): + self.file_name = file_name self.positions = {} self.total_profit = 0 + self.read_data() # read the saved data + + def read_data(self): + filepath = get_file_path(self.file_name) + data = load_json(filepath) + if not bool(data): + data['total_profit'] = self.total_profit + data['positions'] = self.positions + else: + self.total_profit = float(data.get('total_profit', 0)) + self.positions = data.get('positions', {}) + + def save_data(self): + filename = get_file_path(self.file_name) + save_json(filename, {'total_profit': self.total_profit, 'positions': self.positions}) def update(self, symbol: str, trade_amount: float, trade_price: float, min_qty: float, is_buy: bool = False): """