Add price/SL/TP validation for trade instructions
- Buy orders must satisfy: sl < price < tp - Sell orders must satisfy: tp < price < sl - Invalid instructions are rejected with message feedback - Updated test suite with validation examples - Updated documentation to clarify rules
This commit is contained in:
@@ -91,6 +91,11 @@ stats = client.get_statistics(count=5)
|
|||||||
### 方式 C: HTTP请求
|
### 方式 C: HTTP请求
|
||||||
|
|
||||||
> **说明**: 对于 `POST /send_trade_instructions` 接口,若指令中未提供 `tp` 或 `tp<=0`,服务端会自动设置为 `0.005`;`sl`缺失则保留为`0.0`。
|
> **说明**: 对于 `POST /send_trade_instructions` 接口,若指令中未提供 `tp` 或 `tp<=0`,服务端会自动设置为 `0.005`;`sl`缺失则保留为`0.0`。
|
||||||
|
>
|
||||||
|
> **价格验证规则**:
|
||||||
|
> - 买入指令必须满足 `sl < price < tp`。
|
||||||
|
> - 卖出指令必须满足 `tp < price < sl`。
|
||||||
|
> 若同时指定了 sl 与 tp 但不满足条件,该条指令会被拒绝。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 发送买入订单
|
# 发送买入订单
|
||||||
|
|||||||
@@ -179,6 +179,11 @@ python test_trading_service.py
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**新规则**:若同时指定 `sl` 和 `tp`,
|
||||||
|
- 买入指令要求 `sl < price < tp`;
|
||||||
|
- 卖出指令要求 `tp < price < sl`。
|
||||||
|
不满足规则的指令将被服务器拒绝并忽略。
|
||||||
|
|
||||||
**响应**:
|
**响应**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
+31
-1
@@ -39,6 +39,35 @@ def example_1_basic_trade():
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def example_2a_invalid_order():
|
||||||
|
"""示例 2a: 包含无效指令的下单示例"""
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print("示例 2a: 发送包含错误价格的订单")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
url = "http://localhost:8000/send_trade_instructions"
|
||||||
|
|
||||||
|
trades = [
|
||||||
|
{
|
||||||
|
"symbol": "eurusd",
|
||||||
|
"action": "b",
|
||||||
|
"mount": 0.01,
|
||||||
|
# 指令价格低于止损,应该被拒绝
|
||||||
|
"price": 1.0000,
|
||||||
|
"sl": 1.0050,
|
||||||
|
"tp": 1.0100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
response = requests.post(url, json=trades)
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
print(f"请求: {json.dumps(trades, ensure_ascii=False)}")
|
||||||
|
print(f"响应: {json.dumps(result, indent=2, ensure_ascii=False)}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
# ==================== 示例 2: 批量下单 ====================
|
# ==================== 示例 2: 批量下单 ====================
|
||||||
|
|
||||||
def example_2_batch_orders():
|
def example_2_batch_orders():
|
||||||
@@ -142,7 +171,7 @@ def example_4_query_statistics():
|
|||||||
if positions:
|
if positions:
|
||||||
print(f" 持仓数: {len(positions)}")
|
print(f" 持仓数: {len(positions)}")
|
||||||
for pos in positions:
|
for pos in positions:
|
||||||
print(f" - 票证: {pos.get('ticket')}, 浮盈: ${pos.get('profit'):.2f}}")
|
print(f" - 票证: {pos.get('ticket')}, 浮盈: ${pos.get('profit'):.2f}")
|
||||||
|
|
||||||
trades = stat.get('trades', [])
|
trades = stat.get('trades', [])
|
||||||
if trades:
|
if trades:
|
||||||
@@ -391,6 +420,7 @@ def main():
|
|||||||
examples = [
|
examples = [
|
||||||
("基础交易", example_1_basic_trade),
|
("基础交易", example_1_basic_trade),
|
||||||
("批量下单", example_2_batch_orders),
|
("批量下单", example_2_batch_orders),
|
||||||
|
("错误指令示例", example_2a_invalid_order),
|
||||||
("查询指令", example_3_query_pending),
|
("查询指令", example_3_query_pending),
|
||||||
("查询统计", example_4_query_statistics),
|
("查询统计", example_4_query_statistics),
|
||||||
("清空指令", example_5_clear_orders),
|
("清空指令", example_5_clear_orders),
|
||||||
|
|||||||
+7
-2
@@ -58,10 +58,15 @@ def create_trader_routes(server: TradingServer) -> APIRouter:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
count = server.add_trade_instruction(instructions)
|
result = server.add_trade_instruction(instructions)
|
||||||
|
added = result.get("added", 0)
|
||||||
|
rejected = result.get("rejected", 0)
|
||||||
|
msg = f"已添加 {added} 条交易指令"
|
||||||
|
if rejected > 0:
|
||||||
|
msg += f",{rejected} 条因价格不合法被拒绝"
|
||||||
return {
|
return {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"message": f"已添加 {count} 条交易指令"
|
"message": msg
|
||||||
}
|
}
|
||||||
|
|
||||||
@router.get("/query_pending_trades")
|
@router.get("/query_pending_trades")
|
||||||
|
|||||||
@@ -28,16 +28,23 @@ class TradingServer:
|
|||||||
|
|
||||||
print("[信息] 交易服务已初始化")
|
print("[信息] 交易服务已初始化")
|
||||||
|
|
||||||
def add_trade_instruction(self, instructions: List[TradeInstruction]) -> int:
|
def add_trade_instruction(self, instructions: List[TradeInstruction]) -> dict:
|
||||||
"""
|
"""
|
||||||
添加交易指令
|
添加交易指令
|
||||||
返回添加的指令数量
|
返回一个字典,包含添加和拒绝的数量。
|
||||||
|
|
||||||
在此处对缺失的 sl/tp 值进行补全:
|
在此处对缺失的 sl/tp 值进行补全:
|
||||||
- sl 若未设置保持0.0
|
- sl 若未设置保持0.0
|
||||||
- tp 若未设置则默认 0.005
|
- tp 若未设置则默认 0.005
|
||||||
|
|
||||||
|
同时按照规则进行价格检查:
|
||||||
|
* 买入指令: price 需大于 sl 且小于 tp
|
||||||
|
* 卖出指令: price 需小于 sl 且大于 tp
|
||||||
|
若 sl 或 tp 未设置(<=0),则忽略检查。
|
||||||
"""
|
"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
count = 0
|
added = 0
|
||||||
|
rejected = 0
|
||||||
for instruction in instructions:
|
for instruction in instructions:
|
||||||
# 填充默认值
|
# 填充默认值
|
||||||
if instruction.sl is None:
|
if instruction.sl is None:
|
||||||
@@ -45,15 +52,29 @@ class TradingServer:
|
|||||||
if instruction.tp is None or instruction.tp <= 0.0:
|
if instruction.tp is None or instruction.tp <= 0.0:
|
||||||
instruction.tp = 0.005
|
instruction.tp = 0.005
|
||||||
|
|
||||||
|
# 验证价格与 SL/TP 关系
|
||||||
|
if instruction.sl > 0 and instruction.tp > 0:
|
||||||
|
if instruction.action.lower() == 'b':
|
||||||
|
if not (instruction.price > instruction.sl and instruction.price < instruction.tp):
|
||||||
|
print(f"[警告] 忽略无效买入指令: {instruction}")
|
||||||
|
rejected += 1
|
||||||
|
continue
|
||||||
|
elif instruction.action.lower() == 's':
|
||||||
|
if not (instruction.price < instruction.sl and instruction.price > instruction.tp):
|
||||||
|
print(f"[警告] 忽略无效卖出指令: {instruction}")
|
||||||
|
rejected += 1
|
||||||
|
continue
|
||||||
|
|
||||||
symbol = instruction.symbol.upper()
|
symbol = instruction.symbol.upper()
|
||||||
self.trade_instructions[symbol].append(instruction)
|
self.trade_instructions[symbol].append(instruction)
|
||||||
count += 1
|
added += 1
|
||||||
|
|
||||||
print(f"[信息] 已添加 {count} 条交易指令")
|
print(f"[信息] 已添加 {added} 条交易指令, 拒绝 {rejected} 条")
|
||||||
for symbol, trades in self.trade_instructions.items():
|
for symbol, trades in self.trade_instructions.items():
|
||||||
print(f" {symbol}: {len(trades)} 条待执行")
|
print(f" {symbol}: {len(trades)} 条待执行")
|
||||||
|
|
||||||
return count
|
return {"added": added, "rejected": rejected}
|
||||||
|
|
||||||
|
|
||||||
def get_trades_by_symbol(self, symbol: str, price: Optional[float] = None) -> List[Dict]:
|
def get_trades_by_symbol(self, symbol: str, price: Optional[float] = None) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
+18
-1
@@ -85,13 +85,30 @@ class TradingServiceTester:
|
|||||||
print(f"指令内容: {json.dumps(instructions, indent=2, ensure_ascii=False)}")
|
print(f"指令内容: {json.dumps(instructions, indent=2, ensure_ascii=False)}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# 首先发送几条合法指令
|
||||||
response = self.session.post(
|
response = self.session.post(
|
||||||
f"{self.base_url}/send_trade_instructions",
|
f"{self.base_url}/send_trade_instructions",
|
||||||
json=instructions
|
json=instructions
|
||||||
)
|
)
|
||||||
print(f"状态码: {response.status_code}")
|
print(f"状态码: {response.status_code}")
|
||||||
print(f"响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
|
print(f"响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
|
||||||
return response.status_code == 200
|
if response.status_code != 200:
|
||||||
|
return False
|
||||||
|
# 再发送一条无效指令,以验证检查逻辑
|
||||||
|
bad = [{
|
||||||
|
"symbol": "eurusd",
|
||||||
|
"action": "b",
|
||||||
|
"mount": 0.01,
|
||||||
|
"price": 1.0000,
|
||||||
|
"sl": 1.0050, # 价格低于SL
|
||||||
|
"tp": 1.0100
|
||||||
|
}]
|
||||||
|
bad_resp = self.session.post(
|
||||||
|
f"{self.base_url}/send_trade_instructions",
|
||||||
|
json=bad
|
||||||
|
)
|
||||||
|
print("发送无效指令后响应:", bad_resp.status_code, bad_resp.json())
|
||||||
|
return bad_resp.status_code == 200 and "拒绝" in bad_resp.json().get("message", "")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"错误: {str(e)}")
|
print(f"错误: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|||||||
Reference in New Issue
Block a user