Procházet zdrojové kódy

修复小数位过多时价格错误问题

skyfffire před 2 měsíci
rodič
revize
1773bcabb0
4 změnil soubory, kde provedl 200 přidání a 15 odebrání
  1. 176 0
      decimal_utils.py
  2. 9 8
      mexc_client.py
  3. 11 7
      s_erc20_to_mexc.py
  4. 4 0
      toto.readme

+ 176 - 0
decimal_utils.py

@@ -0,0 +1,176 @@
+from decimal import Decimal, getcontext
+
+# 增加精度以处理更多小数位,如果需要的话
+getcontext().prec = 50
+
+def decimal_to_string_no_scientific(d_value: Decimal) -> str:
+    """
+    将 Decimal 类型转换为无科学计数法的字符串。
+    尝试使用 normalize() 确保消除科学计数法,并保持所有有效数字。
+    """
+    if not isinstance(d_value, Decimal):
+        raise TypeError("Input must be a Decimal type.")
+
+    # normalize() 方法可以移除尾随的0并调整指数,但不会强制转换为非科学计数法
+    # 转换为字符串时,Python的Decimal默认会尽可能不使用科学计数法,
+    # 除非数字非常大或非常小。
+    # 对于极小的数,直接str()可能会出现科学计数法。
+
+    # 更稳妥的方法是,将其格式化为字符串,并确保它没有科学计数法。
+    # 我们可以通过精确控制小数点后的位数来避免科学计数法。
+    # 但由于我们不知道具体需要多少位,所以直接用str()通常是最直接的。
+
+    s_value = str(d_value)
+
+    # 检查是否包含科学计数法标识 'e' 或 'E'
+    if 'e' in s_value.lower():
+        # 如果包含,我们需要更精细地处理
+        # 先将其标准化,然后转换为字符串
+        normalized_d = d_value.normalize()
+        s_value = str(normalized_d)
+
+        # 即使normalize(),对于非常大的或非常小的数,str()仍然可能使用科学计数法。
+        # 强制转换为非科学计数法,可以通过指定一个非常大的小数点后位数来实现。
+        # 这里,我们查找小数点后的最大位数来避免信息丢失。
+        if 'e' in s_value.lower(): # 再次检查,以防normalize()后仍有
+            # 通常,当Decimal转换成字符串时显示科学计数法,说明它要么很大,要么很小。
+            # 对于很小的数,我们通过扩展精度来确保所有零被显示。
+            # 例如:0.00000001
+            # 可以通过创建负指数的Decimal来表示小数点后的位数
+            # 例如 Decimal(10)**(-N)
+            # 但一个更通用的方法是,将其转换为字符串后,如果发现'e',则手动解析和格式化。
+
+            # Decimal模块通常在str()时会避免科学计数法,除非绝对必要。
+            # 如果出现,尝试使用to_eng_string(),它可能看起来更像非科学计数法。
+            # 但最直接的是手动控制精度
+            s_value = _format_decimal_to_plain_string(d_value)
+
+    return s_value
+
+def _format_decimal_to_plain_string(d_value: Decimal) -> str:
+    """
+    辅助函数:将Decimal强制转换为无科学计数法的字符串。
+    通过动态确定小数点后的位数。
+    """
+    s = str(d_value)
+    if 'E' in s or 'e' in s:
+        # 提取指数部分
+        if 'E' in s:
+            parts = s.split('E')
+        else:
+            parts = s.split('e')
+        
+        coefficient = parts[0]
+        exponent = int(parts[1])
+
+        # 处理负号
+        sign = ""
+        if coefficient.startswith('-'):
+            sign = "-"
+            coefficient = coefficient[1:]
+
+        # 处理小数点
+        if '.' in coefficient:
+            int_part, frac_part = coefficient.split('.')
+        else:
+            int_part = coefficient
+            frac_part = ""
+
+        # 根据指数移动小数点
+        if exponent >= 0:
+            # 扩大整数部分,补零
+            num_zeros = exponent - len(frac_part)
+            if num_zeros >= 0:
+                result = int_part + frac_part + '0' * num_zeros
+            else:
+                result = int_part + frac_part[:exponent] + '.' + frac_part[exponent:]
+        else: # exponent < 0
+            # 缩小整数部分,在前面补零
+            abs_exponent = abs(exponent)
+            if len(int_part) <= abs_exponent:
+                # 例如 1.23E-4 -> 0.000123
+                result = '0.' + '0' * (abs_exponent - len(int_part)) + int_part + frac_part
+            else:
+                # 例如 123.45E-1 -> 12.345
+                insert_pos = len(int_part) - abs_exponent
+                result = int_part[:insert_pos] + '.' + int_part[insert_pos:] + frac_part
+        
+        # 移除结果中可能的尾随小数点(例如 '123.' -> '123')
+        if result.endswith('.'):
+            result = result[:-1]
+        
+        # 移除结果中多余的前导零(例如 '007' -> '7',但保留 '0.xx')
+        if result != '0' and result.startswith('0') and not result.startswith('0.'):
+            result = result.lstrip('0')
+            if not result: # 如果只剩下0,则保留一个0
+                result = '0'
+        
+        return sign + result
+    else:
+        return s
+
+# ---- 测试 ----
+if __name__ == "__main__":
+    # 示例 1: 原始示例中成功的 case
+    params1 = {
+        'price': Decimal('0.0004098'),
+        'quantity': 160000,
+        'side': 'SELL',
+        'symbol': 'HASHAIUSDT',
+        'type': 'LIMIT'
+    }
+    price_str1 = decimal_to_string_no_scientific(params1['price'])
+    print(f"Original Decimal: {params1['price']}, Converted String: '{price_str1}'")
+    # 预期输出: '0.0004098'
+
+    # 示例 2: 原始示例中失败的 case
+    params2 = {
+        'price': Decimal('1.617015400E-8'),
+        'quantity': 320000000,
+        'side': 'BUY',
+        'symbol': 'MANYUSDT',
+        'type': 'LIMIT'
+    }
+    price_str2 = decimal_to_string_no_scientific(params2['price'])
+    print(f"Original Decimal: {params2['price']}, Converted String: '{price_str2}'")
+    # 预期输出: '0.00000001617015400'
+
+    # 更多测试用例
+    # 较大整数
+    d3 = Decimal('12345678901234567890.1')
+    s3 = decimal_to_string_no_scientific(d3)
+    print(f"Original Decimal: {d3}, Converted String: '{s3}'") 
+    # 预期输出: '12345678901234567890.1'
+
+    # 较大整数带科学计数法表示
+    d4 = Decimal('1.23E+10')
+    s4 = decimal_to_string_no_scientific(d4)
+    print(f"Original Decimal: {d4}, Converted String: '{s4}'")
+    # 预期输出: '12300000000'
+
+    # 较小浮点数,且 str() 本身可能显示科学计数法
+    d5 = Decimal('0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') # 这是一个非常非常小的数
+    s5 = decimal_to_string_no_scientific(d5)
+    print(f"Original Decimal: {d5}, Converted String: '{s5}'")
+    # 预期输出: 应该是一长串0.000...000后跟着一个1
+
+    # 负数
+    d6 = Decimal('-1.2345E-5')
+    s6 = decimal_to_string_no_scientific(d6)
+    print(f"Original Decimal: {d6}, Converted String: '{s6}'")
+    # 预期输出: '-0.000012345'
+
+    d7 = Decimal('0')
+    s7 = decimal_to_string_no_scientific(d7)
+    print(f"Original Decimal: {d7}, Converted String: '{s7}'")
+    # 预期输出: '0'
+
+    d8 = Decimal('-0.00') # 保持尾随的0
+    s8 = decimal_to_string_no_scientific(d8)
+    print(f"Original Decimal: {d8}, Converted String: '{s8}'")
+    # 预期输出: '-0.00'
+
+    d9 = Decimal('12300.00')
+    s9 = decimal_to_string_no_scientific(d9)
+    print(f"Original Decimal: {d9}, Converted String: '{s9}'")
+    # 预期输出: '12300.00'

+ 9 - 8
mexc_client.py

@@ -505,15 +505,15 @@ if __name__ == '__main__':
             #    print(f"  批量下单请求期间出错: {e_batch}")
 
 
-            # #测试获取挂单示例
+            #测试获取挂单示例
             # print("\n获取 MEALUSDT 当前挂单 (如有)...")
-            # open_orders = client.trade.get_openorders(params={"symbol": "MEALUSDT"})
+            # open_orders = client.trade.get_openorders(params={"symbol": "MANYUUSDT"})
             # if isinstance(open_orders, list):
-            #     print(f"  找到 {len(open_orders)} 个 MEALUSDT 的挂单。")
+            #     print(f"  找到 {len(open_orders)} 个 MANYUUSDT 的挂单。")
             #     for order in open_orders:
             #         print(f"    - 订单 ID: {order.get('orderId')}, 价格: {order.get('price')}")
 
-            #         # print(client.trade.delete_order(params={"symbol": "MEALUSDT", "orderId": order.get('orderId')}))
+            #         print(client.trade.delete_order(params={"symbol": "MANYUUSDT", "orderId": order.get('orderId')}))
             # else:
             #     print(f"  挂单响应: {open_orders}")
 
@@ -573,11 +573,12 @@ if __name__ == '__main__':
 
             # print("\n测试下单 (POST /order/test)...")
             # test_order_params = {
-            #     "symbol": "NEIROETHUSDT",  # 使用常见的交易对,如 MXUSDT 或 BTCUSDT
-            #     "side": "SELL",  "quantity": "110",   # 以【幣】的數量進行買賣
-            #     # "side": "BUY", "quoteOrderQty": "15" # 以【U】的數量進行買賣!
+            #     "symbol": "MANYUUSDT",  # 使用常见的交易对,如 MXUSDT 或 BTCUSDT
+            #     "side": "BUY",  "quantity": "3200000000",   # 以【幣】的數量進行買賣
+            #     # "side": "BUY", "quoteOrderQty": "60" # 以【U】的數量進行買賣!
             #     "type": "MARKET",
-            #     # "price": "0.0000290"     # 调整价格以便于测试
+            #     # "type": "LIMIT",
+            #     # "price": '0.00000001617015400'     # 调整价格以便于测试
             # }
             # print(f"  测试订单参数: {test_order_params}")
             # test_order_response = client.trade.post_order(params=test_order_params)

+ 11 - 7
s_erc20_to_mexc.py

@@ -8,6 +8,7 @@ import json
 from mexc_client import MexcClient
 from decimal import Decimal, ROUND_DOWN
 from as_utils import add_state_flow_entry
+from decimal_utils import decimal_to_string_no_scientific
 from checker.logger_config import get_logger
 from pprint import pformat
 from pprint import pprint
@@ -280,6 +281,7 @@ class ArbitrageProcess:
             # 处理精度
             pseudo_amount_to_sell = pseudo_amount_to_sell.quantize(self.coin_asset_precision, rounding=ROUND_DOWN)
             price_for_api = self.dex_price.quantize(self.price_precision, rounding=ROUND_DOWN)
+            price_for_api = decimal_to_string_no_scientific(price_for_api)
             # 初始化 quantity 变量
             quantity_for_api = None
             # 用求余法判断是否是整数
@@ -410,10 +412,12 @@ class ArbitrageProcess:
                     dex_price = Decimal(table_data['dex_price'])
                     dex_price = dex_price.quantize(self.price_precision, rounding=ROUND_DOWN)
 
-                    ready_order_price = dex_price * (Decimal(1) - self.close_limit)
+                    price_for_api = dex_price * (Decimal(1) - self.close_limit)
+                    price_for_api = price_for_api.quantize(self.price_precision, rounding=ROUND_DOWN)
+                    price_for_api = decimal_to_string_no_scientific(price_for_api)
 
                     # 准备购入的价值, 如果小于2u就不要提交了
-                    pseudo_value_to_buy = ready_order_price * (self.already_sold_amount - self.already_bought_amount)
+                    pseudo_value_to_buy = price_for_api * (self.already_sold_amount - self.already_bought_amount)
 
                     if pseudo_value_to_buy < 2:
                         break
@@ -435,13 +439,13 @@ class ArbitrageProcess:
                                         self._set_state(self.STATE_FAILED)
                                         return
                                     else:
-                                        msg = f"交易所剩余{self.base_coin}: {free_balance}, 准备使用 {pseudo_value_to_buy}, fp {dex_price}, 挂单价格{ready_order_price},  余额校验通过。"
+                                        msg = f"交易所剩余{self.base_coin}: {free_balance}, 准备使用 {pseudo_value_to_buy}, fp {dex_price}, 挂单价格{price_for_api},  余额校验通过。"
                                         logger.info(msg)
                                         # add_state_flow_entry(self.process_item, self.current_state, msg, "success")
                                         break
 
                         # 实际能购入的数量(可能会亏损导致买不回来, 所以要考虑实际有多少money)
-                        quantity_for_api = pseudo_value_to_buy / ready_order_price
+                        quantity_for_api = pseudo_value_to_buy / price_for_api
                         quantity_for_api = quantity_for_api.quantize(self.coin_asset_precision, rounding=ROUND_DOWN)
                         # 用求余法判断是否是整数
                         if quantity_for_api % 1 == 0:
@@ -455,7 +459,7 @@ class ArbitrageProcess:
                             "symbol": self.symbol.replace('_', ''),
                             "side": "BUY",
                             "type": "LIMIT",
-                            "price": ready_order_price,
+                            "price": price_for_api,
                             "quantity": quantity_for_api,
                         }
                         order_params_formated = pformat(order_params, indent=2)
@@ -471,7 +475,7 @@ class ArbitrageProcess:
                             order_error_times = order_error_times + 1
                         else:
                             self.exchange_buy_order_id = exchange_buy_order['orderId']
-                            order_price = ready_order_price
+                            order_price = price_for_api
                     # 有订单时的逻辑
                     else:
                         # 获取订单状态, 直到完全成交或超时
@@ -499,7 +503,7 @@ class ArbitrageProcess:
                             exchange_buy_order = None
                         
                         # 如果没有成交或取消则判断是否达到取消条件了, 这里面不能置空
-                        elif (Decimal(1) - order_price / ready_order_price) > Decimal(0.0005):
+                        elif (Decimal(1) - order_price / price_for_api) > Decimal(0.0005):
                             params = {
                                 "symbol": self.symbol.replace('_', ''),
                                 "orderId": self.exchange_buy_order_id

+ 4 - 0
toto.readme

@@ -28,8 +28,12 @@
 [-] 平仓逻辑
 [-] 价差变动了0.0005了才重新挂单
 
+2025-08-29
+[-] 修复小数位过多时价格错误问题
+
 待定
 [ ] 网格改造(每一格之间的开单间距)
 [ ] 其它链的尝试 实在不行就直接eth,反正有一堆eth链的
+[ ] mexc_lead_erc20尝试
 [ ] 选币系统(与交易量或相对交易量有关系吗)
 [ ] 日志细化