Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0f78b9b
refactor: remove unnecessary using statements
moconnell Feb 23, 2026
1687a5e
fix(broker): bug where LMT order is already filled yet broker sends a…
moconnell Feb 23, 2026
6a06cc5
chore: update UnravelDaily TradeBuffer -> 0.00025
moconnell Feb 23, 2026
2689868
fix(Trade): IsTradable
moconnell Feb 23, 2026
6271abd
fix(HyperliquidBroker): MKT order without valid LimitPrice rejected
moconnell Feb 23, 2026
9adff79
chore(appsettings.json): update Unravel Schedule, TradeBuffer -> 0.001
moconnell Feb 26, 2026
518d09d
chore: bump deps
moconnell Mar 19, 2026
0ff5819
fix(appsettings.json): default to testnet
moconnell Mar 19, 2026
028681f
fix(settings.json): do not break long XML lines
moconnell Mar 19, 2026
3fd054d
refactor: extract OrderManager
moconnell Mar 19, 2026
60bffdd
refactor(RebalanceCommand): formatting
moconnell Mar 19, 2026
ea70003
fix(vscode): task config
moconnell Mar 21, 2026
470723a
fix: various order update subscription bugs
moconnell Mar 21, 2026
e329e1a
feat: reprice LMT order n times and then place MKT
moconnell Mar 21, 2026
f3ea977
fix: build
moconnell Mar 21, 2026
0f1e57a
test: improve coverage
moconnell Mar 21, 2026
cd8338f
test: increase coverage
moconnell Mar 21, 2026
c520a91
fix(settings.json): remove .net path from workspace settings
moconnell Mar 21, 2026
a9c0c2e
fix(YoloKonsole): DI should register OrderManager
moconnell Mar 22, 2026
6971185
fix(OrderManagerTest): wait until stream is consumed to assert
moconnell Mar 22, 2026
81df924
fix(AddStrategyServicesTest): tighten exception assertion
moconnell Mar 22, 2026
c79efff
fix(EffectiveWeightsFunctionBase): handle duplicate input weights exc…
moconnell Mar 22, 2026
8098625
fix(HyperliquidBroker): store subscription disposable and dispose whe…
moconnell Mar 22, 2026
0a76688
fix(OrderManager): validate OrderManagementSettings
moconnell Mar 22, 2026
770af46
fix(YoloConfigExtensions): add more assertions
moconnell Mar 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"name": "Azure Functions (host + attach)",
"type": "coreclr",
"request": "attach",
"preLaunchTask": "func: host start (with azurite)",
"preLaunchTask": "func: host start",
"processId": "${command:azureFunctions.pickProcess}"
},
{
Expand Down
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"dotnetAcquisitionExtension.sharedExistingDotnetPath": "/usr/local/share/dotnet/dotnet",
"dotnet.server.useOmnisharp": false,
"dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true,
"dotnet.codeLens.enableReferencesCodeLens": true,
Expand All @@ -7,5 +8,7 @@
"azureFunctions.projectRuntime": "~4",
"debug.internalConsoleOptions": "neverOpen",
"azureFunctions.preDeployTask": "publish (functions)",
"azureFunctions.projectSubpath": "src/YoloFunk"
"azureFunctions.projectSubpath": "src/YoloFunk",
"xml.format.maxLineWidth": 0,
"xml.format.splitAttributes": "preserve"
}
48 changes: 13 additions & 35 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@
"version": "2.0.0",
"tasks": [
{
"type": "dotnet",
"task": "build",
"label": "build",
"type": "process",
"command": "dotnet",
"args": [
"build",
"${workspaceFolder}/Yolo.slnx",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"group": "build",
"problemMatcher": [],
"label": "build"
"problemMatcher": "$msCompile",
"options": {
"cwd": "${workspaceFolder}"
}
},
{
"label": "clean (functions)",
Expand Down Expand Up @@ -78,37 +87,6 @@
"cwd": "${workspaceFolder}"
}
},
{
"label": "Azurite: Start",
"type": "process",
"command": "npx",
"args": [
"--yes",
"azurite",
"--location",
"${workspaceFolder}",
"--silent"
],
"isBackground": true,
"problemMatcher": {
"owner": "azurite",
"pattern": {
"regexp": "^(.*)$",
"message": 1
},
"background": {
"activeOnStart": true,
"beginsPattern": ".*Azurite Blob service is starting.*",
"endsPattern": ".*Azurite Table service is successfully listening at.*"
}
}
},
{
"label": "func: host start (with azurite)",
"dependsOn": ["Azurite: Start", "func: host start"],
"dependsOrder": "sequence",
"problemMatcher": []
},
{
"type": "func",
"label": "func: host start",
Expand Down
51 changes: 24 additions & 27 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -1,50 +1,47 @@
<Project>
<ItemGroup>
<PackageVersion Include="Azure.Data.Tables" Version="12.11.0" />
<PackageVersion Include="Azure.Identity" Version="1.17.1" />
<PackageVersion Include="Azure.Security.KeyVault.Keys" Version="4.8.0" />
<PackageVersion Include="Azure.Identity" Version="1.19.0" />
<PackageVersion Include="Azure.Security.KeyVault.Keys" Version="4.9.0" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.27.0" />
<PackageVersion Include="coverlet.collector" Version="8.0.0" />
<PackageVersion Include="coverlet.msbuild" Version="8.0.0" />
<PackageVersion Include="coverlet.collector" Version="8.0.1" />
<PackageVersion Include="coverlet.msbuild" Version="8.0.1" />
<PackageVersion Include="CsvHelper" Version="33.1.0" />
<PackageVersion Include="HyperLiquid.Net" Version="3.8.0" />
<PackageVersion Include="HyperLiquid.Net" Version="3.10.0" />
<PackageVersion Include="MathNet.Numerics" Version="5.0.0" />
<PackageVersion Include="Microsoft.ApplicationInsights.WorkerService" Version="2.23.0" />
<PackageVersion Include="Microsoft.ApplicationInsights" Version="2.23.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker" Version="2.51.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="2.50.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask"
Version="1.14.1" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore"
Version="2.1.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.16.1" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.1.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" Version="4.3.1" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.7" />
<PackageVersion Include="Microsoft.Data.Analysis" Version="0.23.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables"
Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.KeyPerFile" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Configuration.KeyPerFile" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.5" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.5" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="Moq.Contrib.HttpClient" Version="1.4.0" />
<PackageVersion Include="Nethereum.Signer" Version="5.8.0" />
<PackageVersion Include="Nethereum.Signer.EIP712" Version="5.8.0" />
<PackageVersion Include="Nethereum.Util" Version="5.8.0" />
<PackageVersion Include="Nethereum.Signer" Version="6.0.4" />
<PackageVersion Include="Nethereum.Signer.EIP712" Version="6.0.4" />
<PackageVersion Include="Nethereum.Util" Version="6.0.4" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="10.0.0" />
<PackageVersion Include="Serilog.Extensions.Logging.File" Version="3.0.0" />
<PackageVersion Include="Shouldly" Version="4.3.0" />
<PackageVersion Include="Snapshooter.Xunit" Version="1.3.0" />
<PackageVersion Include="Snapshooter.Xunit" Version="1.3.1" />
<PackageVersion Include="Spectre.Console" Version="0.54.0" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.3" />
<PackageVersion Include="System.Text.Json" Version="10.0.3" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.5" />
<PackageVersion Include="System.Text.Json" Version="10.0.5" />
<PackageVersion Include="Utility.CommandLine.Arguments" Version="6.0.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
Expand Down
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,52 @@ Center // (default)
Edge
```

## Azure Functions - Effective Weights Verification

The function app exposes verification endpoints that calculate and return effective rebalance weights using each strategy's configured Hyperliquid account context.

Routes:

- `GET /api/rebalance/yolodaily/effective-weights`
- `GET /api/rebalance/unraveldaily/effective-weights`

The account context is taken from:

- `Strategies.<Strategy>.Hyperliquid.Address`
- `Strategies.<Strategy>.Hyperliquid.VaultAddress`

Notes:

- Endpoints use `AuthorizationLevel.Function`.
- Callers cannot override `address` or `vault` via query parameters.
- Response includes both raw target and constrained/effective weights to validate rebalance behavior.

Example response shape:

```json
{
"strategy": "yolodaily",
"address": "0x...",
"vaultAddress": "0x...",
"generatedAtUtc": "2026-02-23T12:34:56Z",
"nominal": 100000.0,
"weightConstraint": 0.85,
"weights": [
{
"token": "BTC",
"rawTargetWeight": 0.12,
"constrainedTargetWeight": 0.102,
"currentWeight": 0.09,
"effectiveWeight": 0.102,
"deltaWeight": 0.012,
"isInUniverse": true,
"withinTradeBuffer": false,
"hasTradableMarket": true
}
]
}
```

### Yolo/SpreadSplit

This setting determines the placement of the limit price within the bid-ask price spread and can take any value between 0 and 1 (values greater than 1 will be treated as 1).
Expand Down
3 changes: 3 additions & 0 deletions src/YoloAbstractions/BrokerAccountContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace YoloAbstractions;

public sealed record BrokerAccountContext(string? Address, string? VaultAddress);
8 changes: 8 additions & 0 deletions src/YoloAbstractions/BrokerOrderEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace YoloAbstractions;

public record BrokerOrderEvent(
string ClientOrderId,
Order Order,
bool Success,
string? Error = null,
int? ErrorCode = null);
3 changes: 2 additions & 1 deletion src/YoloAbstractions/Config/YoloConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public record YoloConfig
public decimal SpreadSplit { get; init; } = 0.5m;
public decimal? MinOrderValue { get; init; } = 10;
public bool KillOpenOrders { get; init; } = false;
public string UnfilledOrderTimeout { get; init; } = "00:05:00"; // Default 5 minutes
public string UnfilledOrderTimeout { get; init; } = "00:00:30";
public int MaxRepriceRetries { get; init; } = 2;
public double? MaxWeightingAbs { get; init; }
public IReadOnlyDictionary<FactorType, decimal> FactorWeights { get; init; } = new Dictionary<FactorType, decimal>();
public NormalizationMethod NormalizationMethod { get; init; } = NormalizationMethod.None;
Expand Down
10 changes: 10 additions & 0 deletions src/YoloAbstractions/Interfaces/ITradeAdvisor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace YoloAbstractions.Interfaces;

public interface ITradeAdvisor
{
/// <summary>
/// Called when a limit order times out. Returns the replacement trade to place (with fresh
/// prices and positions), or null if the position is already within target (nothing to do).
/// </summary>
Task<Trade?> GetReplacementTradeAsync(Trade timedOutTrade, CancellationToken ct = default);
}
11 changes: 1 addition & 10 deletions src/YoloAbstractions/OrderManagementSettings.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
namespace YoloAbstractions;

public record OrderManagementSettings(
TimeSpan UnfilledOrderTimeout = default,
bool SwitchToMarketOnTimeout = true,
TimeSpan StatusCheckInterval = default)
{
public static OrderManagementSettings Default => new(
UnfilledOrderTimeout: TimeSpan.FromMinutes(5),
SwitchToMarketOnTimeout: true,
StatusCheckInterval: TimeSpan.FromSeconds(30));
}
public record OrderManagementSettings(TimeSpan UnfilledOrderTimeout, int MaxRepriceRetries);
2 changes: 1 addition & 1 deletion src/YoloAbstractions/Trade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public bool IsTradable(decimal? minOrderValue = null)
return false;

// Ensure the order value meets the minimum requirement
return ReduceOnly == true || !minOrderValue.HasValue || !LimitPrice.HasValue || !(AbsoluteAmount * LimitPrice.Value < minOrderValue);
return !minOrderValue.HasValue || !LimitPrice.HasValue || !(AbsoluteAmount * LimitPrice.Value < minOrderValue);
}

public decimal AbsoluteAmount => Math.Abs(Amount);
Expand Down
29 changes: 15 additions & 14 deletions src/YoloApp/Commands/RebalanceCommand.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,44 @@
using Microsoft.Extensions.Logging;

using System.Threading.Channels;
using System.Threading.Channels;

using YoloAbstractions;
using YoloAbstractions.Config;
using YoloAbstractions.Extensions;
using YoloAbstractions.Interfaces;
using YoloApp.Extensions;
using YoloBroker.Interface;
using YoloTrades;

using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;

namespace YoloApp.Commands;

public class RebalanceCommand : ICommand
{
private readonly ICalcWeights _weightsService;
private readonly ITradeFactory _tradeFactory;
private readonly IOrderManager _orderManager;
private readonly IYoloBroker _broker;
private readonly YoloConfig _yoloConfig;
private readonly ILogger<RebalanceCommand> _logger;
public RebalanceCommand(ICalcWeights weightsService, ITradeFactory tradeFactory, IYoloBroker broker, IOptions<YoloConfig> options, ILogger<RebalanceCommand> logger)
: this(weightsService, tradeFactory, broker, options.Value, logger)

public RebalanceCommand(ICalcWeights weightsService, ITradeFactory tradeFactory, IOrderManager orderManager, IYoloBroker broker, IOptions<YoloConfig> options, ILogger<RebalanceCommand> logger)
: this(weightsService, tradeFactory, orderManager, broker, options.Value, logger)
{
}

public RebalanceCommand(ICalcWeights weightsService, ITradeFactory tradeFactory, IYoloBroker broker, YoloConfig yoloConfig, ILogger<RebalanceCommand> logger)
public RebalanceCommand(ICalcWeights weightsService, ITradeFactory tradeFactory, IOrderManager orderManager, IYoloBroker broker, YoloConfig yoloConfig, ILogger<RebalanceCommand> logger)
{
ArgumentNullException.ThrowIfNull(weightsService, nameof(weightsService));
ArgumentNullException.ThrowIfNull(tradeFactory, nameof(tradeFactory));
ArgumentNullException.ThrowIfNull(orderManager, nameof(orderManager));
ArgumentNullException.ThrowIfNull(broker, nameof(broker));
ArgumentNullException.ThrowIfNull(yoloConfig, nameof(yoloConfig));
ArgumentNullException.ThrowIfNull(logger, nameof(logger));

_weightsService = weightsService;
_tradeFactory = tradeFactory;
_orderManager = orderManager;
_broker = broker;
_yoloConfig = yoloConfig;
_logger = logger;
Expand Down Expand Up @@ -90,18 +95,14 @@ public async Task ExecuteAsync(CancellationToken cancellationToken = default)
return;
}

var settings = OrderManagementSettings.Default with
{
UnfilledOrderTimeout = TimeSpan.TryParse(_yoloConfig.UnfilledOrderTimeout, out var timeout)
? timeout
: OrderManagementSettings.Default.UnfilledOrderTimeout
};
var settings = new OrderManagementSettings(TimeSpan.Parse(_yoloConfig.UnfilledOrderTimeout), _yoloConfig.MaxRepriceRetries);
var advisor = new TradeAdvisor(weights, _tradeFactory, _broker, _yoloConfig.BaseAsset, _yoloConfig.AssetPermissions);

_logger.LogInformation("Managing orders for {TradeCount} trades", trades.Length);
_logger.LogInformation("Order management settings: {Settings}, Advisor={AdvisorType}", settings, advisor.GetType().Name);

try
{
await foreach (var update in _broker.ManageOrdersAsync(trades, settings, cancellationToken))
await foreach (var update in _orderManager.ManageOrdersAsync(trades, settings, advisor, cancellationToken))
{
if (update.Type == OrderUpdateType.Error)
{
Expand Down
Loading
Loading