Skip to content
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

feat: Remove Scanner class #54

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open

feat: Remove Scanner class #54

wants to merge 4 commits into from

Conversation

Chocksy
Copy link
Owner

@Chocksy Chocksy commented Dec 5, 2024

Deprecate the Alpha/Utils/Scanner.py class as we can better implement the same functionality in the other classes.

Summary by CodeRabbit

  • New Features

    • Updated Python version to 3.8.20.
    • Enhanced option trading strategy with broader wing sizes.
    • Introduced new methods for market checks and position limits in order management.
    • Added dynamic DTE selection for option filtering.
    • Improved option chain filtering with enhanced logging and error handling.
  • Bug Fixes

    • Improved error handling for option initialization and market status checks.
  • Documentation

    • Enhanced logging for better traceability in option filtering processes.
  • Tests

    • Expanded test coverage for market conditions, scheduling, and order management functionalities.
  • Chores

    • Removed obsolete Scanner class and associated tests.

@Chocksy Chocksy self-assigned this Dec 5, 2024
Copy link

codecov bot commented Dec 5, 2024

Codecov Report

Attention: Patch coverage is 56.00000% with 33 lines in your changes missing coverage. Please review.

Project coverage is 69.60%. Comparing base (a382960) to head (13c278c).

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
Tools/DataHandler.py 56.16% 32 Missing ⚠️
Initialization/SetupBaseStructure.py 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #54      +/-   ##
==========================================
- Coverage   71.12%   69.60%   -1.53%     
==========================================
  Files          30       29       -1     
  Lines        2691     2586     -105     
==========================================
- Hits         1914     1800     -114     
- Misses        777      786       +9     
Flag Coverage Δ
unittests 69.60% <56.00%> (-1.53%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@Chocksy Chocksy force-pushed the feat/deprecate_scanner branch from 4ed97a1 to 8f7f3d4 Compare December 6, 2024 21:40
@Chocksy Chocksy closed this Dec 10, 2024
@Chocksy Chocksy reopened this Dec 10, 2024
Copy link

coderabbitai bot commented Dec 10, 2024

Walkthrough

The changes in this pull request involve updates to multiple files, including modifications to the Python version, significant restructuring of classes and methods in the Alpha module, and enhancements to logging and error handling in the DataHandler. Notably, the Scanner class has been removed, leading to adjustments in related imports and tests. New methods have been introduced to improve order management and market checks, while tests have been expanded to cover these new functionalities and ensure robustness.

Changes

File Change Summary
.python-version Updated Python version from 3.8.16 to 3.8.20.
Alpha/Base.py - Removed Scanner import.
- Added methods: isMarketClosed, check_market_schedule.
- Modified update method for streamlined logic.
- Initialized last_trade_time.
Alpha/FutureSpread.py Updated parameters: putWingSize and callWingSize changed from 20 to 50.
Alpha/Utils/Scanner.py Class and multiple methods removed.
Alpha/Utils/__init__.py Removed import of Scanner class.
Initialization/SetupBaseStructure.py Enhanced error handling and logging; updated several method signatures.
Order/Base.py Added methods for position checks: hasReachedMaxActivePositions, hasReachedMaxOpenPositions, check_position_limits, hasDuplicateLegs.
Tests/specs/alpha/base_spec.py Expanded tests for market conditions and scheduling; added mocks for dataHandler.
Tests/specs/alpha/utils/scanner_spec.py Deleted file containing tests for the Scanner class.
Tests/specs/order/base_spec.py Restructured tests for order management; added checks for position limits and duplicate positions.
Tools/DataHandler.py Enhanced logging and error handling in optionChainProviderFilter; added _initializeGreeks method.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Base
    participant DataHandler
    participant FutureSpread

    User->>Base: Call update()
    Base->>Base: Check if market is closed
    Base->>DataHandler: Get option contracts
    DataHandler-->>Base: Return contracts
    Base->>Base: Generate insights
    Base->>FutureSpread: Update wing sizes
    FutureSpread-->>Base: Confirm update
    Base-->>User: Return results
Loading

🐇 "In the code, a change we see,
Python's version now is 3.8.20!
With methods new and classes refined,
A smoother path for trades we find.
So hop along, let’s celebrate,
For better code will surely await!" 🐰✨

Tip

CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command @coderabbitai generate docstrings to have CodeRabbit automatically generate docstrings for your pull request. We would love to hear your feedback on Discord.


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Chocksy
Copy link
Owner Author

Chocksy commented Dec 10, 2024

@coderabbitai full review

Copy link

coderabbitai bot commented Dec 10, 2024

✅ Actions performed

Full review triggered.

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: 12

🧹 Outside diff range and nitpick comments (7)
Alpha/FutureSpread.py (2)

Line range hint 31-32: Address TODO comment regarding maxOrderQuantity

There's an unresolved TODO comment about fixing maxOrderQuantity based on buying power. This should be addressed to ensure proper position sizing.

Would you like me to help implement the buying power-based maxOrderQuantity calculation?

🧰 Tools
🪛 Ruff (0.8.0)

50-50: time may be undefined, or defined from star imports

(F405)


Line range hint 89-91: Consider moving trade times to configuration

The trade times are currently hardcoded in the getOrder method. Consider moving these to the PARAMETERS dictionary for better configurability and maintenance.

     PARAMETERS = {
         "scheduleStartTime": time(9, 30, 0),
         "scheduleStopTime": time(16, 0, 0),
+        "tradeTimes": [time(9, 35, 0), time(9, 40, 0), time(9, 45, 0)],
         ...
     }
🧰 Tools
🪛 Ruff (0.8.0)

50-50: time may be undefined, or defined from star imports

(F405)

Order/Base.py (3)

38-40: Add logging when position limits prevent order creation

While checking position limits before order creation is important, consider adding a log statement to inform when an order is not created due to position limits. This will aid in debugging and monitoring system behavior.

Apply this diff to include a log message:

 def buildOrderPosition(self, order, lastClosedOrderTag=None):
     # ... existing code ...
     # Check position limits first
     if not self.check_position_limits(order):
+        self.logger.info("Order not created: Position limits reached due to position limits.")
         return [None, None]
     # ... existing code ...

232-234: Simplify nested if statements in check_position_limits

The nested if statements can be combined into a single condition for improved readability.

Apply this diff to simplify the code:

-        if self.strategy.parameter("checkForDuplicatePositions", True):
-            if self.hasDuplicateLegs(order):
+        if self.strategy.parameter("checkForDuplicatePositions", True) and self.hasDuplicateLegs(order):
             return False
🧰 Tools
🪛 Ruff (0.8.0)

232-233: Use a single if statement instead of nested if statements

(SIM102)


255-257: Simplify nested if statements in hasDuplicateLegs

Again, nested if statements can be simplified to a single condition.

Apply this diff:

-            if self.strategy.parameter("allowMultipleEntriesPerExpiry", False):
-                if position.expiryStr != order_expiry:
+            if self.strategy.parameter("allowMultipleEntriesPerExpiry", False) and position.expiryStr != order_expiry:
                 continue
🧰 Tools
🪛 Ruff (0.8.0)

255-256: Use a single if statement instead of nested if statements

(SIM102)

Tests/specs/order/base_spec.py (2)

159-200: Enhance tests for position limits

While you have added tests for position limits, consider adding assertions to verify that positions are not created when limits are exceeded, and that they are created when within limits. This will ensure that the position limit checks are functioning as intended.

Example:

# After setting up conditions where limits are exceeded
expect(self.base.buildOrderPosition(self.order)).to(equal([None, None]))

# When within limits
expect(self.base.buildOrderPosition(self.order)).not_to(equal([None, None]))

267-268: Test the actual logic of hasDuplicateLegs

Mocking self.base.hasDuplicateLegs bypasses the actual logic you want to test. Instead, consider setting up test cases with real data to verify that hasDuplicateLegs correctly identifies duplicate legs.

Example:

# Set up positions with specific legs
self.base.context.openPositions = {"pos1": "order1"}
self.base.context.allPositions = {
    "order1": MagicMock(
        strategyId="test_strategy",
        legs=[MagicMock(strike=100, contractSide="Sell")]
    )
}

# Call hasDuplicateLegs with an order that should be identified as duplicate
expect(self.base.hasDuplicateLegs(self.mock_order)).to(be_true)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a382960 and 2abef00.

📒 Files selected for processing (11)
  • .python-version (1 hunks)
  • Alpha/Base.py (4 hunks)
  • Alpha/FutureSpread.py (1 hunks)
  • Alpha/Utils/Scanner.py (0 hunks)
  • Alpha/Utils/__init__.py (0 hunks)
  • Initialization/SetupBaseStructure.py (1 hunks)
  • Order/Base.py (2 hunks)
  • Tests/specs/alpha/base_spec.py (3 hunks)
  • Tests/specs/alpha/utils/scanner_spec.py (0 hunks)
  • Tests/specs/order/base_spec.py (2 hunks)
  • Tools/DataHandler.py (3 hunks)
💤 Files with no reviewable changes (3)
  • Alpha/Utils/init.py
  • Tests/specs/alpha/utils/scanner_spec.py
  • Alpha/Utils/Scanner.py
✅ Files skipped from review due to trivial changes (1)
  • .python-version
🧰 Additional context used
🪛 Ruff (0.8.0)
Order/Base.py

232-233: Use a single if statement instead of nested if statements

(SIM102)


255-256: Use a single if statement instead of nested if statements

(SIM102)

Tools/DataHandler.py

279-279: SecurityType may be undefined, or defined from star imports

(F405)


279-279: SecurityType may be undefined, or defined from star imports

(F405)


280-280: OptionRight may be undefined, or defined from star imports

(F405)


280-280: OptionRight may be undefined, or defined from star imports

(F405)


280-280: OptionRight may be undefined, or defined from star imports

(F405)


281-281: Symbol may be undefined, or defined from star imports

(F405)

Alpha/Base.py

265-265: Insight may be undefined, or defined from star imports

(F405)

Initialization/SetupBaseStructure.py

204-204: SecurityType may be undefined, or defined from star imports

(F405)


205-205: NullOptionAssignmentModel may be undefined, or defined from star imports

(F405)

🔇 Additional comments (17)
Tools/DataHandler.py (4)

68-70: Proper handling of empty symbols list

The added condition correctly checks for an empty symbols list, logging a warning and returning None if necessary.


73-84: Enhanced DTE filtering with detailed logging

The updated code improves the DTE filtering process by adding informative logging statements before and after filtering. This enhances traceability and aids in debugging when no symbols remain after filtering.


243-244: Initialize Greeks after adding option contracts

Initializing the Greeks immediately after adding the option contracts ensures that the necessary calculations are performed for each contract.


290-296: Handle potential exceptions when initializing Greeks

While initializing Greeks, ensure that any exceptions are logged with sufficient detail to aid in debugging. The current exception handling is appropriate.

Tests/specs/alpha/base_spec.py (5)

49-52: Addition of mock dataHandler in test setup

Mocking the dataHandler with getOptionContracts returning None is a good practice to isolate the tests from external dependencies.


101-143: Refactored test setup for update method

Consolidating the common attributes in the before.each block improves test readability and maintainability.


457-458: Correct assertion of updateCharts method call

The use of assert_called_once_with ensures that the updateCharts method is called with the correct symbol.


460-472: Comprehensive tests for isMarketClosed method

The added tests effectively cover scenarios where the market is closed due to warming up or actual market closure, as well as when the market is open.


473-497: Robust tests for check_market_schedule method

The tests encompass various edge cases, ensuring that trades are only allowed within the specified schedule window and respecting the minimum trade distance between trades.

Alpha/Base.py (4)

6-6: Removed unused Scanner import

The removal of Scanner from the imports reflects the codebase's shift away from using it, reducing unnecessary dependencies.


204-205: Initialize last_trade_time in constructor

Adding self.last_trade_time = None properly initializes the attribute used for tracking the timing of trades.


245-263: Refactored update method for clarity and efficiency

The update method now uses isMarketClosed and check_market_schedule methods to determine if it should proceed, improving readability and maintainability.


267-296: Well-implemented market condition checks

The new methods isMarketClosed and check_market_schedule encapsulate market condition logic effectively, aiding in code clarity.

Alpha/FutureSpread.py (1)

Line range hint 67-68: Verify market check functionality after Scanner removal

With the Scanner class being removed, ensure that market checks are properly handled through the Base class inheritance. The current implementation relies on Base for market status checks.

Let's verify the market check implementations:

Also applies to: 71-72

✅ Verification successful

Market check functionality is properly handled through Base class inheritance

The verification confirms that:

  • FutureSpread class inherits from Base class (Alpha/Base.py)
  • Base class implements both required market check methods:
    • isMarketClosed()
    • check_market_schedule()
  • These methods are actively used and tested as shown by the test files
  • The market check functionality is properly maintained after Scanner removal
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check Base class market check implementations
ast-grep --pattern 'def isMarketClosed' 
ast-grep --pattern 'def check_market_schedule'

Length of output: 90


Script:

#!/bin/bash
# Let's try a broader search for market-related methods in Base and the current file
rg -l "class Base" 
rg "def.*market" -i
rg "isMarketClosed|check_market_schedule" -i

# Also check for Base class inheritance
rg "class.*\(.*Base.*\)"

Length of output: 3908

🧰 Tools
🪛 Ruff (0.8.0)

50-50: time may be undefined, or defined from star imports

(F405)

Tests/specs/order/base_spec.py (3)

55-66: Mock strategy parameters correctly in tests

The strategy mock is properly set up to return specific parameters using side_effect. This allows for flexible testing of different scenarios based on strategy configurations.


118-125: Test order quantity validation effectively

Modifying orderQuantity and maxOrderQuantity to test the validation logic in buildOrderPosition is appropriate. The assertions correctly check that no position or working order is created when the quantity exceeds the maximum.


221-230: Ensure all strategy parameters are set for position limit tests

In the position limit checks context, make sure that all parameters used in check_position_limits are provided in the side_effect. Missing parameters could lead to unexpected defaults being used in the method.

Comment on lines 93 to 98
security = self.context.Securities[symbol.ID.Symbol]
if security.IsTradable:
tradable_symbols.append(symbol)
else:
non_tradable_reasons.append(f"Symbol {symbol.ID.Symbol}: IsTradable={security.IsTradable}")

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Prevent potential KeyError when accessing Securities dictionary

Accessing self.context.Securities[symbol.ID.Symbol] may raise a KeyError if the symbol is not present in the Securities dictionary. To ensure robustness, check if the security exists before accessing it.

Apply this diff to handle missing securities:

+                    if symbol.ID.Symbol in self.context.Securities:
+                        security = self.context.Securities[symbol.ID.Symbol]
+                        if security.IsTradable:
+                            tradable_symbols.append(symbol)
+                        else:
+                            non_tradable_reasons.append(f"Symbol {symbol.ID.Symbol}: IsTradable={security.IsTradable}")
+                    else:
+                        non_tradable_reasons.append(f"Symbol {symbol.ID.Symbol} not found in Securities dictionary")
-                    security = self.context.Securities[symbol.ID.Symbol]
-                    if security.IsTradable:
-                        tradable_symbols.append(symbol)
-                    else:
-                        non_tradable_reasons.append(f"Symbol {symbol.ID.Symbol}: IsTradable={security.IsTradable}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
security = self.context.Securities[symbol.ID.Symbol]
if security.IsTradable:
tradable_symbols.append(symbol)
else:
non_tradable_reasons.append(f"Symbol {symbol.ID.Symbol}: IsTradable={security.IsTradable}")
if symbol.ID.Symbol in self.context.Securities:
security = self.context.Securities[symbol.ID.Symbol]
if security.IsTradable:
tradable_symbols.append(symbol)
else:
non_tradable_reasons.append(f"Symbol {symbol.ID.Symbol}: IsTradable={security.IsTradable}")
else:
non_tradable_reasons.append(f"Symbol {symbol.ID.Symbol} not found in Securities dictionary")

Tools/DataHandler.py Show resolved Hide resolved
if not filteredSymbols:
self.context.logger.warning("No tradable symbols left after filtering")
return None
self.context.logger.warning(f"Tradability filter: {before_tradable} -> {len(filteredSymbols)} symbols")

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Corrected logging variable

Ensure that the variable used in the logging statement reflects the current filtered symbols list.

Apply this diff to log the correct number of symbols:

-                self.context.logger.warning(f"Tradability filter: {before_tradable} -> {len(filteredSymbols)} symbols")
+                self.context.logger.warning(f"Tradability filter: {before_tradable} -> {len(tradable_symbols)} symbols")

Committable suggestion skipped: line range outside the PR's diff.

Initialization/SetupBaseStructure.py Show resolved Hide resolved
Alpha/Base.py Show resolved Hide resolved
Comment on lines +225 to +228
# Check max open orders
max_open = self.strategy.parameter("maxOpenPositions", 2)
open_orders = len([o for o in self.context.Transactions.GetOpenOrders()])
if open_orders >= max_open:
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Filter open orders by strategy

Similarly, when counting open_orders, ensure that only open orders for the current strategy are included. This prevents interference between different strategies.

Apply this diff to filter open orders:

 open_orders = len([
-    o for o in self.context.Transactions.GetOpenOrders()
+    o for o in self.context.Transactions.GetOpenOrders()
+    if o.Symbol in self.context.Securities
+    and self.context.Securities[o.Symbol].strategyTag == self.nameTag
 ])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Check max open orders
max_open = self.strategy.parameter("maxOpenPositions", 2)
open_orders = len([o for o in self.context.Transactions.GetOpenOrders()])
if open_orders >= max_open:
# Check max open orders
max_open = self.strategy.parameter("maxOpenPositions", 2)
open_orders = len([
o for o in self.context.Transactions.GetOpenOrders()
if o.Symbol in self.context.Securities
and self.context.Securities[o.Symbol].strategyTag == self.nameTag
])
if open_orders >= max_open:

Comment on lines +190 to +191
return Base.orderCount

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix usage of self in getNextOrderId

The getNextOrderId method is currently decorated with @staticmethod but references self.context, which will raise an error because self is not available in static methods. You should remove the @staticmethod decorator to make it an instance method.

Apply this diff to correct the method:

-    @staticmethod
     def getNextOrderId(self):
         try:
             max_order_id = max(orderId for _, orderId in self.context.openPositions.items())
         except:
             max_order_id = 0

         if max_order_id > 0 and Base.orderCount == 0:
             Base.orderCount = max_order_id + 1
         else:
             Base.orderCount += 1

         return Base.orderCount
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return Base.orderCount
def getNextOrderId(self):
try:
max_order_id = max(orderId for _, orderId in self.context.openPositions.items())
except:
max_order_id = 0
if max_order_id > 0 and Base.orderCount == 0:
Base.orderCount = max_order_id + 1
else:
Base.orderCount += 1
return Base.orderCount

Comment on lines +192 to +204
def hasReachedMaxActivePositions(self) -> bool:
"""Check if maximum number of active positions has been reached."""
openPositionsByStrategy = {
tag: pos for tag, pos in self.context.openPositions.items()
if self.context.allPositions[pos].strategyTag == self.nameTag
}
workingOrdersByStrategy = {
tag: order for tag, order in self.context.workingOrders.items()
if order.strategyTag == self.nameTag
}

return (len(openPositionsByStrategy) + len(workingOrdersByStrategy)) >= self.maxActivePositions

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Initialize maxActivePositions and maxOpenPositions attributes

The methods hasReachedMaxActivePositions and hasReachedMaxOpenPositions reference self.maxActivePositions and self.maxOpenPositions, but these attributes are not initialized in the __init__ method. This will result in an AttributeError.

Apply this diff to initialize the attributes:

 def __init__(self, context, strategy):
     self.context = context
     # Set the logger
     self.logger = Logger(context, className=type(self).__name__, logLevel=context.logLevel)
     self.strategy = strategy
     # Set default name (use the class name)
     self.name = strategy.name
     # Set the Strategy Name (optional)
     self.nameTag = strategy.nameTag
     # Initialize the contract utils
     self.contractUtils = ContractUtils(context)
+    # Initialize position limits
+    self.maxActivePositions = self.strategy.parameter("maxActivePositions", 1)
+    self.maxOpenPositions = self.strategy.parameter("maxOpenPositions", 1)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def hasReachedMaxActivePositions(self) -> bool:
"""Check if maximum number of active positions has been reached."""
openPositionsByStrategy = {
tag: pos for tag, pos in self.context.openPositions.items()
if self.context.allPositions[pos].strategyTag == self.nameTag
}
workingOrdersByStrategy = {
tag: order for tag, order in self.context.workingOrders.items()
if order.strategyTag == self.nameTag
}
return (len(openPositionsByStrategy) + len(workingOrdersByStrategy)) >= self.maxActivePositions
def __init__(self, context, strategy):
self.context = context
# Set the logger
self.logger = Logger(context, className=type(self).__name__, logLevel=context.logLevel)
self.strategy = strategy
# Set default name (use the class name)
self.name = strategy.name
# Set the Strategy Name (optional)
self.nameTag = strategy.nameTag
# Initialize the contract utils
self.contractUtils = ContractUtils(context)
# Initialize position limits
self.maxActivePositions = self.strategy.parameter("maxActivePositions", 1)
self.maxOpenPositions = self.strategy.parameter("maxOpenPositions", 1)

Comment on lines +221 to +223
active_positions = len([p for p in self.context.Portfolio.Values if p.Invested])
if active_positions >= max_active:
return False
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Filter active positions by strategy

In check_position_limits, active_positions counts all invested positions in the portfolio, including those not related to the current strategy. This can lead to incorrect limit checks if multiple strategies are used. Filter the positions to include only those belonging to the current strategy.

Apply this diff to filter positions:

 active_positions = len([
-    p for p in self.context.Portfolio.Values if p.Invested
+    p for p in self.context.Portfolio.Values
+    if p.Invested and p.Symbol in self.context.openPositions.values()
+    and self.context.allPositions[self.context.openPositions[p.Symbol]].strategyTag == self.nameTag
 ])

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +157 to +158

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Adjust test calls to getNextOrderId

Since getNextOrderId is no longer a @staticmethod, you should call it on the instance (self.base) rather than the class (Base).

Apply this diff to update the test:

- first_id = Base.getNextOrderId()
- second_id = Base.getNextOrderId()
+ first_id = self.base.getNextOrderId()
+ second_id = self.base.getNextOrderId()

Committable suggestion skipped: line range outside the PR's diff.

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: 1

🧹 Outside diff range and nitpick comments (2)
Order/Base.py (2)

232-233: Simplify nested if statements

You can combine the nested if statements into a single if statement for better readability.

Apply this diff:

-        if self.strategy.parameter("checkForDuplicatePositions", True):
-            if self.hasDuplicateLegs(order):
+        if self.strategy.parameter("checkForDuplicatePositions", True) and self.hasDuplicateLegs(order):
             return False
🧰 Tools
🪛 Ruff (0.8.0)

232-233: Use a single if statement instead of nested if statements

(SIM102)


255-256: Simplify nested if statements

You can combine the nested if statements into a single if statement for better readability.

Apply this diff:

-            if self.strategy.parameter("allowMultipleEntriesPerExpiry", False):
-                if position.expiryStr != order_expiry:
+            if self.strategy.parameter("allowMultipleEntriesPerExpiry", False) and position.expiryStr != order_expiry:
                 continue
🧰 Tools
🪛 Ruff (0.8.0)

255-256: Use a single if statement instead of nested if statements

(SIM102)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a382960 and 2abef00.

📒 Files selected for processing (11)
  • .python-version (1 hunks)
  • Alpha/Base.py (4 hunks)
  • Alpha/FutureSpread.py (1 hunks)
  • Alpha/Utils/Scanner.py (0 hunks)
  • Alpha/Utils/__init__.py (0 hunks)
  • Initialization/SetupBaseStructure.py (1 hunks)
  • Order/Base.py (2 hunks)
  • Tests/specs/alpha/base_spec.py (3 hunks)
  • Tests/specs/alpha/utils/scanner_spec.py (0 hunks)
  • Tests/specs/order/base_spec.py (2 hunks)
  • Tools/DataHandler.py (3 hunks)
💤 Files with no reviewable changes (3)
  • Alpha/Utils/init.py
  • Tests/specs/alpha/utils/scanner_spec.py
  • Alpha/Utils/Scanner.py
🧰 Additional context used
🪛 Ruff (0.8.0)
Tools/DataHandler.py

279-279: SecurityType may be undefined, or defined from star imports

(F405)


279-279: SecurityType may be undefined, or defined from star imports

(F405)


280-280: OptionRight may be undefined, or defined from star imports

(F405)


280-280: OptionRight may be undefined, or defined from star imports

(F405)


280-280: OptionRight may be undefined, or defined from star imports

(F405)


281-281: Symbol may be undefined, or defined from star imports

(F405)

Alpha/Base.py

265-265: Insight may be undefined, or defined from star imports

(F405)

Initialization/SetupBaseStructure.py

204-204: SecurityType may be undefined, or defined from star imports

(F405)


205-205: NullOptionAssignmentModel may be undefined, or defined from star imports

(F405)

Order/Base.py

232-233: Use a single if statement instead of nested if statements

(SIM102)


255-256: Use a single if statement instead of nested if statements

(SIM102)

🔇 Additional comments (24)
Order/Base.py (6)

38-40: Position limits are appropriately checked before building the order

The added check ensures that orders respect position limits defined in the strategy.


190-191: ⚠️ Potential issue

Fix usage of self in getNextOrderId

The getNextOrderId method is still decorated with @staticmethod but references self.context, which will raise an error because self is not available in static methods. You should remove the @staticmethod decorator to make it an instance method.

Apply this diff to correct the method:

-    @staticmethod
     def getNextOrderId(self):
         try:
             max_order_id = max(orderId for _, orderId in self.context.openPositions.items())
         except:
             max_order_id = 0

         if max_order_id > 0 and Base.orderCount == 0:
             Base.orderCount = max_order_id + 1
         else:
             Base.orderCount += 1

         return Base.orderCount

192-204: ⚠️ Potential issue

Initialize maxActivePositions and maxOpenPositions attributes

The methods hasReachedMaxActivePositions and hasReachedMaxOpenPositions reference self.maxActivePositions and self.maxOpenPositions, but these attributes are not initialized in the __init__ method. This will result in an AttributeError.

Apply this diff to initialize the attributes:

 def __init__(self, context, strategy):
     self.context = context
     # Set the logger
     self.logger = Logger(context, className=type(self).__name__, logLevel=context.logLevel)
     self.strategy = strategy
     # Set default name (use the class name)
     self.name = strategy.name
     # Set the Strategy Name (optional)
     self.nameTag = strategy.nameTag
     # Initialize the contract utils
     self.contractUtils = ContractUtils(context)
+    # Initialize position limits
+    self.maxActivePositions = self.strategy.parameter("maxActivePositions", 1)
+    self.maxOpenPositions = self.strategy.parameter("maxOpenPositions", 1)

221-223: ⚠️ Potential issue

Filter active positions by strategy

In check_position_limits, active_positions counts all invested positions in the portfolio, including those not related to the current strategy. This can lead to incorrect limit checks if multiple strategies are used. Filter the positions to include only those belonging to the current strategy.

Apply this diff to filter positions:

 active_positions = len([
-    p for p in self.context.Portfolio.Values if p.Invested
+    p for p in self.context.Portfolio.Values
+    if p.Invested and p.Symbol in self.context.openPositions.values()
+    and self.context.allPositions[self.context.openPositions[p.Symbol]].strategyTag == self.nameTag
 ])

227-228: ⚠️ Potential issue

Filter open orders by strategy

Ensure that only open orders for the current strategy are included when counting open_orders. This prevents interference between different strategies.

Apply this diff to filter open orders:

 open_orders = len([
-    o for o in self.context.Transactions.GetOpenOrders()
+    o for o in self.context.Transactions.GetOpenOrders()
+    if o.Symbol in self.context.Securities
+    and self.context.Securities[o.Symbol].strategyTag == self.nameTag
 ])

260-265: ⚠️ Potential issue

Verify comparison logic in hasDuplicateLegs

The comparison in the nested loops may not correctly identify duplicate legs due to potential mismatches in data types or values for leg.contractSide and order["contractSide"][contract.Symbol]. Ensure that the contract sides are consistent and properly comparable.

Review and, if necessary, adjust the comparison to:

  • Use consistent data types (e.g., both as strings or both as integers).
  • Confirm that contractSide values match the expected format.
Tests/specs/order/base_spec.py (3)

55-66: Strategy parameters are correctly mocked for tests

The setup of self.base.strategy.parameter provides appropriate test parameters.


118-124: Tests for validating order quantity are correctly implemented

The test cases appropriately check that orders exceeding maxOrderQuantity are rejected.


157-158: ⚠️ Potential issue

Adjust test calls to getNextOrderId

Since getNextOrderId is no longer a @staticmethod, you should call it on the instance (self.base) rather than the class (Base).

Apply this diff to update the test:

- next_id = Base.getNextOrderId()
+ next_id = self.base.getNextOrderId()
Initialization/SetupBaseStructure.py (1)

204-205: ⚠️ Potential issue

Import SecurityType and NullOptionAssignmentModel explicitly

The use of SecurityType and NullOptionAssignmentModel may lead to undefined name warnings due to star imports. To address this, import these classes explicitly.

Apply this diff to import the missing names:

+from QuantConnect import SecurityType, NullOptionAssignmentModel
🧰 Tools
🪛 Ruff (0.8.0)

204-204: SecurityType may be undefined, or defined from star imports

(F405)


205-205: NullOptionAssignmentModel may be undefined, or defined from star imports

(F405)

Tests/specs/alpha/base_spec.py (4)

49-52: Mock dataHandler is correctly added for testing

The addition of self.mock_data_handler simulates the dataHandler for testing purposes.


101-143: Enhanced test setup for the update method

The test setup includes all necessary mocks and initializations to accurately test the update method under various conditions.


459-472: Tests for market status detection are well-implemented

The test cases accurately check the isMarketClosed method under different scenarios, ensuring correct behavior during warm-up and market hours.


473-497: Market schedule checks are thoroughly tested

The check_market_schedule method is tested for various conditions, including trading windows and minimum trade intervals, ensuring adherence to the trading schedule.

Alpha/Base.py (5)

204-204: Initialization of last_trade_time is appropriate

The addition of self.last_trade_time = None ensures that the tracking of the last trade time is properly initialized.


245-248: Performance tracking update is correct

Including performance tracking with self.context.performance.OnUpdate(data) enhances the monitoring of algorithm performance.


267-270: Undefined self.underlyingSymbol in isMarketClosed method

As mentioned earlier, self.underlyingSymbol is not initialized in the class. Ensure it is defined to prevent errors.


263-263: Import Insight explicitly to resolve undefined name

The name Insight may be undefined due to star imports. To ensure Insight.Group(insights) works correctly, import Insight explicitly.

Apply this diff to import Insight:

+from QuantConnect.Algorithm.Framework import Insight

272-296: Verify that schedule parameters are correctly initialized

In the check_market_schedule method, ensure that self.scheduleStartTime, self.scheduleStopTime, and self.minimumTradeScheduleDistance are properly initialized and accessible as instance attributes.

Run the following script to confirm that schedule parameters are set:

✅ Verification successful

Schedule parameters are properly initialized through class defaults

The verification shows that the schedule parameters are correctly defined in the DEFAULT_PARAMETERS dictionary of the Base class:

  • scheduleStartTime is initialized to time(9, 30, 0)
  • scheduleStopTime is set to None by default
  • minimumTradeScheduleDistance is set to timedelta(days=1)

These parameters are accessible through class parameter merging (DEFAULT_PARAMETERS + PARAMETERS) and are properly used in the check_market_schedule method.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if schedule parameters are defined in the Base class.

# Search for assignments of schedule parameters in the class definition
rg -A 5 -B 5 'class Base' Alpha/Base.py | rg 'self\.schedule(StartTime|StopTime|Frequency|MinimumTradeScheduleDistance)'

Length of output: 122


Script:

#!/bin/bash
# Let's search for the initialization of these parameters in the Base class
# First, let's check the constructor and any initialization methods
ast-grep --pattern 'class Base {
  $$$
  def __init__($$$) {
    $$$
  }
  $$$
}'

# Also search for any property definitions or assignments of these parameters
rg "scheduleStartTime|scheduleStopTime|minimumTradeScheduleDistance" Alpha/Base.py

Length of output: 472


Script:

#!/bin/bash
# Let's get more context around where these parameters are defined
rg -B 5 -A 5 '"scheduleStartTime": time\(' Alpha/Base.py

# Also check if these are being set from configuration or parameters
rg -B 2 -A 2 "def __init__" Alpha/Base.py

Length of output: 843


Script:

#!/bin/bash
# Let's check how these default parameters are used in the class
rg -B 3 -A 3 "DEFAULT_PARAMETERS" Alpha/Base.py

# Also check for any parameter initialization or configuration loading
ast-grep --pattern 'def initialize($$$) {
  $$$
}'

Length of output: 880

.python-version (1)

1-1: Python version update is acceptable

Updating the Python version to 3.8.20 keeps the environment up-to-date with the latest patches.

Alpha/FutureSpread.py (1)

52-53: Wing size increase requires careful consideration

The increase of putWingSize and callWingSize from 20 to 50 significantly widens the spreads. This change could impact:

  • Risk/reward profile
  • Margin requirements
  • Premium collection
  • Position sizing

Please review and justify this aggressive parameter change, or consider aligning with the more conservative wing sizes (5-20 points) used elsewhere in the codebase.

Tools/DataHandler.py (3)

68-70: Logging enhancement improves observability

Adding a warning when no initial symbols are provided enhances the visibility of the filtering process.


243-244: Initialize Greeks after adding contracts

Calling _initializeGreeks ensures that option Greeks are calculated promptly after adding the contracts, which is essential for accurate option pricing and strategy implementation.


276-297: Import SecurityType, OptionRight, and Symbol explicitly

The use of SecurityType, OptionRight, and Symbol in _initializeGreeks may cause NameError if they are not imported explicitly.

Apply this diff to import the missing names:

+from QuantConnect import SecurityType, OptionRight, Symbol
🧰 Tools
🪛 Ruff (0.8.0)

279-279: SecurityType may be undefined, or defined from star imports

(F405)


279-279: SecurityType may be undefined, or defined from star imports

(F405)


280-280: OptionRight may be undefined, or defined from star imports

(F405)


280-280: OptionRight may be undefined, or defined from star imports

(F405)


280-280: OptionRight may be undefined, or defined from star imports

(F405)


281-281: Symbol may be undefined, or defined from star imports

(F405)

Comment on lines +252 to +254
if self.isMarketClosed():
return []

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential issue: self.underlyingSymbol may not be defined

The method isMarketClosed uses self.underlyingSymbol, but self.underlyingSymbol is not defined in the Base class. This may lead to an AttributeError at runtime.

Apply this diff to define self.underlyingSymbol in the __init__ method:

 def __init__(self, context):
     self.context = context
     self.name = type(self).__name__  # Set default name (use the class name)
     self.nameTag = self.name # Set the Strategy Name (optional)
+    self.underlyingSymbol = context.symbol  # Define underlyingSymbol
     self.logger = Logger(context, className=type(self).__name__, logLevel=context.logLevel) # Set the logger

Committable suggestion skipped: line range outside the PR's diff.

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

♻️ Duplicate comments (1)
Tools/DataHandler.py (1)

133-156: ⚠️ Potential issue

Add safety check when accessing Securities dictionary.

The code could raise a KeyError when accessing self.context.Securities[symbol.ID.Symbol]. Add a safety check:

-            for symbol in filteredSymbols[:5]:  # Sample first 5 symbols for detailed logging
-                security = self.context.Securities[symbol.ID.Symbol]
-                if security.IsTradable:
-                    tradable_symbols.append(symbol)
-                else:
-                    non_tradable_reasons.append(f"Symbol {symbol.ID.Symbol}: IsTradable={security.IsTradable}")
+            for symbol in filteredSymbols[:5]:  # Sample first 5 symbols for detailed logging
+                if symbol.ID.Symbol in self.context.Securities:
+                    security = self.context.Securities[symbol.ID.Symbol]
+                    if security.IsTradable:
+                        tradable_symbols.append(symbol)
+                    else:
+                        non_tradable_reasons.append(f"Symbol {symbol.ID.Symbol}: IsTradable={security.IsTradable}")
+                else:
+                    non_tradable_reasons.append(f"Symbol {symbol.ID.Symbol} not found in Securities dictionary")
🧹 Nitpick comments (1)
Tools/DataHandler.py (1)

98-132: Consider extracting DTE selection logic into helper methods.

The DTE selection logic is complex and handles multiple scenarios. Consider extracting into helper methods for better maintainability:

  • _selectExpiryDate
  • _handleDynamicDTESelection
  • _filterMultipleEntries
 def optionChainProviderFilter(self, symbols, min_strike_rank, max_strike_rank, minDte, maxDte):
     # ... existing code ...
-    # Get unique expiry dates
-    expiry_dates = sorted(set(symbol.ID.Date for symbol in filteredSymbols), reverse=True)
-    
-    # Handle dynamic DTE selection if enabled
-    if (hasattr(self.strategy, 'dynamicDTESelection') and self.strategy.dynamicDTESelection and 
-        hasattr(self.context, 'recentlyClosedDTE') and self.context.recentlyClosedDTE):
-        valid_closed_trades = [
-            trade for trade in self.context.recentlyClosedDTE 
-            if trade["closeDte"] >= minDte
-        ]
-        if valid_closed_trades:
-            last_closed_dte = valid_closed_trades[0]["closeDte"]
-            # Find expiry date closest to last closed DTE
-            target_expiry = min(expiry_dates, 
-                              key=lambda x: abs((x.date() - self.context.Time.date()).days - last_closed_dte))
-            filteredSymbols = [s for s in filteredSymbols if s.ID.Date == target_expiry]
-    else:
-        # Use furthest/earliest expiry based on useFurthestExpiry
-        selected_expiry = expiry_dates[0 if self.strategy.useFurthestExpiry else -1]
-        filteredSymbols = [s for s in filteredSymbols if s.ID.Date == selected_expiry]
+    filteredSymbols = self._selectExpiryDate(filteredSymbols, minDte)
+    
+    if not self._allowMultipleEntries():
+        filteredSymbols = self._filterMultipleEntries(filteredSymbols)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2abef00 and 13c278c.

📒 Files selected for processing (3)
  • Tests/specs/alpha/base_spec.py (7 hunks)
  • Tests/specs/tools/data_handler_spec.py (3 hunks)
  • Tools/DataHandler.py (3 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
Tools/DataHandler.py

76-76: timedelta may be undefined, or defined from star imports

(F405)


303-303: SecurityType may be undefined, or defined from star imports

(F405)


303-303: SecurityType may be undefined, or defined from star imports

(F405)


304-304: OptionRight may be undefined, or defined from star imports

(F405)


304-304: OptionRight may be undefined, or defined from star imports

(F405)


304-304: OptionRight may be undefined, or defined from star imports

(F405)


305-305: Symbol may be undefined, or defined from star imports

(F405)

Tests/specs/alpha/base_spec.py

16-16: Tools.DataHandler imported but unused

Remove unused import: Tools.DataHandler

(F401)


18-18: Tests.mocks.algorithm_imports.SecurityType imported but unused

Remove unused import

(F401)


18-18: Tests.mocks.algorithm_imports.Resolution imported but unused

Remove unused import

(F401)


18-18: Tests.mocks.algorithm_imports.OptionRight imported but unused

Remove unused import

(F401)


19-19: Tests.mocks.algorithm_imports.TradeBar imported but unused

Remove unused import

(F401)


19-19: Tests.mocks.algorithm_imports.PortfolioTarget imported but unused

Remove unused import

(F401)


20-20: Tests.mocks.algorithm_imports.List imported but unused

Remove unused import

(F401)


20-20: Tests.mocks.algorithm_imports.SecurityChanges imported but unused

Remove unused import

(F401)


20-20: Tests.mocks.algorithm_imports.AlphaModel imported but unused

Remove unused import

(F401)


21-21: Tests.mocks.algorithm_imports.Slice imported but unused

Remove unused import

(F401)

🔇 Additional comments (8)
Tests/specs/tools/data_handler_spec.py (2)

Line range hint 118-155: LGTM! Test data setup is comprehensive.

The test data setup effectively covers different strike prices around ATM (95, 100, 105) and properly configures mock objects with all required attributes.


161-164: LGTM! Test assertions are accurate.

The assertions correctly verify that all three strikes (95, 100, 105) are included in the filtered results and that performance is tracked via execution timer.

Tools/DataHandler.py (3)

70-87: LGTM! Enhanced logging improves debuggability.

The added logging provides comprehensive information about the initial state and detailed data for the first 5 symbols, which will help with debugging and monitoring.

🧰 Tools
🪛 Ruff (0.8.2)

76-76: timedelta may be undefined, or defined from star imports

(F405)


176-195: LGTM! Strike filtering logic is robust and well-documented.

The strike filtering implementation is thorough with:

  • Clear ATM strike calculation
  • Proper bounds checking for strike indices
  • Comprehensive logging of strike ranges

300-321: LGTM! Greeks initialization is comprehensive.

The _initializeGreeks method effectively:

  • Handles both Option and IndexOption types
  • Creates mirror symbols correctly
  • Initializes all Greeks (iv, delta, gamma, vega, rho, theta)
  • Includes proper error handling
🧰 Tools
🪛 Ruff (0.8.2)

303-303: SecurityType may be undefined, or defined from star imports

(F405)


303-303: SecurityType may be undefined, or defined from star imports

(F405)


304-304: OptionRight may be undefined, or defined from star imports

(F405)


304-304: OptionRight may be undefined, or defined from star imports

(F405)


304-304: OptionRight may be undefined, or defined from star imports

(F405)


305-305: Symbol may be undefined, or defined from star imports

(F405)

Tests/specs/alpha/base_spec.py (3)

24-56: LGTM! MockUnderlying implementation is robust.

The MockUnderlying class is well-designed with:

  • Proper singleton pattern implementation
  • Clean price management
  • Useful reset functionality for test isolation

461-474: LGTM! Market checks tests are comprehensive.

The tests effectively cover all key market scenarios:

  • Market closed during warmup
  • Closed market detection
  • Open market detection

475-499: LGTM! Market schedule tests are thorough.

The tests effectively verify:

  • Trading within schedule window
  • Prevention of trading outside schedule
  • Minimum trade distance requirements

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