-
Notifications
You must be signed in to change notification settings - Fork 57
fix precision is over the maximum defined #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ | |
| from datetime import datetime | ||
| from src.config import config | ||
| from src.utils.logger import log | ||
| from decimal import Decimal, ROUND_DOWN | ||
| import time | ||
|
|
||
|
|
||
| class BinanceClient: | ||
|
|
@@ -30,6 +32,13 @@ def __init__(self, api_key: str = None, api_secret: str = None, testnet: bool = | |
| ) | ||
| else: | ||
| self.client = Client(self.api_key, self.api_secret) | ||
|
|
||
| self._futures_rules = {} | ||
| self._futures_rules_ts = 0.0 | ||
| self._futures_rules_ttl = 3600 # 1h | ||
| if self.client is not None: | ||
| self._refresh_futures_rules() | ||
|
|
||
| except Exception as e: | ||
| # Allow dashboard to start even if Binance is unreachable | ||
| self.client = None | ||
|
|
@@ -44,6 +53,53 @@ def __init__(self, api_key: str = None, api_secret: str = None, testnet: bool = | |
|
|
||
| log.info(f"Binance client initialized (testnet: {self.testnet})") | ||
|
|
||
| def _refresh_futures_rules(self) -> None: | ||
| info = self.client.futures_exchange_info() | ||
| rules = {} | ||
| for s in info.get("symbols", []): | ||
| fs = {f["filterType"]: f for f in s.get("filters", [])} | ||
| lot = fs.get("LOT_SIZE", {}) | ||
| mlot = fs.get("MARKET_LOT_SIZE", {}) | ||
| pf = fs.get("PRICE_FILTER", {}) | ||
| rules[s["symbol"]] = { | ||
| "lot_step": Decimal(str(lot.get("stepSize", "0"))), | ||
| "lot_min": Decimal(str(lot.get("minQty", "0"))), | ||
| "mkt_step": Decimal(str(mlot.get("stepSize", "0"))), | ||
| "mkt_min": Decimal(str(mlot.get("minQty", "0"))), | ||
| "tick": Decimal(str(pf.get("tickSize", "0"))), | ||
| } | ||
| self._futures_rules = rules | ||
| self._futures_rules_ts = time.time() | ||
| # print(rules) | ||
|
|
||
| def _get_rules(self, symbol: str): | ||
| if time.time() - self._futures_rules_ts > self._futures_rules_ttl: | ||
| self._refresh_futures_rules() | ||
| if symbol not in self._futures_rules: | ||
| self._refresh_futures_rules() | ||
| return self._futures_rules[symbol] | ||
|
|
||
| @staticmethod | ||
| def _floor_to_step(v: Decimal, step: Decimal) -> Decimal: | ||
| if step <= 0: | ||
| return v | ||
| return (v / step).to_integral_value(rounding=ROUND_DOWN) * step | ||
|
|
||
| def normalize_futures_qty(self, symbol: str, qty: float, is_market: bool = True) -> str: | ||
| r = self._get_rules(symbol) | ||
| q = Decimal(str(qty)) | ||
| step = r["mkt_step"] if (is_market and r["mkt_step"] > 0) else r["lot_step"] | ||
| min_q = r["mkt_min"] if (is_market and r["mkt_step"] > 0) else r["lot_min"] | ||
| q = self._floor_to_step(q, step) | ||
| if q <= 0 or q < min_q: | ||
| raise ValueError(f"{symbol} qty invalid: {q} < minQty {min_q}") | ||
| return format(q.normalize(), "f") | ||
|
|
||
| def normalize_futures_price(self, symbol: str, price: float) -> str: | ||
| r = self._get_rules(symbol) | ||
| p = self._floor_to_step(Decimal(str(price)), r["tick"]) | ||
| return format(p.normalize(), "f") | ||
|
|
||
| def get_klines(self, symbol: str, interval: str, limit: int = 500, start_time: int = None) -> List[Dict]: | ||
| """ | ||
| 获取K线数据 | ||
|
|
@@ -348,13 +404,14 @@ def place_market_order( | |
| position_side: 持仓方向 (BOTH/LONG/SHORT), 双向持仓用LONG/SHORT | ||
| """ | ||
| try: | ||
| # 构建订单参数 | ||
| norm_qty = self.normalize_futures_qty(symbol, quantity, is_market=True) | ||
| # Build order parameters | ||
| order_params = { | ||
| 'symbol': symbol, | ||
| 'side': side, | ||
| 'type': 'MARKET', | ||
| 'quantity': quantity, | ||
| 'positionSide': position_side | ||
| 'quantity': norm_qty | ||
| # 'positionSide': position_side | ||
|
Comment on lines
+413
to
+414
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Commenting out the 'quantity': norm_qty,
'positionSide': position_side |
||
| } | ||
|
|
||
| # 只在需要时添加 reduceOnly 参数 | ||
|
|
@@ -363,7 +420,7 @@ def place_market_order( | |
|
|
||
| order = self.client.futures_create_order(**order_params) | ||
|
|
||
| log.info(f"Market order placed: {side} {quantity} {symbol} (positionSide={position_side})") | ||
| log.info(f"Market order placed: {side} {norm_qty} {symbol} (positionSide={position_side})") | ||
| return order | ||
|
|
||
| except BinanceAPIException as e: | ||
|
|
@@ -380,16 +437,18 @@ def place_limit_order( | |
| ) -> Dict: | ||
| """下限价单""" | ||
| try: | ||
| norm_qty = self.normalize_futures_qty(symbol, quantity, is_market=False) | ||
| norm_price = self.normalize_futures_price(symbol, price) | ||
| order = self.client.futures_create_order( | ||
| symbol=symbol, | ||
| side=side, | ||
| type='LIMIT', | ||
| quantity=quantity, | ||
| price=price, | ||
| quantity=norm_qty, | ||
| price=norm_price, | ||
| timeInForce=time_in_force | ||
| ) | ||
|
|
||
| log.info(f"Limit order placed: {side} {quantity} {symbol} @ {price}") | ||
| log.info(f"Limit order placed: {side} {norm_qty} {symbol} @ {norm_price}") | ||
| return order | ||
|
|
||
| except BinanceAPIException as e: | ||
|
|
@@ -426,29 +485,33 @@ def set_stop_loss_take_profit( | |
|
|
||
| # 止损单 | ||
| if stop_loss_price: | ||
| norm_sl = self.normalize_futures_price(symbol, stop_loss_price) | ||
| sl_order = self.client.futures_create_order( | ||
| symbol=symbol, | ||
| side=side, | ||
| type='STOP_MARKET', | ||
| stopPrice=stop_loss_price, | ||
| stopPrice=norm_sl, | ||
| closePosition=True, | ||
| positionSide=position_side # 添加持仓方向 | ||
| positionSide='BOTH' | ||
| # positionSide=position_side # specify position side | ||
|
Comment on lines
+495
to
+496
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoding positionSide=position_side |
||
| ) | ||
| orders.append(sl_order) | ||
| log.info(f"Stop loss set: {stop_loss_price} (positionSide={position_side})") | ||
| log.info(f"Stop loss set: {norm_sl} (positionSide={position_side})") | ||
|
|
||
| # 止盈单 | ||
| if take_profit_price: | ||
| norm_tp = self.normalize_futures_price(symbol, take_profit_price) | ||
| tp_order = self.client.futures_create_order( | ||
| symbol=symbol, | ||
| side=side, | ||
| type='TAKE_PROFIT_MARKET', | ||
| stopPrice=take_profit_price, | ||
| stopPrice=norm_tp, | ||
| closePosition=True, | ||
| positionSide=position_side # 添加持仓方向 | ||
| positionSide='BOTH' | ||
| # positionSide=position_side # specify position side | ||
| ) | ||
| orders.append(tp_order) | ||
| log.info(f"Take profit set: {take_profit_price} (positionSide={position_side})") | ||
| log.info(f"Take profit set: {norm_tp} (positionSide={position_side})") | ||
|
|
||
| return orders | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -11,7 +11,7 @@ | |||||
| class QuantClient: | ||||||
| """外部量化 API 客户端""" | ||||||
|
|
||||||
| BASE_URL = "http://nofxaios.com:30006/api/coin" | ||||||
| BASE_URL = "http://nofxos.ai/api/coin" | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line uses the unencrypted HTTP protocol to communicate with
Suggested change
|
||||||
| @property | ||||||
| def auth_token(self) -> str: | ||||||
| """从环境变量动态获取最新的认证令牌""" | ||||||
|
|
@@ -78,7 +78,7 @@ async def fetch_ai500_list(self) -> Dict: | |||||
| """ | ||||||
| 获取 AI500 优质币池列表 | ||||||
| """ | ||||||
| url = f"http://nofxaios.com:30006/api/ai500/list?auth={self.auth_token}" | ||||||
| url = f"http://nofxos.ai/api/ai500/list?auth={self.auth_token}" | ||||||
|
|
||||||
| try: | ||||||
| session = await self._get_session() | ||||||
|
|
@@ -103,7 +103,7 @@ async def fetch_oi_ranking(self, ranking_type: str = 'top', limit: int = 20, dur | |||||
| duration: 时间周期 (1h, 4h, 24h) | ||||||
| """ | ||||||
| endpoint = "top-ranking" if ranking_type == 'top' else "low-ranking" | ||||||
| url = f"http://nofxaios.com:30006/api/oi/{endpoint}?limit={limit}&duration={duration}&auth={self.auth_token}" | ||||||
| url = f"http://nofxos.ai/api/oi/{endpoint}?limit={limit}&duration={duration}&auth={self.auth_token}" | ||||||
|
|
||||||
| try: | ||||||
| session = await self._get_session() | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method can be made more efficient and clearer. The two
ifstatements can be combined into one to avoid a potential redundant call to_refresh_futures_rules(). Also, adding a return type hint (-> Dict) would improve code readability and allow static analysis tools to catch potential issues.