|
|
@@ -160,7 +160,7 @@ impl Strategy {
|
|
|
match self.check_order_filled_status(&order_id, dm).await {
|
|
|
Ok(status) => {
|
|
|
let has_filled = matches!(status.as_str(), "FILLED");
|
|
|
-
|
|
|
+
|
|
|
if has_filled {
|
|
|
info!("限价单已成交,准备执行市价单");
|
|
|
self.state = StrategyState::ExecutingMarketOrder { };
|
|
|
@@ -277,7 +277,7 @@ impl Strategy {
|
|
|
}
|
|
|
"CANCELLED" => {
|
|
|
info!("市价单已被取消,重新下单");
|
|
|
-
|
|
|
+
|
|
|
self.state = StrategyState::ExecutingMarketOrder { };
|
|
|
|
|
|
Ok(())
|
|
|
@@ -389,12 +389,12 @@ impl Strategy {
|
|
|
.and_then(|v| Decimal::from_str(v)
|
|
|
.map_err(|e| anyhow!("查单-解析 'data.filledQty' 为 Decimal 失败: {}, 值: {}", e, v))
|
|
|
)?;
|
|
|
-
|
|
|
+
|
|
|
self.filled_quantity = filled_qty.normalize();
|
|
|
if self.filled_quantity > Decimal::ZERO {
|
|
|
info!("订单 {} ,成交数量: {}", order_id, self.filled_quantity);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
Ok(status.to_string())
|
|
|
}
|
|
|
|
|
|
@@ -457,4 +457,186 @@ impl Strategy {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /// 策略清退:取消所有订单并平掉所有仓位
|
|
|
+ pub async fn shutdown(&mut self, market: &str, dm: &DataManager) -> Result<()> {
|
|
|
+ info!("========================================");
|
|
|
+ info!("开始执行策略清退流程");
|
|
|
+ info!("========================================");
|
|
|
+
|
|
|
+ // 第一步:取消所有订单
|
|
|
+ self.cancel_all_orders(market).await?;
|
|
|
+
|
|
|
+ // 等待一段时间确保订单取消完成
|
|
|
+ sleep(Duration::from_millis(2000)).await;
|
|
|
+
|
|
|
+ // 第二步:平掉所有仓位
|
|
|
+ self.close_all_positions(dm).await?;
|
|
|
+
|
|
|
+ info!("========================================");
|
|
|
+ info!("策略清退流程完成");
|
|
|
+ info!("========================================");
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 取消指定市场的所有订单
|
|
|
+ async fn cancel_all_orders(&self, market: &str) -> Result<()> {
|
|
|
+ info!("步骤 1/2: 取消所有订单 (市场: {})", market);
|
|
|
+
|
|
|
+ let response = {
|
|
|
+ let mut client = self.rest_client.lock().await;
|
|
|
+ client.mass_cancel_by_market(market).await
|
|
|
+ };
|
|
|
+
|
|
|
+ let value = &response.data;
|
|
|
+
|
|
|
+ // 预先捕获整个 Value 的字符串表示,用于错误报告
|
|
|
+ let value_str = serde_json::to_string(&value)
|
|
|
+ .unwrap_or_else(|_| "无法序列化 JSON Value".to_string());
|
|
|
+
|
|
|
+ // 获取status
|
|
|
+ let status = value.get("status")
|
|
|
+ .and_then(|v| v.as_str())
|
|
|
+ .ok_or_else(|| anyhow!("批量撤单-获取 'status' 失败,原始JSON:{}", value_str))?;
|
|
|
+
|
|
|
+ // 判定status
|
|
|
+ if status != "OK" {
|
|
|
+ bail!("批量撤单失败,状态不为OK,原始JSON:{}", value_str)
|
|
|
+ }
|
|
|
+
|
|
|
+ info!("批量撤单成功,详情: {:?}", value_str);
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 平掉所有仓位
|
|
|
+ async fn close_all_positions(&self, dm: &DataManager) -> Result<()> {
|
|
|
+ info!("步骤 2/2: 平掉所有仓位");
|
|
|
+
|
|
|
+ let response = {
|
|
|
+ let mut client = self.rest_client.lock().await;
|
|
|
+ client.get_positions().await
|
|
|
+ };
|
|
|
+
|
|
|
+ let value = &response.data;
|
|
|
+
|
|
|
+ // 预先捕获整个 Value 的字符串表示,用于错误报告
|
|
|
+ let value_str = serde_json::to_string(&value)
|
|
|
+ .unwrap_or_else(|_| "无法序列化 JSON Value".to_string());
|
|
|
+
|
|
|
+ // 获取status
|
|
|
+ let status = value.get("status")
|
|
|
+ .and_then(|v| v.as_str())
|
|
|
+ .ok_or_else(|| anyhow!("获取仓位-获取 'status' 失败,原始JSON:{}", value_str))?;
|
|
|
+
|
|
|
+ // 判定status
|
|
|
+ if status != "OK" {
|
|
|
+ bail!("获取仓位失败,状态不为OK,原始JSON:{}", value_str)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取 data 字段(仓位数组)
|
|
|
+ let positions = value.get("data")
|
|
|
+ .and_then(|v| v.as_array())
|
|
|
+ .ok_or_else(|| anyhow!("获取仓位-获取 'data' 字段失败,原始 JSON: {}", value_str))?;
|
|
|
+
|
|
|
+ if positions.is_empty() {
|
|
|
+ info!("当前无持仓,无需平仓");
|
|
|
+ return Ok(());
|
|
|
+ }
|
|
|
+
|
|
|
+ info!("发现 {} 个持仓,准备平仓", positions.len());
|
|
|
+
|
|
|
+ // 遍历所有仓位并平仓
|
|
|
+ for (index, position) in positions.iter().enumerate() {
|
|
|
+ if let Err(e) = self.close_single_position(position, index + 1, dm).await {
|
|
|
+ warn!("平仓失败 (仓位 {}/{}): {}", index + 1, positions.len(), e);
|
|
|
+ // 继续处理其他仓位,不中断
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ /// 平掉单个仓位
|
|
|
+ async fn close_single_position(&self, position: &Value, position_number: usize, dm: &DataManager) -> Result<()> {
|
|
|
+ let position_str = serde_json::to_string(position)
|
|
|
+ .unwrap_or_else(|_| "无法序列化 JSON Value".to_string());
|
|
|
+
|
|
|
+ // 解析仓位信息
|
|
|
+ let market = position.get("market")
|
|
|
+ .and_then(|v| v.as_str())
|
|
|
+ .ok_or_else(|| anyhow!("解析仓位-获取 'market' 失败,原始JSON:{}", position_str))?;
|
|
|
+
|
|
|
+ let side = position.get("side")
|
|
|
+ .and_then(|v| v.as_str())
|
|
|
+ .ok_or_else(|| anyhow!("解析仓位-获取 'side' 失败,原始JSON:{}", position_str))?;
|
|
|
+
|
|
|
+ let size_str = position.get("size")
|
|
|
+ .and_then(|v| v.as_str())
|
|
|
+ .ok_or_else(|| anyhow!("解析仓位-获取 'size' 失败,原始JSON:{}", position_str))?;
|
|
|
+
|
|
|
+ let size = Decimal::from_str(size_str)
|
|
|
+ .map_err(|e| anyhow!("解析仓位-解析 'size' 为 Decimal 失败: {}, 值: {}", e, size_str))?;
|
|
|
+
|
|
|
+ // 获取当前标记价格(用于日志显示)
|
|
|
+ let mark_price = position.get("markPrice")
|
|
|
+ .and_then(|v| v.as_str())
|
|
|
+ .unwrap_or("N/A");
|
|
|
+
|
|
|
+ info!("----------------------------------------");
|
|
|
+ info!("平仓 ({}/总数未知)", position_number);
|
|
|
+ info!(" 市场: {}", market);
|
|
|
+ info!(" 方向: {}", side);
|
|
|
+ info!(" 数量: {}", size);
|
|
|
+ info!(" 标记价格: {}", mark_price);
|
|
|
+
|
|
|
+ // 确定平仓方向(与持仓方向相反)
|
|
|
+ let close_side = match side {
|
|
|
+ "LONG" => "SELL", // 多头平仓用卖单
|
|
|
+ "SHORT" => "BUY", // 空头平仓用买单
|
|
|
+ _ => bail!("未知的持仓方向: {}", side),
|
|
|
+ };
|
|
|
+
|
|
|
+ info!(" 平仓方向: {}", close_side);
|
|
|
+
|
|
|
+
|
|
|
+ let price = match close_side {
|
|
|
+ "BUY" => dm.best_ask, // 用卖一价平仓
|
|
|
+ "SELL" => dm.best_bid, // 用买一价平仓
|
|
|
+ _ => bail!("未知的平仓方向: {}", side),
|
|
|
+ };
|
|
|
+
|
|
|
+ // 下市价单平仓
|
|
|
+ let create_result = {
|
|
|
+ let mut client = self.rest_client.lock().await;
|
|
|
+ client.post_order(
|
|
|
+ "MARKET",
|
|
|
+ close_side,
|
|
|
+ size.normalize().to_string().as_str(),
|
|
|
+ price.to_string().as_str(), // 市价单价格传 0 或当前价格
|
|
|
+ false, // postOnly = false
|
|
|
+ true, // reduceOnly = true (只减仓)
|
|
|
+ ).await
|
|
|
+ };
|
|
|
+
|
|
|
+ // 解析平仓结果
|
|
|
+ match self.match_create_order_result(&create_result) {
|
|
|
+ Ok(order_id) => {
|
|
|
+ info!("✅ 平仓订单提交成功");
|
|
|
+ info!(" 订单ID: {}", order_id);
|
|
|
+ info!(" 市场: {}", market);
|
|
|
+ info!(" 方向: {}", close_side);
|
|
|
+ info!(" 数量: {}", size);
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+ Err(e) => {
|
|
|
+ warn!("❌ 平仓订单提交失败");
|
|
|
+ warn!(" 错误: {}", e);
|
|
|
+ warn!(" 市场: {}", market);
|
|
|
+ warn!(" 原始仓位数据: {}", position_str);
|
|
|
+ Err(e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|