2025-07-22 09:59:18 +00:00
|
|
|
|
import logging
|
2025-08-05 07:30:50 +00:00
|
|
|
|
from core.biz.quant_trader import QuantTrader
|
|
|
|
|
|
from core.biz.strategy import QuantStrategy
|
2025-07-21 05:05:59 +00:00
|
|
|
|
|
2025-09-09 06:15:29 +00:00
|
|
|
|
from config import MYSQL_CONFIG
|
|
|
|
|
|
from sqlalchemy import create_engine, exc, text
|
|
|
|
|
|
import pandas as pd
|
|
|
|
|
|
|
2025-07-22 09:59:18 +00:00
|
|
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s')
|
2025-07-21 07:46:48 +00:00
|
|
|
|
|
2025-07-22 09:59:18 +00:00
|
|
|
|
def main() -> None:
|
2025-07-21 05:05:59 +00:00
|
|
|
|
"""主函数"""
|
2025-07-22 09:59:18 +00:00
|
|
|
|
logging.info("=== 比特币量化交易系统 ===")
|
2025-07-21 05:05:59 +00:00
|
|
|
|
# 导入配置
|
|
|
|
|
|
try:
|
2025-07-24 10:23:00 +00:00
|
|
|
|
from config import API_KEY, SECRET_KEY, PASSPHRASE, SANDBOX, TRADING_CONFIG, TIME_CONFIG
|
2025-07-21 05:05:59 +00:00
|
|
|
|
except ImportError:
|
2025-07-22 09:59:18 +00:00
|
|
|
|
logging.error("找不到config.py文件,请确保配置文件存在")
|
2025-07-21 05:05:59 +00:00
|
|
|
|
return
|
|
|
|
|
|
# 检查是否配置了API密钥
|
|
|
|
|
|
if API_KEY == "your_api_key_here":
|
2025-07-22 09:59:18 +00:00
|
|
|
|
logging.error("请先在config.py中配置你的OKX API密钥!\n1. 登录OKX官网\n2. 进入API管理页面\n3. 创建API Key、Secret Key和Passphrase\n4. 将密钥填入config.py文件中的相应位置")
|
2025-07-21 05:05:59 +00:00
|
|
|
|
return
|
|
|
|
|
|
# 创建交易器实例
|
2025-07-24 10:23:00 +00:00
|
|
|
|
# sandbox = TRADING_CONFIG.get("sandbox", True)
|
2025-07-22 09:59:18 +00:00
|
|
|
|
symbol = TRADING_CONFIG.get("symbol", "BTC-USDT")
|
|
|
|
|
|
position_size = TRADING_CONFIG.get("position_size", 0.001)
|
|
|
|
|
|
trader = QuantTrader(
|
|
|
|
|
|
API_KEY, SECRET_KEY, PASSPHRASE,
|
2025-07-24 10:23:00 +00:00
|
|
|
|
sandbox=SANDBOX,
|
2025-07-22 09:59:18 +00:00
|
|
|
|
symbol=symbol,
|
|
|
|
|
|
position_size=position_size
|
|
|
|
|
|
)
|
|
|
|
|
|
strategy = QuantStrategy(
|
2025-07-21 05:05:59 +00:00
|
|
|
|
API_KEY, SECRET_KEY, PASSPHRASE,
|
2025-07-24 10:23:00 +00:00
|
|
|
|
sandbox=SANDBOX,
|
2025-07-22 09:59:18 +00:00
|
|
|
|
symbol=symbol,
|
|
|
|
|
|
position_size=position_size
|
2025-07-21 05:05:59 +00:00
|
|
|
|
)
|
|
|
|
|
|
# 显示菜单
|
|
|
|
|
|
while True:
|
2025-07-22 09:59:18 +00:00
|
|
|
|
logging.info("\n请选择操作:\n1. 查看账户余额\n2. 查看当前价格\n3. 执行移动平均线策略\n4. 执行RSI策略\n5. 执行网格交易策略\n6. 运行策略循环\n7. 买入测试\n8. 卖出测试\n9. 获取最小交易量\n0. 退出")
|
2025-07-21 07:46:48 +00:00
|
|
|
|
choice = input("请输入选择 (0-9): ").strip()
|
2025-07-21 05:05:59 +00:00
|
|
|
|
if choice == '0':
|
2025-07-22 09:59:18 +00:00
|
|
|
|
logging.info("退出程序")
|
2025-07-21 05:05:59 +00:00
|
|
|
|
break
|
|
|
|
|
|
elif choice == '1':
|
|
|
|
|
|
trader.get_account_balance()
|
|
|
|
|
|
elif choice == '2':
|
|
|
|
|
|
trader.get_current_price()
|
|
|
|
|
|
elif choice == '3':
|
2025-07-22 09:59:18 +00:00
|
|
|
|
sma_short_period = TRADING_CONFIG.get("sma_short_period", 5)
|
|
|
|
|
|
sma_long_period = TRADING_CONFIG.get("sma_long_period", 20)
|
|
|
|
|
|
strategy.simple_moving_average_strategy(sma_short_period, sma_long_period)
|
2025-07-21 05:05:59 +00:00
|
|
|
|
elif choice == '4':
|
2025-07-22 09:59:18 +00:00
|
|
|
|
period = TRADING_CONFIG.get("rsi_period", 14)
|
|
|
|
|
|
rsi_oversold = TRADING_CONFIG.get("rsi_oversold", 30)
|
|
|
|
|
|
rsi_overbought = TRADING_CONFIG.get("rsi_overbought", 70)
|
|
|
|
|
|
strategy.rsi_strategy(period, rsi_oversold, rsi_overbought)
|
2025-07-21 05:05:59 +00:00
|
|
|
|
elif choice == '5':
|
2025-07-22 09:59:18 +00:00
|
|
|
|
grid_levels = TRADING_CONFIG.get("grid_levels", 5)
|
|
|
|
|
|
grid_range = TRADING_CONFIG.get("grid_range", 0.02)
|
|
|
|
|
|
strategy.grid_trading_strategy(grid_levels, grid_range)
|
2025-07-21 05:05:59 +00:00
|
|
|
|
elif choice == '6':
|
2025-07-22 09:59:18 +00:00
|
|
|
|
strategy_name = input("选择策略 (sma/rsi/grid): ").strip()
|
|
|
|
|
|
interval = TIME_CONFIG.get("strategy_interval", 30)
|
|
|
|
|
|
strategy.run_strategy_loop(strategy_name, interval, TRADING_CONFIG)
|
2025-07-21 07:46:48 +00:00
|
|
|
|
elif choice == '7':
|
2025-07-22 09:59:18 +00:00
|
|
|
|
defalt_position_size = 0.01
|
2025-07-21 07:46:48 +00:00
|
|
|
|
input_size = input("请输入买入数量: ")
|
|
|
|
|
|
if input_size:
|
|
|
|
|
|
try:
|
|
|
|
|
|
position_size = float(input_size)
|
2025-07-22 09:59:18 +00:00
|
|
|
|
logging.info(f"买入{position_size}BTC")
|
2025-07-21 07:46:48 +00:00
|
|
|
|
trader.place_market_order('buy', position_size)
|
|
|
|
|
|
except ValueError:
|
2025-07-22 09:59:18 +00:00
|
|
|
|
logging.warning(f"输入无效,默认买入{defalt_position_size}BTC")
|
|
|
|
|
|
trader.place_market_order('buy', defalt_position_size)
|
|
|
|
|
|
else:
|
|
|
|
|
|
logging.info(f"默认买入{defalt_position_size}BTC")
|
|
|
|
|
|
trader.place_market_order('buy', defalt_position_size)
|
2025-07-21 07:46:48 +00:00
|
|
|
|
elif choice == '8':
|
2025-07-22 09:59:18 +00:00
|
|
|
|
defalt_position_size = 0.01
|
2025-07-21 07:46:48 +00:00
|
|
|
|
input_size = input("请输入卖出数量: ")
|
|
|
|
|
|
if input_size:
|
|
|
|
|
|
try:
|
|
|
|
|
|
position_size = float(input_size)
|
2025-07-22 09:59:18 +00:00
|
|
|
|
logging.info(f"卖出{position_size}BTC")
|
2025-07-21 07:46:48 +00:00
|
|
|
|
trader.place_market_order('sell', position_size)
|
|
|
|
|
|
except ValueError:
|
2025-07-22 09:59:18 +00:00
|
|
|
|
logging.warning(f"输入无效,默认卖出{defalt_position_size}BTC")
|
|
|
|
|
|
trader.place_market_order('sell', defalt_position_size)
|
|
|
|
|
|
else:
|
|
|
|
|
|
logging.info(f"默认卖出{defalt_position_size}BTC")
|
|
|
|
|
|
trader.place_market_order('sell', defalt_position_size)
|
2025-07-21 07:46:48 +00:00
|
|
|
|
elif choice == '9':
|
|
|
|
|
|
trader.get_minimun_order_size()
|
2025-07-21 05:05:59 +00:00
|
|
|
|
else:
|
2025-07-22 09:59:18 +00:00
|
|
|
|
logging.warning("无效选择,请重新输入")
|
2025-07-21 05:05:59 +00:00
|
|
|
|
|
2025-09-09 06:15:29 +00:00
|
|
|
|
|
|
|
|
|
|
def test_query():
|
|
|
|
|
|
mysql_user = MYSQL_CONFIG.get("user", "xch")
|
|
|
|
|
|
mysql_password = MYSQL_CONFIG.get("password", "")
|
|
|
|
|
|
if not mysql_password:
|
|
|
|
|
|
raise ValueError("MySQL password is not set")
|
|
|
|
|
|
mysql_host = MYSQL_CONFIG.get("host", "localhost")
|
|
|
|
|
|
mysql_port = MYSQL_CONFIG.get("port", 3306)
|
|
|
|
|
|
mysql_database = MYSQL_CONFIG.get("database", "okx")
|
|
|
|
|
|
db_url = f"mysql+pymysql://{mysql_user}:{mysql_password}@{mysql_host}:{mysql_port}/{mysql_database}"
|
|
|
|
|
|
db_engine = create_engine(
|
|
|
|
|
|
db_url,
|
|
|
|
|
|
pool_size=25, # 连接池大小
|
|
|
|
|
|
max_overflow=10, # 允许的最大溢出连接
|
|
|
|
|
|
pool_timeout=30, # 连接超时时间(秒)
|
|
|
|
|
|
pool_recycle=60, # 连接回收时间(秒),避免长时间闲置
|
|
|
|
|
|
)
|
|
|
|
|
|
sql = "SELECT symbol, min(date_time), max(date_time) FROM okx.crypto_binance_data where bar='5m' group by symbol;"
|
|
|
|
|
|
condition_dict = {}
|
|
|
|
|
|
return_multi = True
|
|
|
|
|
|
try:
|
|
|
|
|
|
result = query_data(db_engine, sql, condition_dict, return_multi)
|
|
|
|
|
|
if result is not None and len(result) > 0:
|
|
|
|
|
|
data = pd.DataFrame(result)
|
|
|
|
|
|
data.columns = ["symbol", "min_date_time", "max_date_time"]
|
|
|
|
|
|
print(data)
|
|
|
|
|
|
# output to excel
|
|
|
|
|
|
data.to_excel("./data/binance/crypto_binance_data_5m.xlsx", index=False)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"查询数据出错: {e}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def transform_data_type(data: dict):
|
|
|
|
|
|
"""
|
|
|
|
|
|
遍历字典,将所有Decimal类型的值转换为float类型
|
|
|
|
|
|
"""
|
|
|
|
|
|
from decimal import Decimal
|
|
|
|
|
|
for key, value in data.items():
|
|
|
|
|
|
if isinstance(value, Decimal):
|
|
|
|
|
|
data[key] = float(value)
|
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def query_data(db_engine, sql: str, condition_dict: dict, return_multi: bool = True):
|
|
|
|
|
|
"""
|
|
|
|
|
|
查询数据
|
|
|
|
|
|
:param sql: 查询SQL
|
|
|
|
|
|
:param db_url: 数据库连接URL
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
with db_engine.connect() as conn:
|
|
|
|
|
|
result = conn.execute(text(sql), condition_dict)
|
|
|
|
|
|
if return_multi:
|
|
|
|
|
|
result = result.fetchall()
|
|
|
|
|
|
if result:
|
|
|
|
|
|
result_list = [
|
|
|
|
|
|
transform_data_type(dict(row._mapping)) for row in result
|
|
|
|
|
|
]
|
|
|
|
|
|
return result_list
|
|
|
|
|
|
else:
|
|
|
|
|
|
return None
|
|
|
|
|
|
else:
|
|
|
|
|
|
result = result.fetchone()
|
|
|
|
|
|
if result:
|
|
|
|
|
|
result_dict = transform_data_type(dict(result._mapping))
|
|
|
|
|
|
return result_dict
|
|
|
|
|
|
else:
|
|
|
|
|
|
return None
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"查询数据出错: {e}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
2025-07-21 05:05:59 +00:00
|
|
|
|
if __name__ == "__main__":
|
2025-09-09 06:15:29 +00:00
|
|
|
|
# main()
|
|
|
|
|
|
test_query()
|