Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
18fc676
Refactor market depth project structure
codez0mb1e Nov 19, 2025
21dc1b4
Inject MarketSymbol entity
codez0mb1e Nov 19, 2025
c548df5
Add support for Perpetual contract
codez0mb1e Nov 19, 2025
5776c30
Inject `MarketSymbol`
codez0mb1e Nov 19, 2025
ab5ae81
Updates and improve event buffering
codez0mb1e Nov 20, 2025
af6761a
More consistent naming
codez0mb1e Nov 20, 2025
eb7e60d
Improve output format
codez0mb1e Nov 20, 2025
d6fc89f
More comprehensive naming
codez0mb1e Nov 20, 2025
bd1ea3f
Decrease buffering interval
codez0mb1e Nov 20, 2025
d6641e6
Refactor MarketSymbol: consolidate into a single file and enhance con…
codez0mb1e Nov 20, 2025
65a2c18
Minor improvements
codez0mb1e Nov 20, 2025
2039400
Remove unnecessary import
codez0mb1e Nov 20, 2025
9ed1fca
Extra leading whitespace fix
codez0mb1e Nov 20, 2025
dcf0d40
Remove unused import
codez0mb1e Nov 20, 2025
64b3a5b
Formatting
codez0mb1e Nov 20, 2025
40377c5
Repair checking for missed updates
codez0mb1e Nov 21, 2025
3d33f06
Push reuse logic to GetOrderBookSnapshotAsync()
codez0mb1e Nov 21, 2025
5390207
Revert "Push reuse logic to GetOrderBookSnapshotAsync()"
codez0mb1e Nov 21, 2025
952dba3
Enhance missed updates validation for Spot market
codez0mb1e Nov 21, 2025
083f55c
Add method to retrieve order book snapshot
codez0mb1e Nov 21, 2025
eac4f77
Improve naming and add documentations
codez0mb1e Nov 21, 2025
24219d9
Improve parameter validation and add unit tests
codez0mb1e Nov 21, 2025
14419f0
Fix tests
codez0mb1e Nov 21, 2025
e00a745
Nullable Error
codez0mb1e Nov 21, 2025
5298b8e
Fix typo
codez0mb1e Nov 21, 2025
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
5 changes: 3 additions & 2 deletions src/BinanceBot.Market/Abstracts/BaseMarketBot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Binance.Net.Objects.Models.Spot;
using BinanceBot.Market.Domain;
using NLog;

namespace BinanceBot.Market;
Expand All @@ -19,15 +20,15 @@ public abstract class BaseMarketBot<TStrategy> :
protected readonly TStrategy MarketStrategy;


protected BaseMarketBot(string symbol, TStrategy marketStrategy, Logger logger)
protected BaseMarketBot(MarketSymbol symbol, TStrategy marketStrategy, Logger logger)
{
Symbol = symbol ?? throw new ArgumentNullException(nameof(symbol));
MarketStrategy = marketStrategy ?? throw new ArgumentNullException(nameof(marketStrategy));
Logger = logger ?? LogManager.GetCurrentClassLogger();
}


public string Symbol { get; }
public MarketSymbol Symbol { get; }


public abstract Task RunAsync();
Expand Down
3 changes: 2 additions & 1 deletion src/BinanceBot.Market/Abstracts/IMarketBot.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Binance.Net.Objects.Models.Spot;
using BinanceBot.Market.Domain;


namespace BinanceBot.Market;
Expand All @@ -13,7 +14,7 @@ public interface IMarketBot
/// <summary>
/// Symbol
/// </summary>
string Symbol { get; }
MarketSymbol Symbol { get; }


/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/BinanceBot.Market/Abstracts/IMarketDepthPublisher.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using BinanceBot.Market.Core;
using BinanceBot.Market.Domain;

namespace BinanceBot.Market;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Collections.Generic;

namespace BinanceBot.Market.Utility;
namespace BinanceBot.Market.Core;

/// <summary>
/// Descending decimal comparer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@
using System.Linq;
using Binance.Net.Enums;
using Binance.Net.Objects.Models;
using BinanceBot.Market.Utility;
using BinanceBot.Market.Core;
using BinanceBot.Market.Extensions;

namespace BinanceBot.Market.Core;
namespace BinanceBot.Market.Domain;

/// <summary>
/// Order book
/// </summary>
public class MarketDepth : IMarketDepthPublisher
{
public MarketDepth(string symbol)
public MarketDepth(MarketSymbol symbol)
{
if (string.IsNullOrEmpty(symbol))
if (symbol == null)
throw new ArgumentException("Invalid symbol value", nameof(symbol));

Symbol = symbol;
}


public string Symbol { get; }

public MarketSymbol Symbol { get; }

#region Ask section
private readonly IDictionary<decimal, decimal> _asks = new SortedDictionary<decimal, decimal>();
Expand Down Expand Up @@ -59,14 +59,14 @@ public MarketDepth(string symbol)
/// <summary>
/// The best pair. If the order book is empty, will be returned <see langword="null"/>.
/// </summary>
public MarketDepthPair BestPair => LastUpdateTime.HasValue
? new MarketDepthPair(BestAsk, BestBid, LastUpdateTime.Value)
public MarketDepthPair BestPair => LastUpdateId.HasValue
? new MarketDepthPair(BestAsk, BestBid, LastUpdateId.Value)
: null;

/// <summary>
/// Last update of market depth
/// </summary>
public long? LastUpdateTime { get; private set; }
public long? LastUpdateId { get; private set; }



Expand All @@ -77,13 +77,13 @@ public MarketDepth(string symbol)
/// Update market depth
/// </summary>
/// <remarks>
public void UpdateDepth(IEnumerable<BinanceOrderBookEntry> asks, IEnumerable<BinanceOrderBookEntry> bids, long updateTime)
public void UpdateDepth(IEnumerable<BinanceOrderBookEntry> asks, IEnumerable<BinanceOrderBookEntry> bids, long updateId)
{
if (updateTime <= 0)
throw new ArgumentOutOfRangeException(nameof(updateTime));
if (updateId <= 0)
throw new ArgumentOutOfRangeException(nameof(updateId));

// if nothing was changed then return
if (updateTime <= LastUpdateTime) return;
if (updateId <= LastUpdateId) return;
if (asks == null && bids == null) return;


Expand Down Expand Up @@ -111,10 +111,10 @@ static void UpdateOrderBook(IEnumerable<BinanceOrderBookEntry> updates, IDiction
UpdateOrderBook(asks, _asks);
UpdateOrderBook(bids, _bids);
// set new update time
LastUpdateTime = updateTime;
LastUpdateId = updateId;

// raise events
OnMarketDepthChanged(new MarketDepthChangedEventArgs(Asks, Bids, LastUpdateTime.Value));
OnMarketDepthChanged(new MarketDepthChangedEventArgs(Asks, Bids, LastUpdateId.Value));
if(!BestPair.Equals(prevBestPair))
OnMarketBestPairChanged(new MarketBestPairChangedEventArgs(BestPair));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;

namespace BinanceBot.Market.Core;
namespace BinanceBot.Market.Domain;

/// <summary>
/// Order book's ask-bid pair
Expand Down
96 changes: 96 additions & 0 deletions src/BinanceBot.Market/Domain/MarketSymbol.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@

using System;

namespace BinanceBot.Market.Domain;


/// <summary>
/// Market Contract type
/// </summary>
public enum ContractType
{
/// <summary>
/// Spot market
/// </summary>
Spot,
/// <summary>
/// Futures contract
/// </summary>
Futures
}


/// <summary>
/// A market symbol representation based on a Base and Quote asset and Contract type
/// </summary>
public record MarketSymbol
{
/// <summary>
/// Initializes a new instance of the <see cref="MarketSymbol"/> class.
/// </summary>
/// <param name="baseAsset">The base asset of the trading pair (e.g., "BTC", "ETH")</param>
/// <param name="quoteAsset">The quote asset of the trading pair (e.g., "USDT", "BTC")</param>
/// <param name="contractType">The contract type (Spot or Futures)</param>
/// <exception cref="ArgumentException">Thrown when baseAsset or quoteAsset is null, empty or whitespace.</exception>
public MarketSymbol(string baseAsset, string quoteAsset, ContractType contractType)
{
if (string.IsNullOrWhiteSpace(baseAsset))
throw new ArgumentException("Base asset cannot be empty or whitespace", nameof(baseAsset));
if (string.IsNullOrWhiteSpace(quoteAsset))
throw new ArgumentException("Quote asset cannot be empty or whitespace", nameof(quoteAsset));

BaseAsset = baseAsset.Trim().ToUpperInvariant();
QuoteAsset = quoteAsset.Trim().ToUpperInvariant();
ContractType = contractType;
}

/// <summary>
/// Initializes a new instance of the <see cref="MarketSymbol"/> class from a pair string.
/// </summary>
/// <param name="pair">The trading pair in the format "BASE/QUOTE" (e.g., "BTC/USDT", "ETH/BTC")</param>
/// <param name="contractType">The contract type (Spot or Futures). Defaults to Spot.</param>
/// <exception cref="ArgumentException">Thrown when the pair is null, empty, or not in the correct format.</exception>
public MarketSymbol(string pair, ContractType contractType = ContractType.Spot)
{
if (string.IsNullOrWhiteSpace(pair))
throw new ArgumentException("Pair cannot be null or empty", nameof(pair));

var assets = pair.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (assets.Length != 2)
throw new ArgumentException("Pair must be in the format 'BASE/QUOTE' (e.g., 'BTC/USDT')", nameof(pair));

if (string.IsNullOrWhiteSpace(assets[0]))
throw new ArgumentException("Base asset cannot be empty", nameof(pair));
if (string.IsNullOrWhiteSpace(assets[1]))
throw new ArgumentException("Quote asset cannot be empty", nameof(pair));

BaseAsset = assets[0].ToUpperInvariant();
QuoteAsset = assets[1].ToUpperInvariant();
ContractType = contractType;
}

/// <summary>
/// The base asset of the symbol
/// </summary>
public string BaseAsset { get; init; }

/// <summary>
/// The quote asset of the symbol
/// </summary>
public string QuoteAsset { get; init; }

/// <summary>
/// The symbol name in Binance API format (e.g., "BTCUSDT")
/// </summary>
public string FullName => $"{BaseAsset}{QuoteAsset}";

/// <summary>
/// The contract type of the symbol (Spot or Futures)
/// </summary>
public ContractType ContractType { get; init; }

/// <summary>
/// Returns a string representation in the format "BASE/QUOTE (ContractType)" (e.g., "BTC/USDT (Spot)")
/// </summary>
public override string ToString() => $"{BaseAsset}/{QuoteAsset} ({ContractType})";
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using Binance.Net.Enums;

namespace BinanceBot.Market.Core;
namespace BinanceBot.Market.Domain;

/// <summary>
/// <see cref="MarketDepth"/> quote representing bid or ask
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using Binance.Net.Enums;
using BinanceBot.Market.Core;
using BinanceBot.Market.Domain;

namespace BinanceBot.Market.Utility;
namespace BinanceBot.Market.Extensions;

internal static class QuoteExtensions
{
Expand Down
Loading