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

137 arbitrage agent #511

Merged
merged 36 commits into from
Oct 29, 2024
Merged

137 arbitrage agent #511

merged 36 commits into from
Oct 29, 2024

Conversation

gabrielfior
Copy link
Contributor

@gabrielfior gabrielfior commented Oct 10, 2024

-> Added Arbitrage agent for placing YES/NO or NO/YES trades between correlated markets
-> Added test


Some examples of correlated markets + expected profit

"correlated_markets[0].correlation=1.0 
correlated_markets[0].main_market.question='Will the average global temperature in 2024 exceed 2023?' correlated_markets[0].related_market.question='Will 2024 be even hotter than 2023? 🥵 '"
 potential_profit = 0.06270421212134791

- [main_market Will Donald Trump be the Republican nominee for president in 2024? related_market_question Will Donald Trump Be The GOP Candidate for The 2024 Presidential Election potential profit 0.23373115547666723]
- [main_market Will the LK-99 room temp, ambient pressure superconductivity pre-print replicate before 2025? related_market_question Will the Meissner effect be confirmed near room temperature in copper-substituted lead apatite? potential profit 9.799516753616899e-11]
- [main_market Will Donald Trump win the 2024 presidential election? related_market_question Will Donald Trump win the 2024 US Presidential Election? potential profit 0.004095501879286534, main_market Will Donald Trump win the 2024 presidential election? related_market_question Will Trump win the 2024 US election and be elected as president? potential profit 0.42199872849628406]
- [main_market Will Netanyahu remain Israel's PM until the end of 2024? related_market_question [ACX 2024] Will Benjamin Netanyahu remain Prime Minister of Israel throughout 2024? potential profit 0.0, main_market Will Netanyahu remain Israel's PM until the end of 2024? related_market_question Will Netanyahu still be the prime minister of Israel at the end of October 7th 2024? potential profit 0.30326927261165015, main_market Will Netanyahu remain Israel's PM until the end of 2024? related_market_question Will Benjamin Netanyahu (Bibi) be the prime minister of Israel at the end of 2024 potential profit 0.0]

Edit - now that correlation < 0 (near_perfect_correlation = False) can also be handled, found some inversely correlated pairs

Correlation(near_perfect_correlation=False, reasoning="The two markets are inversely correlated. MARKET 1 asks if Netanyahu will remain Israel's PM until the end of 2024, while MARKET 2 asks if Netanyahu will die before October 7th, 2024. If MARKET 2 resolves to 'Yes' (Netanyahu dies before October 7th, 2024), then MARKET 1 must resolve to 'No' (he cannot remain PM until the end of 2024 if he dies before then). Conversely, if MARKET 1 resolves to 'Yes', then MARKET 2 must resolve to 'No'. Therefore, the outcomes of these markets are perfectly inversely correlated.")

@gabrielfior gabrielfior linked an issue Oct 10, 2024 that may be closed by this pull request
@gabrielfior gabrielfior marked this pull request as ready for review October 10, 2024 19:14
Copy link
Contributor

coderabbitai bot commented Oct 10, 2024

Walkthrough

This pull request introduces significant updates to the prediction market agent's architecture, including the addition of new data models for handling correlations and arbitrage betting strategies. It defines classes such as Correlation, CorrelatedMarketPair, and ArbitrageBet, enhancing data validation and profit calculation methods. Additionally, a new deployable agent, DeployableArbitrageAgent, is introduced, which focuses on placing mirror bets based on correlation analysis. The changes also include various updates to testing frameworks and modifications in existing agent classes to improve functionality and error handling.

Changes

File Change Summary
prediction_market_agent/agents/arbitrage_agent/data_models.py Added classes: Correlation, CorrelatedMarketPair, ArbitrageBet. Enhanced methods for calculating potential profits and determining betting directions. Updated split_bet_amount_between_yes_and_no method.
prediction_market_agent/agents/arbitrage_agent/deploy.py Introduced DeployableArbitrageAgent class with methods for market correlation analysis and trade building. Updated run method to enforce operation on Omen markets.
tests/agents/arbitrage_agent/test_arbitrage_agent.py Added unit tests for DeployableArbitrageAgent using pytest, including fixtures for market instances and a parameterized test for market correlation.
tests/agents/arbitrage_agent/test_correlated_market_pair.py Introduced tests for CorrelatedMarketPair, validating profit calculations and betting amounts through parameterized tests.
prediction_market_agent/agents/think_thoroughly_agent/models.py Added close_time_timestamp field to PineconeMetadata class and modified the from_omen_market method to include this field.
prediction_market_agent/agents/think_thoroughly_agent/think_thoroughly_agent.py Removed update_markets method from ThinkThoroughlyBase. Enhanced error handling in TavilySearchResultsThatWillThrow class with retry logic. Updated logging in generate_final_decision method.
prediction_market_agent/db/pinecone_handler.py Updated PineconeHandler methods for improved filtering and ID retrieval. Added get_existing_ids_in_index method.
prediction_market_agent/agents/think_thoroughly_agent/deploy.py Replaced update_markets with insert_all_omen_markets_if_not_exists in before_process_markets method.
prediction_market_agent/agents/think_thoroughly_agent/benchmark.py Modified build_binary_agent_market_from_question to include market fees in the agent market creation process.
tests/db/test_pinecone_handler.py Updated PineconeMetadata instantiation in tests to include the new close_time_timestamp field.
prediction_market_agent/run_agent.py Added DeployableArbitrageAgent to RunnableAgent enum and updated RUNNABLE_AGENTS dictionary accordingly.
prediction_market_agent/agents/invalid_agent/deploy.py Updated verify_market method logic to utilize dynamic interval retrieval. Refined comments in answer_binary_market method.
prediction_market_agent/agents/specialized_agent/deploy.py Introduced n_markets_to_fetch class attribute in GetMarketCreatorsStalkerMarkets class, replacing hardcoded limit in get_markets method.

Possibly related issues

Possibly related PRs

Suggested reviewers

  • evangriffiths: Suggested as a reviewer for expertise in the relevant areas of the codebase.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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 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.

Copy link
Contributor

@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: 6

🧹 Outside diff range and nitpick comments (2)
tests/agents/arbitrage_agent/test_arbitrage_agent.py (2)

13-45: LGTM: Well-designed fixtures with a minor suggestion.

The fixtures are well-structured and effectively isolate components for testing. The use of mocking in the arbitrage_agent fixture and the creation of distinct market scenarios are particularly good practices.

Consider adding type hints to the mock objects in the market fixtures for improved code clarity:

m1 = Mock(spec=OmenAgentMarket)

This change would make the intended type of the mock object more explicit.


48-66: LGTM: Well-structured test with parameterization. Consider additional test cases.

The test function is well-designed, using parameterization to efficiently test both related and unrelated market scenarios. It effectively utilizes the fixtures and checks the correlation calculation against a threshold.

Consider adding the following test cases to increase coverage:

  1. Test with edge case correlations (e.g., correlation very close to the threshold).
  2. Test the behavior when markets have identical questions.
  3. Test with markets in different languages to ensure language-agnostic correlation.

Example:

@pytest.mark.parametrize(
    "market1_question, market2_question, expected_correlation",
    [
        ("Will X happen?", "Will X occur?", 0.99),  # Nearly identical
        ("Will X happen?", "Will X happen?", 1.0),  # Identical
        ("Will X happen?", "Wird X passieren?", 0.9),  # Different languages
    ]
)
def test_correlation_edge_cases(
    arbitrage_agent: DeployableOmenArbitrageAgent,
    market1_question: str,
    market2_question: str,
    expected_correlation: float,
) -> None:
    market1 = Mock(spec=OmenAgentMarket, question=market1_question)
    market2 = Mock(spec=OmenAgentMarket, question=market2_question)
    correlation = arbitrage_agent.calculate_correlation_between_markets(
        market=market1, related_market=market2
    )
    assert pytest.approx(correlation.correlation, abs=0.01) == expected_correlation

This additional test would cover more scenarios and increase the robustness of your test suite.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 9354244 and 433656d.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (3)
  • prediction_market_agent/agents/arbitrage_agent/data_models.py (1 hunks)
  • prediction_market_agent/agents/arbitrage_agent/deploy.py (1 hunks)
  • tests/agents/arbitrage_agent/test_arbitrage_agent.py (1 hunks)
🧰 Additional context used
🔇 Additional comments (5)
tests/agents/arbitrage_agent/test_arbitrage_agent.py (2)

1-10: LGTM: Imports are appropriate and well-organized.

The imports section includes all necessary modules and classes for the test file. The organization is logical, separating standard library imports from project-specific ones.


1-66: Overall, excellent test structure with room for minor enhancements.

This test file is well-organized and effectively tests the core functionality of the arbitrage agent. The use of fixtures, parameterization, and mocking demonstrates good testing practices.

To further improve the test suite:

  1. Consider adding type hints to mock objects in fixtures.
  2. Expand test cases to cover edge scenarios and language-agnostic correlation.

These enhancements will increase the robustness and clarity of your test suite, ensuring better coverage of the arbitrage agent's functionality.

prediction_market_agent/agents/arbitrage_agent/data_models.py (1)

16-18: Verify compatibility with Pydantic version and decorator usage

The use of @computed_field alongside @property may not be necessary and could lead to unexpected behavior. Additionally, the @computed_field decorator is available in Pydantic v2.

Ensure that the project uses Pydantic v2. If not, consider one of the following:

  • Option 1: Remove the @computed_field decorator if not required.
  • Option 2: Adjust the code to match the appropriate Pydantic version's conventions.

Example adjustment:

-    @computed_field  # type: ignore[prop-decorator]
     @property
     def potential_profit_per_bet_unit(self) -> float:

Please verify that the computed properties behave as intended after making changes.

Also applies to: 36-38, 45-47

prediction_market_agent/agents/arbitrage_agent/deploy.py (2)

74-74: Verify the model name 'gpt-4o-mini'

In the _build_chain method, the model parameter is set to 'gpt-4o-mini'. Please ensure that this is the correct model name and that it is accessible via the OpenAI API.


138-138: Ensure unique market identification

The condition related_market.id != market.id checks if the markets are different. Verify that id attributes are correctly populated and comparable between AgentMarket and the market objects retrieved from the subgraph handler.

Comment on lines +133 to +134
"main_market_question": market,
"related_market_question": related_market,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect arguments passed to self.chain.invoke

In the get_correlated_markets method, you're passing market and related_market objects to self.chain.invoke, whereas the chain expects strings containing the market questions.

Apply this diff to fix the arguments:

-                "main_market_question": market,
-                "related_market_question": related_market,
+                "main_market_question": market.question,
+                "related_market_question": related_market.question,
📝 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
"main_market_question": market,
"related_market_question": related_market,
"main_market_question": market.question,
"related_market_question": related_market.question,

Copy link
Contributor

@evangriffiths evangriffiths left a comment

Choose a reason for hiding this comment

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

Could you share some examples of real-world correlated markets found by this agent (+ the trades it makes + the expected profit) in the PR description, so we can eyeball that it looks like the agent will work when released in the wild?

self, pair: CorrelatedMarketPair
) -> list[Trade]:
market_to_bet_yes, market_to_bet_no = pair.main_market, pair.related_market
total_amount: BetAmount = pair.main_market.get_tiny_bet_amount()
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like something that should be configurable at the top level

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented

Comment on lines 162 to 175
Trade(
trade_type=TradeType.BUY,
outcome=True,
amount=TokenAmount(
amount=amount_yes, currency=market_to_bet_yes.currency
),
),
Trade(
trade_type=TradeType.BUY,
outcome=False,
amount=TokenAmount(
amount=amount_no, currency=market_to_bet_no.currency
),
),
Copy link
Contributor

Choose a reason for hiding this comment

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

Buying [market1: yes, market2: no] assumes positive correlation between markets. I think you need to have some logic that chooses the directions based on correlation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Positive correlation is assumed here -> https://github.com/gnosis/prediction-market-agent/pull/511/files/433656d955694050935c662e1574f80c552d3cc7#diff-1b9d8df9eb7cb55918e642ebc0f26902d5211d4946a8c4d0454e9889dd05c371R138-R140

I didn't implement logic for the negatively correlated case (it was on an earlier version), I think positive correlation is a good start. Also, negative correlation is harder to determine, e.g. Trump president vs Kamala president is 100% negatively correlated but not trivial for LLM to tell, hence preferred to stick to 100% correlation case.

Happy with suggestions though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh okay. Fwiw I think it's okay if the LLMs find it hard to determine that, as long as there aren't false positives.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Going with your bool suggestion from the other thread.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, negative correlation is harder to determine, e.g. Trump president vs Kamala president is 100% negatively correlated but not trivial for LLM to tell, hence preferred to stick to 100% correlation case.

What did you try? It seems to work fine for me, see the comment above.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe I addressed this case (negative correlation) with my latest changes, please see 7afbb41#diff-7b9692129117b2cc9d653a28cdd7bb8f5fbaf3cc6020d6ed791b8642efc63467R62-R65)

prediction_market_agent/agents/arbitrage_agent/deploy.py Outdated Show resolved Hide resolved
correlation_threshold: float = 0.8

def load(self) -> None:
self.subgraph_handler = OmenSubgraphHandler()
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason you've made this agent omen-specific? Looks like the only thing you've used the sgh for is to get markets. So it looks like this could easily be changed to become market-platform agnostic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made it Omen-specific because the related_markets should be queryable via Pinecone (because of the TTA agent), and only Omen agents are stored there currently.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I didn't realise that. Where does the indexing of omen markets in the pincone db happen? If it's not part of this agent, it goes against @kongzii's philosophy of "anyone should be able to run the agents (if they have appropriate api keys), and get the same kind of results".

Regardless, there doesn't seem to be a need to have any Omen dependencies here - you could just have a if market_type != MarketType.Omen: raise ... check

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indexing occurs below

self.pinecone_handler.insert_all_omen_markets_if_not_exists(

I don't think it prevents someone from running the agent - you just need Pinecone api keys and, on the first run, it will fill out Pinecone and have the same dataset as we do.

Regardless, there doesn't seem to be a need to have any Omen dependencies here - you could just have a if market_type != MarketType.Omen: raise ... check

Makes sense to me, implementing this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Well it does if the user wants to run just this agent, and not the DeployableThinkThoroughlyAgent as well! I think we need to do self.pinecone_handler.insert_all_omen_markets_if_not_exists(...) somewhere here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure - added 55aee24

Copy link
Contributor

Choose a reason for hiding this comment

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

Just had a look at what pinecone_handler.update_markets is doing. Is this what we want? Two things I'm not sure about:

  1. It doesn't look like it's removing closed markets from the index
  2. It only fetches the last 7 days' worth of created markets created_after = utcnow() - datetime.timedelta(days=7). Don't we want all open markets?

Copy link
Contributor Author

@gabrielfior gabrielfior Oct 15, 2024

Choose a reason for hiding this comment

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

Implemented changes as suggested by @evangriffiths and discussed with @kongzii :
-> Closing markets are NOT removed, they might be useful for TTA - only issue here is using closed markets inside ArbitrageAgent, we filter those out now (see DeployableArbitrageAgent::get_correlated_markets).
-> Fetching all open markets instead of markets created in the last 7 days (pinecone_handler::insert_all_omen_markets_if_not_exists) and storing them in Pinecone.

Copy link
Contributor

@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: 4

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 433656d and 2104e4d.

📒 Files selected for processing (2)
  • prediction_market_agent/agents/arbitrage_agent/deploy.py (1 hunks)
  • prediction_market_agent/agents/arbitrage_agent/prompt.py (1 hunks)
🧰 Additional context used

prediction_market_agent/agents/arbitrage_agent/prompt.py Outdated Show resolved Hide resolved
prediction_market_agent/agents/arbitrage_agent/deploy.py Outdated Show resolved Hide resolved
prediction_market_agent/agents/arbitrage_agent/deploy.py Outdated Show resolved Hide resolved
prediction_market_agent/agents/arbitrage_agent/deploy.py Outdated Show resolved Hide resolved
Copy link
Contributor

@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: 9

🧹 Outside diff range and nitpick comments (1)
prediction_market_agent/agents/arbitrage_agent/data_models.py (1)

12-35: LGTM: CorrelatedMarketPair class and potential_profit_per_bet_unit method are well-implemented.

The class structure and method implementation are appropriate. The method handles the case where total probability is zero, avoiding potential division by zero errors.

However, there's a minor improvement we can make to the comment:

Update the comment to remove the reference to LLM prompt:

-        # Negative correlations are not yet supported by the current LLM prompt, hence not handling those for now.
+        # Negative correlations are not yet supported, hence not handling those for now.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 2104e4d and d8d2601.

⛔ Files ignored due to path filters (1)
  • poetry.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (4)
  • prediction_market_agent/agents/arbitrage_agent/data_models.py (1 hunks)
  • prediction_market_agent/agents/arbitrage_agent/deploy.py (1 hunks)
  • prediction_market_agent/agents/arbitrage_agent/prompt.py (1 hunks)
  • tests/agents/arbitrage_agent/test_arbitrage_agent.py (1 hunks)
🧰 Additional context used
🔇 Additional comments (12)
tests/agents/arbitrage_agent/test_arbitrage_agent.py (5)

1-10: LGTM: Imports are well-organized and follow best practices.

The imports are correctly structured, following the PEP 8 style guide. They include all necessary modules and classes for the test file.


13-24: LGTM: Well-implemented arbitrage_agent fixture.

The fixture correctly mocks the load method and builds the agent's chain, ensuring isolated testing of the DeployableOmenArbitrageAgent.


27-31: LGTM: Market fixtures are well-defined for testing correlation scenarios.

The main_market, related_market, and unrelated_market fixtures create appropriate mock OmenAgentMarket instances with relevant questions for testing market correlations.

Also applies to: 33-38, 40-45


48-63: LGTM: Well-structured and comprehensive test function.

The test_correlation_for_similar_markets function is well-implemented:

  • It uses parameterization to test both correlated and uncorrelated scenarios efficiently.
  • The function correctly utilizes the fixtures to set up test cases.
  • It tests the calculate_correlation_between_markets method of the arbitrage agent.
  • The assertion checks for the expected correlation result.

The test covers both positive (correlated) and negative (uncorrelated) cases, ensuring thorough testing of the correlation calculation functionality.


1-63: Great job on implementing comprehensive tests for the arbitrage agent!

This test file is well-structured and follows best practices for Python testing:

  • Proper use of fixtures for setting up test scenarios.
  • Effective use of parameterization for testing multiple cases.
  • Clear and descriptive test function that covers both correlated and uncorrelated markets.
  • Good isolation of the arbitrage agent through mocking.

The tests provide solid coverage for the correlation calculation functionality of the arbitrage agent. This will help ensure the reliability and correctness of the agent's behavior in different market scenarios.

prediction_market_agent/agents/arbitrage_agent/data_models.py (3)

1-5: LGTM: Imports are appropriate and concise.

The imports are well-chosen for the functionality implemented in this file. They include necessary components from typing, Pydantic, and the custom AgentMarket class.


7-10: LGTM: Correlation class is well-defined and addresses previous feedback.

The Correlation class is simple and clear. The addition of the 'reasoning' field addresses the previous review comment, which will aid in debugging from langfuse traces.


55-71: ⚠️ Potential issue

Handle potential division by zero in split_bet_amount_between_yes_and_no

The method correctly implements the splitting logic as described. However, there's still a risk of division by zero if the sum of probabilities is zero.

Add a check to ensure the denominator is not zero before performing the division:

     def split_bet_amount_between_yes_and_no(
         self, total_bet_amount: float
     ) -> t.Tuple[float, float]:
         """Splits total bet amount following equations below:
         A1/p1 = A2/p2 (same profit regardless of outcome resolution)
         A1 + A2 = total bet amount
         """
+        denominator = (
+            self.market_to_bet_yes.current_p_yes
+            + self.market_to_bet_no.current_p_no
+        )
+        if denominator == 0:
+            raise ValueError("Sum of probabilities is zero, cannot split bet amount")
         amount_to_bet_yes = (
             total_bet_amount
             * self.market_to_bet_yes.current_p_yes
-            / (
-                self.market_to_bet_yes.current_p_yes
-                + self.market_to_bet_no.current_p_no
-            )
+            / denominator
         )
         amount_to_bet_no = total_bet_amount - amount_to_bet_yes
         return amount_to_bet_yes, amount_to_bet_no

This change ensures that we don't attempt to divide by zero, which would cause a runtime error.

Likely invalid or redundant comment.

prediction_market_agent/agents/arbitrage_agent/deploy.py (4)

1-42: LGTM: Import statements are appropriate and well-organized.

The import statements are comprehensive and relevant to the functionality of the DeployableOmenArbitrageAgent class. They include necessary modules from both external libraries and internal packages.


73-89: LGTM: _build_chain method is well-implemented

The _build_chain method effectively constructs a language model chain using OpenAI's ChatGPT, a custom prompt template, and a Pydantic output parser. The use of APIKeys().openai_api_key_secretstr_v1 for the API key is a good practice for keeping sensitive information secure.


1-179: Overall assessment: Well-structured implementation with room for improvements

The DeployableOmenArbitrageAgent class provides a solid foundation for arbitrage trading on the Omen platform. The code is well-organized and implements complex logic for market correlation analysis and trade building. However, there are several areas where the implementation could be improved:

  1. Error handling: Add try-except blocks in methods making external API calls to improve robustness.
  2. Configurability: Make certain parameters (e.g., model, bet amounts) configurable to enhance flexibility.
  3. Parameter utilization: Incorporate answer and existing_position parameters in the trade building logic for more informed decision-making.
  4. Input validation: Ensure correct data types are passed to methods, especially when invoking the language model chain.

Addressing these points will significantly enhance the agent's reliability, flexibility, and effectiveness in arbitrage trading scenarios.


55-68: ⚠️ Potential issue

Use the input limit parameter in get_markets

The get_markets method currently ignores the input limit parameter and hardcodes it to 100. To improve flexibility, consider using the input limit parameter:

 return super().get_markets(
     market_type=market_type,
-    limit=100,
+    limit=limit,
     sort_by=SortBy.HIGHEST_LIQUIDITY,
     filter_by=FilterBy.OPEN,
 )

This change allows the caller to control the number of markets fetched while still focusing on high liquidity markets.

Likely invalid or redundant comment.

prediction_market_agent/agents/arbitrage_agent/prompt.py Outdated Show resolved Hide resolved
prediction_market_agent/agents/arbitrage_agent/deploy.py Outdated Show resolved Hide resolved
prediction_market_agent/agents/arbitrage_agent/deploy.py Outdated Show resolved Hide resolved
prediction_market_agent/agents/arbitrage_agent/deploy.py Outdated Show resolved Hide resolved
from prediction_market_agent.utils import APIKeys


class DeployableArbitrageAgent(DeployableTraderAgent):
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason you're subclassing DeployableTraderAgent instead of DeployableAgent? Not sure which is better, just asking

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm doing so because other trading agents (KnownOutcome, TTA and even coinflip) are subclassing the DeployableTraderAgent - as I understand,it does some langfuse magic and checks for some balances, which doesn't hurt.

Copy link
Contributor

@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: 10

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d8d2601 and 6f1efbd.

📒 Files selected for processing (4)
  • prediction_market_agent/agents/arbitrage_agent/data_models.py (1 hunks)
  • prediction_market_agent/agents/arbitrage_agent/deploy.py (1 hunks)
  • prediction_market_agent/run_agent.py (3 hunks)
  • tests/agents/arbitrage_agent/test_arbitrage_agent.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/agents/arbitrage_agent/test_arbitrage_agent.py
🧰 Additional context used
🔇 Additional comments (9)
prediction_market_agent/agents/arbitrage_agent/data_models.py (4)

1-14: LGTM: Well-structured data models

The imports and class definitions are appropriate and well-structured. The Correlation and CorrelatedMarketPair classes extend Pydantic's BaseModel, which is a good practice for data validation and serialization.


16-17: LGTM: Clear and concise __repr__ method

The __repr__ method provides a clear and informative string representation of the CorrelatedMarketPair instance, showing the questions for both the main and related markets.


19-37: Remove LLM prompt reference and consider adding reasoning

The potential_profit_per_bet_unit method is well-implemented and handles edge cases correctly. However, there are two points to address:

  1. As mentioned in a previous review, remove the reference to the LLM prompt in the comment on line 28.

  2. Consider adding a reasoning field to this method, as suggested in a past review comment. This would help with debugging from langfuse traces.

Update the comment on line 28:

-        # Negative correlations are not yet supported by the current LLM prompt, hence not handling those for now.
+        # Negative correlations are not yet supported, hence not handling those for now.

Consider adding a reasoning field to provide context for the calculated profit:

reasoning = f"Potential profit calculated based on p_yes={p_yes:.2f} and p_no={p_no:.2f}"
return (1.0 / total_probability) - 1.0, reasoning

This change would require updating the return type and adjusting any code that calls this method.


39-46: LGTM: Correct implementation of market_to_bet_yes

The market_to_bet_yes method correctly selects the market with the lower "YES" probability, which is the appropriate logic for determining where to place a "YES" bet in an arbitrage scenario.

prediction_market_agent/run_agent.py (3)

15-17: LGTM: Import statement for DeployableArbitrageAgent.

The import statement for DeployableArbitrageAgent is correctly added and follows the existing import style in the file.


80-80: LGTM: Addition of 'arbitrage' to RunnableAgent enum.

The arbitrage member is correctly added to the RunnableAgent enum, following the existing pattern and maintaining the current structure.


Line range hint 1-124: Summary: Successful integration of DeployableArbitrageAgent

The changes in this file successfully integrate the new DeployableArbitrageAgent into the existing agent framework. The modifications are consistent with the PR objectives and follow the established patterns in the file. The new agent is properly imported, added to the RunnableAgent enum, and included in the RUNNABLE_AGENTS dictionary.

These changes enable the arbitrage agent to be recognized and executed within the system, allowing for YES/NO or NO/YES trades between correlated markets as described in the PR objectives.

No existing functionality appears to be modified or removed, minimizing the risk of unintended side effects. However, as with any new feature, thorough testing is recommended to ensure the arbitrage agent functions correctly within the broader system.

prediction_market_agent/agents/arbitrage_agent/deploy.py (2)

1-47: LGTM: Imports and class definition look good.

The imports are comprehensive and well-organized. The class definition extends DeployableTraderAgent and provides a clear docstring explaining its purpose.


107-138: ⚠️ Potential issue

Fix arguments passed to self.chain.invoke

In the get_correlated_markets method, you're passing market and related_market objects to self.chain.invoke, whereas the chain expects strings containing the market questions. Update the code as follows:

-                "main_market_question": market,
-                "related_market_question": related_market,
+                "main_market_question": market.question,
+                "related_market_question": related_market.question,

This change ensures that the correct data is passed to the language model for correlation analysis.

Likely invalid or redundant comment.

prediction_market_agent/run_agent.py Show resolved Hide resolved
prediction_market_agent/agents/arbitrage_agent/deploy.py Outdated Show resolved Hide resolved
Copy link
Contributor

@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: 6

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 6f1efbd and 75d1b04.

📒 Files selected for processing (1)
  • prediction_market_agent/agents/arbitrage_agent/deploy.py (1 hunks)
🧰 Additional context used
🔇 Additional comments (1)
prediction_market_agent/agents/arbitrage_agent/deploy.py (1)

125-131: ⚠️ Potential issue

Fix arguments passed to self.chain.invoke.

The self.chain.invoke call is passing market and related_market objects directly, but it should be passing their question attributes instead.

Apply this diff to fix the issue:

result: Correlation = self.chain.invoke(
    {
-        "main_market_question": market,
-        "related_market_question": related_market,
+        "main_market_question": market.question,
+        "related_market_question": related_market.question,
    },
    config=get_langfuse_langchain_config(),
)

This change ensures that the correct data (the market questions) is passed to the language model for correlation analysis.

Likely invalid or redundant comment.

prediction_market_agent/agents/arbitrage_agent/deploy.py Outdated Show resolved Hide resolved
Copy link
Contributor

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

🧹 Outside diff range and nitpick comments (1)
prediction_market_agent/agents/arbitrage_agent/deploy.py (1)

80-82: Clarify the purpose of the answer_binary_market method.

The answer_binary_market method currently returns a fixed 50% probability with full confidence for all markets. This doesn't provide any real market analysis. If this is a placeholder, consider adding a TODO comment explaining the intended future implementation.

Example:

def answer_binary_market(self, market: AgentMarket) -> ProbabilisticAnswer | None:
    # TODO: Implement actual market analysis logic
    # Current implementation returns a placeholder value
    return ProbabilisticAnswer(p_yes=Probability(0.5), confidence=1.0)

This would make it clear that the current implementation is temporary and needs to be replaced with actual analysis logic in the future.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between dae075a and 50b932b.

⛔ Files ignored due to path filters (2)
  • poetry.lock is excluded by !**/*.lock, !**/*.lock
  • pyproject.toml is excluded by !**/*.toml
📒 Files selected for processing (2)
  • prediction_market_agent/agents/arbitrage_agent/deploy.py (1 hunks)
  • prediction_market_agent/agents/think_thoroughly_agent/think_thoroughly_agent.py (0 hunks)
💤 Files with no reviewable changes (1)
  • prediction_market_agent/agents/think_thoroughly_agent/think_thoroughly_agent.py
🧰 Additional context used
🔇 Additional comments (2)
prediction_market_agent/agents/arbitrage_agent/deploy.py (2)

1-43: LGTM: Import statements are well-organized and relevant.

The import statements are appropriately grouped and include all necessary modules for the implementation of the DeployableArbitrageAgent class. There are no apparent issues or unused imports.


158-188: LGTM: build_trades_for_correlated_markets implementation looks good.

The method correctly implements the logic for building trades for a pair of correlated markets. It appropriately splits the bet amount and creates Trade objects for both the main and related markets. The implementation is clear and follows the expected behavior for arbitrage trading.

prediction_market_agent/agents/arbitrage_agent/deploy.py Outdated Show resolved Hide resolved
Copy link
Contributor

@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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between dae075a and 11a82cb.

📒 Files selected for processing (1)
  • prediction_market_agent/agents/arbitrage_agent/deploy.py (1 hunks)
🧰 Additional context used

@@ -30,7 +30,7 @@ def answer_binary_market(self, market: AgentMarket) -> ProbabilisticAnswer | Non
)

def before_process_markets(self, market_type: MarketType) -> None:
self.agent.update_markets()
self.agent.pinecone_handler.update_markets()
Copy link
Contributor

Choose a reason for hiding this comment

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

according to https://github.com/gnosis/prediction-market-agent/pull/511/files#r1800902856 you're not intending to remove the closed markets from the pinecone db for the TTA. But you are changing the behaviour to only add open markets to the db (because PineconeHandler.insert_open_omen_markets_if_not_exists is called for both. Is this intended? how come?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My understanding is this
-> We have closed (old) markets in the DB, since PineconeHandler has been running for a while - hence we should have all markets (up to the very fresh ones) in the DB
-> Now the current implementation inserts only open markets (if ID not in DB) - this makes sense because the closed markets are already in the DB, only fresh (and new ones) will be missing.
-> In the logic above, we could miss some short-lived markets, that are open-closed in the timespan of 2h (between agent runs). But I don't think these markets are very relevant.
-> Important thing here is to have the market ID (and its question title) on Pinecone for similarity_search - market liquidity and outcome token prices come from the subgraph (for a given market ID).

@evangriffiths does the above clear your concerns, or do you suggest a different implementation?

Copy link
Contributor

Choose a reason for hiding this comment

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

-> In the logic above, we could miss some short-lived markets, that are open-closed in the timespan of 2h (between agent runs). But I don't think these markets are very relevant.

It's a strong and already a few times disproven assumption that our agent will run every 2 hours or that we won't need to turn them off even for the whole weekend or more. 🙈

Screenshot by Dropbox Capture

Previous behavior (fetching all of them but having a reasonable limit for created_after to not fetch thousands of markets every time) seemed fine to me.

I see Evan was worried about

It only fetches the last 7 days' worth of created markets created_after = utcnow() - datetime.timedelta(days=7). Don't we want all open markets?

But there should be older markets in the DB as well right? Updated from previous runs. It's cumulating in DB.

On the other hand... both implementation are wrong only in edge-cases so I guess I don't care in the end... okay my 2 cents are finished 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

What if we have to nuke our pinecone db for some reason, so we have to start from scratch - we'd want to pull in all old markets right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Old markets are now being retrieved

Comment on lines +27 to +30
from prediction_market_agent_tooling.markets.omen.omen import OmenAgentMarket
from prediction_market_agent_tooling.markets.omen.omen_subgraph_handler import (
OmenSubgraphHandler,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

As mentioned before, would be preferable IMO to not have any omen-specific dependencies. Should be easy enough to remove now!

Copy link
Contributor

@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: 9

🧹 Outside diff range and nitpick comments (1)
prediction_market_agent/agents/think_thoroughly_agent/models.py (1)

15-17: LGTM with a suggestion: Consider adding a safety check.

The update to from_omen_market method correctly includes the new close_time_timestamp field. However, to improve robustness, consider adding a safety check for the close_time attribute.

Here's a suggested improvement:

@staticmethod
def from_omen_market(market: OmenMarket) -> "PineconeMetadata":
    return PineconeMetadata(
        question_title=market.question_title,
        market_address=market.id,
        close_time_timestamp=int(market.close_time.timestamp()) if market.close_time else None,
    )

This change ensures that the method won't raise an AttributeError if market.close_time is None.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 11a82cb and 224036c.

⛔ Files ignored due to path filters (2)
  • poetry.lock is excluded by !**/*.lock, !**/*.lock
  • pyproject.toml is excluded by !**/*.toml
📒 Files selected for processing (4)
  • prediction_market_agent/agents/arbitrage_agent/deploy.py (1 hunks)
  • prediction_market_agent/agents/think_thoroughly_agent/models.py (1 hunks)
  • prediction_market_agent/agents/think_thoroughly_agent/think_thoroughly_agent.py (0 hunks)
  • prediction_market_agent/db/pinecone_handler.py (3 hunks)
💤 Files with no reviewable changes (1)
  • prediction_market_agent/agents/think_thoroughly_agent/think_thoroughly_agent.py
🧰 Additional context used
🔇 Additional comments (8)
prediction_market_agent/agents/think_thoroughly_agent/models.py (2)

10-10: LGTM: New field added to enhance market data.

The addition of close_time_timestamp: int to the PineconeMetadata class is a good improvement. It enhances the market data model by including the closing time, which can be crucial for various market analyses and operations.


Line range hint 1-33: Overall assessment: Good improvements to the market data model.

The changes in this file enhance the PineconeMetadata class by adding a close_time_timestamp field and updating the from_omen_market method accordingly. These improvements align well with the PR objectives of enhancing market data handling and correlation functionalities. The changes are well-implemented, with only a minor suggestion for adding a safety check in the from_omen_market method.

prediction_market_agent/agents/arbitrage_agent/deploy.py (2)

143-143: ⚠️ Potential issue

Replace print statement with logger

Using print statements for logging is not recommended. Consider using the logger for consistent logging throughout the application.

Apply this diff to fix the issue:

-    print(f"Fetched {len(omen_markets)} related markets for market {market.id}")
+    logger.info(f"Fetched {len(omen_markets)} related markets for market {market.id}")

Likely invalid or redundant comment.


146-152: ⚠️ Potential issue

Fix arguments passed to self.chain.invoke

In the get_correlated_markets method, you're passing market and related_market objects to self.chain.invoke, whereas the chain expects strings containing the market questions.

Apply this diff to fix the arguments:

-                "main_market_question": market,
-                "related_market_question": related_market,
+                "main_market_question": market.question,
+                "related_market_question": related_market.question,

This change ensures that the correct data is passed to the language model for correlation analysis.

Likely invalid or redundant comment.

prediction_market_agent/db/pinecone_handler.py (4)

16-16: Import Statement Addition Approved

The addition of the DatetimeUTC import is appropriate and necessary for handling timestamps in the updated methods.


74-74: Use of get_existing_ids_in_index

The refactoring to use get_existing_ids_in_index improves code readability by encapsulating the ID retrieval logic.


79-82: Consider Using describe_index_stats() for Accurate ID Retrieval

The current implementation of get_existing_ids_in_index uses self.index.list(), which may not reliably retrieve all existing IDs from the index. A previous review suggested using self.index.describe_index_stats() for a more accurate approach.


115-119: Introduction of update_markets Method Approved

The new update_markets method enhances code organization by consolidating the market update process. This improves maintainability and clarity.

Comment on lines +46 to +53
class DeployableArbitrageAgent(DeployableTraderAgent):
"""Agent that places mirror bets on Omen for (quasi) risk-neutral profit."""

model = "gpt-4o"
# trade amount will be divided between correlated markets.
total_trade_amount = BetAmount(amount=0.1, currency=OmenAgentMarket.currency)
bet_on_n_markets_per_run = 5
max_related_markets_per_market = 10
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider making class attributes configurable

The model, total_trade_amount, bet_on_n_markets_per_run, and max_related_markets_per_market attributes are hardcoded. To improve flexibility and allow for easier testing or adjustments, consider making these configurable through constructor parameters or a configuration file.

Example:

def __init__(self, model: str = "gpt-4o", total_trade_amount: float = 0.1, 
             bet_on_n_markets_per_run: int = 5, max_related_markets_per_market: int = 10):
    self.model = model
    self.total_trade_amount = BetAmount(amount=total_trade_amount, currency=OmenAgentMarket.currency)
    self.bet_on_n_markets_per_run = bet_on_n_markets_per_run
    self.max_related_markets_per_market = max_related_markets_per_market

This change would allow users to easily adjust these parameters when instantiating the agent.

Comment on lines 55 to 64
def run(self, market_type: MarketType) -> None:
if market_type != MarketType.OMEN:
raise RuntimeError(
"Can arbitrage only on Omen since related markets embeddings available only for Omen markets."
)
self.subgraph_handler = OmenSubgraphHandler()
self.pinecone_handler = PineconeHandler()
self.pinecone_handler.update_markets()
self.chain = self._build_chain()
super().run(market_type=market_type)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for initialization steps

The run method initializes important components (subgraph_handler, pinecone_handler, and chain) without any error handling. Consider wrapping these initializations in a try-except block to gracefully handle any potential errors during setup.

Example implementation:

def run(self, market_type: MarketType) -> None:
    if market_type != MarketType.OMEN:
        raise RuntimeError(
            "Can arbitrage only on Omen since related markets embeddings available only for Omen markets."
        )
    try:
        self.subgraph_handler = OmenSubgraphHandler()
        self.pinecone_handler = PineconeHandler()
        self.pinecone_handler.update_markets()
        self.chain = self._build_chain()
    except Exception as e:
        logger.error(f"Error initializing arbitrage agent: {str(e)}")
        raise
    super().run(market_type=market_type)

This change would improve the robustness of the initialization process and provide more informative error messages if something goes wrong.

Comment on lines 127 to 129
created_after=DatetimeUTC.fromtimestamp(start_timestamp)
if start_timestamp
else None,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure Correct Handling of start_timestamp When Zero

The condition if start_timestamp may fail when start_timestamp is 0, a valid UNIX timestamp. This could result in created_after being set to None unintentionally. Consider checking if start_timestamp is not None to handle zero correctly.

Apply this diff to fix the condition:

             created_after=DatetimeUTC.fromtimestamp(start_timestamp)
-            if start_timestamp
+            if start_timestamp is not None
             else None,
📝 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
created_after=DatetimeUTC.fromtimestamp(start_timestamp)
if start_timestamp
else None,
created_after=DatetimeUTC.fromtimestamp(start_timestamp)
if start_timestamp is not None
else None,

Comment on lines 120 to 129
def insert_open_omen_markets_if_not_exists(
self, start_timestamp: int | None = None
) -> None:
subgraph_handler = OmenSubgraphHandler()
markets = subgraph_handler.get_omen_binary_markets_simple(
limit=sys.maxsize,
filter_by=FilterBy.NONE,
created_after=DatetimeUTC.fromtimestamp(start_timestamp)
if start_timestamp
else None,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential Performance Issue with limit=sys.maxsize

Using limit=sys.maxsize in get_omen_binary_markets_simple may lead to performance issues if there is a large number of markets. Consider implementing pagination or processing the markets in batches to handle large datasets efficiently.

Consider applying this refactor to implement pagination:

 def insert_open_omen_markets_if_not_exists(
     self, start_timestamp: int | None = None
 ) -> None:
     subgraph_handler = OmenSubgraphHandler()
-    markets = subgraph_handler.get_omen_binary_markets_simple(
-        limit=sys.maxsize,
-        filter_by=FilterBy.NONE,
-        created_after=DatetimeUTC.fromtimestamp(start_timestamp)
-        if start_timestamp
-        else None,
-        sort_by=SortBy.NEWEST,
-    )
+    markets = []
+    batch_size = 1000
+    offset = 0
+    while True:
+        batch = subgraph_handler.get_omen_binary_markets_simple(
+            limit=batch_size,
+            offset=offset,
+            filter_by=FilterBy.NONE,
+            created_after=DatetimeUTC.fromtimestamp(start_timestamp)
+            if start_timestamp is not None
+            else None,
+            sort_by=SortBy.NEWEST,
+        )
+        if not batch:
+            break
+        markets.extend(batch)
+        offset += batch_size
📝 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 insert_open_omen_markets_if_not_exists(
self, start_timestamp: int | None = None
) -> None:
subgraph_handler = OmenSubgraphHandler()
markets = subgraph_handler.get_omen_binary_markets_simple(
limit=sys.maxsize,
filter_by=FilterBy.NONE,
created_after=DatetimeUTC.fromtimestamp(start_timestamp)
if start_timestamp
else None,
def insert_open_omen_markets_if_not_exists(
self, start_timestamp: int | None = None
) -> None:
subgraph_handler = OmenSubgraphHandler()
markets = []
batch_size = 1000
offset = 0
while True:
batch = subgraph_handler.get_omen_binary_markets_simple(
limit=batch_size,
offset=offset,
filter_by=FilterBy.NONE,
created_after=DatetimeUTC.fromtimestamp(start_timestamp)
if start_timestamp is not None
else None,
sort_by=SortBy.NEWEST,
)
if not batch:
break
markets.extend(batch)
offset += batch_size

@@ -109,15 +112,22 @@ def deduplicate_markets(markets: list[OmenMarket]) -> list[OmenMarket]:

return list(unique_market_titles.values())

def insert_all_omen_markets_if_not_exists(
self, created_after: DatetimeUTC | None = None
def update_markets(self) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

  • Can remove this function as it just wraps insert_open_omen_markets_if_not_exists
  • insert_all_omen_markets_if_not_exists has been changed to insert_open_omen_markets_if_not_exists, but it's (nearly) the same as the old function (i.e. not getting open markets only). So no need for rename.
  • created_after: DatetimeUTC has been changed to start_timestamp: int. But OmenSubgraphHandler.get_omen_binary_markets_simple(created_after=...) takes DatetimeUTC. So should change back

Copy link
Contributor Author

Choose a reason for hiding this comment

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

3 points implemented here 9889efc

Copy link
Contributor

@kongzii kongzii left a comment

Choose a reason for hiding this comment

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

We are the builders of PMAT, if you see something doesn't add up (like the need for returning the dummy prediction answer, or the need for over-ruling the arguments passed into get_markets function, let's think about how to design PMAT better, not hacking it around 🙏

) -> t.Sequence[AgentMarket]:
return super().get_markets(
market_type=market_type,
limit=50,
Copy link
Contributor

Choose a reason for hiding this comment

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

get_markets accepts the limit argument, but it's overruled by this. Not a nice design.

Suggested change
limit=50,
limit=limit,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand your concern (expressed in 4 separate comments) - 50 is not limit, we should use limit.
So what's the recommended way to provide a custom limit apart from overriding the method process_markets?

Copy link
Contributor

@kongzii kongzii Oct 22, 2024

Choose a reason for hiding this comment

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

For example, you could change the definition of get_markets (in PMAT) from

 def get_markets(
        self,
        market_type: MarketType,
        limit: int = MAX_AVAILABLE_MARKETS,
        sort_by: SortBy = SortBy.CLOSING_SOONEST,
        filter_by: FilterBy = FilterBy.OPEN,
    ) -> t.Sequence[AgentMarket]:

to just def get_markets(self) -> t.Sequence[AgentMarket], and then it's fine to overload the method like this.

Or you could introduce a new configuration variable, n_markets_to_fetch, similarly as we have bet_on_n_markets_per_run.

Or, like we have BettingStrategy, we could have GetMarketsStrategy.

No strong preferences from my side here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Or you could introduce a new configuration variable, n_markets_to_fetch, similarly as we have bet_on_n_markets_per_run.

Me and @evangriffiths also like this. Implementing this on PMAT and referencing the new release on this PR afterwards.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented, removing get_markets. This thread can be closed IMO.

return super().get_markets(
market_type=market_type,
limit=50,
sort_by=SortBy.HIGHEST_LIQUIDITY,
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

limit=50,
sort_by=SortBy.HIGHEST_LIQUIDITY,
# Fetching most liquid markets since more likely they will have related markets
filter_by=FilterBy.OPEN,
Copy link
Contributor

Choose a reason for hiding this comment

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

And here.

prediction_market_agent/agents/arbitrage_agent/deploy.py Outdated Show resolved Hide resolved
prediction_market_agent/db/pinecone_handler.py Outdated Show resolved Hide resolved
Co-authored-by: Peter Jung <peter@jung.ninja>
Copy link
Contributor

@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)
prediction_market_agent/db/pinecone_handler.py (2)

115-119: Consider removing redundant method

As pointed out in a previous review comment, this update_markets method appears to be just a wrapper for insert_open_omen_markets_if_not_exists. If there's no additional functionality or future plans for this method, consider removing it to simplify the class structure.


120-129: LGTM with a minor suggestion

The changes to insert_open_omen_markets_if_not_exists improve flexibility by allowing filtering based on a start timestamp. The use of DatetimeUTC.to_datetime_utc(start_timestamp) is correct.

However, there's a minor issue with the condition:

if start_timestamp

This condition will fail when start_timestamp is 0, which is a valid UNIX timestamp. Consider changing it to:

if start_timestamp is not None

This will correctly handle the case where start_timestamp is 0.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 224036c and a068094.

📒 Files selected for processing (1)
  • prediction_market_agent/db/pinecone_handler.py (3 hunks)
🧰 Additional context used
🔇 Additional comments (2)
prediction_market_agent/db/pinecone_handler.py (2)

16-16: LGTM: Import path update

The import statement for DatetimeUTC has been updated, which is likely due to project restructuring. This change looks good and should maintain the correct import path.


74-77: LGTM: Improved code structure

The use of the new helper method get_existing_ids_in_index enhances code readability and maintainability. This refactoring maintains the same functionality while making the code more modular.

Copy link
Contributor

@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: 11

🧹 Outside diff range and nitpick comments (1)
prediction_market_agent/agents/arbitrage_agent/deploy.py (1)

55-59: Consider making the agent more market-agnostic.

The current implementation is tightly coupled with Omen markets. To improve flexibility and potential reusability, consider designing the agent to be more market-agnostic. This could involve creating an interface for market handlers and allowing different market types to be plugged in.

For example, you could create a MarketHandler interface with methods like get_markets, insert_markets, etc., and then implement this interface for different market types (Omen, others). This would allow the agent to work with different market types in the future without significant changes to its core logic.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between a068094 and 8ee15f0.

📒 Files selected for processing (3)
  • prediction_market_agent/agents/arbitrage_agent/deploy.py (1 hunks)
  • prediction_market_agent/agents/think_thoroughly_agent/deploy.py (1 hunks)
  • prediction_market_agent/db/pinecone_handler.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • prediction_market_agent/db/pinecone_handler.py
🧰 Additional context used
🔇 Additional comments (1)
prediction_market_agent/agents/arbitrage_agent/deploy.py (1)

146-150: ⚠️ Potential issue

Fix arguments passed to self.chain.invoke.

In the get_correlated_markets method, you're passing market and related_market objects to self.chain.invoke, whereas the chain expects strings containing the market questions.

Update the code as follows:

 result: Correlation = self.chain.invoke(
     {
-        "main_market_question": market,
-        "related_market_question": related_market,
+        "main_market_question": market.question,
+        "related_market_question": related_market.question,
     },
     config=get_langfuse_langchain_config(),
 )

This change ensures that the correct data is passed to the language model for correlation analysis.

Likely invalid or redundant comment.

prediction_market_agent/agents/arbitrage_agent/deploy.py Outdated Show resolved Hide resolved
Comment on lines +81 to +82
def answer_binary_market(self, market: AgentMarket) -> ProbabilisticAnswer | None:
return ProbabilisticAnswer(p_yes=Probability(0.5), confidence=1.0)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implement actual market analysis in answer_binary_market.

The answer_binary_market method currently returns a fixed 50% probability with full confidence for all markets. This doesn't provide any real market analysis. If this is a placeholder, consider adding a TODO comment explaining the intended future implementation.

Example:

def answer_binary_market(self, market: AgentMarket) -> ProbabilisticAnswer | None:
    # TODO: Implement actual market analysis logic
    # Current implementation returns a placeholder value
    return ProbabilisticAnswer(p_yes=Probability(0.5), confidence=1.0)

This would make it clear that the current implementation is temporary and needs to be replaced with actual analysis logic in the future.

prediction_market_agent/agents/arbitrage_agent/deploy.py Outdated Show resolved Hide resolved
Copy link
Contributor

@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: 3

🧹 Outside diff range and nitpick comments (7)
tests/db/test_pinecone_handler.py (2)

40-42: Consider using realistic timestamp values in tests

The hardcoded close_time_timestamp=0 might not effectively test real-world scenarios. Consider using realistic future timestamps that match the question dates (e.g., July 2024) mentioned in the test data.

Here's a suggested improvement:

-            close_time_timestamp=0,
+            # July 8, 2024 00:00:00 UTC
+            close_time_timestamp=1717977600,

Line range hint 52-60: Add test cases for timestamp-related functionality

The current tests don't verify how close_time_timestamp affects the similarity search results. Consider adding test cases for:

  • Markets with different closing times
  • Expired markets (past timestamps)
  • Invalid timestamp values

Would you like me to help generate these additional test cases?

prediction_market_agent/agents/metaculus_agent/deploy.py (1)

85-87: Consider documenting the standardized prediction storage pattern.

The switch to ProcessedMarket and APIKeys appears to be part of a broader standardization effort. This pattern should be consistently applied across all agents, including the new arbitrage agent.

Consider:

  1. Adding documentation about this standard pattern
  2. Creating a base class that enforces this pattern
  3. Updating the README to reflect these architectural changes
scripts/agent_app.py (1)

Line range hint 1-184: Consider enhancing UI for arbitrage agent scenarios.

Since this PR introduces an arbitrage agent for correlated markets, the Streamlit UI could be enhanced to better support arbitrage scenarios. Consider:

  1. Adding UI elements to display correlated market pairs
  2. Showing potential arbitrage opportunities
  3. Visualizing the expected profit calculations

Example enhancement for the agent_app function:

def agent_app() -> None:
    # ... existing code ...
    
    # Add UI elements for arbitrage scenarios when arbitrage agent is selected
    if any("ArbitrageAgent" in name for name in agent_class_names):
        st.subheader("Arbitrage Settings")
        show_correlated_markets = st.checkbox("Show correlated markets", value=False)
        if show_correlated_markets:
            st.info("Displaying markets with correlation > 0.8")
            # Add logic to display correlated market pairs
            # Add expected profit calculations
prediction_market_agent/agents/known_outcome_agent/benchmark.py (1)

Line range hint 91-130: Consider parameterizing test data for better maintainability.

The test data includes hardcoded dates and real-world events from March 2024. These will become outdated quickly. Consider:

  1. Moving test data to a separate fixture file
  2. Using relative dates (e.g., "tomorrow", "next year") instead of specific dates
  3. Using more generic, timeless examples that don't reference specific events

Example implementation:

# test_data.py
from datetime import timedelta
from prediction_market_agent_tooling.tools.utils import utcnow

def get_test_questions():
    tomorrow = utcnow() + timedelta(days=1)
    next_year = utcnow() + timedelta(days=365)
    
    return [
        QuestionWithKnownOutcome(
            question=f"Will Event A occur by {tomorrow.strftime('%d %B %Y')}?",
            result=Result.YES,
            notes="Generic example for known positive outcome",
        ),
        # ... more generic test cases
    ]
prediction_market_agent/agents/arbitrage_agent/deploy.py (2)

39-41: Enhance the class docstring.

The current docstring is minimal. Consider expanding it to include:

  • Detailed description of the agent's purpose
  • Key features and capabilities
  • Requirements and limitations
  • Example usage
-    """Agent that places mirror bets on Omen for (quasi) risk-neutral profit."""
+    """An agent that identifies and exploits arbitrage opportunities in correlated prediction markets.
+    
+    This agent:
+    - Identifies correlated markets using semantic similarity and LLM-based correlation analysis
+    - Places mirror bets (YES/NO or NO/YES) on correlated markets for risk-neutral profit
+    - Operates only on Omen markets due to the requirement of market embeddings
+    
+    Requirements:
+    - Pinecone API access for market similarity search
+    - OpenAI API access for correlation analysis
+    
+    Example:
+        agent = DeployableArbitrageAgent()
+        agent.run(market_type=MarketType.OMEN)
+    """

60-61: Document placeholder implementation.

The method returns a fixed probability without actual market analysis.

     def answer_binary_market(self, market: AgentMarket) -> ProbabilisticAnswer | None:
+        # TODO: This is a placeholder implementation.
+        # The arbitrage agent doesn't use market probabilities directly,
+        # as it relies on correlation analysis and price differences between markets.
         return ProbabilisticAnswer(p_yes=Probability(0.5), confidence=1.0)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 8ee15f0 and 546f323.

⛔ Files ignored due to path filters (2)
  • poetry.lock is excluded by !**/*.lock, !**/*.lock
  • pyproject.toml is excluded by !**/*.toml
📒 Files selected for processing (10)
  • prediction_market_agent/agents/arbitrage_agent/deploy.py (1 hunks)
  • prediction_market_agent/agents/known_outcome_agent/benchmark.py (2 hunks)
  • prediction_market_agent/agents/metaculus_agent/deploy.py (2 hunks)
  • prediction_market_agent/agents/think_thoroughly_agent/benchmark.py (2 hunks)
  • prediction_market_agent/agents/think_thoroughly_agent/deploy.py (2 hunks)
  • prediction_market_agent/agents/think_thoroughly_agent/models.py (1 hunks)
  • prediction_market_agent/db/pinecone_handler.py (4 hunks)
  • scripts/agent_app.py (1 hunks)
  • tests/agents/arbitrage_agent/test_arbitrage_agent.py (1 hunks)
  • tests/db/test_pinecone_handler.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • prediction_market_agent/agents/think_thoroughly_agent/deploy.py
  • prediction_market_agent/agents/think_thoroughly_agent/models.py
  • prediction_market_agent/db/pinecone_handler.py
  • tests/agents/arbitrage_agent/test_arbitrage_agent.py
🧰 Additional context used
📓 Learnings (1)
prediction_market_agent/agents/metaculus_agent/deploy.py (1)
Learnt from: kongzii
PR: gnosis/prediction-market-agent#529
File: prediction_market_agent/agents/metaculus_agent/deploy.py:71-75
Timestamp: 2024-10-24T08:53:49.415Z
Learning: Ensure that code suggestions result in meaningful changes before proposing them.
🔇 Additional comments (12)
tests/db/test_pinecone_handler.py (1)

40-42: Verify timestamp handling across different timezones

Since the questions reference specific dates (e.g., "by 5 July 2024", "on 8 July 2024"), it's important to ensure the timestamp handling works correctly across different timezones.

✅ Verification successful

Based on the search results, I can now generate a final response regarding the timestamp handling verification:

Timestamp handling is correctly implemented with UTC standardization

The codebase consistently uses UTC for timestamp handling through:

  • Standardized DatetimeUTC type and utcnow() utility function
  • Explicit UTC datetime creation via utc_datetime() function
  • Proper timestamp storage and conversion in database models

The test file's use of timestamp=0 is just a test fixture and doesn't reflect the actual timestamp handling in production code.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for timezone-related handling in the codebase
rg -A 5 "timestamp|timezone|tzinfo|datetime" --type python

Length of output: 91


Script:

#!/bin/bash
# Search for timezone-related handling in the codebase using ripgrep without file type restriction
rg -A 5 "timestamp|timezone|tzinfo|datetime"

Length of output: 51614

prediction_market_agent/agents/metaculus_agent/deploy.py (2)

4-4: LGTM: Import changes are well-organized.

The new imports for APIKeys and ProcessedMarket are properly organized and support the updated prediction storage mechanism.

Also applies to: 8-12


85-87: ⚠️ Potential issue

Add error handling and secure API key management.

The current implementation has potential security and reliability concerns:

  1. Creating APIKeys() without parameters might not be secure or could fail if keys are required
  2. No error handling for potential API authentication failures

Let's verify the APIKeys implementation:

Consider implementing the following improvements:

-                market.store_prediction(
-                    processed_market=processed_market, keys=APIKeys()
-                )
+                try:
+                    keys = APIKeys.from_environment()  # Or another secure method
+                    market.store_prediction(
+                        processed_market=processed_market, 
+                        keys=keys
+                    )
+                except (ValueError, AuthenticationError) as e:
+                    logger.error(f"Failed to store prediction: {e}")
+                    continue
prediction_market_agent/agents/think_thoroughly_agent/benchmark.py (2)

22-22: LGTM!

The import of MarketFees is correctly placed with other market-related imports and is necessary for setting market fees in the benchmark environment.


48-48: LGTM! Setting zero fees is appropriate for benchmarking.

Setting fees=MarketFees.get_zero_fees() ensures that market fees don't affect the benchmark results, providing cleaner performance metrics for agent evaluation.

scripts/agent_app.py (1)

81-81: Parameter rename aligns with trading terminology.

The rename from place_bet to place_trades better reflects the nature of the operations, especially with the introduction of the arbitrage agent that performs correlated market trades.

Let's verify the consistency of this parameter name change across the codebase:

✅ Verification successful

Parameter rename is consistent with trading terminology

The change from place_bet to place_trades is verified to be consistent across the codebase:

  • No remaining instances of place_bet were found
  • place_trades parameter is used consistently in agent instantiations in scripts/agent_app.py and prediction_market_agent/agents/think_thoroughly_agent/deploy.py
  • The parameter aligns with the trading-focused nature of the agents, especially with the new arbitrage agent functionality
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining instances of 'place_bet' that might need updating
# and verify the consistent usage of 'place_trades'

echo "Checking for any remaining 'place_bet' instances:"
rg "place_bet" -l

echo -e "\nVerifying 'place_trades' usage in agent instantiations:"
rg "place_trades\s*=" -l

Length of output: 377


Script:

#!/bin/bash
# Let's examine the actual usage context of place_trades in both files

echo "Content of place_trades in agent_app.py:"
rg "place_trades" -B 2 -A 2 scripts/agent_app.py

echo -e "\nContent of place_trades in deploy.py:"
rg "place_trades" -B 2 -A 2 prediction_market_agent/agents/think_thoroughly_agent/deploy.py

# Let's also check for any related test files that might need updates
echo -e "\nChecking for related test files:"
fd "test.*\.py$" --exec rg "place_bet|place_trades" -l {}

Length of output: 727


Script:

#!/bin/bash
# Let's check the agent class definitions to understand the parameter usage

echo "Checking AgentClass definition and inheritance:"
ast-grep --pattern 'class $_ {
  $$$
  def __init__($$$) {
    $$$
  }
  $$$
}'

echo -e "\nChecking for any agent base classes:"
rg "class.*Agent.*\(" -A 5

Length of output: 19004


Script:

#!/bin/bash
# Let's check the base trader agent class to understand the parameter inheritance

echo "Checking DeployableTraderAgent class definition:"
rg "class DeployableTraderAgent" -A 10 -B 2

echo -e "\nChecking for place_trades parameter in __init__ methods:"
ast-grep --pattern 'def __init__($$$place_trades$$$) {
  $$$
}'

Length of output: 1474

prediction_market_agent/agents/known_outcome_agent/benchmark.py (2)

14-14: LGTM: Clean import addition.

The import follows the existing style and is appropriately placed with other market-related imports.


48-48: Verify consistent zero fees usage across agent markets.

The addition of zero fees is logical for benchmarking purposes. However, let's verify that this approach is consistently applied across other agent market implementations.

✅ Verification successful

Zero fees consistently used in agent market benchmarks

Verification confirms that MarketFees.get_zero_fees() is consistently used in both agent market implementations:

  • prediction_market_agent/agents/known_outcome_agent/benchmark.py
  • prediction_market_agent/agents/think_thoroughly_agent/benchmark.py
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent usage of MarketFees.get_zero_fees() across agent market implementations
# Test: Search for AgentMarket instantiations and verify fees parameter usage

# Look for AgentMarket instantiations
ast-grep --pattern 'AgentMarket($$$)'

# Look for MarketFees usage
rg -A 2 'MarketFees.get_zero_fees'

Length of output: 3786

prediction_market_agent/agents/arbitrage_agent/deploy.py (4)

143-174: LGTM! Well-structured implementation.

The method correctly builds trades for correlated markets with appropriate logging.


80-90: ⚠️ Potential issue

Add error handling for chain invocation.

The method should handle potential chain invocation failures gracefully.

     @observe()
     def calculate_correlation_between_markets(
         self, market: AgentMarket, related_market: AgentMarket
-    ) -> Correlation:
+    ) -> Correlation | None:
+        """Calculate correlation between two markets using LLM analysis.
+        
+        Args:
+            market: The main market
+            related_market: The market to compare with
+            
+        Returns:
+            Correlation object if successful, None if analysis fails
+        """
+        try:
             correlation: Correlation = self.chain.invoke(
                 {
                     "main_market_question": market.question,
                     "related_market_question": related_market.question,
                 }
             )
             return correlation
+        except Exception as e:
+            logger.error(
+                f"Failed to calculate correlation between markets "
+                f"{market.id} and {related_market.id}: {str(e)}"
+            )
+            return None

Likely invalid or redundant comment.


63-78: ⚠️ Potential issue

Add error handling and make LLM parameters configurable.

The method lacks error handling and has hardcoded LLM parameters.

-    def _build_chain(self) -> RunnableSerializable[t.Any, t.Any]:
+    def _build_chain(self, temperature: float = 0) -> RunnableSerializable[t.Any, t.Any]:
+        """Build the LangChain chain for correlation analysis.
+        
+        Args:
+            temperature: Controls randomness in the model's output (0 to 1)
+            
+        Returns:
+            A chain that processes market questions and returns correlation analysis
+            
+        Raises:
+            RuntimeError: If chain initialization fails
+        """
+        try:
             llm = ChatOpenAI(
-                temperature=0,
+                temperature=temperature,
                 model=self.model,
                 api_key=APIKeys().openai_api_key_secretstr_v1,
             )
             parser = PydanticOutputParser(pydantic_object=Correlation)
             prompt = PromptTemplate(
                 template=PROMPT_TEMPLATE,
                 input_variables=["main_market_question", "related_market_question"],
                 partial_variables={"format_instructions": parser.get_format_instructions()},
             )
             chain = prompt | llm | parser
             return chain
+        except Exception as e:
+            logger.error(f"Failed to build correlation chain: {str(e)}")
+            raise RuntimeError(f"Chain initialization failed: {str(e)}") from e

Likely invalid or redundant comment.


42-47: 🛠️ Refactor suggestion

Make configuration parameters customizable.

The class has several hardcoded values that should be configurable through constructor parameters to improve flexibility and testability.

-    model = "gpt-4o"
-    total_trade_amount = BetAmount(amount=0.1, currency=OmenAgentMarket.currency)
-    bet_on_n_markets_per_run = 5
-    max_related_markets_per_market = 10
-    n_markets_to_fetch = 50

+    def __init__(
+        self,
+        model: str = "gpt-4o",
+        total_trade_amount: float = 0.1,
+        bet_on_n_markets_per_run: int = 5,
+        max_related_markets_per_market: int = 10,
+        n_markets_to_fetch: int = 50,
+    ):
+        """Initialize the arbitrage agent with custom configuration.
+        
+        Args:
+            model: The OpenAI model to use for correlation analysis
+            total_trade_amount: Total amount to be divided between correlated markets
+            bet_on_n_markets_per_run: Maximum number of markets to bet on per run
+            max_related_markets_per_market: Maximum number of related markets to fetch per market
+            n_markets_to_fetch: Total number of markets to fetch from the chain
+        """
+        super().__init__()
+        self.model = model
+        self.total_trade_amount = BetAmount(amount=total_trade_amount, currency=OmenAgentMarket.currency)
+        self.bet_on_n_markets_per_run = bet_on_n_markets_per_run
+        self.max_related_markets_per_market = max_related_markets_per_market
+        self.n_markets_to_fetch = n_markets_to_fetch

Likely invalid or redundant comment.

Comment on lines +175 to +190
@observe()
def build_trades(
self,
market: AgentMarket,
answer: ProbabilisticAnswer,
existing_position: Position | None,
) -> list[Trade]:
trades = []
correlated_markets = self.get_correlated_markets(market=market)
for pair in correlated_markets:
# We want to profit at least 0.5% per market (value chosen as initial baseline).
if pair.potential_profit_per_bet_unit() > 0.005:
trades_for_pair = self.build_trades_for_correlated_markets(pair)
trades.extend(trades_for_pair)

return trades
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make profit threshold configurable and address unused parameters.

The method has a hardcoded profit threshold and unused parameters.

     @observe()
     def build_trades(
         self,
         market: AgentMarket,
-        answer: ProbabilisticAnswer,
-        existing_position: Position | None,
+        min_profit_threshold: float = 0.005,
     ) -> list[Trade]:
+        """Build trades for correlated markets with sufficient profit potential.
+        
+        Args:
+            market: The market to build trades for
+            min_profit_threshold: Minimum profit threshold (default: 0.5%)
+            
+        Returns:
+            List of trades to execute
+        """
         trades = []
         correlated_markets = self.get_correlated_markets(market=market)
         for pair in correlated_markets:
-            # We want to profit at least 0.5% per market (value chosen as initial baseline).
-            if pair.potential_profit_per_bet_unit() > 0.005:
+            if pair.potential_profit_per_bet_unit() > min_profit_threshold:
                 trades_for_pair = self.build_trades_for_correlated_markets(pair)
                 trades.extend(trades_for_pair)
         return trades
📝 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
@observe()
def build_trades(
self,
market: AgentMarket,
answer: ProbabilisticAnswer,
existing_position: Position | None,
) -> list[Trade]:
trades = []
correlated_markets = self.get_correlated_markets(market=market)
for pair in correlated_markets:
# We want to profit at least 0.5% per market (value chosen as initial baseline).
if pair.potential_profit_per_bet_unit() > 0.005:
trades_for_pair = self.build_trades_for_correlated_markets(pair)
trades.extend(trades_for_pair)
return trades
@observe()
def build_trades(
self,
market: AgentMarket,
min_profit_threshold: float = 0.005,
) -> list[Trade]:
"""Build trades for correlated markets with sufficient profit potential.
Args:
market: The market to build trades for
min_profit_threshold: Minimum profit threshold (default: 0.5%)
Returns:
List of trades to execute
"""
trades = []
correlated_markets = self.get_correlated_markets(market=market)
for pair in correlated_markets:
if pair.potential_profit_per_bet_unit() > min_profit_threshold:
trades_for_pair = self.build_trades_for_correlated_markets(pair)
trades.extend(trades_for_pair)
return trades

Comment on lines +92 to +142
@observe()
def get_correlated_markets(self, market: AgentMarket) -> list[CorrelatedMarketPair]:
# We try to find similar, open markets which point to the same outcome.
correlated_markets = []
# We only wanted to find related markets that are open.
# We intentionally query more markets in the hope it yields open markets.
# We could store market_status (open, closed) in Pinecone, but we refrain from it
# to keep the chain data (or graph) as the source-of-truth, instead of managing the
# update process of the vectorDB.

related = self.pinecone_handler.find_nearest_questions_with_threshold(
limit=self.max_related_markets_per_market,
text=market.question,
filter_on_metadata={
"close_time_timestamp": {"gte": utcnow().timestamp() + 3600}
}, # closing 1h from now
)

omen_markets = self.subgraph_handler.get_omen_binary_markets(
limit=len(related),
id_in=[i.market_address for i in related if i.market_address != market.id],
resolved=False,
)

# Order omen_markets in the same order as related
related_market_addresses = [i.market_address for i in related]
omen_markets = sorted(
omen_markets, key=lambda m: related_market_addresses.index(m.id)
)

print(f"Fetched {len(omen_markets)} related markets for market {market.id}")

for related_market in omen_markets:
result: Correlation = self.chain.invoke(
{
"main_market_question": market,
"related_market_question": related_market,
},
config=get_langfuse_langchain_config(),
)
if result.near_perfect_correlation is not None:
related_agent_market = OmenAgentMarket.from_data_model(related_market)
correlated_markets.append(
CorrelatedMarketPair(
main_market=market,
related_market=related_agent_market,
correlation=result,
)
)
return correlated_markets

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix multiple issues in get_correlated_markets method.

The method has several issues that need to be addressed:

  1. Using print instead of logger
  2. Incorrect arguments to chain.invoke
  3. Missing error handling for API calls
     @observe()
     def get_correlated_markets(self, market: AgentMarket) -> list[CorrelatedMarketPair]:
+        """Find markets that are correlated with the given market.
+        
+        Args:
+            market: The market to find correlations for
+            
+        Returns:
+            List of correlated market pairs
+        """
         correlated_markets = []
+        try:
             related = self.pinecone_handler.find_nearest_questions_with_threshold(
                 limit=self.max_related_markets_per_market,
                 text=market.question,
                 filter_on_metadata={
                     "close_time_timestamp": {"gte": utcnow().timestamp() + 3600}
                 },
             )
+        except Exception as e:
+            logger.error(f"Failed to fetch related markets from Pinecone: {str(e)}")
+            return []

+        try:
             omen_markets = self.subgraph_handler.get_omen_binary_markets(
                 limit=len(related),
                 id_in=[i.market_address for i in related if i.market_address != market.id],
                 resolved=False,
             )
+        except Exception as e:
+            logger.error(f"Failed to fetch markets from Omen subgraph: {str(e)}")
+            return []

         related_market_addresses = [i.market_address for i in related]
         omen_markets = sorted(
             omen_markets, key=lambda m: related_market_addresses.index(m.id)
         )

-        print(f"Fetched {len(omen_markets)} related markets for market {market.id}")
+        logger.info(f"Fetched {len(omen_markets)} related markets for market {market.id}")

         for related_market in omen_markets:
+            try:
                 result: Correlation = self.chain.invoke(
                     {
-                        "main_market_question": market,
-                        "related_market_question": related_market,
+                        "main_market_question": market.question,
+                        "related_market_question": related_market.question,
                     },
                     config=get_langfuse_langchain_config(),
                 )
+            except Exception as e:
+                logger.error(f"Failed to calculate correlation: {str(e)}")
+                continue

             if result.near_perfect_correlation is not None:
                 related_agent_market = OmenAgentMarket.from_data_model(related_market)
                 correlated_markets.append(
                     CorrelatedMarketPair(
                         main_market=market,
                         related_market=related_agent_market,
                         correlation=result,
                     )
                 )
         return correlated_markets
📝 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
@observe()
def get_correlated_markets(self, market: AgentMarket) -> list[CorrelatedMarketPair]:
# We try to find similar, open markets which point to the same outcome.
correlated_markets = []
# We only wanted to find related markets that are open.
# We intentionally query more markets in the hope it yields open markets.
# We could store market_status (open, closed) in Pinecone, but we refrain from it
# to keep the chain data (or graph) as the source-of-truth, instead of managing the
# update process of the vectorDB.
related = self.pinecone_handler.find_nearest_questions_with_threshold(
limit=self.max_related_markets_per_market,
text=market.question,
filter_on_metadata={
"close_time_timestamp": {"gte": utcnow().timestamp() + 3600}
}, # closing 1h from now
)
omen_markets = self.subgraph_handler.get_omen_binary_markets(
limit=len(related),
id_in=[i.market_address for i in related if i.market_address != market.id],
resolved=False,
)
# Order omen_markets in the same order as related
related_market_addresses = [i.market_address for i in related]
omen_markets = sorted(
omen_markets, key=lambda m: related_market_addresses.index(m.id)
)
print(f"Fetched {len(omen_markets)} related markets for market {market.id}")
for related_market in omen_markets:
result: Correlation = self.chain.invoke(
{
"main_market_question": market,
"related_market_question": related_market,
},
config=get_langfuse_langchain_config(),
)
if result.near_perfect_correlation is not None:
related_agent_market = OmenAgentMarket.from_data_model(related_market)
correlated_markets.append(
CorrelatedMarketPair(
main_market=market,
related_market=related_agent_market,
correlation=result,
)
)
return correlated_markets
@observe()
def get_correlated_markets(self, market: AgentMarket) -> list[CorrelatedMarketPair]:
"""Find markets that are correlated with the given market.
Args:
market: The market to find correlations for
Returns:
List of correlated market pairs
"""
correlated_markets = []
try:
related = self.pinecone_handler.find_nearest_questions_with_threshold(
limit=self.max_related_markets_per_market,
text=market.question,
filter_on_metadata={
"close_time_timestamp": {"gte": utcnow().timestamp() + 3600}
}, # closing 1h from now
)
except Exception as e:
logger.error(f"Failed to fetch related markets from Pinecone: {str(e)}")
return []
try:
omen_markets = self.subgraph_handler.get_omen_binary_markets(
limit=len(related),
id_in=[i.market_address for i in related if i.market_address != market.id],
resolved=False,
)
except Exception as e:
logger.error(f"Failed to fetch markets from Omen subgraph: {str(e)}")
return []
# Order omen_markets in the same order as related
related_market_addresses = [i.market_address for i in related]
omen_markets = sorted(
omen_markets, key=lambda m: related_market_addresses.index(m.id)
)
logger.info(f"Fetched {len(omen_markets)} related markets for market {market.id}")
for related_market in omen_markets:
try:
result: Correlation = self.chain.invoke(
{
"main_market_question": market.question,
"related_market_question": related_market.question,
},
config=get_langfuse_langchain_config(),
)
except Exception as e:
logger.error(f"Failed to calculate correlation: {str(e)}")
continue
if result.near_perfect_correlation is not None:
related_agent_market = OmenAgentMarket.from_data_model(related_market)
correlated_markets.append(
CorrelatedMarketPair(
main_market=market,
related_market=related_agent_market,
correlation=result,
)
)
return correlated_markets

Copy link
Contributor

@evangriffiths evangriffiths left a comment

Choose a reason for hiding this comment

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

🎉

…trage-agent

# Conflicts:
#	poetry.lock
#	prediction_market_agent/agents/metaculus_agent/deploy.py
#	prediction_market_agent/agents/think_thoroughly_agent/deploy.py
#	prediction_market_agent/run_agent.py
#	pyproject.toml
#	scripts/agent_app.py
Copy link
Contributor

@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 (1)
prediction_market_agent/agents/think_thoroughly_agent/benchmark.py (1)

Line range hint 34-47: Consider making market fees configurable for realistic benchmarking.

While using zero fees (MarketFees.get_zero_fees()) is suitable for basic testing, it might not accurately represent real-world scenarios where fees impact arbitrage opportunities. Consider making the fees configurable through the function parameters to enable testing with different fee structures.

Here's a suggested implementation:

-def build_binary_agent_market_from_question(question: str) -> AgentMarket:
+def build_binary_agent_market_from_question(
+    question: str,
+    fees: t.Optional[MarketFees] = None
+) -> AgentMarket:
     return AgentMarket(
         id=question,
         description=None,
         url=question,
         close_time=utcnow() + timedelta(days=1),
         volume=None,
         question=question,
         current_p_yes=Probability(0.5),
         created_time=utc_datetime(2024, 1, 1),
         resolution=None,
         outcomes=["YES", "NO"],
         outcome_token_pool=None,
-        fees=MarketFees.get_zero_fees(),
+        fees=fees if fees is not None else MarketFees.get_zero_fees(),
     )
🧰 Tools
🪛 Ruff

23-23: Redefinition of unused MarketFees from line 20

(F811)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 546f323 and 3ce98c0.

📒 Files selected for processing (4)
  • prediction_market_agent/agents/think_thoroughly_agent/benchmark.py (1 hunks)
  • prediction_market_agent/agents/think_thoroughly_agent/deploy.py (1 hunks)
  • prediction_market_agent/agents/think_thoroughly_agent/think_thoroughly_agent.py (0 hunks)
  • prediction_market_agent/run_agent.py (3 hunks)
💤 Files with no reviewable changes (1)
  • prediction_market_agent/agents/think_thoroughly_agent/think_thoroughly_agent.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • prediction_market_agent/agents/think_thoroughly_agent/deploy.py
🧰 Additional context used
🪛 Ruff
prediction_market_agent/agents/think_thoroughly_agent/benchmark.py

23-23: Redefinition of unused MarketFees from line 20

(F811)

🔇 Additional comments (3)
prediction_market_agent/run_agent.py (3)

15-17: LGTM: Import statement properly added.

The import statement for DeployableArbitrageAgent has been correctly added, resolving the previously identified issue.


84-84: LGTM: RunnableAgent enum properly updated.

The addition of the arbitrage enum value follows the established naming conventions and structure.


112-112: LGTM: Agent mapping correctly configured.

The DeployableArbitrageAgent is properly mapped to RunnableAgent.arbitrage in the RUNNABLE_AGENTS dictionary.

Let's verify the integration:

✅ Verification successful

Mapping and implementation verified successfully

The verification confirms that:

  • DeployableArbitrageAgent is properly defined in prediction_market_agent/agents/arbitrage_agent/deploy.py
  • It correctly inherits from DeployableTraderAgent
  • The mapping in run_agent.py is accurate: RunnableAgent.arbitrage: DeployableArbitrageAgent
  • The agent has a complete implementation with required methods like run() and proper test coverage
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the complete integration of DeployableArbitrageAgent

# Test 1: Verify the agent class exists and inherits from DeployableAgent
echo "Checking agent class definition:"
ast-grep --pattern 'class DeployableArbitrageAgent($$$): $$$'

# Test 2: Verify the agent can be instantiated and run
echo "Checking run method implementation:"
ast-grep --pattern 'def run(self, market_type: MarketType) -> None: $$$'

# Test 3: Check for any direct usage of the agent
echo "Checking agent usage:"
rg -A 2 'DeployableArbitrageAgent' --type python

Length of output: 20990


Script:

#!/bin/bash
# Let's verify the agent usage and mapping
echo "Checking agent mapping and usage:"
rg -A 2 'DeployableArbitrageAgent'

Length of output: 2089

@@ -20,6 +20,7 @@
MarketFees,
SortBy,
)
from prediction_market_agent_tooling.markets.market_fees import MarketFees
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove duplicate import of MarketFees.

The MarketFees class is already imported through the agent_market module on line 20.

Apply this diff to fix the duplicate import:

-from prediction_market_agent_tooling.markets.market_fees import MarketFees
📝 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
from prediction_market_agent_tooling.markets.market_fees import MarketFees
🧰 Tools
🪛 Ruff

23-23: Redefinition of unused MarketFees from line 20

(F811)

Copy link
Contributor

@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

🧹 Outside diff range and nitpick comments (2)
prediction_market_agent/agents/specialized_agent/deploy.py (1)

Line range hint 31-49: Consider enhancing market fetching for arbitrage scenarios.

Since this PR introduces arbitrage functionality, consider adding support for fetching correlated markets. This could involve:

  1. Adding methods to fetch markets with potential correlations
  2. Supporting filtering by market characteristics that are relevant for arbitrage

Would you like assistance in designing these enhancements? This could help integrate the arbitrage agent's requirements with the existing market fetching infrastructure.

prediction_market_agent/agents/invalid_agent/deploy.py (1)

Line range hint 1-50: Consider documenting agent interaction strategy.

Given that this PR introduces a new arbitrage agent that will coexist with the InvalidAgent, it would be valuable to document how these agents coordinate their activities, particularly in scenarios where a market might be both invalid and part of a correlated pair.

Consider addressing these points:

  1. How do we prevent conflicting bets between invalid and arbitrage agents?
  2. Should the arbitrage agent skip markets that are likely to be invalid?
  3. How does the trade interval coordination work between different agent types?

Would you like me to help draft documentation covering these interaction patterns?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 3ce98c0 and f069349.

📒 Files selected for processing (2)
  • prediction_market_agent/agents/invalid_agent/deploy.py (1 hunks)
  • prediction_market_agent/agents/specialized_agent/deploy.py (1 hunks)
🔇 Additional comments (3)
prediction_market_agent/agents/specialized_agent/deploy.py (2)

31-31: LGTM! Good refactoring of the market fetch limit.

Moving the limit to a class attribute improves configurability and makes the market fetching behavior more consistent across agent instances.


45-45: Verify impact on derived classes.

The change from parameter to class attribute looks good. However, let's verify that no derived classes are overriding get_markets() with the old signature.

✅ Verification successful

No impact on derived classes found

The verification shows that there are no derived classes overriding get_markets() with the old signature containing the limit parameter. The only implementations found are:

  • The base implementation in prediction_market_agent/agents/specialized_agent/deploy.py which is the one being modified
  • An unrelated implementation in prediction_market_agent/agents/metaculus_agent/deploy.py which uses a different parameter structure

The change from parameter to class attribute is safe to proceed with.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any classes that override get_markets with the old signature
# containing the 'limit' parameter

# Search for class definitions that inherit from GetMarketCreatorsStalkerMarkets
ast-grep --pattern 'class $_ ($$$GetMarketCreatorsStalkerMarkets$$$) {
  $$$
  get_markets($$$limit$$$) {
    $$$
  }
  $$$
}'

Length of output: 128


Script:

#!/bin/bash
# Let's try a different approach to find derived classes and their get_markets implementations

# First, find all Python files that might contain derived classes
rg -l "GetMarketCreatorsStalkerMarkets" --type py

# Then look for any get_markets method implementations with context
rg "def get_markets" -A 5 --type py

Length of output: 1493

prediction_market_agent/agents/invalid_agent/deploy.py (1)

25-27: Verify dynamic trade interval behavior.

The change to use a dynamic trade interval per market (same_market_trade_interval.get(market=market)) provides better flexibility, but we should ensure it works correctly with all market types.

Let's verify the implementation of the trade interval getter:

Consider adding a docstring or comment explaining how the trade interval is calculated for different market types, as this knowledge would be valuable for maintainers working with both invalid and arbitrage agents.

@gabrielfior gabrielfior merged commit 4b3d1c0 into main Oct 29, 2024
9 checks passed
@gabrielfior gabrielfior deleted the 137-arbitrage-agent branch October 29, 2024 18:41
@coderabbitai coderabbitai bot mentioned this pull request Nov 4, 2024
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.

Arbitrage Agent
3 participants