Compare commits

..

3 Commits

4 changed files with 192 additions and 85 deletions

View File

@@ -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]]:

View File

@@ -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 {}

View File

@@ -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()),

View File

@@ -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,20 +214,41 @@ 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"
signal = { for action in self.actions:
"id": str(uuid.uuid4()), action_type = action.get("type", "")
"bot_id": self.bot_id, if action_type == "buy":
"run_id": self.run_id, amount_percent = action.get("amount_percent", 10)
"signal_type": "signal", amount = self.current_balance * (amount_percent / 100)
"token": token, self.position += amount / price
"price": price, self.position_token = token
"confidence": 0.8, self.entry_price = price
"reasoning": reasoning, self.entry_time = timestamp
"executed": self.auto_execute, self.current_balance -= amount
"created_at": datetime.utcnow(), self.trades.append(
} {
"type": "buy",
"token": token,
"price": price,
"amount": amount,
"quantity": amount / price,
"timestamp": timestamp,
}
)
self.signals.append(signal) signal = {
"id": str(uuid.uuid4()),
"bot_id": self.bot_id,
"run_id": self.run_id,
"signal_type": action_type,
"token": token,
"price": price,
"confidence": 0.8,
"reasoning": reasoning,
"executed": self.auto_execute,
"created_at": datetime.utcnow(),
}
self.signals.append(signal)
async def stop(self): async def stop(self):
self.running = False self.running = False