-
Notifications
You must be signed in to change notification settings - Fork 731
fix: patch 5 medium-severity findings from deep security audit ,Support fractional quantities for crypto spot trading on Delta Exchange ,Fix position book for crypto spot, rmoney FD audit fix and Groww: Optimize master contract and normalize streaming logs #1087
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
Changes from all commits
2ce1f0b
957e0b6
59ff01e
e2f0d64
595aad3
c1e78e4
5c0bb4d
8081281
75cf1c1
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 |
|---|---|---|
|
|
@@ -228,20 +228,61 @@ def get_trade_book(auth): | |
| # --------------------------------------------------------------------------- | ||
|
|
||
| def get_positions(auth): | ||
| """Fetch all open margined positions.""" | ||
| """ | ||
| Fetch all open positions — both derivatives (margined) and spot (wallet). | ||
|
|
||
| Derivatives come from GET /v2/positions/margined. | ||
| Spot holdings come from GET /v2/wallet/balances — non-INR assets with | ||
| a non-zero balance are synthesised into position-like dicts so they | ||
| appear in the OpenAlgo position book alongside derivative positions. | ||
| """ | ||
| positions = [] | ||
|
|
||
| # 1. Derivative positions (perpetual futures, options) | ||
| try: | ||
| result = get_api_response("/v2/positions/margined", auth, method="GET") | ||
| if result.get("success"): | ||
| return result.get("result", []) | ||
| logger.warning(f"[DeltaExchange] get_positions unexpected response: {result}") | ||
| return [] | ||
| positions.extend(result.get("result", [])) | ||
| else: | ||
| logger.warning(f"[DeltaExchange] get_positions/margined unexpected: {result}") | ||
| except Exception as e: | ||
| logger.error(f"[DeltaExchange] Exception in get_positions: {e}") | ||
| return [] | ||
| logger.error(f"[DeltaExchange] Exception in get_positions/margined: {e}") | ||
|
|
||
| # 2. Spot holdings from wallet balances | ||
| try: | ||
| wallet_result = get_api_response("/v2/wallet/balances", auth, method="GET") | ||
| if wallet_result.get("success"): | ||
| for asset in wallet_result.get("result", []): | ||
| if not isinstance(asset, dict): | ||
| continue | ||
| symbol = asset.get("asset_symbol", "") or asset.get("symbol", "") | ||
| # Skip INR (settlement currency) and zero-balance assets | ||
| if symbol in ("INR", "USD", "") or not symbol: | ||
| continue | ||
| balance = float(asset.get("balance", 0) or 0) | ||
| blocked = float(asset.get("blocked_margin", 0) or 0) | ||
| size = balance - blocked # available spot holding | ||
| if size <= 0: | ||
| continue | ||
| # Synthesise a position-like dict matching /v2/positions/margined structure | ||
| spot_symbol = f"{symbol}_INR" | ||
| positions.append({ | ||
| "product_id": asset.get("asset_id", ""), | ||
| "product_symbol": spot_symbol, | ||
| "size": size, | ||
| "entry_price": "0", # Wallet doesn't track entry price | ||
| "realized_pnl": "0", | ||
| "unrealized_pnl": "0", | ||
| "_is_spot": True, # Internal flag for downstream mapping | ||
| }) | ||
| except Exception as e: | ||
| logger.error(f"[DeltaExchange] Exception fetching spot wallet positions: {e}") | ||
|
|
||
| return positions | ||
|
|
||
|
|
||
| def get_holdings(auth): | ||
| """Delta Exchange is a derivatives-only exchange; equity holdings are not applicable.""" | ||
| """Delta Exchange has no equity holdings concept; spot is shown in positions.""" | ||
| return [] | ||
|
|
||
|
|
||
|
|
@@ -406,22 +447,22 @@ def place_smartorder_api(data, auth): | |
| symbol = data.get("symbol") | ||
| exchange = data.get("exchange") | ||
| product = data.get("product") | ||
| position_size = int(data.get("position_size", "0")) | ||
| position_size = float(data.get("position_size", "0")) | ||
|
Contributor
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. P1: Using float for smart-order position arithmetic can produce precision drift that gets truncated by downstream Prompt for AI agents |
||
|
|
||
| current_position = int( | ||
| current_position = float( | ||
| get_open_position(symbol, exchange, map_product_type(product), auth) | ||
| ) | ||
| logger.info( | ||
| f"[DeltaExchange] SmartOrder: target={position_size} current={current_position}" | ||
| ) | ||
|
|
||
| if position_size == 0 and current_position == 0 and int(data["quantity"]) != 0: | ||
| if position_size == 0 and current_position == 0 and float(data["quantity"]) != 0: | ||
| return place_order_api(data, auth) | ||
|
|
||
| if position_size == current_position: | ||
| msg = ( | ||
| "No OpenPosition Found. Not placing Exit order." | ||
| if int(data["quantity"]) == 0 | ||
| if float(data["quantity"]) == 0 | ||
| else "No action needed. Position size matches current position" | ||
| ) | ||
| return res, {"status": "success", "message": msg}, None | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -253,6 +253,8 @@ def _to_canonical_symbol(delta_symbol: str, instrument_type: str, expiry: str) - | |
| "futures": "FUT", | ||
| "call_options": "CE", | ||
| "put_options": "PE", | ||
| "spot": "SPOT", | ||
| "move_options": "MOVE", | ||
| "interest_rate_swaps": "IRS", | ||
| "spreads": "SPREAD", | ||
| "options_combos": "COMBO", | ||
|
|
@@ -386,7 +388,7 @@ def process_delta_products(products): | |
| expiry ← settlement_time (None → "" for perpetuals; | ||
| ISO string → "DD-MON-YY" for futures/options) | ||
| strike ← 0.0 (strike is encoded in the symbol for options) | ||
| lotsize ← 1 (1 contract; contract_value gives underlying units) | ||
| lotsize ← product_specs.min_order_size or 1 (fractional for spot, e.g. 0.0001 BTC) | ||
| instrumenttype ← contract_type (mapped via CONTRACT_TYPE_MAP) | ||
| tick_size ← tick_size (string → float) | ||
| """ | ||
|
|
@@ -437,16 +439,30 @@ def process_delta_products(products): | |
| except (ValueError, TypeError): | ||
| tick_size = 0.0 | ||
|
|
||
| # Extract strike for option contracts from symbol (e.g. C-BTC-80000-280225 -> 80000.0) | ||
| # Extract strike price — use the API field directly when available, | ||
| # fall back to parsing from the symbol (e.g. C-BTC-80000-280225 -> 80000.0) | ||
| symbol_str = p.get("symbol", "") | ||
| strike_val = 0.0 | ||
| if instrument_type in ("CE", "PE", "TCE", "TPE", "SYNCE", "SYNPE"): | ||
| parts_s = symbol_str.split("-") | ||
| if len(parts_s) >= 3: | ||
| try: | ||
| strike_val = float(parts_s[2]) | ||
| except (ValueError, TypeError): | ||
| strike_val = 0.0 | ||
| try: | ||
| strike_val = float(p.get("strike_price") or 0) | ||
| except (ValueError, TypeError): | ||
| strike_val = 0.0 | ||
| # Fallback: parse from symbol if API field missing | ||
| if strike_val == 0.0: | ||
| parts_s = symbol_str.split("-") | ||
| if len(parts_s) >= 3: | ||
| try: | ||
| strike_val = float(parts_s[2]) | ||
| except (ValueError, TypeError): | ||
| strike_val = 0.0 | ||
|
|
||
| # Lot size: use min_order_size from product_specs (important for spot | ||
| # instruments where fractional quantities are allowed, e.g. 0.0001 BTC) | ||
| try: | ||
| lotsize = float(product_specs.get("min_order_size") or 1) | ||
|
Contributor
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. P1: Prompt for AI agents |
||
| except (ValueError, TypeError): | ||
| lotsize = 1.0 | ||
|
|
||
| # Build OpenAlgo canonical symbol (exchange = CRYPTO, broker-agnostic format) | ||
| canonical_symbol = _to_canonical_symbol(symbol_str, instrument_type, expiry) | ||
|
|
@@ -461,7 +477,7 @@ def process_delta_products(products): | |
| "brexchange": "DELTAIN", # Broker identifier (Delta Exchange India) | ||
| "expiry": expiry, | ||
| "strike": strike_val, | ||
| "lotsize": 1, | ||
| "lotsize": lotsize, | ||
| "instrumenttype": instrument_type, | ||
| "tick_size": tick_size, | ||
| "contract_value": float(p.get("contract_value") or 1.0), | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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.
P2: Fractional spot quantities are added to positions, but downstream close-all logic truncates size to int, causing sub-1 spot positions to be silently skipped.
Prompt for AI agents