Compare commits

..

3 Commits

6 changed files with 132 additions and 78 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
output/ output/
public/ public/
config/
__pycache__/ __pycache__/

View File

@@ -16,7 +16,7 @@ MEXC 交易机器人是一个自动化交易工具,用于在 MEXC 交易所执
## 安装与使用 ## 安装与使用
1. 创建配置文件`trading_config.json` 1. 创建配置文件`config/trading_config.json`(可有多个不同名字的`.json`文件,程序会自动遍历所有)
2. 运行程序: 2. 运行程序:
```bash ```bash
@@ -31,6 +31,11 @@ MEXC 交易机器人是一个自动化交易工具,用于在 MEXC 交易所执
```json ```json
{ {
"api": {
"mexc_host": "https://api.mexc.com",
"api_key": "mxxxxxxxxxx",
"secret_key": "xxxxxxxxxxxxxxxxx"
},
"symbol_mapping": { "symbol_mapping": {
"BTCUSDC": "BTCUSDT" "BTCUSDC": "BTCUSDT"
}, },
@@ -51,12 +56,14 @@ MEXC 交易机器人是一个自动化交易工具,用于在 MEXC 交易所执
### 配置字段详解 ### 配置字段详解
#### 1. 证券代码映射 (`symbol_mapping`) #### 1. API 配置 (`api`)
#### 2. 证券代码映射 (`symbol_mapping`)
- 类型:数组 - 类型:数组
- 描述API 代码: CSV 记录代码,如`"BTCUSDC": "BTCUSDT"`代表向 API 请求`BTCUSDC`,但 CSV 中记录证券代码`BTCUSDT` - 描述API 代码: CSV 记录代码,如`"BTCUSDC": "BTCUSDT"`代表向 API 请求`BTCUSDC`,但 CSV 中记录证券代码`BTCUSDT`
#### 2. 交易列表 (`trades`) #### 3. 交易列表 (`trades`)
- 类型:数组 - 类型:数组
- 描述:包含所有交易指令的列表 - 描述:包含所有交易指令的列表

View File

@@ -1,3 +0,0 @@
mexc_host = "https://api.mexc.com"
api_key = "mx0vglky5BuzlcK5HQ"
secret_key = "e2a0c4737b4643bbac4ad5f8b26dcce2"

View File

@@ -13,7 +13,7 @@ logging.basicConfig(
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def load_config(config_path="trading_config.json"): def load_config(config_path="config/trading_config.json"):
"""加载交易配置文件。 """加载交易配置文件。
Args: Args:
@@ -100,7 +100,9 @@ def generate_ics(config):
date_obj = datetime.strptime(date_str, "%Y-%m-%d").date() date_obj = datetime.strptime(date_str, "%Y-%m-%d").date()
event = create_event(symbol, comment, date_obj, description) event = create_event(symbol, comment, date_obj, description)
cal.add_component(event) cal.add_component(event)
logger.info("已创建项目:%s 交易 (%s) %s", symbol, comment, date_str) logger.info(
"已创建项目:%s 交易 (%s) %s", symbol, comment, date_str
)
except ValueError as ex: except ValueError as ex:
logger.warning("跳过无效日期 %s: %s", date_str, ex) logger.warning("跳过无效日期 %s: %s", date_str, ex)
@@ -121,13 +123,33 @@ def save_ics(calendar, output_path="public/trading.ics"):
def main(): def main():
"""主执行函数。""" """主执行函数遍历config目录下的所有json文件并生成对应的ics文件"""
try: try:
config = load_config() # 遍历config目录下的所有json文件
for filename in os.listdir("config"):
if filename.endswith(".json"):
config_path = os.path.join("config", filename)
try:
# 加载配置
config = load_config(config_path)
# 生成ics文件名保留原文件名只改扩展名
ics_filename = os.path.splitext(filename)[0] + ".ics"
ics_path = os.path.join("public", ics_filename)
# 生成并保存ics文件
calendar = generate_ics(config) calendar = generate_ics(config)
save_ics(calendar) save_ics(calendar, ics_path)
except Exception as ex: # pylint: disable=broad-except
logger.error("生成失败: %s", ex, exc_info=True) logger.info("成功生成: %s%s", config_path, ics_path)
except Exception as ex:
logger.error("处理文件 %s 失败: %s", config_path, ex, exc_info=True)
continue # 继续处理下一个文件
logger.info("所有文件处理完成")
except Exception as ex:
logger.error("程序执行失败: %s", ex, exc_info=True)
raise raise

82
main.py
View File

@@ -27,6 +27,7 @@ import logging
import os import os
import time import time
import json import json
from pathlib import Path
from datetime import datetime, date from datetime import datetime, date
from typing import Dict, Any, Optional, Tuple, List from typing import Dict, Any, Optional, Tuple, List
@@ -55,9 +56,9 @@ class MexcSpotMarket:
- get_price(symbol): 获取指定交易对的当前价格 - get_price(symbol): 获取指定交易对的当前价格
""" """
def __init__(self): def __init__(self, config):
"""初始化市场数据查询接口""" """初始化市场数据查询接口"""
self.market = mexc_spot_v3.mexc_market() self.market = mexc_spot_v3.mexc_market(config)
def get_price(self, symbol: str) -> Optional[float]: def get_price(self, symbol: str) -> Optional[float]:
""" """
@@ -123,12 +124,12 @@ class MexcSpotTrade:
"FILL_OR_KILL": ["quantity", "price"], "FILL_OR_KILL": ["quantity", "price"],
} }
def __init__(self): def __init__(self, config, symbol_mapping):
"""初始化交易机器人""" """初始化交易机器人"""
self.trader = mexc_spot_v3.mexc_trade() self.trader = mexc_spot_v3.mexc_trade(config)
self.market = MexcSpotMarket() self.market = MexcSpotMarket(config)
self.csv_file = "output/mexc_spot_trade.csv" self.csv_file = "output/mexc_spot_trade.csv"
self.symbol_mapping = TradingConfig().config_data.get("symbol_mapping", {}) self.symbol_mapping = symbol_mapping
def _api_get_order(self, symbol: str, order_id: str) -> Optional[Dict[str, Any]]: def _api_get_order(self, symbol: str, order_id: str) -> Optional[Dict[str, Any]]:
""" """
@@ -306,8 +307,12 @@ class MexcSpotTrade:
if current_price is None: if current_price is None:
logger.error("无法获取实时价格,交易取消") logger.error("无法获取实时价格,交易取消")
return None return None
processed_kwargs["price"] = current_price # 防止挂单不成交
logger.info("使用实时价格作为限价: %f", current_price) if side == "BUY":
processed_kwargs["price"] = current_price * 1.01 # 买入加价0.5%
elif side == "SELL":
processed_kwargs["price"] = current_price * 0.91 # 卖出减价0.5%
logger.info("使用调整0.5%%后价格作为限价: %f", processed_kwargs["price"])
# 处理LIMIT订单只有quoteOrderQty没有quantity的情况 # 处理LIMIT订单只有quoteOrderQty没有quantity的情况
if ( if (
@@ -320,6 +325,7 @@ class MexcSpotTrade:
price = float(processed_kwargs["price"]) price = float(processed_kwargs["price"])
quantity = quote_amount / price quantity = quote_amount / price
processed_kwargs["quantity"] = str(quantity) processed_kwargs["quantity"] = str(quantity)
processed_kwargs.pop("quoteOrderQty")
logger.info("根据quoteOrderQty计算quantity: %f", quantity) logger.info("根据quoteOrderQty计算quantity: %f", quantity)
except (ValueError, KeyError) as e: except (ValueError, KeyError) as e:
logger.error("计算quantity失败: %s", str(e)) logger.error("计算quantity失败: %s", str(e))
@@ -414,7 +420,7 @@ class TradingConfig:
- _load_config(): 加载JSON配置文件 - _load_config(): 加载JSON配置文件
""" """
def __init__(self, config_file: str = "public/trading_config.json"): def __init__(self, config_file: str = "config/trading_config.json"):
""" """
初始化交易配置 初始化交易配置
@@ -481,36 +487,54 @@ class TradingConfig:
def main(): def main():
"""主函数""" """主函数"""
# 初始化交易配置 # 确保config目录存在
config = TradingConfig() if not os.path.exists("config"):
logger.error("配置目录 config 不存在")
return
# 获取今天需要执行的交易 # 获取config目录下所有json文件
config_files = list(Path("config").glob("*.json"))
if not config_files:
logger.info("配置目录中没有找到任何JSON文件")
return
logger.info("找到 %d 个配置文件需要处理", len(config_files))
for config_file in config_files:
try:
# 提取交易参数
logger.info("处理配置文件: %s", config_file)
config = TradingConfig(str(config_file))
spot_trader = MexcSpotTrade(
config.config_data.get("api", {}),
config.config_data.get("symbol_mapping", {}),
)
today_trades = config.get_today_trades() today_trades = config.get_today_trades()
if not today_trades: if not today_trades:
logger.info("今天没有需要执行的交易") logger.info("%s - 今天没有需要执行的交易", config_file)
return continue
logger.info("今天有 %d 个交易需要执行", len(today_trades)) logger.info("%s - 今天有 %d 个交易需要执行", config_file, len(today_trades))
# 初始化交易类
spot_trader = MexcSpotTrade()
# 执行每个交易
for trade_config in today_trades: for trade_config in today_trades:
try: try:
# 提取交易参数
symbol = trade_config.get("symbol") symbol = trade_config.get("symbol")
order_type = trade_config.get("order_type") order_type = trade_config.get("order_type")
side = trade_config.get("side") side = trade_config.get("side")
params = trade_config.get("params", {}) params = trade_config.get("params", {})
if not all([symbol, order_type, side]): if not all([symbol, order_type, side]):
logger.error("交易配置缺少必要参数: %s", trade_config) logger.error(
"%s - 交易配置缺少必要参数: %s", config_file, trade_config
)
continue continue
logger.info("执行交易: %s %s %s", symbol, order_type, side) logger.info(
logger.debug("交易参数: %s", params) "%s - 执行交易: %s %s %s", config_file, symbol, order_type, side
)
logger.debug("%s - 交易参数: %s", config_file, params)
# 执行交易 # 执行交易
result = spot_trader.trade( result = spot_trader.trade(
@@ -518,15 +542,19 @@ def main():
) )
if result: if result:
logger.info("交易执行成功: %s", result) logger.info("%s - 交易执行成功: %s", config_file, result)
else: else:
logger.error("交易执行失败") logger.error("%s - 交易执行失败", config_file)
except Exception as e: except Exception as e:
logger.error("执行交易时出错: %s", str(e)) logger.error("%s - 执行交易时出错: %s", config_file, str(e))
continue continue
logger.info("执行完毕") except Exception as e:
logger.error("处理配置文件 %s 时出错: %s", config_file, str(e))
continue
logger.info("所有配置文件处理完毕")
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -2,7 +2,6 @@ import requests
import hmac import hmac
import hashlib import hashlib
from urllib.parse import urlencode, quote from urllib.parse import urlencode, quote
import config
# ServerTime、Signature # ServerTime、Signature
class TOOL(object): class TOOL(object):
@@ -42,9 +41,9 @@ class TOOL(object):
# Market Data # Market Data
class mexc_market(TOOL): class mexc_market(TOOL):
def __init__(self): def __init__(self, config):
self.api = '/api/v3' self.api = '/api/v3'
self.hosts = config.mexc_host self.hosts = config["mexc_host"]
self.method = 'GET' self.method = 'GET'
def get_ping(self): def get_ping(self):
@@ -129,11 +128,11 @@ class mexc_market(TOOL):
# Spot Trade # Spot Trade
class mexc_trade(TOOL): class mexc_trade(TOOL):
def __init__(self): def __init__(self, config):
self.api = '/api/v3' self.api = '/api/v3'
self.hosts = config.mexc_host self.hosts = config["mexc_host"]
self.mexc_key = config.api_key self.mexc_key = config["api_key"]
self.mexc_secret = config.secret_key self.mexc_secret = config["secret_key"]
def get_selfSymbols(self): def get_selfSymbols(self):
"""get currency information""" """get currency information"""
@@ -246,11 +245,11 @@ class mexc_trade(TOOL):
# Wallet # Wallet
class mexc_wallet(TOOL): class mexc_wallet(TOOL):
def __init__(self): def __init__(self, config):
self.api = '/api/v3/capital' self.api = '/api/v3/capital'
self.hosts = config.mexc_host self.hosts = config["mexc_host"]
self.mexc_key = config.api_key self.mexc_key = config["api_key"]
self.mexc_secret = config.secret_key self.mexc_secret = config["secret_key"]
def get_coinlist(self): def get_coinlist(self):
"""get currency information""" """get currency information"""
@@ -368,11 +367,11 @@ class mexc_wallet(TOOL):
# Sub-Account # Sub-Account
class mexc_subaccount(TOOL): class mexc_subaccount(TOOL):
def __init__(self): def __init__(self, config):
self.api = '/api/v3' self.api = '/api/v3'
self.hosts = config.mexc_host self.hosts = config["mexc_host"]
self.mexc_key = config.api_key self.mexc_key = config["api_key"]
self.mexc_secret = config.secret_key self.mexc_secret = config["secret_key"]
def post_virtualSubAccount(self, params): def post_virtualSubAccount(self, params):
"""create a sub-account""" """create a sub-account"""
@@ -427,11 +426,11 @@ class mexc_subaccount(TOOL):
# Rebate # Rebate
class mexc_rebate(TOOL): class mexc_rebate(TOOL):
def __init__(self): def __init__(self, config):
self.api = '/api/v3/rebate' self.api = '/api/v3/rebate'
self.hosts = config.mexc_host self.hosts = config["mexc_host"]
self.mexc_key = config.api_key self.mexc_key = config["api_key"]
self.mexc_secret = config.secret_key self.mexc_secret = config["secret_key"]
def get_taxQuery(self, params=None): def get_taxQuery(self, params=None):
"""get the rebate commission record""" """get the rebate commission record"""
@@ -500,11 +499,11 @@ class mexc_rebate(TOOL):
# WebSocket ListenKey # WebSocket ListenKey
class mexc_listenkey(TOOL): class mexc_listenkey(TOOL):
def __init__(self): def __init__(self, config):
self.api = '/api/v3' self.api = '/api/v3'
self.hosts = config.mexc_host self.hosts = config["mexc_host"]
self.mexc_key = config.api_key self.mexc_key = config["api_key"]
self.mexc_secret = config.secret_key self.mexc_secret = config["secret_key"]
def post_listenKey(self): def post_listenKey(self):
""" generate ListenKey """ """ generate ListenKey """