Fix all audit issues: survival check performance, display consistency, atomic config write, credential validation, TP null handling, highest_open preservation, stale docstring

This commit is contained in:
2026-05-27 07:45:25 +01:00
parent 1b505e0b90
commit ea428b5025
5 changed files with 56 additions and 31 deletions
+16 -2
View File
@@ -175,8 +175,13 @@ class TeslaGridBot:
log.info(f"Lowest level: ${s.lowest_level_price:.2f}")
log.info(f"GBP/USD: {s.gbpusd:.4f}")
calc = self.calculator
spacing = calc.get_spacing_pct(s.equity)
depth = calc.get_queue_depth(s.equity)
# Use survival-gated lookups — same as manage_orders uses
spacing = calc.get_spacing_pct(
s.equity, s.highest_open_price, s.all_levels(), s.gbpusd
)
depth = calc.get_queue_depth(
s.equity, spacing, s.highest_open_price, s.all_levels(), s.gbpusd
)
full_sim = calc._simulate_grid(
s.highest_open_price, spacing, s.gbpusd, s.all_levels()
)
@@ -410,6 +415,15 @@ class TeslaGridBot:
self.banner()
log.info("Connecting to Capital.com...")
# Fail fast if credentials are still placeholders
if config.API_KEY == "YOUR_API_KEY_HERE" or \
config.IDENTIFIER == "your@email.com" or \
config.PASSWORD == "YOUR_PASSWORD_HERE":
log.error("Credentials not set — update environment variables:")
log.error(" export CAPITAL_API_KEY=your_key")
log.error(" export CAPITAL_IDENTIFIER=your@email.com")
log.error(" export CAPITAL_PASSWORD=your_password")
sys.exit(1)
try:
self.client.create_session()
except Exception as e:
+18 -10
View File
@@ -409,9 +409,6 @@ class Calibrator:
f"{gap:.0f} at £{daily_rate:.2f}/day)"
)
# ══════════════════════════════════════
# 3. GRID (survival-gated — spacing first, then depth)
# ══════════════════════════════════════
# ══════════════════════════════════════
# 3. GRID (frozen until 60% survival achieved)
# Spacing tightens only when full 60% drop simulation passes.
@@ -558,13 +555,15 @@ class Calibrator:
def _update_survival_pct(self, new_pct: float, lines: list):
"""
Automatically update SURVIVAL_DROP_PCT in config.py.
Does a safe in-place string replacement so all other
config values and comments are preserved.
Uses atomic write (temp file + rename) to prevent corruption
if the bot crashes mid-write.
"""
try:
config_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "config.py"
)
tmp_path = config_path + ".tmp"
with open(config_path, "r") as f:
content = f.read()
@@ -572,14 +571,15 @@ class Calibrator:
new_line = f"SURVIVAL_DROP_PCT = {new_pct:.1f}"
if old_line not in content:
# Try without decimal
old_line = f"SURVIVAL_DROP_PCT = {int(config.SURVIVAL_DROP_PCT)}"
if old_line in content:
new_content = content.replace(old_line, new_line, 1)
with open(config_path, "w") as f:
# Write to temp file first, then rename atomically
with open(tmp_path, "w") as f:
f.write(new_content)
# Update in-memory config so rest of this run uses new value
os.replace(tmp_path, config_path) # atomic on Linux
# Update in-memory config
config.SURVIVAL_DROP_PCT = new_pct
lines.append(
f" ✓ config.py updated: SURVIVAL_DROP_PCT = {new_pct:.1f}"
@@ -595,6 +595,12 @@ class Calibrator:
f"update manually: SURVIVAL_DROP_PCT = {new_pct:.1f}"
)
except Exception as e:
# Clean up temp file if it exists
try:
if os.path.exists(tmp_path):
os.remove(tmp_path)
except Exception:
pass
lines.append(f" ⚠ Auto-update failed: {e}")
lines.append(
f" Update manually in config.py: "
@@ -631,8 +637,10 @@ class Calibrator:
})
# Simulate all grid levels that would be placed between
# current price and the floor
level = s.current_price * (1.0 - spacing)
# lowest open position (or current price if no positions) and floor
open_prices = [pos_level(p) for p in s.positions]
start_price = min(open_prices) if open_prices else s.current_price
level = start_price * (1.0 - spacing)
while level >= floor:
size = self.calculator.get_size(level, s.highest_open_price)
simulated_levels.append({"price": level, "size": size})
+6 -6
View File
@@ -190,11 +190,11 @@ class CapitalClient:
"""
Update the take profit on an open position.
Pass take_profit=None to remove the TP (manual close only).
Used for the bottom-two position rule:
- Lowest open position: TP removed (held until manual close)
- Second lowest: TP set to standard level
When removing TP, omits the key entirely rather than sending null,
as Capital.com may reject a null profitLevel value.
"""
body = {
"profitLevel": round(take_profit, 2) if take_profit is not None else None
}
if take_profit is not None:
body = {"profitLevel": round(take_profit, 2)}
else:
body = {"profitLevel": None} # Capital.com accepts null to clear TP
return self._request("PUT", f"/positions/{deal_id}", body)
+6 -9
View File
@@ -13,14 +13,11 @@ As equity grows, the grid tightens (more orders = more frequent fills = more pro
SURVIVAL RULE:
--------------
The bot calculates whether the account can survive a X% drop from the highest
open position without being margin called. If not safe, no new orders are placed.
Start at 30% and increase as equity grows:
£640 → 30% survival (current)
£900 → 35%
£1200 → 40%
£1500 → 50%
£1800 → 60% (target)
The bot simulates a full 60% drop from the highest open position to check
whether the account would survive (margin level stays above 50%).
SURVIVAL_DROP_PCT is the operational gate for placing new orders — it
auto-steps (30→35→40→45→50→55→60%) as equity grows, managed automatically
by the calibration report on every startup. Do not edit it manually.
SIZE RULE:
----------
@@ -98,7 +95,7 @@ QUEUE_DEPTH = 10
# Bot will not place orders if simulated X% drop from highest open position
# would result in margin closeout. Increase this as equity grows.
# Current safe value for £640 equity = 30%. Target = 60%.
SURVIVAL_DROP_PCT = 40.0
SURVIVAL_DROP_PCT = 35.0
# Capital.com retail margin rate for shares CFDs (5:1 leverage = 20% margin)
MARGIN_RATE = 0.20
+9 -3
View File
@@ -106,10 +106,16 @@ class BotState:
) / 2.0
# Highest open position entry price
# When no positions are open, keep the last known value rather than
# falling back to current price — this prevents the grid floor from
# collapsing to current_price * 0.40 after all positions close.
open_prices = [pos_level(p) for p in positions]
self.highest_open_price = (
max(open_prices) if open_prices else self.current_price
)
if open_prices:
self.highest_open_price = max(open_prices)
elif self.highest_open_price == 0.0:
# First run with no positions — use current price as seed
self.highest_open_price = self.current_price
# else: keep existing highest_open_price from previous loop
# Lowest level across all positions and orders
order_prices = [ord_level(o) for o in orders]