• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

moconnell / yolo / 28178294840

25 Jun 2026 02:41PM UTC coverage: 81.953% (+1.0%) from 80.944%
28178294840

push

github

web-flow
Feat: new Unravel factors 2026Q2; bug fixes (#123)

* feat: add new Unravel factor types

* chore: bump packages

* chore(config): update Unravel universe size default 20 -> 40

* fix(TickerExtensions): handle various non-standard tickers

* fix(appsettings.json): add more HL ticker aliases

* fix: portfolio exposure drift due to trade buffer scaling bug

- refactor: introduce RebalancePlanner as single code path for trade buffer calcs

* refactor: create separate API factor type enums

* refactor: fix review snags

* fix: azure deployment error

* refactor(TradeFactoryTests): use Shouldly for assertion

* feat(HyperliquidBroker): log IsTestnet

* fix(TradeUtil): make Trades post-only by default

336 of 392 branches covered (85.71%)

Branch coverage included in aggregate %.

354 of 363 new or added lines in 11 files covered. (97.52%)

1 existing line in 1 file now uncovered.

2652 of 3254 relevant lines covered (81.5%)

26.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.01
/src/YoloFunk/Functions/EffectiveWeightsFunctionBase.cs
1
using System.Net;
2
using Microsoft.Azure.Functions.Worker.Http;
3
using Microsoft.Extensions.DependencyInjection;
4
using Microsoft.Extensions.Logging;
5
using YoloAbstractions;
6
using YoloAbstractions.Config;
7
using YoloAbstractions.Extensions;
8
using YoloAbstractions.Interfaces;
9
using YoloBroker.Interface;
10
using YoloFunk.Dto;
11
using YoloTrades;
12

13
namespace YoloFunk.Functions;
14

15
public abstract class EffectiveWeightsFunctionBase
16
{
17
    private readonly IServiceProvider _serviceProvider;
18
    private readonly ILogger _logger;
19

20
    protected EffectiveWeightsFunctionBase(IServiceProvider serviceProvider, ILogger logger)
9✔
21
    {
9✔
22
        _serviceProvider = serviceProvider;
9✔
23
        _logger = logger;
9✔
24
    }
9✔
25

26
    protected abstract string StrategyKey { get; }
27

28
    protected async Task<HttpResponseData> GetEffectiveWeightsAsync(HttpRequestData req, CancellationToken cancellationToken)
29
    {
9✔
30
        try
31
        {
9✔
32
            var broker = _serviceProvider.GetRequiredKeyedService<IYoloBroker>(StrategyKey);
9✔
33
            var accountContext = broker.GetAccountContext();
7✔
34
            var address = accountContext.Address;
7✔
35
            var vaultAddress = accountContext.VaultAddress;
7✔
36

37
            if (string.IsNullOrWhiteSpace(address))
7✔
38
            {
2✔
39
                var error = req.CreateResponse(HttpStatusCode.InternalServerError);
2✔
40
                await error.WriteAsJsonAsync(
2✔
41
                    new RebalanceErrorResponse(
2✔
42
                        StrategyKey,
2✔
43
                        "Invalid strategy configuration",
2✔
44
                        "Broker account context is missing address."),
2✔
45
                    cancellationToken);
2✔
46
                return error;
2✔
47
            }
48

49
            var weightsService = _serviceProvider.GetRequiredKeyedService<ICalcWeights>(StrategyKey);
5✔
50
            var yoloConfig = _serviceProvider.GetRequiredKeyedService<YoloConfig>(StrategyKey);
5✔
51
            var targetWeights = await weightsService.CalculateWeightsAsync(cancellationToken);
5✔
52

53
            var positions = await broker.GetPositionsAsync(cancellationToken);
5✔
54
            var baseAssetFilter = positions.Keys
5✔
55
                .Union(targetWeights.Keys.Select(x => x.GetBaseAndQuoteAssets().BaseAsset))
56
                .ToHashSet();
5✔
57

58
            var markets = await broker.GetMarketsAsync(
5✔
59
                baseAssetFilter,
5✔
60
                yoloConfig.BaseAsset,
5✔
61
                yoloConfig.AssetPermissions,
5✔
62
                cancellationToken);
5✔
63

64
            var nominal = yoloConfig.NominalCash ?? positions.GetTotalValue(markets, yoloConfig.BaseAsset);
5✔
65
            var verification = CalculateEffectiveWeights(targetWeights, positions, markets, yoloConfig, nominal);
5✔
66

67
            var response = req.CreateResponse(HttpStatusCode.OK);
4✔
68
            await response.WriteAsJsonAsync(
4✔
69
                new EffectiveWeightsResponse(
4✔
70
                    StrategyKey,
4✔
71
                    address,
4✔
72
                    vaultAddress,
4✔
73
                    DateTime.UtcNow,
4✔
74
                    nominal,
4✔
75
                    verification.WeightConstraint,
4✔
76
                    verification.CurrentGrossExposure,
4✔
77
                    verification.CurrentNetExposure,
4✔
78
                    verification.EffectiveGrossExposure,
4✔
79
                    verification.EffectiveNetExposure,
4✔
80
                    verification.BufferAdjustedGrossExposure,
4✔
81
                    verification.BufferAdjustedNetExposure,
4✔
82
                    verification.Weights),
4✔
83
                cancellationToken);
4✔
84
            return response;
4✔
85
        }
86
        catch (DuplicateBaseAssetWeightsException ex)
1✔
87
        {
1✔
88
            _logger.LogWarning(ex,
1✔
89
                "Invalid raw weights for strategy {Strategy}",
1✔
90
                StrategyKey);
1✔
91

92
            var errorResponse = req.CreateResponse(HttpStatusCode.BadRequest);
1✔
93
            await errorResponse.WriteAsJsonAsync(
1✔
94
                new RebalanceErrorResponse(
1✔
95
                    StrategyKey,
1✔
96
                    "Invalid raw weights",
1✔
97
                    ex.Message),
1✔
98
                cancellationToken);
1✔
99
            return errorResponse;
1✔
100
        }
101
        catch (Exception ex)
2✔
102
        {
2✔
103
            _logger.LogError(ex,
2✔
104
                "Failed to calculate effective weights for strategy {Strategy}",
2✔
105
                StrategyKey);
2✔
106

107
            var errorResponse = req.CreateResponse(HttpStatusCode.InternalServerError);
2✔
108
            await errorResponse.WriteAsJsonAsync(
2✔
109
                new RebalanceErrorResponse(
2✔
110
                    StrategyKey,
2✔
111
                    "Failed to calculate effective weights",
2✔
112
                    "An internal error occurred. Check logs for details."),
2✔
113
                cancellationToken);
2✔
114
            return errorResponse;
2✔
115
        }
116
    }
9✔
117

118
    private static EffectiveWeightsVerification CalculateEffectiveWeights(
119
        IReadOnlyDictionary<string, decimal> rawWeights,
120
        IReadOnlyDictionary<string, IReadOnlyList<Position>> positions,
121
        IReadOnlyDictionary<string, IReadOnlyList<MarketInfo>> markets,
122
        YoloConfig yoloConfig,
123
        decimal nominal)
124
    {
5✔
125
        var groupedWeights = rawWeights
5✔
126
            .GroupBy(kvp => kvp.Key.GetBaseAndQuoteAssets().BaseAsset, StringComparer.OrdinalIgnoreCase)
6✔
127
            .ToArray();
5✔
128

129
        var duplicateGroups = groupedWeights
5✔
130
            .Where(group => group.Count() > 1)
5✔
131
            .ToArray();
5✔
132

133
        if (duplicateGroups.Length > 0)
5✔
134
        {
1✔
135
            var duplicatesDescription = string.Join(
1✔
136
                "; ",
1✔
137
                duplicateGroups.Select(group =>
1✔
138
                    $"{group.Key}: {string.Join(", ", group.Select(x => x.Key).OrderBy(x => x, StringComparer.OrdinalIgnoreCase))}"));
6✔
139

140
            throw new DuplicateBaseAssetWeightsException(
1✔
141
                $"Duplicate raw weight symbols normalize to the same base asset: {duplicatesDescription}");
1✔
142
        }
143

144
        var plan = RebalancePlanner.Create(rawWeights, positions, markets, yoloConfig, nominal);
4✔
145
        var effectiveItems = plan.Items
4✔
146
            .OrderBy(item => item.Token, StringComparer.OrdinalIgnoreCase)
4✔
147
            .Select(item => new EffectiveWeightItem(
4✔
148
                item.Token,
4✔
149
                item.RawTargetWeight,
4✔
150
                item.ConstrainedTargetWeight,
4✔
151
                item.CurrentWeight,
4✔
152
                item.HasTradableMarket ? item.ConstrainedTargetWeight : null,
4✔
153
                item.DeltaWeight,
4✔
154
                item.IsInUniverse,
4✔
155
                item.WithinTradeBuffer,
4✔
156
                item.HasTradableMarket))
4✔
157
            .ToArray();
4✔
158

159
        return new EffectiveWeightsVerification(
4✔
160
            plan.WeightConstraint,
4✔
161
            CalculateGrossExposure(effectiveItems, item => item.CurrentWeight),
4✔
162
            CalculateNetExposure(effectiveItems, item => item.CurrentWeight),
4✔
163
            CalculateGrossExposure(effectiveItems, item => item.EffectiveWeight),
4✔
164
            CalculateNetExposure(effectiveItems, item => item.EffectiveWeight),
4✔
165
            CalculateGrossExposure(plan.Items, item => item.BufferAdjustedTargetWeight),
4✔
166
            CalculateNetExposure(plan.Items, item => item.BufferAdjustedTargetWeight),
4✔
167
            effectiveItems);
4✔
168
    }
4✔
169

170
    private static decimal? CalculateGrossExposure<T>(
171
        IReadOnlyList<T> items,
172
        Func<T, decimal?> selectWeight) =>
173
        TryGetCompleteWeights(items, selectWeight, out var weights)
12✔
174
            ? weights.Sum(Math.Abs)
12✔
175
            : null;
12✔
176

177
    private static decimal? CalculateNetExposure<T>(
178
        IReadOnlyList<T> items,
179
        Func<T, decimal?> selectWeight) =>
180
        TryGetCompleteWeights(items, selectWeight, out var weights)
12✔
181
            ? weights.Sum()
12✔
182
            : null;
12✔
183

184
    private static bool TryGetCompleteWeights<T>(
185
        IReadOnlyList<T> items,
186
        Func<T, decimal?> selectWeight,
187
        out decimal[] weights)
188
    {
24✔
189
        var selected = items.Select(selectWeight).ToArray();
24✔
190
        if (selected.Any(weight => !weight.HasValue))
48✔
UNCOV
191
        {
×
NEW
192
            weights = [];
×
NEW
193
            return false;
×
194
        }
195

196
        weights = [.. selected.Select(weight => weight.GetValueOrDefault())];
48✔
197
        return true;
24✔
198
    }
24✔
199

200
    private sealed class DuplicateBaseAssetWeightsException(string message) : InvalidOperationException(message);
1✔
201

202
    private sealed record EffectiveWeightsVerification(
4✔
203
        decimal WeightConstraint,
4✔
204
        decimal? CurrentGrossExposure,
4✔
205
        decimal? CurrentNetExposure,
4✔
206
        decimal? EffectiveGrossExposure,
4✔
207
        decimal? EffectiveNetExposure,
4✔
208
        decimal? BufferAdjustedGrossExposure,
4✔
209
        decimal? BufferAdjustedNetExposure,
4✔
210
        IReadOnlyList<EffectiveWeightItem> Weights);
8✔
211

212
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc