strategy.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. 交易策略模块
  5. 实现基于Lighter和Binance价差的交易策略
  6. """
  7. import logging
  8. from enum import Enum
  9. import os
  10. import lighter
  11. import time
  12. # 配置日志
  13. logs_dir = "logs"
  14. if not os.path.exists(logs_dir):
  15. os.makedirs(logs_dir)
  16. logger = logging.getLogger("strategy")
  17. logger.setLevel(logging.INFO) # 显式设置logger级别为INFO
  18. class StrategyState(Enum):
  19. """策略状态枚举"""
  20. WAITING_INIT = 1 # 等待初始化
  21. IDLE_MONITORING = 2 # 空闲状态监听价差
  22. EXECUTING_OPEN = 3 # 执行开仓操作
  23. CHECKING_OPEN = 4 # 检查开仓结果
  24. WAITING_CONVERGENCE = 5 # 交易完成等待价差收敛
  25. EXECUTING_CLOSE = 6 # 执行平仓操作
  26. CHECKING_CLOSE = 7 # 检查平仓结果
  27. POSITION_CLOSED = 8 # 平仓完成
  28. class TradingStrategy:
  29. """交易策略类"""
  30. def __init__(self):
  31. """初始化策略"""
  32. self.state = StrategyState.WAITING_INIT
  33. self.current_position = None # 当前持仓信息
  34. self.entry_price_bps = 5 # 入场时的价差(单位:bps)
  35. self.target_symbol = "DOGE" # 目标交易对
  36. self.trade_quantity = 1 # 交易数量(买卖数量)
  37. self.account_info = None # 存储账户信息
  38. self.last_account_update_time = 0 # 上次更新账户信息的时间戳
  39. self.last_trade_time = 0 # 上次交易时间戳(开仓或平仓)
  40. self.position_side = None # 持仓方向:'long' 或 'short'
  41. self.account_index = 318163
  42. self.api_key_index = 0
  43. self.api_client = lighter.ApiClient()
  44. self.account_api = lighter.AccountApi(self.api_client)
  45. self.transaction_api = lighter.TransactionApi(self.api_client)
  46. self.signer_client = lighter.SignerClient(
  47. url='https://mainnet.zklighter.elliot.ai',
  48. private_key='72aafa0426f7ff2806c68625ca5c88de153e34fcb23567f3b872cd56334d2848fb223466efff9c21',
  49. account_index=self.account_index,
  50. api_key_index=self.api_key_index
  51. )
  52. # Check client connection
  53. err = self.signer_client.check_client()
  54. if err is not None:
  55. logger.error(f"SignerClient CheckClient error: {err}")
  56. return
  57. logger.info("策略初始化完成,当前状态: WAITING_INIT")
  58. async def do_strategy(self, market_data):
  59. """
  60. 执行策略逻辑
  61. Args:
  62. market_data: 包含市场数据的字典,格式:
  63. {
  64. 'symbol': str,
  65. 'binance_mark_price': float,
  66. 'binance_price': float,
  67. 'lighter_mark_price': float,
  68. 'lighter_price': float,
  69. 'timestamp': int,
  70. 'orderbook': dict # 市场信息(market_info),不是深度信息
  71. }
  72. """
  73. if not market_data:
  74. return
  75. # 更新账户信息,但至少间隔1秒
  76. current_time = time.time()
  77. if current_time - self.last_account_update_time >= 1.0: # 确保至少间隔1秒
  78. try:
  79. account_response = await self.account_api.account(by="index", value=f"{self.account_index}")
  80. if account_response.code == 200:
  81. self.account_info = account_response
  82. self.last_account_update_time = current_time # 更新时间戳
  83. # logger.info(f"账户信息更新成功: 可用余额={account_response.accounts[0].available_balance}, 总资产={account_response.accounts[0].total_asset_value}")
  84. else:
  85. logger.warning(f"账户信息查询失败: code={account_response.code}, message={account_response.message}")
  86. except Exception as e:
  87. logger.error(f"查询账户信息时出错: {str(e)}")
  88. return
  89. symbol = market_data.get('symbol')
  90. # 如果是DOGE交易对,打印实时行情
  91. if symbol == self.target_symbol:
  92. await self._print_market_data(market_data)
  93. # 根据当前状态执行相应逻辑
  94. if self.state == StrategyState.WAITING_INIT:
  95. await self._handle_waiting_init()
  96. elif self.state == StrategyState.IDLE_MONITORING:
  97. await self._handle_idle_monitoring(market_data)
  98. elif self.state == StrategyState.EXECUTING_OPEN:
  99. await self._handle_executing_open(market_data)
  100. elif self.state == StrategyState.CHECKING_OPEN:
  101. await self._handle_checking_open(market_data)
  102. elif self.state == StrategyState.WAITING_CONVERGENCE:
  103. await self._handle_waiting_convergence(market_data)
  104. elif self.state == StrategyState.EXECUTING_CLOSE:
  105. await self._handle_executing_close(market_data)
  106. elif self.state == StrategyState.CHECKING_CLOSE:
  107. await self._handle_checking_close(market_data)
  108. elif self.state == StrategyState.POSITION_CLOSED:
  109. await self._handle_position_closed()
  110. async def _print_market_data(self, market_data):
  111. """打印市场数据"""
  112. symbol = market_data.get('symbol')
  113. # binance_mark = market_data.get('binance_mark_price')
  114. binance_price = market_data.get('binance_price')
  115. # lighter_mark = market_data.get('lighter_mark_price')
  116. lighter_price = market_data.get('lighter_price')
  117. # 计算价差,转换为bps单位
  118. if binance_price and lighter_price:
  119. # 确保两个价格都是浮点数
  120. binance_price_float = float(binance_price) if isinstance(binance_price, str) else binance_price
  121. lighter_price_float = float(lighter_price) if isinstance(lighter_price, str) else lighter_price
  122. # 计算价差并转换为bps (1bps = 0.01%)
  123. price_diff_bps = int((lighter_price_float - binance_price_float) / binance_price_float * 10000) if binance_price_float else 0
  124. else:
  125. price_diff_bps = None
  126. # 格式化输出
  127. price_diff_str = f"{price_diff_bps}bps" if price_diff_bps is not None else "N/A"
  128. logger.info(f"[{symbol}] Binance: 最新价={binance_price} | Lighter: 最新价={lighter_price} | 价差={price_diff_str}")
  129. async def _handle_waiting_init(self):
  130. """处理等待初始化状态"""
  131. # 初始化完成后转到空闲监听状态
  132. self.state = StrategyState.IDLE_MONITORING
  133. logger.info("状态转换: WAITING_INIT -> IDLE_MONITORING")
  134. async def _handle_idle_monitoring(self, market_data):
  135. """处理空闲监听状态 - 监控价差"""
  136. symbol = market_data.get('symbol')
  137. if symbol != self.target_symbol:
  138. return
  139. binance_price = market_data.get('binance_price')
  140. lighter_price = market_data.get('lighter_price')
  141. orderbook = market_data.get('orderbook')
  142. if not binance_price or not lighter_price or not orderbook:
  143. return
  144. # 计算价差(单位:bps)
  145. binance_price_float = float(binance_price) if isinstance(binance_price, str) else binance_price
  146. lighter_price_float = float(lighter_price) if isinstance(lighter_price, str) else lighter_price
  147. price_diff_bps = (lighter_price_float - binance_price_float) / binance_price_float * 10000
  148. # 检查是否触发开仓条件
  149. if price_diff_bps > self.entry_price_bps:
  150. # 做空:价差过大,lighter价格高于binance,卖出lighter
  151. logger.info(f"触发做空条件:价差={price_diff_bps:.2f}bps > {self.entry_price_bps}bps")
  152. self.position_side = 'short'
  153. self.state = StrategyState.EXECUTING_OPEN
  154. logger.info(f"状态转换: IDLE_MONITORING -> EXECUTING_OPEN")
  155. elif price_diff_bps < -self.entry_price_bps:
  156. # 做多:价差过小(负值),lighter价格低于binance,买入lighter
  157. logger.info(f"触发做多条件:价差={price_diff_bps:.2f}bps < -{self.entry_price_bps}bps")
  158. self.position_side = 'long'
  159. self.state = StrategyState.EXECUTING_OPEN
  160. logger.info(f"状态转换: IDLE_MONITORING -> EXECUTING_OPEN")
  161. async def _open_position(self, orderbook, binance_price):
  162. """开仓"""
  163. # 确定开仓方向和价格
  164. if self.position_side == 'long':
  165. # 做多:在Lighter买入(使用ask价格)
  166. price = binance_price
  167. is_ask = False
  168. side_desc = '做多'
  169. else: # short
  170. # 做空:在Lighter卖出(使用bid价格)
  171. price = binance_price
  172. is_ask = True
  173. side_desc = '做空'
  174. logger.info(f"开始开仓:方向={side_desc},数量={self.trade_quantity},价格={price}")
  175. tx_hash, error = await self.create_order_and_send_tx(
  176. orderbook=orderbook,
  177. quantity=self.trade_quantity,
  178. price=price,
  179. is_ask=is_ask,
  180. reduce_only=False
  181. )
  182. if error:
  183. logger.error(f"开仓失败: {error}")
  184. logger.info(f"状态转换: EXECUTING_OPEN -> IDLE_MONITORING")
  185. self.state = StrategyState.IDLE_MONITORING
  186. # 开仓失败,保持在 EXECUTING_OPEN 状态,等待重试
  187. return
  188. # 记录开仓时间
  189. self.last_trade_time = time.time()
  190. # 转换状态到检查开仓
  191. self.state = StrategyState.CHECKING_OPEN
  192. logger.info(f"状态转换: EXECUTING_OPEN -> CHECKING_OPEN,交易哈希={tx_hash}")
  193. logger.info(f"等待1秒后检查持仓...")
  194. async def _handle_executing_open(self, market_data):
  195. """处理执行开仓状态 - 执行开仓操作"""
  196. symbol = market_data.get('symbol')
  197. if symbol != self.target_symbol:
  198. return
  199. orderbook = market_data.get('orderbook')
  200. if not orderbook:
  201. logger.warning("缺少市场信息,无法执行开仓")
  202. return
  203. binance_price = market_data.get('binance_price')
  204. lighter_price = market_data.get('lighter_price')
  205. if binance_price is None or lighter_price is None:
  206. logger.warning("价格数据不完整,无法执行开仓")
  207. return
  208. # 执行开仓操作
  209. await self._open_position(orderbook, binance_price)
  210. async def _handle_checking_open(self, market_data):
  211. """处理检查开仓状态 - 等待1秒后检查持仓"""
  212. # 检查是否已经等待了至少1秒
  213. if time.time() - self.last_trade_time < 1.0:
  214. return
  215. # 检查持仓
  216. symbol = market_data.get('symbol')
  217. if symbol != self.target_symbol:
  218. return
  219. if not self.account_info or not self.account_info.accounts:
  220. logger.warning("账户信息不可用,无法检查持仓")
  221. return
  222. # 查找目标交易对的持仓
  223. position = None
  224. for pos in self.account_info.accounts[0].positions:
  225. if pos.symbol == self.target_symbol:
  226. position = pos
  227. break
  228. if position and int(position.position) != 0:
  229. # 有持仓,转换到等待价差回归状态
  230. self.current_position = position
  231. self.state = StrategyState.WAITING_CONVERGENCE
  232. logger.info(f"检测到持仓:方向={'做多' if position.sign == 1 else '做空'},数量={position.position}")
  233. logger.info(f"状态转换: CHECKING_OPEN -> WAITING_CONVERGENCE")
  234. else:
  235. # 没有持仓,回到检测状态
  236. logger.warning(f"开仓后未检测到持仓,回到检测状态")
  237. self.state = StrategyState.IDLE_MONITORING
  238. logger.info(f"状态转换: CHECKING_OPEN -> IDLE_MONITORING")
  239. async def _handle_waiting_convergence(self, market_data):
  240. """处理等待收敛状态 - 等待价差回归到0轴"""
  241. symbol = market_data.get('symbol')
  242. if symbol != self.target_symbol:
  243. return
  244. binance_price = market_data.get('binance_price')
  245. lighter_price = market_data.get('lighter_price')
  246. orderbook = market_data.get('orderbook')
  247. if not binance_price or not lighter_price or not orderbook:
  248. return
  249. # 计算价差(单位:bps)
  250. binance_price_float = float(binance_price) if isinstance(binance_price, str) else binance_price
  251. lighter_price_float = float(lighter_price) if isinstance(lighter_price, str) else lighter_price
  252. price_diff_bps = (lighter_price_float - binance_price_float) / binance_price_float * 10000
  253. # 检查是否触发平仓条件
  254. should_close = False
  255. if self.position_side == 'long':
  256. # 做多:价差需要往上回归(从负值回到0或正值)
  257. if price_diff_bps >= 0:
  258. should_close = True
  259. logger.info(f"做多平仓条件触发:价差={price_diff_bps:.2f}bps >= 0")
  260. elif self.position_side == 'short':
  261. # 做空:价差需要往下回归(从正值回到0或负值)
  262. if price_diff_bps <= 0:
  263. should_close = True
  264. logger.info(f"做空平仓条件触发:价差={price_diff_bps:.2f}bps <= 0")
  265. if should_close:
  266. # 转换到执行平仓状态
  267. self.state = StrategyState.EXECUTING_CLOSE
  268. logger.info(f"状态转换: WAITING_CONVERGENCE -> EXECUTING_CLOSE")
  269. async def _close_position(self, orderbook, binance_price, lighter_price):
  270. """平仓"""
  271. # 确定平仓价格:使用不利方向的价格
  272. if self.position_side == 'short':
  273. # 做空平仓(买入):取两者较高的价格
  274. close_price = max(binance_price, lighter_price)
  275. is_ask = False # 买入
  276. else: # long
  277. # 做多平仓(卖出):取两者较低的价格
  278. close_price = min(binance_price, lighter_price)
  279. is_ask = True # 卖出
  280. # 获取实际持仓数量
  281. position_quantity = abs(int(self.current_position.position)) if self.current_position else self.trade_quantity
  282. logger.info(f"开始平仓:方向={'做空' if self.position_side == 'short' else '做多'},价格={close_price},数量={position_quantity}")
  283. tx_hash, error = await self.create_order_and_send_tx(
  284. orderbook=orderbook,
  285. quantity=position_quantity,
  286. price=close_price,
  287. is_ask=is_ask,
  288. reduce_only=True
  289. )
  290. if error:
  291. logger.error(f"平仓失败: {error}")
  292. # 平仓失败,保持在执行平仓状态,等待下次重试
  293. logger.info(f"平仓失败,保持在 EXECUTING_CLOSE 状态等待重试")
  294. return
  295. # 记录平仓时间
  296. self.last_trade_time = time.time()
  297. # 转换状态到检查平仓
  298. self.state = StrategyState.CHECKING_CLOSE
  299. logger.info(f"状态转换: EXECUTING_CLOSE -> CHECKING_CLOSE,交易哈希={tx_hash}")
  300. logger.info(f"等待1秒后检查平仓是否生效...")
  301. async def _handle_executing_close(self, market_data):
  302. """处理执行平仓状态 - 执行平仓操作"""
  303. symbol = market_data.get('symbol')
  304. if symbol != self.target_symbol:
  305. return
  306. binance_price = market_data.get('binance_price')
  307. lighter_price = market_data.get('lighter_price')
  308. orderbook = market_data.get('orderbook')
  309. if not binance_price or not lighter_price or not orderbook:
  310. return
  311. # 计算价格
  312. binance_price_float = float(binance_price) if isinstance(binance_price, str) else binance_price
  313. lighter_price_float = float(lighter_price) if isinstance(lighter_price, str) else lighter_price
  314. await self._close_position(orderbook, binance_price_float, lighter_price_float)
  315. async def _handle_checking_close(self, market_data):
  316. """处理检查平仓状态 - 等待1秒后检查持仓是否为0"""
  317. # 检查是否已经等待了至少1秒
  318. if time.time() - self.last_trade_time < 1.0:
  319. return
  320. # 检查持仓
  321. symbol = market_data.get('symbol')
  322. if symbol != self.target_symbol:
  323. return
  324. if not self.account_info or not self.account_info.accounts:
  325. logger.warning("账户信息不可用,无法检查平仓状态")
  326. return
  327. # 查找目标交易对的持仓
  328. position = None
  329. for pos in self.account_info.accounts[0].positions:
  330. if pos.symbol == self.target_symbol:
  331. position = pos
  332. break
  333. if not position or int(position.position) == 0:
  334. # 平仓成功,回到空闲状态
  335. logger.info(f"平仓成功,当前持仓为0")
  336. self.state = StrategyState.IDLE_MONITORING
  337. self.position_side = None
  338. self.current_position = None
  339. logger.info(f"状态转换: CHECKING_CLOSE -> IDLE_MONITORING")
  340. else:
  341. # 平仓未生效,重新执行平仓
  342. logger.warning(f"平仓未生效,当前持仓={position.position},重新执行平仓")
  343. self.state = StrategyState.EXECUTING_CLOSE
  344. logger.info(f"状态转换: CHECKING_CLOSE -> EXECUTING_CLOSE")
  345. async def _handle_position_closed(self):
  346. """处理平仓完成状态"""
  347. # 平仓完成后回到空闲监听状态
  348. self.state = StrategyState.IDLE_MONITORING
  349. logger.info("状态转换: POSITION_CLOSED -> IDLE_MONITORING")
  350. async def create_order_and_send_tx(self, orderbook, quantity, price, is_ask=True, reduce_only=False):
  351. """
  352. 创建订单接口
  353. Args:
  354. orderbook: 市场信息,等价于之前的doge_market
  355. quantity: 下单数量
  356. price: 下单价格
  357. is_ask: 是否为卖单,True为卖,False为买
  358. reduce_only: 是否为只减仓单
  359. Returns:
  360. tuple: (tx_info, error) 交易信息和错误信息
  361. """
  362. try:
  363. # 计算实际下单数量和价格(根据精度转换)
  364. base_amount = int(quantity * (10 ** orderbook.get('supported_size_decimals', 0)))
  365. formatted_price = int(price * (10 ** orderbook.get('supported_price_decimals', 6)))
  366. # 生成客户端订单ID
  367. client_order_index = int(time.time() * 1000)
  368. # 记录下单参数
  369. logger.info(f"创建订单 - 市场: {orderbook.get('symbol')}(ID:{orderbook.get('market_id')})")
  370. logger.info(f"订单参数 - 数量: {quantity}, 价格: {price}, 方向: {'卖出' if is_ask else '买入'}, 只减仓: {reduce_only}")
  371. logger.info(f"格式化参数 - base_amount: {base_amount}, price: {formatted_price}, client_order_index: {client_order_index}")
  372. # 签名创建订单
  373. tx_info, error = self.signer_client.sign_create_order(
  374. market_index=orderbook.get("market_id"),
  375. client_order_index=client_order_index,
  376. base_amount=base_amount,
  377. price=formatted_price,
  378. is_ask=is_ask,
  379. order_type=self.signer_client.ORDER_TYPE_MARKET,
  380. time_in_force=self.signer_client.ORDER_TIME_IN_FORCE_IMMEDIATE_OR_CANCEL,
  381. reduce_only=reduce_only,
  382. trigger_price=0,
  383. order_expiry=0, # 所有市价单(包括减仓市价单)都必须使用NilOrderExpiry (0)
  384. )
  385. if error is not None:
  386. logger.error(f"订单签名失败: {error}")
  387. return None, error
  388. logger.info(f"订单签名成功,准备发送交易: {tx_info}")
  389. # 发送交易
  390. tx_response = await self.transaction_api.send_tx(self.signer_client.TX_TYPE_CREATE_ORDER, tx_info)
  391. # 检查返回状态码
  392. if tx_response.code != 200:
  393. error_msg = f"交易发送失败: code={tx_response.code}, message={tx_response.message}, tx_hash={tx_response.tx_hash}"
  394. logger.error(error_msg)
  395. raise Exception(error_msg)
  396. tx_hash = tx_response.tx_hash
  397. logger.info(f"交易发送成功: tx_hash={tx_hash}")
  398. return tx_hash, None
  399. except Exception as e:
  400. logger.error(f"创建订单时发生错误: {str(e)}")
  401. return None, str(e)
  402. async def main():
  403. strategy = TradingStrategy()
  404. # account = await strategy.account_api.account(by="index", value=f"{strategy.account_index}")
  405. # [AccountPosition(market_id=3, symbol='DOGE', initial_margin_fraction='10.00', open_order_count=0, pending_order_count=0, position_tied_order_count=0, sign=1, position='1', avg_entry_price='0.194368', position_value='0.194360', unrealized_pnl='-0.000008', realized_pnl='0.000000', liquidation_price='0', total_funding_paid_out=None, margin_mode=0, allocated_margin='0.000000', additional_properties={})]
  406. print(account.accounts[0].positions)
  407. # doge_market = {
  408. # "symbol": "DOGE",
  409. # "market_id": 3,
  410. # "status": "active",
  411. # "taker_fee": "0.0000",
  412. # "maker_fee": "0.0000",
  413. # "liquidation_fee": "1.0000",
  414. # "min_base_amount": "10",
  415. # "min_quote_amount": "10.000000",
  416. # "order_quote_limit": "",
  417. # "supported_size_decimals": 0,
  418. # "supported_price_decimals": 6,
  419. # "supported_quote_decimals": 6
  420. # }
  421. # tx_response, error = await strategy.create_order_and_send_tx(doge_market, 1, 0.1, is_ask=True, reduce_only=True)
  422. # if error is not None:
  423. # print(f"Error sending first order (first batch): {error}")
  424. # return
  425. # print(tx_response)
  426. if __name__ == '__main__':
  427. import asyncio
  428. asyncio.run(main())