Compare commits
4 Commits
fix/issue-
...
fix/issue-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
405b35c3ba | ||
| dd25d38e7e | |||
|
|
da8327c0e0 | ||
| 8d33ea9a44 |
@@ -90,6 +90,22 @@ class AveCloudClient:
|
|||||||
return data.get("data", [])
|
return data.get("data", [])
|
||||||
raise Exception(f"Failed to fetch klines: {data}")
|
raise Exception(f"Failed to fetch klines: {data}")
|
||||||
|
|
||||||
|
async def get_token_price(self, token_id: str) -> Optional[Dict[str, Any]]:
|
||||||
|
url = f"{self.DATA_API_URL}/v2/tokens/price"
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
url,
|
||||||
|
headers=self._data_headers(),
|
||||||
|
json={"token_ids": [token_id]},
|
||||||
|
timeout=30.0,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
if data.get("status") == 200:
|
||||||
|
prices = data.get("data", {})
|
||||||
|
return prices.get(token_id)
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_trending_tokens(
|
async def get_trending_tokens(
|
||||||
self, chain: Optional[str] = None, limit: int = 20
|
self, chain: Optional[str] = None, limit: int = 20
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import httpx
|
|
||||||
from typing import List, Dict, Any, Optional
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
|
||||||
class AveCloudClient:
|
|
||||||
BASE_URL = "https://prod.ave-api.com"
|
|
||||||
|
|
||||||
def __init__(self, api_key: str, plan: str = "free"):
|
|
||||||
self.api_key = api_key
|
|
||||||
self.plan = plan
|
|
||||||
|
|
||||||
def _headers(self) -> Dict[str, str]:
|
|
||||||
return {"X-API-KEY": self.api_key}
|
|
||||||
|
|
||||||
async def get_klines(
|
|
||||||
self,
|
|
||||||
token_id: str,
|
|
||||||
interval: str = "1h",
|
|
||||||
limit: int = 100,
|
|
||||||
start_time: Optional[int] = None,
|
|
||||||
end_time: Optional[int] = None,
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
url = f"{self.BASE_URL}/v2/klines/token/{token_id}"
|
|
||||||
params = {"interval": interval, "limit": limit}
|
|
||||||
if start_time:
|
|
||||||
params["start_time"] = start_time
|
|
||||||
if end_time:
|
|
||||||
params["end_time"] = end_time
|
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
response = await client.get(
|
|
||||||
url, headers=self._headers(), params=params, timeout=30.0
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
data = response.json()
|
|
||||||
if data.get("status") == 200:
|
|
||||||
return data.get("data", [])
|
|
||||||
raise Exception(f"Failed to fetch klines: {data}")
|
|
||||||
|
|
||||||
async def get_token_price(self, token_id: str) -> Optional[Dict[str, Any]]:
|
|
||||||
url = f"{self.BASE_URL}/v2/tokens/price"
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
response = await client.post(
|
|
||||||
url,
|
|
||||||
headers=self._headers(),
|
|
||||||
json={"token_ids": [token_id]},
|
|
||||||
timeout=30.0,
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
data = response.json()
|
|
||||||
if data.get("status") == 200:
|
|
||||||
prices = data.get("data", {})
|
|
||||||
return prices.get(token_id)
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_batch_prices(self, token_ids: List[str]) -> Dict[str, Dict[str, Any]]:
|
|
||||||
url = f"{self.BASE_URL}/v2/tokens/price"
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
response = await client.post(
|
|
||||||
url,
|
|
||||||
headers=self._headers(),
|
|
||||||
json={"token_ids": token_ids},
|
|
||||||
timeout=30.0,
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
data = response.json()
|
|
||||||
if data.get("status") == 200:
|
|
||||||
return data.get("data", {})
|
|
||||||
return {}
|
|
||||||
@@ -2,7 +2,7 @@ import uuid
|
|||||||
import asyncio
|
import asyncio
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
from .ave_client import AveCloudClient
|
from ..ave.client import AveCloudClient
|
||||||
|
|
||||||
|
|
||||||
class BacktestEngine:
|
class BacktestEngine:
|
||||||
@@ -20,10 +20,15 @@ class BacktestEngine:
|
|||||||
self.strategy_config = config.get("strategy_config", {})
|
self.strategy_config = config.get("strategy_config", {})
|
||||||
self.conditions = self.strategy_config.get("conditions", [])
|
self.conditions = self.strategy_config.get("conditions", [])
|
||||||
self.actions = self.strategy_config.get("actions", [])
|
self.actions = self.strategy_config.get("actions", [])
|
||||||
|
self.risk_management = self.strategy_config.get("risk_management", {})
|
||||||
|
self.stop_loss_percent = self.risk_management.get("stop_loss_percent")
|
||||||
|
self.take_profit_percent = self.risk_management.get("take_profit_percent")
|
||||||
self.initial_balance = config.get("initial_balance", 10000.0)
|
self.initial_balance = config.get("initial_balance", 10000.0)
|
||||||
self.current_balance = self.initial_balance
|
self.current_balance = self.initial_balance
|
||||||
self.position = 0.0
|
self.position = 0.0
|
||||||
self.position_token = ""
|
self.position_token = ""
|
||||||
|
self.entry_price: Optional[float] = None
|
||||||
|
self.entry_time: Optional[int] = None
|
||||||
self.trades: List[Dict[str, Any]] = []
|
self.trades: List[Dict[str, Any]] = []
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
@@ -103,11 +108,73 @@ class BacktestEngine:
|
|||||||
|
|
||||||
timestamp = kline.get("timestamp", 0)
|
timestamp = kline.get("timestamp", 0)
|
||||||
|
|
||||||
|
if self.position > 0 and self.entry_price is not None:
|
||||||
|
exit_info = self._check_risk_management(price, timestamp)
|
||||||
|
if exit_info:
|
||||||
|
await self._execute_risk_exit(price, timestamp, exit_info)
|
||||||
|
continue
|
||||||
|
|
||||||
for condition in self.conditions:
|
for condition in self.conditions:
|
||||||
if self._check_condition(condition, klines, i, price):
|
if self._check_condition(condition, klines, i, price):
|
||||||
await self._execute_actions(price, timestamp, condition)
|
await self._execute_actions(price, timestamp, condition)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def _check_risk_management(
|
||||||
|
self, current_price: float, timestamp: int
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
if self.position <= 0 or self.entry_price is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.stop_loss_percent is not None:
|
||||||
|
stop_loss_price = self.entry_price * (1 - self.stop_loss_percent / 100)
|
||||||
|
if current_price <= stop_loss_price:
|
||||||
|
return {"reason": "stop_loss", "price": stop_loss_price}
|
||||||
|
|
||||||
|
if self.take_profit_percent is not None:
|
||||||
|
take_profit_price = self.entry_price * (1 + self.take_profit_percent / 100)
|
||||||
|
if current_price >= take_profit_price:
|
||||||
|
return {"reason": "take_profit", "price": take_profit_price}
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _execute_risk_exit(
|
||||||
|
self, price: float, timestamp: int, exit_info: Dict[str, Any]
|
||||||
|
):
|
||||||
|
if self.position <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
reason = exit_info["reason"]
|
||||||
|
sell_amount = self.position * price
|
||||||
|
self.current_balance += sell_amount
|
||||||
|
self.trades.append(
|
||||||
|
{
|
||||||
|
"type": "sell",
|
||||||
|
"token": self.position_token,
|
||||||
|
"price": price,
|
||||||
|
"amount": sell_amount,
|
||||||
|
"quantity": self.position,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"exit_reason": reason,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.signals.append(
|
||||||
|
{
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"bot_id": self.bot_id,
|
||||||
|
"run_id": self.run_id,
|
||||||
|
"signal_type": "sell",
|
||||||
|
"token": self.position_token,
|
||||||
|
"price": price,
|
||||||
|
"confidence": 1.0,
|
||||||
|
"reasoning": f"Risk management triggered {reason}",
|
||||||
|
"executed": False,
|
||||||
|
"created_at": datetime.utcnow(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.position = 0
|
||||||
|
self.entry_price = None
|
||||||
|
self.entry_time = None
|
||||||
|
|
||||||
def _check_condition(
|
def _check_condition(
|
||||||
self,
|
self,
|
||||||
condition: Dict[str, Any],
|
condition: Dict[str, Any],
|
||||||
@@ -173,6 +240,8 @@ class BacktestEngine:
|
|||||||
self.position += amount / price
|
self.position += amount / price
|
||||||
self.current_balance -= amount
|
self.current_balance -= amount
|
||||||
self.position_token = token
|
self.position_token = token
|
||||||
|
self.entry_price = price
|
||||||
|
self.entry_time = timestamp
|
||||||
self.trades.append(
|
self.trades.append(
|
||||||
{
|
{
|
||||||
"type": "buy",
|
"type": "buy",
|
||||||
@@ -209,9 +278,12 @@ class BacktestEngine:
|
|||||||
"amount": sell_amount,
|
"amount": sell_amount,
|
||||||
"quantity": self.position,
|
"quantity": self.position,
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
|
"exit_reason": "manual",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.position = 0
|
self.position = 0
|
||||||
|
self.entry_price = None
|
||||||
|
self.entry_time = None
|
||||||
self.signals.append(
|
self.signals.append(
|
||||||
{
|
{
|
||||||
"id": str(uuid.uuid4()),
|
"id": str(uuid.uuid4()),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import uuid
|
|||||||
import asyncio
|
import asyncio
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
from ..backtest.ave_client import AveCloudClient
|
from ..ave.client import AveCloudClient
|
||||||
|
|
||||||
|
|
||||||
class SimulateEngine:
|
class SimulateEngine:
|
||||||
@@ -20,6 +20,9 @@ class SimulateEngine:
|
|||||||
self.strategy_config = config.get("strategy_config", {})
|
self.strategy_config = config.get("strategy_config", {})
|
||||||
self.conditions = self.strategy_config.get("conditions", [])
|
self.conditions = self.strategy_config.get("conditions", [])
|
||||||
self.actions = self.strategy_config.get("actions", [])
|
self.actions = self.strategy_config.get("actions", [])
|
||||||
|
self.risk_management = self.strategy_config.get("risk_management", {})
|
||||||
|
self.stop_loss_percent = self.risk_management.get("stop_loss_percent")
|
||||||
|
self.take_profit_percent = self.risk_management.get("take_profit_percent")
|
||||||
self.check_interval = config.get("check_interval", 60)
|
self.check_interval = config.get("check_interval", 60)
|
||||||
self.duration_seconds = config.get("duration_seconds", 3600)
|
self.duration_seconds = config.get("duration_seconds", 3600)
|
||||||
self.auto_execute = config.get("auto_execute", False)
|
self.auto_execute = config.get("auto_execute", False)
|
||||||
@@ -29,6 +32,12 @@ class SimulateEngine:
|
|||||||
self.started_at: Optional[datetime] = None
|
self.started_at: Optional[datetime] = None
|
||||||
self.last_price: Optional[float] = None
|
self.last_price: Optional[float] = None
|
||||||
self.last_volume: Optional[float] = None
|
self.last_volume: Optional[float] = None
|
||||||
|
self.position: float = 0.0
|
||||||
|
self.position_token: str = ""
|
||||||
|
self.entry_price: Optional[float] = None
|
||||||
|
self.entry_time: Optional[int] = None
|
||||||
|
self.current_balance: float = config.get("initial_balance", 10000.0)
|
||||||
|
self.trades: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
async def run(self) -> Dict[str, Any]:
|
async def run(self) -> Dict[str, Any]:
|
||||||
self.running = True
|
self.running = True
|
||||||
@@ -94,11 +103,70 @@ class SimulateEngine:
|
|||||||
):
|
):
|
||||||
timestamp = int(datetime.utcnow().timestamp() * 1000)
|
timestamp = int(datetime.utcnow().timestamp() * 1000)
|
||||||
|
|
||||||
|
if self.position > 0 and self.entry_price is not None:
|
||||||
|
exit_info = self._check_risk_management(current_price, timestamp)
|
||||||
|
if exit_info:
|
||||||
|
await self._execute_risk_exit(current_price, timestamp, exit_info)
|
||||||
|
return
|
||||||
|
|
||||||
for condition in self.conditions:
|
for condition in self.conditions:
|
||||||
if self._check_condition(condition, current_price, current_volume):
|
if self._check_condition(condition, current_price, current_volume):
|
||||||
await self._execute_actions(current_price, timestamp, condition)
|
await self._execute_actions(current_price, timestamp, condition)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def _check_risk_management(
|
||||||
|
self, current_price: float, timestamp: int
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
|
if self.position <= 0 or self.entry_price is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.stop_loss_percent is not None:
|
||||||
|
stop_loss_price = self.entry_price * (1 - self.stop_loss_percent / 100)
|
||||||
|
if current_price <= stop_loss_price:
|
||||||
|
return {"reason": "stop_loss", "price": stop_loss_price}
|
||||||
|
|
||||||
|
if self.take_profit_percent is not None:
|
||||||
|
take_profit_price = self.entry_price * (1 + self.take_profit_percent / 100)
|
||||||
|
if current_price >= take_profit_price:
|
||||||
|
return {"reason": "take_profit", "price": take_profit_price}
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _execute_risk_exit(
|
||||||
|
self, price: float, timestamp: int, exit_info: Dict[str, Any]
|
||||||
|
):
|
||||||
|
if self.position <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
reason = exit_info["reason"]
|
||||||
|
self.trades.append(
|
||||||
|
{
|
||||||
|
"type": "sell",
|
||||||
|
"token": self.position_token,
|
||||||
|
"price": price,
|
||||||
|
"quantity": self.position,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"exit_reason": reason,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.signals.append(
|
||||||
|
{
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"bot_id": self.bot_id,
|
||||||
|
"run_id": self.run_id,
|
||||||
|
"signal_type": "sell",
|
||||||
|
"token": self.position_token,
|
||||||
|
"price": price,
|
||||||
|
"confidence": 1.0,
|
||||||
|
"reasoning": f"Risk management triggered {reason}",
|
||||||
|
"executed": self.auto_execute,
|
||||||
|
"created_at": datetime.utcnow(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.position = 0
|
||||||
|
self.entry_price = None
|
||||||
|
self.entry_time = None
|
||||||
|
|
||||||
def _check_condition(
|
def _check_condition(
|
||||||
self,
|
self,
|
||||||
condition: Dict[str, Any],
|
condition: Dict[str, Any],
|
||||||
@@ -146,11 +214,32 @@ class SimulateEngine:
|
|||||||
token = matched_condition.get("token", self.token)
|
token = matched_condition.get("token", self.token)
|
||||||
reasoning = f"Condition {matched_condition.get('type')} triggered"
|
reasoning = f"Condition {matched_condition.get('type')} triggered"
|
||||||
|
|
||||||
|
for action in self.actions:
|
||||||
|
action_type = action.get("type", "")
|
||||||
|
if action_type == "buy":
|
||||||
|
amount_percent = action.get("amount_percent", 10)
|
||||||
|
amount = self.current_balance * (amount_percent / 100)
|
||||||
|
self.position += amount / price
|
||||||
|
self.position_token = token
|
||||||
|
self.entry_price = price
|
||||||
|
self.entry_time = timestamp
|
||||||
|
self.current_balance -= amount
|
||||||
|
self.trades.append(
|
||||||
|
{
|
||||||
|
"type": "buy",
|
||||||
|
"token": token,
|
||||||
|
"price": price,
|
||||||
|
"amount": amount,
|
||||||
|
"quantity": amount / price,
|
||||||
|
"timestamp": timestamp,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
signal = {
|
signal = {
|
||||||
"id": str(uuid.uuid4()),
|
"id": str(uuid.uuid4()),
|
||||||
"bot_id": self.bot_id,
|
"bot_id": self.bot_id,
|
||||||
"run_id": self.run_id,
|
"run_id": self.run_id,
|
||||||
"signal_type": "signal",
|
"signal_type": action_type,
|
||||||
"token": token,
|
"token": token,
|
||||||
"price": price,
|
"price": price,
|
||||||
"confidence": 0.8,
|
"confidence": 0.8,
|
||||||
|
|||||||
Reference in New Issue
Block a user