|
| 1 | +--- |
| 2 | +alwaysApply: true |
| 3 | +--- |
| 4 | + |
| 5 | +## Python |
| 6 | + |
| 7 | +- **Imports ALWAYS go to the top of the file - NEVER import within functions, unless it |
| 8 | + would create circular import** ⚠️ |
| 9 | +- Use Decimal for financial calculations - never use float for money/prices |
| 10 | +- Don't abbreviate packages (use "pandas" not "pd", "numpy" not "np") |
| 11 | + |
| 12 | +## Modern python |
| 13 | + |
| 14 | +We use python 3.13+ and follow modern best practices, including: |
| 15 | + |
| 16 | +- Use `Path` lib for files instead of `open` |
| 17 | +- Use `var!s` instead of `str(var)` |
| 18 | +- Prefer walrus operator (:=) to reduce repetition in code |
| 19 | +- Use modern union syntax `X | Y` instead of `Union[X, Y]` or `(X, Y)` in isinstance |
| 20 | + calls |
| 21 | + |
| 22 | +## Libraries to use |
| 23 | + |
| 24 | +- Use `arrow` for datetime handling, and `dateparse` for parsing human supplied dates |
| 25 | +- Use `rich` for making command line applications more beautiful and more user friendly |
| 26 | +- We are migrating from `requests` to `httpx` so new code should use `httpx` |
| 27 | + |
| 28 | +## Exceptions - READ THIS CAREFULLY 🚨 |
| 29 | + |
| 30 | +### ⚠️ CRITICAL: try/except is FORBIDDEN except in these EXACT 2 scenarios: |
| 31 | + |
| 32 | +**SCENARIO 1: Handling a SPECIFIC exception type with actual handling logic** |
| 33 | + |
| 34 | +```python |
| 35 | +# ✅ ALLOWED: Specific exception + real handling + honeybadger notification |
| 36 | +try: |
| 37 | + result = process_transaction(data) |
| 38 | +except InvalidTransactionError as e: |
| 39 | + honeybadger.notify(e, context={"transaction_id": tx_id, "error_type": "validation"}) |
| 40 | + return {"status": "failed", "reason": "validation_error"} |
| 41 | +``` |
| 42 | + |
| 43 | +**SCENARIO 2: Processing loop where you want to continue on errors** |
| 44 | + |
| 45 | +```python |
| 46 | +# ✅ ALLOWED: Loop processing where individual failures shouldn't stop the loop |
| 47 | +for item in items: |
| 48 | + try: |
| 49 | + process_item(item) |
| 50 | + except ProcessingError as e: |
| 51 | + honeybadger.notify(e, context={"item_id": item.id, "batch_id": batch.id}) |
| 52 | + continue # Continue processing other items |
| 53 | +``` |
| 54 | + |
| 55 | +### 🚫 ABSOLUTELY FORBIDDEN - NEVER EVER DO THESE: |
| 56 | + |
| 57 | +```python |
| 58 | +# 🚫 NEVER: Generic exception catching |
| 59 | +try: |
| 60 | + some_function() |
| 61 | +except Exception as e: |
| 62 | + logger.error("Something went wrong") # This hides errors from us! |
| 63 | + |
| 64 | +# 🚫 NEVER: Log and continue without specific handling |
| 65 | +try: |
| 66 | + some_function() |
| 67 | +except SomeError as e: |
| 68 | + logger.error(f"Error: {e}") # Just logging is NOT handling! |
| 69 | + |
| 70 | +# 🚫 NEVER: Swallowing exceptions |
| 71 | +try: |
| 72 | + some_function() |
| 73 | +except Exception: |
| 74 | + pass # Absolutely forbidden! |
| 75 | +``` |
| 76 | + |
| 77 | +### ✅ DEFAULT APPROACH - 99% of the time, do this: |
| 78 | + |
| 79 | +```python |
| 80 | +# ✅ PREFERRED: Let exceptions bubble up to HoneyBadger |
| 81 | +result = process_transaction(data) # No try/except at all! |
| 82 | +user = User.objects.get(id=user_id) # Let it crash if user doesn't exist! |
| 83 | +api_response = requests.get(url) # Let network errors bubble up! |
| 84 | +``` |
| 85 | + |
| 86 | +### 🎯 REMEMBER: |
| 87 | + |
| 88 | +- **Exceptions are NOT errors to be hidden - they're signals something needs attention** |
| 89 | +- **HoneyBadger catches everything that bubbles up - that's what we want** |
| 90 | +- **If you're tempted to use try/except, ask: "Am I actually HANDLING this or just |
| 91 | + HIDING it?"** |
| 92 | +- **Logging an error is NOT the same as handling an error** |
| 93 | + |
| 94 | +### 🚨 AI INSTRUCTION COMPLIANCE: |
| 95 | + |
| 96 | +If you are an AI reading this and you write try/except code that doesn't fit EXACTLY |
| 97 | +into scenarios 1 or 2 above, you are directly violating explicit instructions. The human |
| 98 | +has been crystal clear about this requirement. Do not rationalize, do not make |
| 99 | +exceptions, do not "be helpful" by catching errors. Follow the rules. |
| 100 | + |
| 101 | +## 🚫 Defensive Programming - hasattr/getattr |
| 102 | + |
| 103 | +**NEVER use hasattr() or getattr() unless there's a REALLY good reason.** |
| 104 | + |
| 105 | +### ❌ BAD: Defensive programming bullshit |
| 106 | + |
| 107 | +```python |
| 108 | +# 🚫 NEVER: Checking if methods exist in your own class |
| 109 | +if hasattr(self, "create_trade_from_fill") and callable(self.create_trade_from_fill): |
| 110 | + self.create_trade_from_fill(position, fill, trade_type) |
| 111 | + |
| 112 | +# 🚫 NEVER: Using getattr with defaults for config |
| 113 | +value = getattr(config, "SOME_SETTING", "default") |
| 114 | +``` |
| 115 | + |
| 116 | +### ✅ GOOD: Trust your code |
| 117 | + |
| 118 | +```python |
| 119 | +# ✅ PREFERRED: Just call the method - it exists! |
| 120 | +self.create_trade_from_fill(position, fill, trade_type) |
| 121 | + |
| 122 | +# ✅ PREFERRED: Config entries exist - don't use defaults |
| 123 | +value = config.SOME_SETTING |
| 124 | +``` |
| 125 | + |
| 126 | +### 🎯 PHILOSOPHY: |
| 127 | + |
| 128 | +- **Trust Django ORM** - Model fields and methods exist |
| 129 | +- **Trust your own classes** - Methods you defined exist |
| 130 | +- **Fail fast** - If something doesn't exist, let it crash |
| 131 | +- **Broken is better than wrong** - Don't hide missing attributes with defaults |
| 132 | + |
| 133 | +### 🤏 RARE EXCEPTIONS: |
| 134 | + |
| 135 | +Only use hasattr/getattr when: |
| 136 | + |
| 137 | +- Dynamically handling external APIs with unknown schemas |
| 138 | +- Plugin systems where attributes genuinely might not exist |
| 139 | +- Legacy compatibility layers (temporarily) |
| 140 | + |
| 141 | +**But 99% of the time - just trust your code and let it fail explicitly.** |
| 142 | + |
| 143 | +## 🚫 Defensive Programming - .get() with defaults |
| 144 | + |
| 145 | +**NEVER use dict.get() with default values for critical data that SHOULD exist.** |
| 146 | + |
| 147 | +### ❌ BAD: Silent failures that hide bugs |
| 148 | + |
| 149 | +```python |
| 150 | +# 🚫 NEVER: Using .get() with defaults for critical API data |
| 151 | +filled_size = Decimal(str(order_data.get("filledSz", "0"))) # Hides missing data! |
| 152 | +position_size = position_data.get("szi", "0") # Could hide real positions! |
| 153 | +error_msg = result.get("error", "") # Wrong error message passed to handler! |
| 154 | + |
| 155 | +# 🚫 NEVER: Using .get() for required fields |
| 156 | +if fill.get("coin") == symbol and fill.get("time", 0) > cutoff: # Wrong filter logic! |
| 157 | +``` |
| 158 | + |
| 159 | +### ✅ GOOD: Fail fast on missing data |
| 160 | + |
| 161 | +```python |
| 162 | +# ✅ PREFERRED: Critical fields must exist or crash immediately |
| 163 | +filled_size = Decimal(str(order_data["filledSz"])) # Crashes if data is malformed |
| 164 | +position_size = position_data["szi"] # Crashes if position data is incomplete |
| 165 | +error_msg = result["error_msg"] # Crashes if error handling is broken |
| 166 | + |
| 167 | +# ✅ PREFERRED: Required fields for filtering/logic |
| 168 | +if fill["coin"] == symbol and fill["time"] > cutoff: # Crashes if data is incomplete |
| 169 | +``` |
| 170 | + |
| 171 | +### 🎯 WHEN TO USE .get() WITH DEFAULTS: |
| 172 | + |
| 173 | +Only use `.get()` with defaults when: |
| 174 | + |
| 175 | +1. **Optional configuration** - `config.get("optional_setting", "default")` |
| 176 | +2. **Optional metadata** - `metadata.get("description", "")` |
| 177 | +3. **Legitimate optional fields** - `kwargs.get("timeout", 30)` |
| 178 | +4. **External API responses with genuinely optional fields** - |
| 179 | + `api_response.get("optional_field")` |
| 180 | + |
| 181 | +### 🚨 THE DANGER: |
| 182 | + |
| 183 | +Using `.get()` with defaults for critical data creates **silent failures** that: |
| 184 | + |
| 185 | +- **Hide API schema changes** - If Hyperliquid changes field names, you get 0 instead of |
| 186 | + an error |
| 187 | +- **Corrupt financial data** - Missing position sizes default to 0, hiding real |
| 188 | + positions |
| 189 | +- **Break error handling** - Wrong error messages passed to exception mappers |
| 190 | +- **Create logic bugs** - Filters work on wrong data, missing critical records |
| 191 | +- **Cause hours of debugging** - Bugs are hidden instead of failing loudly |
| 192 | + |
| 193 | +### 💡 PHILOSOPHY: |
| 194 | + |
| 195 | +**"Broken is better than wrong"** - If the data structure isn't what we expect, crash |
| 196 | +immediately and alert us through HoneyBadger rather than silently continuing with |
| 197 | +potentially wrong default values that could lead to financial losses. |
| 198 | + |
| 199 | +## 🚨 Database Transactions |
| 200 | + |
| 201 | +**NEVER use Django's `transaction.atomic()`** - neither as decorator nor context |
| 202 | +manager. |
| 203 | + |
| 204 | +Instead: |
| 205 | + |
| 206 | +- Use individual atomic operations (update_or_create, etc.) |
| 207 | +- Design operations to be idempotent |
| 208 | +- Use database constraints for consistency |
| 209 | + |
| 210 | +## Logging |
| 211 | + |
| 212 | +Do not use built in logger. Use our logger which is loguru, from `helpers.logger` Use |
| 213 | +emojis when they are helpful. Use other log levels from loguru when helpful, such as |
| 214 | +logger.success() when a command completes. Add logging that is helpful not only for |
| 215 | +humans, but for AI to troubleshoot. Always use logger.exception in an exception catch, |
| 216 | +not logger.error() |
| 217 | + |
| 218 | +```python |
| 219 | +from helpers.logger import logger |
| 220 | +logger.info("Launch sequence initiated", extra={"mission_id": mission.id}) |
| 221 | +``` |
| 222 | + |
| 223 | +## 🎨 Formatting Helpers |
| 224 | + |
| 225 | +We have a set of consistent formatting helpers in `helpers.formatting` for displaying |
| 226 | +values across the app. Always use these instead of creating custom formatting logic. |
| 227 | + |
| 228 | +[formatting.py](mdc:helpers/formatting.py) |
| 229 | + |
| 230 | +For json formatting, see [json_utils.py](mdc:helpers/json_utils.py) |
0 commit comments