Skip to content

Conversation

@moconnell
Copy link
Owner

@moconnell moconnell commented Dec 10, 2025

Summary by CodeRabbit

  • Chores

    • Updated .NET runtime requirement to version 10.
  • Documentation

    • Updated system requirements and clarified RebalanceMode examples and formatting.
  • Bug Fixes

    • Improved API response error handling with clearer messages.
    • Better handling of missing/null factor data so absent values propagate as NaN.
  • Tests

    • Added and expanded tests covering API deserialization, missing values/tickers, and error scenarios.

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

@moconnell moconnell self-assigned this Dec 10, 2025
@moconnell moconnell added bug Something isn't working Unravel .NET Pull requests that update .NET code labels Dec 10, 2025
@moconnell moconnell linked an issue Dec 10, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Dec 10, 2025

Walkthrough

Updated docs and VSCode launch config to .NET 10; API response model changed to nullable doubles, deserialization now validates and surfaces detailed errors, service layer maps nulls to NaN, tests expanded; numerous files had using-directive pruning and minor formatting changes.

Changes

Cohort / File(s) Summary
Framework & Documentation
.vscode/launch.json, README.md
Launch config and README updated to reference net10.0; README formatting and examples clarified.
Unravel API — Model & Service
src/Unravel.Api/Data/FactorsLiveResponse.cs, src/Unravel.Api/UnravelApiService.cs, test/Unravel.Api.Test/UnravelApiServiceTest.cs
FactorsLiveResponse changed from IApiResponse<double> / IReadOnlyList<double> to IApiResponse<double?> / IReadOnlyList<double?>; UnravelApiService.GetFactorsLiveAsync adjusted to handle double? by mapping null → double.NaN; tests expanded for missing values/tickers and NaN propagation.
HTTP deserialization & tests
src/YoloAbstractions/Extensions/HttpClientExtensions.cs, test/YoloAbstractions.Test/HttpClientExtensionsTest.cs, test/YoloAbstractions.Test/YoloAbstractions.Test.csproj
Added try/catch around JSON deserialization, validate non-null/non-empty ApiResponse.Data, throw detailed ApiException with raw response on failure; new unit tests and test dependencies (Moq) added.
Using-directive pruning (many files)
src/... (multiple) — e.g., src/YoloAbstractions/*, src/YoloBroker/*, src/YoloKonsole/*, src/YoloTrades/*, src/RobotWealth.Api/*, src/YoloBroker.Hyperliquid/*, src/YoloKonsole/Program.cs, test/*
Numerous files had top-level using directives removed (System, System.Collections.Generic, System.Linq, threading/task usings, etc.). No API signature changes; potential compile surface relies on global usings or qualification.
Formatting-only
src/YoloAbstractions/FactorDataFrame.cs
Minor whitespace/cast formatting changes (e.g., (Type)value); no logic/signature changes.
Small removals / minimal edits
assorted single-file removals (e.g., src/RobotWealth.Api/Data/RwApiResponse.cs, src/RobotWealth.Api/Data/RwVolatility.cs, src/RobotWealth.Api/Data/RwWeight.cs)
Removed unused using directives; no signature or behavior changes in public types.

Sequence Diagram(s)

sequenceDiagram
  participant Caller as Caller (service)
  participant Unravel as UnravelApiService
  participant HTTP as HttpClientExtensions
  participant API as Remote API

  Caller->>Unravel: GetFactorsLiveAsync(tickers, ...)
  Unravel->>HTTP: Send GET request
  HTTP->>API: HTTP request
  API-->>HTTP: HTTP 200 + JSON payload (Data: [double?, ...])
  alt JSON deserializes and Data present
    HTTP-->>Unravel: ApiResponse<T> with nullable doubles
    Unravel->>Unravel: Map null -> double.NaN, align missing tickers, build DataFrame
    Unravel-->>Caller: DataFrame (with NaN)
  else Deserialization error or missing/empty Data
    HTTP-->>Unravel: throws ApiException (detailed message + raw content)
    Unravel-->>Caller: propagate ApiException
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

  • Areas needing extra attention:
    • src/YoloAbstractions/Extensions/HttpClientExtensions.cs — new try/catch and validation messages; ensure no masking of inner exceptions and message formatting.
    • src/Unravel.Api/UnravelApiService.cs & src/Unravel.Api/Data/FactorsLiveResponse.cs — nullable handling, NaN mapping, and partial/missing-ticker alignment.
    • Broad removal of using directives across many files — verify project/global usings cover removed namespaces and that no compilation regressions occur.
    • New tests: ensure mocks correctly exercise error paths and messages.

Possibly related PRs

Poem

🐰 I hopped through code and found a tweak,
Doubles can nap now — nullable-speak.
Nulls turn to NaN, tidy and bright,
Docs updated, tests snug and tight.
A carrot for safety — hop delight! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix: Unravel Factors Live Handle Null Value' directly and clearly describes the main change, which involves handling nullable double values in the FactorsLiveResponse API response across multiple files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/unravel-factors-live-null

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

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/YoloAbstractions/FactorDataFrame.cs (1)

245-320: Row/column counts cast to int are acceptable for expected sizes

Casting _dataFrame.Rows.Count to int for rows and subsequent loops is fine given the practical data sizes in this domain; if you ever expect >2B rows, you’d want to revisit this to keep everything in long.

test/Unravel.Api.Test/UnravelApiServiceTest.cs (1)

345-389: Test validates null factor values are normalized to NaN

GivenMissingValues_WhenThrowOnMissingValueFalse_ShouldPopulateNaN exercises the case where data contains null for a present ticker and verifies that it comes through as NaN in FactorDataFrame, while non-null entries retain their numeric values. You might consider tweaking the // Missing ADA comment to clarify that it’s a missing value, not a missing ticker.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b166be2 and ca22454.

📒 Files selected for processing (7)
  • .vscode/launch.json (1 hunks)
  • README.md (3 hunks)
  • src/Unravel.Api/Data/FactorsLiveResponse.cs (1 hunks)
  • src/Unravel.Api/UnravelApiService.cs (2 hunks)
  • src/YoloAbstractions/Extensions/HttpClientExtensions.cs (1 hunks)
  • src/YoloAbstractions/FactorDataFrame.cs (7 hunks)
  • test/Unravel.Api.Test/UnravelApiServiceTest.cs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.cs: Use C# 12+ features including nullable reference types (Nullable is enabled)
Follow implicit usings pattern (ImplicitUsings is enabled)
Maintain existing code style and conventions
Avoid adding comments unless they explain complex logic or match existing comment patterns

Files:

  • src/Unravel.Api/Data/FactorsLiveResponse.cs
  • src/Unravel.Api/UnravelApiService.cs
  • src/YoloAbstractions/Extensions/HttpClientExtensions.cs
  • src/YoloAbstractions/FactorDataFrame.cs
  • test/Unravel.Api.Test/UnravelApiServiceTest.cs
test/**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

test/**/*.cs: Use xUnit as the testing framework
Use Moq for creating mock objects in tests
Use Shouldly for fluent assertions in tests
Integration tests should be marked with [Trait("Category", "Integration")] attribute

Files:

  • test/Unravel.Api.Test/UnravelApiServiceTest.cs
🧠 Learnings (1)
📚 Learning: 2025-11-02T20:24:42.143Z
Learnt from: moconnell
Repo: moconnell/yolo PR: 37
File: src/YoloBroker/BrokerVolatilityFactorService.cs:26-30
Timestamp: 2025-11-02T20:24:42.143Z
Learning: In implementations of IGetFactors.GetFactorsAsync, the `factors` parameter represents factors that have already been obtained or computed, not a filter of factors to compute. Services should return empty or skip computation when their factor type is already present in this set to avoid duplicate work.

Applied to files:

  • src/Unravel.Api/Data/FactorsLiveResponse.cs
  • src/Unravel.Api/UnravelApiService.cs
  • test/Unravel.Api.Test/UnravelApiServiceTest.cs
🧬 Code graph analysis (4)
src/Unravel.Api/Data/FactorsLiveResponse.cs (2)
src/Unravel.Api/Data/FactorsResponse.cs (1)
  • FactorsResponse (6-16)
src/Unravel.Api/Interfaces/IUnravelApiService.cs (1)
  • IUnravelApiService (5-13)
src/Unravel.Api/UnravelApiService.cs (3)
src/Unravel.Api/Data/FactorsLiveResponse.cs (1)
  • FactorsLiveResponse (6-16)
src/YoloAbstractions/FactorDataFrame.cs (3)
  • FactorDataFrame (15-23)
  • FactorDataFrame (33-60)
  • FactorDataFrame (126-164)
src/Unravel.Api/Interfaces/IUnravelApiService.cs (1)
  • Task (7-10)
src/YoloAbstractions/FactorDataFrame.cs (1)
src/RobotWealth.Api/RobotWealthFactorService.cs (1)
  • FactorDataFrame (41-72)
test/Unravel.Api.Test/UnravelApiServiceTest.cs (2)
src/Unravel.Api/UnravelApiService.cs (4)
  • UnravelApiService (10-122)
  • UnravelApiService (28-36)
  • Task (38-102)
  • Task (104-121)
src/Unravel.Api/Interfaces/IUnravelApiService.cs (2)
  • Task (7-10)
  • Task (12-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (13)
.vscode/launch.json (1)

27-41: Debug profile now targets net10.0 output

Updating the debug program path to bin/Debug/net10.0/YoloKonsole.dll is consistent with the .NET 10 requirement; just ensure the YoloKonsole project actually targets net10.0 so this path exists in all dev environments.

README.md (2)

45-47: .NET 10 requirement wording is clear

Updating the requirement to “.NET 10 installation required” matches the launch configuration change; assuming the projects target net10.0, this keeps docs and tooling aligned.


97-112: RebalanceMode example and values listing are clearer

The expanded tolerance-band example and explicit Center/Edge values block read well and make the trade‑off much easier to understand without changing behavior.

src/YoloAbstractions/FactorDataFrame.cs (4)

15-28: Ticker index and Tickers property remain consistent

Casting "Ticker" to StringDataFrameColumn for _tickerIndex and Tickers keeps the column access strongly typed; these are style-only tweaks and preserve existing semantics.


62-74: Indexer now treats null factor values as NaN

Using (double?)_dataFrame[factorType.ToString()][index] and returning val ?? double.NaN gives a consistent “missing == NaN” contract on the [FactorType, ticker] indexer, which matches the new null‑from‑API semantics and the tests that assert NaN for missing data.


137-164: Normalize keeps factor set via collection expression

The cast to DoubleDataFrameColumn and new FactorDataFrame(normalizedDf, [.. FactorTypes]) are idiomatic C# 12, preserving the exact factor set on the normalized frame without behavior change.


322-385: ToString uses int rowCount consistently

The explicit (int)_dataFrame.Rows.Count for rowCount aligns with the usage patterns above and keeps formatting logic straightforward; no behavioral issues here.

src/Unravel.Api/UnravelApiService.cs (1)

60-91: Nullable factors payload is handled correctly with NaN mapping

Switching to _httpClient.GetAsync<FactorsLiveResponse, double?>(...) and projecting response.Data via d ?? double.NaN in both the full‑match and missing‑ticker branches makes all missing values surface as NaNs in FactorDataFrame, which matches the new API contract and the added tests.

src/Unravel.Api/Data/FactorsLiveResponse.cs (1)

6-15: DTO now correctly models nullable factor data

Updating FactorsLiveResponse to IApiResponse<double?> with IReadOnlyList<double?> Data accurately represents nullable factor values from the API and lines up with the new GetAsync<..., double?> usage.

src/YoloAbstractions/Extensions/HttpClientExtensions.cs (1)

41-56: Stricter deserialization and “no data” checks improve robustness

The try/catch around ReadFromJsonAsync, the explicit guard against null/empty Data, and the detailed ApiException (including raw response) give much clearer failures for bad or empty payloads without masking intentional ApiExceptions.

test/Unravel.Api.Test/UnravelApiServiceTest.cs (3)

299-343: Test covers NaN fill-in when tickers are missing but throwOnMissingValue is false

GivenMissingTickers_WhenThrowOnMissingValueFalse_ShouldPopulateNaN correctly mocks a response missing ADA and asserts that the resulting FactorDataFrame has the requested ticker set with NaN for ADA and the expected numeric values for BTC/ETH, matching the updated service behavior.


574-592: Empty-ticker argument validation uses modern collection syntax

Calling GetFactorsLiveAsync([]) in GivenEmptyTickers_WhenCalled_ShouldThrowArgumentException is a nice use of collection expressions and clearly asserts the expected argument validation behavior.


594-611: Whitespace-only ticker validation is well-covered

The GivenWhitespaceOnlyTickers_WhenCalled_ShouldThrowArgumentException test, using [" ", "\t", ""], cleanly verifies that trimming/normalization eliminates such entries and that an empty effective ticker set results in an ArgumentException.

@moconnell moconnell merged commit d4fae05 into master Dec 10, 2025
10 checks passed
@moconnell moconnell deleted the fix/unravel-factors-live-null branch December 10, 2025 09:20
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/YoloTrades/TradeFactory.cs (1)

223-239: Guard CalcLimitPrice against null bid/ask to avoid runtime exceptions

CalcLimitPrice dereferences market.Ask!.Value and market.Bid!.Value, but the rest of the code explicitly handles Bid/Ask being null and keeps such markets in projectedPositions. If a market has only one side populated (e.g., ask present, bid null), GetPrice will still return a non-null price for one side, and the code can reach CalcLimitPrice, where Bid or Ask being null would throw at runtime.

Consider defensively bailing out when either side is missing and letting the caller fall back to a market order or skip the trade:

-            decimal? CalcLimitPrice()
-            {
-                var factor = isBuy ? SpreadSplit : 1 - SpreadSplit;
-                var spread = market.Ask!.Value - market.Bid!.Value;
+            decimal? CalcLimitPrice()
+            {
+                if (market.Ask is null || market.Bid is null)
+                    return null;
+
+                var factor = isBuy ? SpreadSplit : 1 - SpreadSplit;
+                var spread = market.Ask.Value - market.Bid.Value;
                 var rawLimitPrice = market.Bid!.Value + spread * factor;
                 var limitPriceSteps = rawLimitPrice / market.PriceStep;
                 var limitPrice = limitPriceSteps is null
                     ? null
                     : (isBuy
                           ? Math.Floor(limitPriceSteps.Value)
                           : Math.Ceiling(limitPriceSteps.Value)) *
                       market.PriceStep;
                 return limitPrice;
             }

And, if CalcLimitPrice() returns null, confirm that downstream handling of a null limit price on the Trade type is acceptable or add a guard there as well.

Also applies to: 252-265

🧹 Nitpick comments (1)
test/YoloAbstractions.Test/HttpClientExtensionsTest.cs (1)

138-148: Test does not verify cancellation token propagation.

The assertion cts.Token.IsCancellationRequested.ShouldBeFalse() only checks that the token wasn't cancelled—it doesn't verify the token was actually passed to SendAsync. This test will pass even if the implementation ignores the cancellation token entirely.

Capture and verify the token in the mock:

     [Fact]
     public async Task GetAsync_WithCancellationToken_PassesTokenToRequest()
     {
         var apiResponse = new TestApiResponse { Data = ["item1"] };
-        var httpClient = CreateHttpClient(HttpStatusCode.OK, apiResponse);
-        var cts = new CancellationTokenSource();
+        CancellationToken capturedToken = default;
+        var mockHandler = new Mock<HttpMessageHandler>();
+        mockHandler.Protected()
+            .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
+            .ReturnsAsync((HttpRequestMessage _, CancellationToken ct) =>
+            {
+                capturedToken = ct;
+                return new HttpResponseMessage
+                {
+                    StatusCode = HttpStatusCode.OK,
+                    Content = new StringContent(JsonSerializer.Serialize(apiResponse), Encoding.UTF8, "application/json")
+                };
+            });
+        var httpClient = new HttpClient(mockHandler.Object);
+        using var cts = new CancellationTokenSource();
 
         await httpClient.GetAsync<TestApiResponse, string>("https://api.example.com/data", cancellationToken: cts.Token);
 
-        cts.Token.IsCancellationRequested.ShouldBeFalse();
+        capturedToken.ShouldBe(cts.Token);
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca22454 and 761a39e.

📒 Files selected for processing (48)
  • src/RobotWealth.Api/Data/RwApiResponse.cs (0 hunks)
  • src/RobotWealth.Api/Data/RwVolatility.cs (0 hunks)
  • src/RobotWealth.Api/Data/RwWeight.cs (0 hunks)
  • src/RobotWealth.Api/Interfaces/IRobotWealthApiService.cs (0 hunks)
  • src/RobotWealth.Api/RobotWealthApiService.cs (0 hunks)
  • src/RobotWealth.Api/RobotWealthFactorService.cs (0 hunks)
  • src/YoloAbstractions/AssetPermissions.cs (0 hunks)
  • src/YoloAbstractions/Config/YoloConfig.cs (0 hunks)
  • src/YoloAbstractions/Exceptions/ApiException.cs (0 hunks)
  • src/YoloAbstractions/Exceptions/ConfigException.cs (0 hunks)
  • src/YoloAbstractions/Extensions/ConfigExtensions.cs (0 hunks)
  • src/YoloAbstractions/Extensions/HttpClientExtensions.cs (1 hunks)
  • src/YoloAbstractions/Extensions/Sum.cs (0 hunks)
  • src/YoloAbstractions/Extensions/TickerExtensions.cs (0 hunks)
  • src/YoloAbstractions/Extensions/ToCsv.cs (0 hunks)
  • src/YoloAbstractions/Extensions/VectorExtensions.cs (0 hunks)
  • src/YoloAbstractions/Extensions/VolatilityExtensions.cs (0 hunks)
  • src/YoloAbstractions/FactorDataFrame.cs (7 hunks)
  • src/YoloAbstractions/Interfaces/IApiResponse.cs (0 hunks)
  • src/YoloAbstractions/Interfaces/ICalcWeights.cs (0 hunks)
  • src/YoloAbstractions/Interfaces/IGetFactors.cs (0 hunks)
  • src/YoloAbstractions/Interfaces/ITradeFactory.cs (0 hunks)
  • src/YoloAbstractions/MarketInfo.cs (0 hunks)
  • src/YoloAbstractions/Order.cs (0 hunks)
  • src/YoloAbstractions/OrderManagementSettings.cs (0 hunks)
  • src/YoloAbstractions/OrderUpdate.cs (0 hunks)
  • src/YoloAbstractions/Price.cs (0 hunks)
  • src/YoloAbstractions/Trade.cs (0 hunks)
  • src/YoloBroker.Hyperliquid/Config/HyperliquidConfig.cs (0 hunks)
  • src/YoloBroker.Hyperliquid/CustomSigning/Nethereum.cs (0 hunks)
  • src/YoloBroker.Hyperliquid/Extensions/DecimalExtensions.cs (0 hunks)
  • src/YoloBroker.Hyperliquid/Extensions/TypeConversionExtensions.cs (0 hunks)
  • src/YoloBroker.Hyperliquid/HyperliquidBroker.cs (0 hunks)
  • src/YoloBroker/BrokerVolatilityFactorService.cs (0 hunks)
  • src/YoloBroker/Exceptions/BrokerException.cs (0 hunks)
  • src/YoloBroker/Interface/IYoloBroker.cs (0 hunks)
  • src/YoloBroker/TickerAliasService.cs (0 hunks)
  • src/YoloKonsole/Extensions/LoggerExtensions.cs (0 hunks)
  • src/YoloKonsole/Program.cs (1 hunks)
  • src/YoloTrades/LoggerExtensions.cs (0 hunks)
  • src/YoloTrades/PositionExtensions.cs (0 hunks)
  • src/YoloTrades/PriceExtensions.cs (0 hunks)
  • src/YoloTrades/ProjectedPosition.cs (0 hunks)
  • src/YoloTrades/TradeFactory.cs (1 hunks)
  • test/Robotwealth.Api.Test/RobotWealthFactorServiceTest.cs (0 hunks)
  • test/YoloAbstractions.Test/HttpClientExtensionsTest.cs (1 hunks)
  • test/YoloAbstractions.Test/YoloAbstractions.Test.csproj (2 hunks)
  • test/YoloBroker.Hyperliquid.Test/Extensions/TypeConversionExtensionsTest.cs (0 hunks)
💤 Files with no reviewable changes (42)
  • test/YoloBroker.Hyperliquid.Test/Extensions/TypeConversionExtensionsTest.cs
  • src/YoloAbstractions/Extensions/ConfigExtensions.cs
  • src/YoloTrades/LoggerExtensions.cs
  • src/YoloAbstractions/Order.cs
  • src/YoloBroker.Hyperliquid/Extensions/DecimalExtensions.cs
  • test/Robotwealth.Api.Test/RobotWealthFactorServiceTest.cs
  • src/YoloTrades/PositionExtensions.cs
  • src/YoloAbstractions/Extensions/VectorExtensions.cs
  • src/YoloAbstractions/AssetPermissions.cs
  • src/YoloBroker.Hyperliquid/CustomSigning/Nethereum.cs
  • src/YoloAbstractions/Extensions/TickerExtensions.cs
  • src/YoloBroker.Hyperliquid/HyperliquidBroker.cs
  • src/YoloAbstractions/Interfaces/ITradeFactory.cs
  • src/YoloAbstractions/Trade.cs
  • src/YoloAbstractions/Extensions/VolatilityExtensions.cs
  • src/YoloKonsole/Extensions/LoggerExtensions.cs
  • src/RobotWealth.Api/Data/RwApiResponse.cs
  • src/YoloAbstractions/Extensions/ToCsv.cs
  • src/YoloBroker.Hyperliquid/Config/HyperliquidConfig.cs
  • src/YoloTrades/ProjectedPosition.cs
  • src/RobotWealth.Api/RobotWealthFactorService.cs
  • src/RobotWealth.Api/Data/RwVolatility.cs
  • src/YoloAbstractions/Interfaces/ICalcWeights.cs
  • src/YoloBroker/BrokerVolatilityFactorService.cs
  • src/RobotWealth.Api/RobotWealthApiService.cs
  • src/RobotWealth.Api/Interfaces/IRobotWealthApiService.cs
  • src/YoloBroker.Hyperliquid/Extensions/TypeConversionExtensions.cs
  • src/YoloAbstractions/MarketInfo.cs
  • src/YoloBroker/Interface/IYoloBroker.cs
  • src/YoloBroker/TickerAliasService.cs
  • src/YoloAbstractions/Exceptions/ConfigException.cs
  • src/YoloAbstractions/Interfaces/IGetFactors.cs
  • src/YoloAbstractions/Exceptions/ApiException.cs
  • src/YoloAbstractions/OrderUpdate.cs
  • src/RobotWealth.Api/Data/RwWeight.cs
  • src/YoloAbstractions/OrderManagementSettings.cs
  • src/YoloAbstractions/Extensions/Sum.cs
  • src/YoloAbstractions/Interfaces/IApiResponse.cs
  • src/YoloBroker/Exceptions/BrokerException.cs
  • src/YoloAbstractions/Config/YoloConfig.cs
  • src/YoloAbstractions/Price.cs
  • src/YoloTrades/PriceExtensions.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/YoloAbstractions/FactorDataFrame.cs
🧰 Additional context used
📓 Path-based instructions (4)
test/**/*.csproj

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Test projects must follow naming convention: [ProjectName].Test

Files:

  • test/YoloAbstractions.Test/YoloAbstractions.Test.csproj
**/*.csproj

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.csproj: All package versions are centrally managed in Directory.Packages.props; reference packages in .csproj files without version numbers
Use .NET 9.0 as the target framework for new projects
Enable nullable reference types and implicit usings in new .csproj projects

Files:

  • test/YoloAbstractions.Test/YoloAbstractions.Test.csproj
**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.cs: Use C# 12+ features including nullable reference types (Nullable is enabled)
Follow implicit usings pattern (ImplicitUsings is enabled)
Maintain existing code style and conventions
Avoid adding comments unless they explain complex logic or match existing comment patterns

Files:

  • src/YoloAbstractions/Extensions/HttpClientExtensions.cs
  • src/YoloTrades/TradeFactory.cs
  • test/YoloAbstractions.Test/HttpClientExtensionsTest.cs
  • src/YoloKonsole/Program.cs
test/**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

test/**/*.cs: Use xUnit as the testing framework
Use Moq for creating mock objects in tests
Use Shouldly for fluent assertions in tests
Integration tests should be marked with [Trait("Category", "Integration")] attribute

Files:

  • test/YoloAbstractions.Test/HttpClientExtensionsTest.cs
🧠 Learnings (4)
📚 Learning: 2025-11-28T10:36:40.819Z
Learnt from: CR
Repo: moconnell/yolo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-28T10:36:40.819Z
Learning: Applies to test/**/*.cs : Use Moq for creating mock objects in tests

Applied to files:

  • test/YoloAbstractions.Test/YoloAbstractions.Test.csproj
  • test/YoloAbstractions.Test/HttpClientExtensionsTest.cs
📚 Learning: 2025-11-28T10:36:40.819Z
Learnt from: CR
Repo: moconnell/yolo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-28T10:36:40.819Z
Learning: Applies to test/**/*.cs : Use Shouldly for fluent assertions in tests

Applied to files:

  • test/YoloAbstractions.Test/YoloAbstractions.Test.csproj
  • test/YoloAbstractions.Test/HttpClientExtensionsTest.cs
📚 Learning: 2025-11-28T10:36:40.819Z
Learnt from: CR
Repo: moconnell/yolo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-28T10:36:40.819Z
Learning: Applies to test/**/*.cs : Use xUnit as the testing framework

Applied to files:

  • test/YoloAbstractions.Test/YoloAbstractions.Test.csproj
📚 Learning: 2025-11-28T10:36:40.819Z
Learnt from: CR
Repo: moconnell/yolo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-28T10:36:40.819Z
Learning: Applies to **/*.csproj : Enable nullable reference types and implicit usings in new .csproj projects

Applied to files:

  • test/YoloAbstractions.Test/YoloAbstractions.Test.csproj
🧬 Code graph analysis (2)
test/YoloAbstractions.Test/YoloAbstractions.Test.csproj (3)
test/YoloWeights.Test/YoloWeightsServiceTest.cs (1)
  • YoloWeightsServiceTest (13-471)
test/Unravel.Api.Test/UnravelFactorServiceTest.cs (1)
  • UnravelFactorServiceTest (9-115)
test/Robotwealth.Api.Test/RobotWealthApiServiceTest.cs (1)
  • RobotWealthApiServiceTest (10-158)
src/YoloAbstractions/Extensions/HttpClientExtensions.cs (3)
src/YoloAbstractions/Exceptions/ApiException.cs (3)
  • ApiException (3-14)
  • ApiException (5-8)
  • ApiException (10-13)
src/YoloAbstractions/Interfaces/IApiResponse.cs (1)
  • IApiResponse (5-8)
src/Unravel.Api/UnravelApiService.cs (1)
  • Task (104-121)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (6)
src/YoloKonsole/Program.cs (1)

1-15: Using cleanup correctly relies on SDK implicit usings

The removal of redundant System* usings looks good. All referenced types (e.g., Task, CancellationTokenSource, Path, Directory, LINQ extensions, Console, DateTime, TimeSpan, Math) are covered by the SDK’s implicit usings given ImplicitUsings is enabled as per your guidelines, so there’s no missing-namespace risk and no behavioral change introduced.

src/YoloTrades/TradeFactory.cs (1)

1-1: Using re-introduction looks correct

Re-adding Microsoft.Extensions.Logging is appropriate here given the logger usage and extension methods throughout the class; no issues from this change.

test/YoloAbstractions.Test/YoloAbstractions.Test.csproj (1)

6-6: LGTM!

The Moq package reference and global using are correctly added without version specification (centrally managed per guidelines). This aligns with the learnings indicating Moq should be used for mocking in tests.

Also applies to: 24-24

test/YoloAbstractions.Test/HttpClientExtensionsTest.cs (3)

14-17: LGTM!

The private test DTO correctly implements IApiResponse<string> for testing purposes. Using collection expression [] as the default is idiomatic C# 12+.


19-124: Good test coverage for core functionality.

The tests comprehensively cover:

  • Successful deserialization with valid data
  • Header propagation to requests
  • Argument validation (null client, null/empty URL)
  • HTTP error status handling
  • Null response, null data, and empty data scenarios
  • Invalid JSON with inner exception preservation

Uses Shouldly assertions and Moq as per coding guidelines.


150-172: Well-structured test helpers.

The helper methods properly use Moq.Protected to mock HttpMessageHandler.SendAsync and support request capture for verification. This pattern aligns with existing test infrastructure in RobotWealthApiServiceTest.

Comment on lines +36 to 51
try
{
var apiResponse = await response.Content.ReadFromJsonAsync<TResponse>(cancellationToken: cancellationToken);

if (apiResponse is null || apiResponse.Data is null || apiResponse.Data.Count == 0)
{
throw new ApiException($"No data returned from API ({url})");
}

if (apiResponse is null || apiResponse.Data is null || apiResponse.Data.Count == 0)
return apiResponse;
}
catch (Exception ex) when (ex is not ApiException)
{
throw new ApiException($"No data returned from API ({url})");
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
throw new ApiException($"Error deserializing API response from ({url}): {ex.Message}\r\n{responseContent}", ex);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Response content stream already consumed before error handling.

After ReadFromJsonAsync (line 38) fails partway through, the response content stream has already been consumed or partially read. Since HttpContent streams are typically non-seekable, calling ReadAsStringAsync (line 49) will return an empty string or incomplete content, making the error message unhelpful.

Consider buffering the content first:

+        var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
         try
         {
-            var apiResponse = await response.Content.ReadFromJsonAsync<TResponse>(cancellationToken: cancellationToken);
+            var apiResponse = JsonSerializer.Deserialize<TResponse>(responseContent);
 
             if (apiResponse is null || apiResponse.Data is null || apiResponse.Data.Count == 0)
             {
                 throw new ApiException($"No data returned from API ({url})");
             }
 
             return apiResponse;
         }
         catch (Exception ex) when (ex is not ApiException)
         {
-            var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
             throw new ApiException($"Error deserializing API response from ({url}): {ex.Message}\r\n{responseContent}", ex);
         }

Alternatively, use LoadIntoBufferAsync() before deserialization to allow multiple reads.

📝 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
try
{
var apiResponse = await response.Content.ReadFromJsonAsync<TResponse>(cancellationToken: cancellationToken);
if (apiResponse is null || apiResponse.Data is null || apiResponse.Data.Count == 0)
{
throw new ApiException($"No data returned from API ({url})");
}
if (apiResponse is null || apiResponse.Data is null || apiResponse.Data.Count == 0)
return apiResponse;
}
catch (Exception ex) when (ex is not ApiException)
{
throw new ApiException($"No data returned from API ({url})");
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
throw new ApiException($"Error deserializing API response from ({url}): {ex.Message}\r\n{responseContent}", ex);
}
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
try
{
var apiResponse = JsonSerializer.Deserialize<TResponse>(responseContent);
if (apiResponse is null || apiResponse.Data is null || apiResponse.Data.Count == 0)
{
throw new ApiException($"No data returned from API ({url})");
}
return apiResponse;
}
catch (Exception ex) when (ex is not ApiException)
{
throw new ApiException($"Error deserializing API response from ({url}): {ex.Message}\r\n{responseContent}", ex);
}
🤖 Prompt for AI Agents
In src/YoloAbstractions/Extensions/HttpClientExtensions.cs around lines 36 to
51, the catch attempts to read response.Content after ReadFromJsonAsync may have
already consumed the non-seekable stream, so the fallback ReadAsStringAsync will
be empty; fix by buffering the content before deserialization (e.g., call
response.Content.LoadIntoBufferAsync() or read
response.Content.ReadAsStringAsync() into a local string variable and then
deserialize from that string) so the content can be inspected on error, and use
that buffered string in the ApiException message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working .NET Pull requests that update .NET code Unravel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: Null value returned by Unravel API not handled

2 participants