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:
@@ -174,9 +174,14 @@ class TeslaGridBot:
|
||||
log.info(f"Highest open: ${s.highest_open_price:.2f}")
|
||||
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)
|
||||
calc = self.calculator
|
||||
# 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
@@ -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})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user