Skip to content

Add kill switch liquidation signal handling in strategy executor#912

Open
danphilos wants to merge 4 commits intodevfrom
fix-kill-switch
Open

Add kill switch liquidation signal handling in strategy executor#912
danphilos wants to merge 4 commits intodevfrom
fix-kill-switch

Conversation

@danphilos
Copy link
Collaborator

@danphilos danphilos commented Dec 15, 2025

  • Poll DynamoDB for "liquidating" status during trading iterations
  • Execute sell_all() when liquidation signal detected
  • Update DynamoDB status to "liquidated" after positions closed
  • Add _check_kill_switch_signal() and _handle_kill_switch_liquidation()
  • Check for signal in main trading loop and iteration start

When bot_manager sets status to "liquidating", the bot gracefully closes all positions before being terminated by the kill switch.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a remote kill-switch to halt trading and liquidate all positions; detects signals, performs cleanup, and stops execution.
    • Includes resilient fallback behavior when remote signal access is unavailable.
  • Refactor

    • Simplified date-handling logic by removing an unused computation to reduce clutter.

✏️ Tip: You can customize this high-level summary in your review settings.

danphilos and others added 2 commits January 5, 2026 10:11
- Poll DynamoDB for "liquidating" status during trading iterations
- Execute sell_all() when liquidation signal detected
- Update DynamoDB status to "liquidated" after positions closed
- Add _check_kill_switch_signal() and _handle_kill_switch_liquidation()
- Check for signal in main trading loop and iteration start

When bot_manager sets status to "liquidating", the bot gracefully
closes all positions before being terminated by the kill switch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
When kill switch liquidation triggers, the bot now:
1. Sets stop_event to signal executor shutdown
2. Calls broker.cleanup_streams() to properly close websocket connections

This prevents "cannot schedule new futures after shutdown" errors that
occurred when the Alpaca websocket stream kept trying to reconnect while
the executor was shutting down.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 5, 2026

📝 Walkthrough

Walkthrough

Adds a DynamoDB-based kill switch to StrategyExecutor (remote liquidation checks, status updates, and liquidation workflow) and comments out an unused is_index_asset computation in thetadata_helper.get_missing_dates().

Changes

Cohort / File(s) Summary
DynamoDB Kill Switch Integration
lumibot/strategies/strategy_executor.py
Added optional boto3 import and BOTO3_AVAILABLE flag; introduced private fields for kill-switch state and DynamoDB table/resource initialization when BOT_ID exists. Added _check_kill_switch_signal(), _update_status_to_liquidated(), and _handle_kill_switch_liquidation() methods. Integrated checks at trading-iteration start and pre-session startup to trigger liquidation, cleanup, and stop the executor; uses best-effort error handling for DynamoDB failures.
Unused Variable Removal
lumibot/tools/thetadata_helper.py
Commented out the active computation of is_index_asset in get_missing_dates() (removed its usage). No other control-flow or return-value changes.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Executor as StrategyExecutor
    participant DynamoDB as DynamoDB (Kill Switch)
    participant Broker as Broker / Trading API
    participant Streams as StreamManager

    rect rgb(240,248,255)
    Executor->>DynamoDB: _check_kill_switch_signal()
    end

    alt Kill signal present
        rect rgb(255,240,240)
        DynamoDB-->>Executor: signal=true
        Executor->>Broker: liquidate_all_positions()
        Broker-->>Executor: positions_sold
        Executor->>DynamoDB: _update_status_to_liquidated()
        DynamoDB-->>Executor: status_confirmed
        Executor->>Streams: cleanup_streams()
        Executor->>Executor: stop_executor()
        end
    else No signal / DynamoDB error
        rect rgb(240,255,240)
        DynamoDB-->>Executor: no_signal / error
        Executor->>Executor: continue_trading()
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I sniffed the cloud for a whisper and bell,
Found a switch that said "stop" and rang very well.
I hopped, I sold, wrapped streams safe and neat,
Tucked logs in a burrow and cleaned up my seat. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main change: adding kill switch liquidation signal handling to the strategy executor, which is the primary focus of the pull request.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e22760 and 3b18137.

📒 Files selected for processing (1)
  • lumibot/tools/thetadata_helper.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • lumibot/tools/thetadata_helper.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Backtest Tests (shard 3/4)
  • GitHub Check: Backtest Tests (shard 0/4)
  • GitHub Check: Unit Tests (shard 2/6)
  • GitHub Check: Backtest Tests (shard 2/4)
  • GitHub Check: Unit Tests (shard 3/6)
  • GitHub Check: Backtest Tests (shard 1/4)
  • GitHub Check: Unit Tests (shard 4/6)
  • GitHub Check: Unit Tests (shard 1/6)
  • GitHub Check: Unit Tests (shard 5/6)
  • GitHub Check: Unit Tests (shard 0/6)
  • GitHub Check: build

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Pylint (4.0.4)
lumibot/tools/thetadata_helper.py

************* Module pylintrc
pylintrc:1:0: F0011: error while parsing the configuration: File contains no section headers.
file: 'pylintrc', line: 1
'known-third-party=lumibot' (config-parse-error)
[
{
"type": "convention",
"module": "lumibot.tools.thetadata_helper",
"obj": "",
"line": 143,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "lumibot/tools/thetadata_helper.py",
"symbol": "line-too-long",
"message": "Line too long (134/100)",
"message-id": "C0301"
},
{
"type": "convention",
"module": "lumibot.tools.thetadata_helper",
"obj": "",
"line": 160,
"column": 0,
"endLine": null,
"endColumn": null,
"path": "lumibot/tools/thetadata_helper.py",
"symbol": "line-too-long",
"message": "Line too long (121/100)",
"message-id": "C0301"
},
{
"type": "convention",
"module": "

... [truncated 146300 characters] ...

415"
},
{
"type": "refactor",
"module": "lumibot.tools.thetadata_helper",
"obj": "get_chains_cached",
"line": 5764,
"column": 0,
"endLine": 5764,
"endColumn": 21,
"path": "lumibot/tools/thetadata_helper.py",
"symbol": "too-many-branches",
"message": "Too many branches (29/12)",
"message-id": "R0912"
},
{
"type": "refactor",
"module": "lumibot.tools.thetadata_helper",
"obj": "get_chains_cached",
"line": 5764,
"column": 0,
"endLine": 5764,
"endColumn": 21,
"path": "lumibot/tools/thetadata_helper.py",
"symbol": "too-many-statements",
"message": "Too many statements (96/50)",
"message-id": "R0915"
}
]


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
lumibot/strategies/strategy_executor.py (3)

33-40: Unused ClientError import.

ClientError is imported but all exception handlers use generic Exception. Consider either removing the unused import or using ClientError for more specific DynamoDB error handling.

Option 1: Remove unused import
 try:
     import boto3
-    from botocore.exceptions import ClientError
     BOTO3_AVAILABLE = True
 except ImportError:
     BOTO3_AVAILABLE = False
Option 2: Use ClientError for specific handling
# In _check_kill_switch_signal and other methods:
except ClientError as e:
    error_code = e.response.get('Error', {}).get('Code', '')
    if error_code == 'ResourceNotFoundException':
        # Table doesn't exist - log once and disable further checks
        pass
    # ... handle other specific cases
except Exception as e:
    # Fallback for non-AWS errors

127-138: Silent exception swallowing loses diagnostic information.

When DynamoDB resource creation fails, the exception is silently discarded. This could make it difficult to diagnose connection or credential issues in production. Consider logging a warning.

Proposed fix
         if self._kill_switch_bot_id and BOTO3_AVAILABLE:
             try:
                 self._kill_switch_dynamodb = boto3.resource(
                     "dynamodb",
                     region_name=self._kill_switch_region
                 )
                 self._kill_switch_table_name = f"{self._kill_switch_environment}-bots"
             except Exception as e:
                 # Log but don't fail - kill switch is optional
-                pass
+                logger.warning(f"Failed to initialize DynamoDB for kill switch: {e}")

21-22: Duplicate import of Asset.

Asset is imported twice from lumibot.entities (lines 21 and 22). This appears to be a pre-existing issue or an artifact from the changes.

Proposed fix
 from lumibot.entities import Asset, Order
-from lumibot.entities import Asset
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2955b61 and fcb9035.

📒 Files selected for processing (1)
  • lumibot/strategies/strategy_executor.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always use BACKTESTING_DATA_SOURCE environment variable to override data source selection; set it to 'thetadata', 'yahoo', or 'none'
Only use Data Downloader at http://data-downloader.lumiwealth.com:8080 for backtests; avoid hard-coded IPs as they can change on redeploy
Always compare ThetaData vs Yahoo prices for validation; Yahoo is the gold standard for split-adjusted prices
Use BACKTESTING_DATA_SOURCE=yahoo for dividend-sensitive strategies as ThetaData has known phantom dividend data quality issues

Files:

  • lumibot/strategies/strategy_executor.py
🧬 Code graph analysis (1)
lumibot/strategies/strategy_executor.py (2)
lumibot/strategies/strategy.py (3)
  • log_message (357-403)
  • sell_all (2173-2199)
  • cancel_open_orders (2085-2132)
lumibot/brokers/broker.py (2)
  • sell_all (1577-1602)
  • cancel_open_orders (1521-1553)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
lumibot/strategies/strategy_executor.py (5)

203-240: LGTM!

The kill switch signal check is well-structured with appropriate early returns and non-fatal error handling. The _liquidation_triggered flag correctly prevents duplicate liquidation attempts.


241-272: LGTM!

The status update implementation correctly uses ExpressionAttributeNames to avoid reserved word conflicts, and the error handling appropriately logs failures without blocking the shutdown flow.


273-315: Verify behavior on partial sell_all failure.

The implementation correctly prioritizes updating the status to "liquidated" even if sell_all fails, which signals that liquidation was attempted. However, if sell_all partially fails (e.g., some orders rejected), some positions may remain open while status shows "liquidated".

Consider whether bot_manager should verify that positions are actually closed, or if the current best-effort approach is acceptable for your use case.


1038-1047: LGTM!

Placing the kill switch check at the start of each trading iteration ensures responsive handling. The early return with proper state cleanup (_in_trading_iteration = False) is correct.


2041-2049: LGTM!

The pre-session kill switch check provides coverage for signals received outside of trading iterations (e.g., before market opens). This complements the per-iteration check nicely for comprehensive signal handling.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lumibot/tools/thetadata_helper.py (1)

3594-3605: Remove unused index helper variables instead of commenting out their consumer

Commenting out is_index_asset fixed one Ruff F841, but asset_type_value, symbol_upper, and index_symbols are now also unused and still fail lint/CI. Since this function doesn’t actually need index-specific handling today, it’s cleaner to drop the whole unused block (and avoid commented-out code).

Proposed fix to satisfy Ruff F841 and simplify get_missing_dates
-    asset_type_value = str(getattr(asset, "asset_type", "")).lower()
-    symbol_upper = str(getattr(asset, "symbol", "") or "").upper()
-    index_symbols = {
-        "SPX", "SPXW",
-        "RUT", "RUTW",
-        "VIX", "VIXW",
-        "NDX", "NDXP",
-        "XSP", "DJX", "OEX", "XEO",
-    }
-    # TODO: Unused variable - commenting out to fix Ruff F841
-    # is_index_asset = asset_type_value == "index" or symbol_upper in index_symbols
+    # NOTE: Index-style assets currently reuse the same missing-date logic as other types.
+    # If index-specific behavior is needed here in the future, reintroduce an `is_index_asset`
+    # helper and ensure it’s actually used so Ruff F841 does not trigger.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fcb9035 and 5e22760.

📒 Files selected for processing (1)
  • lumibot/tools/thetadata_helper.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: Always use BACKTESTING_DATA_SOURCE environment variable to override data source selection; set it to 'thetadata', 'yahoo', or 'none'
Only use Data Downloader at http://data-downloader.lumiwealth.com:8080 for backtests; avoid hard-coded IPs as they can change on redeploy
Always compare ThetaData vs Yahoo prices for validation; Yahoo is the gold standard for split-adjusted prices
Use BACKTESTING_DATA_SOURCE=yahoo for dividend-sensitive strategies as ThetaData has known phantom dividend data quality issues

Files:

  • lumibot/tools/thetadata_helper.py
lumibot/tools/thetadata_helper.py

📄 CodeRabbit inference engine (CLAUDE.md)

lumibot/tools/thetadata_helper.py: Add idempotency checks and markers to functions that are called multiple times per backtest (e.g., _apply_corporate_actions_to_frame() with _split_adjusted column marker)
Implement deduplication of dividend events by ex_date in _normalize_dividend_events() to prevent multiple applications of the same dividend
Filter special distributions with less_amount > 0 in dividend processing to exclude non-dividend events like return of capital
Convert ThetaData option strike queries using _get_option_query_strike() to account for split adjustments (GOOG 20:1, AAPL 4:1, TSLA 3:1)

Files:

  • lumibot/tools/thetadata_helper.py
{lumibot/tools/thetadata_helper.py,lumibot/backtesting/thetadata_backtesting_pandas.py}

📄 CodeRabbit inference engine (CLAUDE.md)

Filter zero-price OHLC rows when loading from cache and when receiving new data from ThetaData to prevent ZeroDivisionError

Files:

  • lumibot/tools/thetadata_helper.py
🧠 Learnings (2)
📚 Learning: 2026-01-05T06:15:00.886Z
Learnt from: CR
Repo: Lumiwealth/lumibot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T06:15:00.886Z
Learning: Applies to lumibot/tools/thetadata_helper.py : Add idempotency checks and markers to functions that are called multiple times per backtest (e.g., `_apply_corporate_actions_to_frame()` with `_split_adjusted` column marker)

Applied to files:

  • lumibot/tools/thetadata_helper.py
📚 Learning: 2026-01-05T06:15:00.886Z
Learnt from: CR
Repo: Lumiwealth/lumibot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-05T06:15:00.886Z
Learning: Applies to lumibot/tools/thetadata_helper.py : Implement deduplication of dividend events by ex_date in `_normalize_dividend_events()` to prevent multiple applications of the same dividend

Applied to files:

  • lumibot/tools/thetadata_helper.py
🪛 GitHub Actions: LumiBot CI/CD
lumibot/tools/thetadata_helper.py

[error] 3612-3614: F841 Local variable 'asset_type_value' is assigned to but never used; F841 Local variable 'symbol_upper' is assigned to but never used; F841 Local variable 'index_symbols' is assigned to but never used. Remove assignments.


[error] 3612-3614: Three unused variable assignments detected by Ruff: asset_type_value, symbol_upper, index_symbols. Remove them or use them.


[error] 3612-3614: Ruff check failed with 3 linting errors. No fixes available (use --unsafe-fixes to reveal hidden fixes).

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant