среда, 24 июня 2026 г.

Qwen, Trading,

Qwen, Trading, FractalCellSln\FractalCellSln.slnx

https://chat.qwen.ai/s/ce99bcbe-5569-49f6-b92d-60d502e40b8c?fev=0.2.63

D:\Projects\VS02\2606\FractalNet\DeepSeek\FractalCell\FractalCellSln\FractalCellSln.slnx

D:\Projects\VS02\2606\FractalNet\DeepSeek\FractalCell\FractalCellSln\FractalCell05\FractalCell05.csproj

D:\Projects\VS02\2606\FractalNet\DeepSeek\FractalCell\FractalCellSln\FractalCell08\FractalCell08.csproj

-------------------------------------------------------------------------------

Шаг 5: Создаем трейдинговые события

Создайте папку Trading/Events/ и файл Trading/Events/TradingEvents.cs:

using FractalCell02.Core.Interfaces;

namespace FractalCell02.Trading.Events;

public enum OrderSide { Buy, Sell }

public enum OrderType { Market, Limit }

public enum OrderStatus { Pending, Filled, Cancelled, PartiallyFilled }

/// <summary>

/// Котировка (Тик) - генерируется MarketDataProvider и рассылается всем

/// </summary>

public record QuoteEvent(

    string EventId,

    DateTime Timestamp,

    string Symbol,

    decimal Bid,

    decimal Ask,

    decimal Last,

    long Volume

) : IApplicationEvent

{

    public decimal MidPrice => (Bid + Ask) / 2m;

    public decimal Spread => Ask - Bid;

}

/// <summary>

/// Заявка на создание ордера - приходит от клиента/UI

/// </summary>

public record CreateOrderCommand(

    string EventId,

    DateTime Timestamp,

    string ClientId,

    string Symbol,

    decimal Price,

    decimal Quantity,

    OrderSide Side,

    OrderType Type

) : IApplicationEvent;

/// <summary>

/// Ордер (Заявка) - создается OrderGateway и отправляется в MatchingEngine

/// </summary>

public record OrderEvent(

    string EventId,

    DateTime Timestamp,

    string OrderId,

    string ClientId,

    string Symbol,

    decimal Price,

    decimal Quantity,

    decimal FilledQuantity,

    OrderSide Side,

    OrderType Type,

    OrderStatus Status

) : IApplicationEvent

{

    public decimal RemainingQuantity => Quantity - FilledQuantity;

    public bool IsFilled => FilledQuantity >= Quantity;

}

/// <summary>

/// Сделка (Trade) - создается MatchingEngine при исполнении ордера

/// </summary>

public record TradeEvent(

    string EventId,

    DateTime Timestamp,

    string TradeId,

    string OrderId,

    string ClientId,

    string Symbol,

    decimal Price,

    decimal Quantity,

    OrderSide Side

) : IApplicationEvent

{

    public decimal TotalCost => Price * Quantity;

}


/// <summary>

/// Обновление позиции - отправляется Portfolio после обработки Trade

/// </summary>

public record PositionUpdateEvent(

    string EventId,

    DateTime Timestamp,

    string ClientId,

    string Symbol,

    decimal Position,

    decimal AveragePrice,

    decimal RealizedPnL,

    decimal UnrealizedPnL

) : IApplicationEvent;


/// <summary>

/// Отмена ордера

/// </summary>

public record CancelOrderCommand(

    string EventId,

    DateTime Timestamp,

    string OrderId,

    string ClientId

) : IApplicationEvent;

----------

Шаг 6: Создаем трейдинговые поведения

Создайте папку Trading/Behaviors/ и следующие файлы:

Trading/Behaviors/QuoteGeneratorBehavior.cs:

using FractalCell02.Core.Behaviors;

using FractalCell02.Core.Interfaces;

using FractalCell02.Trading.Events;

using Microsoft.Extensions.Logging;


namespace FractalCell02.Trading.Behaviors;


/// <summary>

/// Генератор котировок - симулирует рыночные данные

/// </summary>

public class QuoteGeneratorBehavior : ICellBehavior

{

    private readonly string _symbol;

    private decimal _currentPrice;

    private readonly Random _random = new();

    private readonly decimal _volatility;

    private readonly int _delayMs;

    private Task? _generatorTask;


    public QuoteGeneratorBehavior(

        string symbol,

        decimal startPrice,

        decimal volatility = 0.01m,

        int delayMs = 500)

    {

        _symbol = symbol;

        _currentPrice = startPrice;

        _volatility = volatility;

        _delayMs = delayMs;

    }


    public Task OnStartAsync(ICellContext context)

    {

        context.Logger.LogInformation(

            "📈 QuoteGenerator starting for {Symbol} at price {Price}",

            _symbol, _currentPrice);


        _generatorTask = Task.Run(async () =>

        {

            while (!context.StoppingToken.IsCancellationRequested)

            {

                try

                {

                    // Random Walk с mean reversion

                    var change = (decimal)(_random.NextDouble() - 0.5) * _volatility * _currentPrice;

                    _currentPrice += change;

                    _currentPrice = Math.Max(_currentPrice, 0.01m);


                    var spread = _currentPrice * 0.001m; // 0.1% спред

                    var volume = _random.Next(100, 10000);


                    var quote = new QuoteEvent(

                        EventId: Guid.NewGuid().ToString(),

                        Timestamp: DateTime.UtcNow,

                        Symbol: _symbol,

                        Bid: _currentPrice - spread,

                        Ask: _currentPrice + spread,

                        Last: _currentPrice,

                        Volume: volume

                    );


                    // Broadcast всем заинтересованным

                    await context.ExternalBus.BroadcastAsync(quote);


                    context.Logger.LogDebug(

                        "📊 Quote: {Symbol} Bid={Bid:F2} Ask={Ask:F2} Last={Last:F2}",

                        _symbol, quote.Bid, quote.Ask, quote.Last);


                    await Task.Delay(_delayMs, context.StoppingToken);

                }

                catch (OperationCanceledException)

                {

                    break;

                }

                catch (Exception ex)

                {

                    context.Logger.LogError(ex, "Error generating quote");

                    await Task.Delay(1000, context.StoppingToken);

                }

            }

        }, context.StoppingToken);


        return Task.CompletedTask;

    }


    public Task OnMessageAsync(IApplicationEvent @event, ICellContext context)

    {

        // Генератор котировок не обрабатывает входящие сообщения

        return Task.CompletedTask;

    }


    public async Task OnStopAsync(ICellContext context)

    {

        context.Logger.LogInformation("📉 QuoteGenerator stopping for {Symbol}", _symbol);

        

        if (_generatorTask != null)

        {

            try

            {

                await _generatorTask.WaitAsync(TimeSpan.FromSeconds(5));

            }

            catch (OperationCanceledException)

            {

                // Нормально

            }

        }

    }

}

-------------------------------

Trading/Behaviors/OrderGatewayBehavior.cs:

using FractalCell02.Core.Behaviors;

using FractalCell02.Core.Interfaces;

using FractalCell02.Trading.Events;

using Microsoft.Extensions.Logging;


namespace FractalCell02.Trading.Behaviors;


/// <summary>

/// Шлюз для приема ордеров от клиентов и отправки их в MatchingEngine

/// </summary>

public class OrderGatewayBehavior : ICellBehavior

{

    private readonly string _matchingEngineCellId;


    public OrderGatewayBehavior(string matchingEngineCellId)

    {

        _matchingEngineCellId = matchingEngineCellId;

    }


    public Task OnStartAsync(ICellContext context)

    {

        context.Logger.LogInformation(

            "🚪 OrderGateway started, routing to MatchingEngine: {Target}",

            _matchingEngineCellId);


        // Подписываемся на команды создания ордеров

        context.InternalBus.Subscribe<CreateOrderCommand>(cmd => HandleCreateOrder(cmd, context));

        context.InternalBus.Subscribe<CancelOrderCommand>(cmd => HandleCancelOrder(cmd, context));


        return Task.CompletedTask;

    }


    private async Task HandleCreateOrder(CreateOrderCommand cmd, ICellContext context)

    {

        context.Logger.LogInformation(

            "📝 Creating order: {ClientId} {Side} {Quantity} {Symbol} @ {Price} ({Type})",

            cmd.ClientId, cmd.Side, cmd.Quantity, cmd.Symbol, cmd.Price, cmd.Type);


        var order = new OrderEvent(

            EventId: Guid.NewGuid().ToString(),

            Timestamp: DateTime.UtcNow,

            OrderId: Guid.NewGuid().ToString(),

            ClientId: cmd.ClientId,

            Symbol: cmd.Symbol,

            Price: cmd.Price,

            Quantity: cmd.Quantity,

            FilledQuantity: 0m,

            Side: cmd.Side,

            Type: cmd.Type,

            Status: OrderStatus.Pending

        );


        // Отправляем ордер в MatchingEngine

        await context.ExternalBus.SendToCellAsync(_matchingEngineCellId, order);


        context.Logger.LogInformation(

            "✅ Order {OrderId} sent to MatchingEngine",

            order.OrderId);

    }


    private async Task HandleCancelOrder(CancelOrderCommand cmd, ICellContext context)

    {

        context.Logger.LogInformation(

            "❌ Cancel order request: {OrderId} for {ClientId}",

            cmd.OrderId, cmd.ClientId);


        // В реальной системе здесь была бы логика отмены

        // Пока просто логируем

        await Task.CompletedTask;

    }


    public Task OnMessageAsync(IApplicationEvent @event, ICellContext context)

    {

        // Обработка других типов событий если нужно

        return Task.CompletedTask;

    }


    public Task OnStopAsync(ICellContext context)

    {

        context.Logger.LogInformation("🚪 OrderGateway stopping");

        return Task.CompletedTask;

    }

}

--------------

Trading/Behaviors/MatchingEngineBehavior.cs:

------------------

using FractalCell02.Core.Behaviors;

using FractalCell02.Core.Interfaces;

using FractalCell02.Trading.Events;

using Microsoft.Extensions.Logging;

using System.Collections.Concurrent;


namespace FractalCell02.Trading.Behaviors;


/// <summary>

/// Движок сопоставления ордеров (Matching Engine)

/// Хранит стакан заявок и исполняет ордера при пересечении с котировками

/// </summary>

public class MatchingEngineBehavior : ICellBehavior

{

    // Стакан лимитных ордеров по символам

    private readonly ConcurrentDictionary<string, List<OrderEvent>> _orderBook = new();

    

    // Последние котировки по символам

    private readonly ConcurrentDictionary<string, QuoteEvent> _latestQuotes = new();


    public Task OnStartAsync(ICellContext context)

    {

        context.Logger.LogInformation("⚙️ MatchingEngine started");


        // Подписываемся на котировки и ордера

        context.InternalBus.Subscribe<QuoteEvent>(quote => HandleQuote(quote, context));

        context.InternalBus.Subscribe<OrderEvent>(order => HandleOrder(order, context));


        return Task.CompletedTask;

    }


    private async Task HandleOrder(OrderEvent order, ICellContext context)

    {

        context.Logger.LogInformation(

            "📥 MatchingEngine received order {OrderId}: {Side} {Quantity} {Symbol} @ {Price}",

            order.OrderId, order.Side, order.Quantity, order.Symbol, order.Price);


        if (order.Type == OrderType.Market)

        {

            // Market ордер исполняется сразу по текущей котировке

            await TryExecuteMarketOrder(order, context);

        }

        else

        {

            // Limit ордер попадает в стакан

            var book = _orderBook.GetOrAdd(order.Symbol, _ => new List<OrderEvent>());

            lock (book)

            {

                book.Add(order);

            }

            context.Logger.LogInformation(

                "📚 Limit order {OrderId} added to order book for {Symbol}",

                order.OrderId, order.Symbol);

        }

    }


    private async Task HandleQuote(QuoteEvent quote, ICellContext context)

    {

        _latestQuotes[quote.Symbol] = quote;


        context.Logger.LogDebug(

            "📊 MatchingEngine received quote for {Symbol}: Bid={Bid:F2} Ask={Ask:F2}",

            quote.Symbol, quote.Bid, quote.Ask);


        // Проверяем, не исполнились ли лимитные ордера

        await TryExecuteLimitOrders(quote, context);

    }


    private async Task TryExecuteMarketOrder(OrderEvent order, ICellContext context)

    {

        if (!_latestQuotes.TryGetValue(order.Symbol, out var quote))

        {

            context.Logger.LogWarning(

                "⚠️ No quote available for {Symbol}, cannot execute market order",

                order.Symbol);

            return;

        }


        // Market Buy исполняется по Ask, Market Sell по Bid

        var executionPrice = order.Side == OrderSide.Buy ? quote.Ask : quote.Bid;


        await ExecuteTrade(order, executionPrice, order.Quantity, context);

    }


    private async Task TryExecuteLimitOrders(QuoteEvent quote, ICellContext context)

    {

        if (!_orderBook.TryGetValue(quote.Symbol, out var book))

            return;


        List<OrderEvent> filledOrders = new();


        lock (book)

        {

            foreach (var order in book.ToList())

            {

                bool shouldExecute = false;


                if (order.Side == OrderSide.Buy && quote.Ask <= order.Price)

                {

                    // Buy Limit исполнился (Ask упал до или ниже цены ордера)

                    shouldExecute = true;

                }

                else if (order.Side == OrderSide.Sell && quote.Bid >= order.Price)

                {

                    // Sell Limit исполнился (Bid поднялся до или выше цены ордера)

                    shouldExecute = true;

                }


                if (shouldExecute)

                {

                    filledOrders.Add(order);

                    book.Remove(order);

                }

            }

        }


        // Исполняем заполненные ордера

        foreach (var order in filledOrders)

        {

            context.Logger.LogInformation(

                "✅ Limit order {OrderId} executed at price {Price}",

                order.OrderId, order.Price);


            await ExecuteTrade(order, order.Price, order.Quantity, context);

        }

    }


    private async Task ExecuteTrade(

        OrderEvent order,

        decimal executionPrice,

        decimal quantity,

        ICellContext context)

    {

        var trade = new TradeEvent(

            EventId: Guid.NewGuid().ToString(),

            Timestamp: DateTime.UtcNow,

            TradeId: Guid.NewGuid().ToString(),

            OrderId: order.OrderId,

            ClientId: order.ClientId,

            Symbol: order.Symbol,

            Price: executionPrice,

            Quantity: quantity,

            Side: order.Side

        );


        context.Logger.LogInformation(

            "💹 Trade executed: {TradeId} - {ClientId} {Side} {Quantity} {Symbol} @ {Price}",

            trade.TradeId, trade.ClientId, trade.Side, trade.Quantity, trade.Symbol, trade.Price);


        // Отправляем Trade конкретному клиенту (в его Portfolio)

        await context.ExternalBus.SendToCellAsync(order.ClientId, trade);


        // Также можно отправить обновление ордера (статус Filled)

        var filledOrder = order with

        {

            EventId = Guid.NewGuid().ToString(),

            Timestamp = DateTime.UtcNow,

            FilledQuantity = quantity,

            Status = OrderStatus.Filled

        };


        await context.ExternalBus.SendToCellAsync(order.ClientId, filledOrder);

    }


    public Task OnMessageAsync(IApplicationEvent @event, ICellContext context)

    {

        return Task.CompletedTask;

    }


    public Task OnStopAsync(ICellContext context)

    {

        context.Logger.LogInformation("⚙️ MatchingEngine stopping");

        

        var totalOrders = _orderBook.Values.Sum(b => b.Count);

        context.Logger.LogInformation(

            "📊 Order book stats: {Symbols} symbols, {Orders} pending orders",

            _orderBook.Count, totalOrders);


        return Task.CompletedTask;

    }

}

--------------------------------------------

Trading/Behaviors/PortfolioBehavior.cs:

-----------------------

using FractalCell02.Core.Behaviors;

using FractalCell02.Core.Interfaces;

using FractalCell02.Trading.Events;

using Microsoft.Extensions.Logging;

using System.Collections.Concurrent;


namespace FractalCell02.Trading.Behaviors;


/// <summary>

/// Портфель клиента - управляет позициями и балансом

/// </summary>

public class PortfolioBehavior : ICellBehavior

{

    private decimal _cash;

    private readonly ConcurrentDictionary<string, Position> _positions = new();

    private readonly string _clientId;


    private record Position(

        string Symbol,

        decimal Quantity,

        decimal AveragePrice,

        decimal RealizedPnL

    )

    {

        public decimal UnrealizedPnL(decimal currentPrice) =>

            Quantity * (currentPrice - AveragePrice);

    }


    public PortfolioBehavior(string clientId, decimal initialCash = 100000m)

    {

        _clientId = clientId;

        _cash = initialCash;

    }


    public Task OnStartAsync(ICellContext context)

    {

        context.Logger.LogInformation(

            "💼 Portfolio started for {ClientId} with cash {Cash:C}",

            _clientId, _cash);


        // Подписываемся на сделки и обновления ордеров

        context.InternalBus.Subscribe<TradeEvent>(trade => HandleTrade(trade, context));

        context.InternalBus.Subscribe<OrderEvent>(order => HandleOrderUpdate(order, context));


        return Task.CompletedTask;

    }


    private async Task HandleTrade(TradeEvent trade, ICellContext context)

    {

        context.Logger.LogInformation(

            "💹 Portfolio received trade: {Side} {Quantity} {Symbol} @ {Price}",

            trade.Side, trade.Quantity, trade.Symbol, trade.Price);


        var position = _positions.GetOrAdd(trade.Symbol, 

            _ => new Position(trade.Symbol, 0m, 0m, 0m));


        var cost = trade.Price * trade.Quantity;


        if (trade.Side == OrderSide.Buy)

        {

            // Покупка: списываем кэш, увеличиваем позицию

            if (_cash < cost)

            {

                context.Logger.LogWarning(

                    "⚠️ Insufficient cash for trade. Required: {Cost:C}, Available: {Cash:C}",

                    cost, _cash);

                return;

            }


            _cash -= cost;


            lock (position)

            {

                var newQuantity = position.Quantity + trade.Quantity;

                var newAvgPrice = ((position.Quantity * position.AveragePrice) + cost) / newQuantity;

                

                _positions[trade.Symbol] = position with

                {

                    Quantity = newQuantity,

                    AveragePrice = newAvgPrice

                };

            }

        }

        else // Sell

        {

            // Продажа: добавляем кэш, уменьшаем позицию

            _cash += cost;


            lock (position)

            {

                if (position.Quantity < trade.Quantity)

                {

                    context.Logger.LogWarning(

                        "⚠️ Insufficient position for sell. Required: {Required}, Available: {Available}",

                        trade.Quantity, position.Quantity);

                    return;

                }


                var realizedPnL = (trade.Price - position.AveragePrice) * trade.Quantity;

                var newQuantity = position.Quantity - trade.Quantity;


                _positions[trade.Symbol] = position with

                {

                    Quantity = newQuantity,

                    RealizedPnL = position.RealizedPnL + realizedPnL

                };


                context.Logger.LogInformation(

                    "💰 Realized P&L: {PnL:C} on {Symbol}",

                    realizedPnL, trade.Symbol);

            }

        }


        // Логируем состояние портфеля

        LogPortfolioState(context);


        // Отправляем обновление позиции (опционально)

        var currentPosition = _positions[trade.Symbol];

        var update = new PositionUpdateEvent(

            EventId: Guid.NewGuid().ToString(),

            Timestamp: DateTime.UtcNow,

            ClientId: _clientId,

            Symbol: trade.Symbol,

            Position: currentPosition.Quantity,

            AveragePrice: currentPosition.AveragePrice,

            RealizedPnL: currentPosition.RealizedPnL,

            UnrealizedPnL: 0m // Можно рассчитать если есть текущая котировка

        );


        await context.ExternalBus.BroadcastAsync(update);

    }


    private Task HandleOrderUpdate(OrderEvent order, ICellContext context)

    {

        context.Logger.LogDebug(

            "📋 Order update: {OrderId} status {Status}",

            order.OrderId, order.Status);


        return Task.CompletedTask;

    }


    private void LogPortfolioState(ICellContext context)

    {

        context.Logger.LogInformation(

            "💼 Portfolio state - Cash: {Cash:C}, Positions: {Count}",

            _cash, _positions.Count);


        foreach (var pos in _positions.Values.Where(p => p.Quantity != 0))

        {

            context.Logger.LogInformation(

                "📊 {Symbol}: Qty={Quantity}, AvgPrice={AvgPrice:F2}, RealizedPnL={PnL:C}",

                pos.Symbol, pos.Quantity, pos.AveragePrice, pos.RealizedPnL);

        }

    }


    public Task OnMessageAsync(IApplicationEvent @event, ICellContext context)

    {

        return Task.CompletedTask;

    }


    public Task OnStopAsync(ICellContext context)

    {

        context.Logger.LogInformation("💼 Portfolio stopping for {ClientId}", _clientId);

        LogPortfolioState(context);

        return Task.CompletedTask;

    }

}

--------------------------

Шаг 7: Обновляем Worker для трейдинговой топологии

Замените содержимое Worker.cs:

using FractalCell02.Core;

using FractalCell02.Core.Configuration;

using FractalCell02.Core.Interfaces;

using FractalCell02.Trading.Behaviors;

using FractalCell02.Trading.Events;

using Microsoft.Extensions.Hosting;

using Microsoft.Extensions.Logging;


namespace FractalCell02;


public class Worker : BackgroundService

{

    private readonly ILogger<Worker> _logger;

    private readonly IFractalEventHub _hub;

    private readonly ILoggerFactory _loggerFactory;

    private readonly List<IFractalCell> _cells = new();


    public Worker(

        ILogger<Worker> logger,

        IFractalEventHub hub,

        ILoggerFactory loggerFactory)

    {

        _logger = logger;

        _hub = hub;

        _loggerFactory = loggerFactory;

    }


    protected override async Task ExecuteAsync(CancellationToken stoppingToken)

    {

        _logger.LogInformation("🚀 Worker (Orchestrator) started");


        try

        {

            await InitializeTradingSystemAsync(stoppingToken);

            _logger.LogInformation("✅ Trading system initialized. Starting simulation...");


            // Запускаем симуляцию торговли

            await SimulateTradingAsync(stoppingToken);

        }

        catch (OperationCanceledException)

        {

            _logger.LogInformation("👋 Worker stopping due to cancellation");

        }

        catch (Exception ex)

        {

            _logger.LogError(ex, "❌ Critical worker error");

        }

        finally

        {

            _logger.LogInformation("🏁 Worker (Orchestrator) finished");

        }

    }


    private async Task InitializeTradingSystemAsync(CancellationToken ct)

    {

        _logger.LogInformation("🏗️ Initializing trading system...");


        // 1. Генератор котировок (Market Data Provider)

        var quoteGenerator = await CreateCellAsync(

            "QuoteGenerator-AAPL",

            new QuoteGeneratorBehavior("AAPL", 150.00m, 0.02m, 1000),

            ct);

        _cells.Add(quoteGenerator);


        // 2. Matching Engine (Исполнитель ордеров)

        var matchingEngine = await CreateCellAsync(

            "MatchingEngine",

            new MatchingEngineBehavior(),

            ct);

        _cells.Add(matchingEngine);


        // 3. Order Gateway (Шлюз для приема ордеров)

        var orderGateway = await CreateCellAsync(

            "OrderGateway",

            new OrderGatewayBehavior("MatchingEngine"),

            ct);

        _cells.Add(orderGateway);


        // 4. Портфели клиентов

        var portfolio1 = await CreateCellAsync(

            "Client-Alice",

            new PortfolioBehavior("Client-Alice", 100000m),

            ct);

        _cells.Add(portfolio1);


        var portfolio2 = await CreateCellAsync(

            "Client-Bob",

            new PortfolioBehavior("Client-Bob", 50000m),

            ct);

        _cells.Add(portfolio2);


        _logger.LogInformation("🔍 System cells: {Count}", _cells.Count);

        foreach (var cell in _cells)

        {

            _logger.LogInformation("🔍 Cell: {CellId}", cell.CellId);

        }


        _logger.LogInformation("▶️ Starting all cells...");

        await Task.WhenAll(_cells.Select(c => c.StartAsync(ct)));

        _logger.LogInformation("✅ Trading system initialized with {Count} cells", _cells.Count);

    }


    private async Task SimulateTradingAsync(CancellationToken ct)

    {

        var random = new Random();

        var orderGateway = _cells.First(c => c.CellId == "OrderGateway");

        var clients = new[] { "Client-Alice", "Client-Bob" };

        var symbols = new[] { "AAPL" };


        while (!ct.IsCancellationRequested)

        {

            try

            {

                // Ждем немного между ордерами

                await Task.Delay(TimeSpan.FromSeconds(3), ct);


                // Генерируем случайный ордер

                var client = clients[random.Next(clients.Length)];

                var symbol = symbols[random.Next(symbols.Length)];

                var side = random.Next(2) == 0 ? OrderSide.Buy : OrderSide.Sell;

                var type = random.Next(2) == 0 ? OrderType.Market : OrderType.Limit;

                var price = 150m + (decimal)(random.NextDouble() - 0.5) * 10m;

                var quantity = random.Next(10, 100);


                var command = new CreateOrderCommand(

                    EventId: Guid.NewGuid().ToString(),

                    Timestamp: DateTime.UtcNow,

                    ClientId: client,

                    Symbol: symbol,

                    Price: price,

                    Quantity: quantity,

                    Side: side,

                    Type: type

                );


                _logger.LogInformation(

                    "📤 Sending order: {Client} {Side} {Quantity} {Symbol} @ {Price:F2} ({Type})",

                    client, side, quantity, symbol, price, type);


                await orderGateway.ExternalBus.SendToCellAsync("OrderGateway", command);

            }

            catch (OperationCanceledException)

            {

                break;

            }

            catch (Exception ex)

            {

                _logger.LogError(ex, "❌ Error in trading simulation");

                await Task.Delay(TimeSpan.FromSeconds(5), ct);

            }

        }

    }


    private async Task<IFractalCell> CreateCellAsync<TBehavior>(

        string cellId,

        TBehavior behavior,

        CancellationToken ct) where TBehavior : class, FractalCell02.Core.Behaviors.ICellBehavior

    {

        var config = new FractalCellConfiguration

        {

            CellId = cellId,

            BackgroundServiceCount = 2,

            InternalBusType = BusType.Channels,

            ExternalBusType = BusType.Channels,

            BusSettings = new BusSettings

            {

                Capacity = 1000,

                MaxParallelism = 4

            }

        };


        var cell = await FractalCellFactory.CreateAsync(config, _hub, _loggerFactory, behavior, ct);

        _logger.LogInformation("📦 Cell {CellId} created with behavior {Behavior}", 

            cellId, typeof(TBehavior).Name);

        return cell;

    }


    public override async Task StopAsync(CancellationToken cancellationToken)

    {

        _logger.LogInformation("🛑 Stopping all cells...");

        await Task.WhenAll(_cells.Select(c => c.StopAsync(cancellationToken)));

        _logger.LogInformation("✅ All cells stopped");

        await base.StopAsync(cancellationToken);

    }

}

---------------------------------

Теперь у вас полностью рабочая трейдинговая система! Запускайте проект и смотрите, как:

QuoteGenerator генерирует котировки AAPL

OrderGateway принимает ордера от симуляции

MatchingEngine сопоставляет ордера с котировками и генерирует сделки

Portfolio (Alice и Bob) получают сделки и обновляют свои позиции

Попробуйте запустить и расскажите, что получилось! 🚀

______________________________________________________________________

----------------------------------------------------------------------

ответ2

------------------------

Отлично, продолжаем! Сейчас создадим полную трейдинговую систему поверх вашей архитектуры.

Шаг 5: Трейдинговые события

Создайте файл Trading/Events/TradingEvents.cs:

--------------------------

using FractalCell02.Core.Interfaces;


namespace FractalCell02.Trading.Events;


public enum OrderSide { Buy, Sell }

public enum OrderType { Market, Limit }


/// <summary>

/// Котировка (Тик) — рассылается генератором всем через Broadcast

/// </summary>

public record QuoteEvent(

    string EventId,

    DateTime Timestamp,

    string Symbol,

    decimal Bid,

    decimal Ask,

    string SourceCellId = ""

) : IApplicationEvent;


/// <summary>

/// Заявка на торговлю — отправляется клиентом в MatchingEngine

/// </summary>

public record OrderEvent(

    string EventId,

    DateTime Timestamp,

    string OrderId,

    string ClientCellId,     // CellId портфеля-отправителя

    string Symbol,

    decimal Price,

    decimal Quantity,

    OrderSide Side,

    OrderType Type,

    string TargetCellId = ""  // CellId MatchingEngine

) : IApplicationEvent;


/// <summary>

/// Сделка — генерируется MatchingEngine при исполнении ордера

/// </summary>

public record TradeEvent(

    string EventId,

    DateTime Timestamp,

    string TradeId,

    string OrderId,

    string ClientCellId,

    string Symbol,

    decimal ExecutedPrice,

    decimal Quantity,

    OrderSide Side,

    string SourceCellId = ""

) : IApplicationEvent;


/// <summary>

/// Обновление позиции — отправляется портфелем самому себе (или в лог)

/// </summary>

public record PositionUpdateEvent(

    string EventId,

    DateTime Timestamp,

    string ClientCellId,

    string Symbol,

    decimal NewQuantity,

    decimal AveragePrice,

    decimal CashBalance

) : IApplicationEvent;

----------------------

Шаг 6: Поведение — Генератор Котировок

Создайте файл Trading/Behaviors/QuoteGeneratorBehavior.cs:

--------------------------

using FractalCell02.Core.Behaviors;

using FractalCell02.Core.Interfaces;

using FractalCell02.Trading.Events;

using Microsoft.Extensions.Logging;


namespace FractalCell02.Trading.Behaviors;


/// <summary>

/// Генератор котировок: каждые N мс генерирует новый тик с Random Walk

/// и рассылает всем через Broadcast

/// </summary>

public class QuoteGeneratorBehavior : ICellBehavior

{

    private readonly string _symbol;

    private decimal _currentMidPrice;

    private readonly decimal _spread;

    private readonly int _intervalMs;

    private readonly Random _random = new();


    public QuoteGeneratorBehavior(

        string symbol,

        decimal startPrice,

        decimal spread = 0.05m,

        int intervalMs = 500)

    {

        _symbol = symbol;

        _currentMidPrice = startPrice;

        _spread = spread;

        _intervalMs = intervalMs;

    }


    public Task OnStartAsync(ICellContext context)

    {

        context.Logger.LogInformation(

            "📈 QuoteGenerator started for {Symbol} at {Price}",

            _symbol, _currentMidPrice);


        // Запускаем фоновую генерацию тиков

        _ = Task.Run(async () =>

        {

            while (!context.StoppingToken.IsCancellationRequested)

            {

                try

                {

                    // Random Walk: движение цены ±0.50

                    var delta = (decimal)(_random.NextDouble() - 0.5) * 1.0m;

                    _currentMidPrice = Math.Max(1.0m, _currentMidPrice + delta);


                    var bid = Math.Round(_currentMidPrice - _spread, 2);

                    var ask = Math.Round(_currentMidPrice + _spread, 2);


                    var quote = new QuoteEvent(

                        EventId: $"quote-{Guid.NewGuid():N}"[..16],

                        Timestamp: DateTime.UtcNow,

                        Symbol: _symbol,

                        Bid: bid,

                        Ask: ask,

                        SourceCellId: context.CellId);


                    // Broadcast всем ячейкам

                    await context.ExternalBus.BroadcastAsync(quote);


                    context.Logger.LogDebug(

                        "📈 [{Symbol}] Bid={Bid} Ask={Ask}",

                        _symbol, bid, ask);


                    await Task.Delay(_intervalMs, context.StoppingToken);

                }

                catch (OperationCanceledException)

                {

                    break;

                }

                catch (Exception ex)

                {

                    context.Logger.LogError(ex, "Error generating quote");

                    await Task.Delay(1000, context.StoppingToken);

                }

            }

        }, context.StoppingToken);


        return Task.CompletedTask;

    }


    // Генератор не реагирует на входящие события

    public Task OnMessageAsync(IApplicationEvent @event, ICellContext context)

        => Task.CompletedTask;


    public Task OnStopAsync(ICellContext context)

    {

        context.Logger.LogInformation("📈 QuoteGenerator stopped");

        return Task.CompletedTask;

    }

}

----------------------------------

Шаг 7: Поведение — Исполнитель Ордеров (Matching Engine)

Создайте файл Trading/Behaviors/MatchingEngineBehavior.cs:

---------------------------

using FractalCell02.Core.Behaviors;

using FractalCell02.Core.Interfaces;

using FractalCell02.Trading.Events;

using Microsoft.Extensions.Logging;


namespace FractalCell02.Trading.Behaviors;


/// <summary>

/// Движок исполнения ордеров: хранит стакан заявок,

/// при каждой новой котировке проверяет пересечение,

/// генерирует TradeEvent и отправляет его портфелю клиента

/// </summary>

public class MatchingEngineBehavior : ICellBehavior

{

    private readonly string _symbol;

    private readonly List<OrderEvent> _pendingOrders = new();

    private readonly object _lock = new();


    private decimal _lastBid;

    private decimal _lastAsk;

    private int _tradeCount;


    public MatchingEngineBehavior(string symbol)

    {

        _symbol = symbol;

    }


    public Task OnStartAsync(ICellContext context)

    {

        context.Logger.LogInformation(

            "⚙️ MatchingEngine started for {Symbol}", _symbol);

        return Task.CompletedTask;

    }


    public async Task OnMessageAsync(IApplicationEvent @event, ICellContext context)

    {

        switch (@event)

        {

            case QuoteEvent quote when quote.Symbol == _symbol:

                await HandleQuote(quote, context);

                break;


            case OrderEvent order when order.Symbol == _symbol:

                await HandleOrder(order, context);

                break;

        }

    }


    private async Task HandleQuote(QuoteEvent quote, ICellContext context)

    {

        _lastBid = quote.Bid;

        _lastAsk = quote.Ask;


        context.Logger.LogDebug(

            "⚙️ [ME] New quote: Bid={Bid} Ask={Ask}, pending orders: {Count}",

            _lastBid, _lastAsk, _pendingOrders.Count);


        // Проверяем лимитные ордера на исполнение

        List<OrderEvent> filled;

        lock (_lock)

        {

            filled = _pendingOrders.Where(o => IsExecutable(o)).ToList();

            foreach (var order in filled)

            {

                _pendingOrders.Remove(order);

            }

        }


        foreach (var order in filled)

        {

            await ExecuteTrade(order, context);

        }

    }


    private async Task HandleOrder(OrderEvent order, ICellContext context)

    {

        context.Logger.LogInformation(

            "📝 [ME] New order: {Side} {Qty} {Symbol} @ {Price} ({Type}) from {Client}",

            order.Side, order.Quantity, order.Symbol, order.Price,

            order.Type, order.ClientCellId);


        if (order.Type == OrderType.Market)

        {

            // Маркет-ордер исполняется сразу по текущей цене

            var execPrice = order.Side == OrderSide.Buy ? _lastAsk : _lastBid;

            if (execPrice > 0)

            {

                await ExecuteTrade(order, context, execPrice);

            }

            else

            {

                context.Logger.LogWarning(

                    "⚠️ [ME] Cannot execute market order — no quotes yet");

            }

        }

        else

        {

            // Лимитный ордер — проверяем немедленное исполнение

            if (IsExecutable(order))

            {

                await ExecuteTrade(order, context);

            }

            else

            {

                lock (_lock)

                {

                    _pendingOrders.Add(order);

                }

                context.Logger.LogInformation(

                    "📋 [ME] Order {OrderId} added to book. Pending: {Count}",

                    order.OrderId, _pendingOrders.Count);

            }

        }

    }


    private bool IsExecutable(OrderEvent order)

    {

        if (_lastBid == 0 && _lastAsk == 0) return false;


        return order.Side switch

        {

            // Buy: исполняется если Ask <= лимитная цена

            OrderSide.Buy => _lastAsk <= order.Price,

            // Sell: исполняется если Bid >= лимитная цена

            OrderSide.Sell => _lastBid >= order.Price,

            _ => false

        };

    }


    private async Task ExecuteTrade(OrderEvent order, ICellContext context, decimal? overridePrice = null)

    {

        var execPrice = overridePrice ?? order.Price;

        _tradeCount++;


        var trade = new TradeEvent(

            EventId: $"trade-{Guid.NewGuid():N}"[..16],

            Timestamp: DateTime.UtcNow,

            TradeId: $"T{_tradeCount:D5}",

            OrderId: order.OrderId,

            ClientCellId: order.ClientCellId,

            Symbol: order.Symbol,

            ExecutedPrice: execPrice,

            Quantity: order.Quantity,

            Side: order.Side,

            SourceCellId: context.CellId);


        context.Logger.LogInformation(

            "✅ [ME] TRADE EXECUTED: {TradeId} | {Side} {Qty} {Symbol} @ {Price} → {Client}",

            trade.TradeId, trade.Side, trade.Quantity, trade.Symbol,

            trade.ExecutedPrice, trade.ClientCellId);


        // Отправляем Trade конкретному портфелю клиента

        await context.ExternalBus.SendToCellAsync(order.ClientCellId, trade);

    }


    public Task OnStopAsync(ICellContext context)

    {

        context.Logger.LogInformation(

            "⚙️ MatchingEngine stopped. Total trades: {Count}, Pending: {Pending}",

            _tradeCount, _pendingOrders.Count);

        return Task.CompletedTask;

    }

}

------------------

Шаг 8: Поведение — Портфель Клиента

Создайте файл Trading/Behaviors/PortfolioBehavior.cs:

------------------

using FractalCell02.Core.Behaviors;

using FractalCell02.Core.Interfaces;

using FractalCell02.Trading.Events;

using Microsoft.Extensions.Logging;


namespace FractalCell02.Trading.Behaviors;


/// <summary>

/// Портфель клиента: отслеживает кэш, позиции и P&L.

/// Получает TradeEvent и обновляет внутреннее состояние.

/// Может генерировать новые OrderEvent на основе стратегии.

/// </summary>

public class PortfolioBehavior : ICellBehavior

{

    private readonly string _clientName;

    private readonly string _matchingEngineCellId;


    private decimal _cash;

    private readonly Dictionary<string, Position> _positions = new();

    private int _orderSequence;


    public record Position(string Symbol, decimal Quantity, decimal AveragePrice);


    public PortfolioBehavior(

        string clientName,

        decimal initialCash,

        string matchingEngineCellId)

    {

        _clientName = clientName;

        _cash = initialCash;

        _matchingEngineCellId = matchingEngineCellId;

    }


    public Task OnStartAsync(ICellContext context)

    {

        context.Logger.LogInformation(

            "💼 [{Client}] Portfolio started. Cash: {Cash:C}",

            _clientName, _cash);

        return Task.CompletedTask;

    }


    public async Task OnMessageAsync(IApplicationEvent @event, ICellContext context)

    {

        switch (@event)

        {

            case TradeEvent trade:

                await HandleTrade(trade, context);

                break;


            case QuoteEvent quote:

                // Портфель может реагировать на котировки (автостратегия)

                await EvaluateStrategy(quote, context);

                break;

        }

    }


    private Task HandleTrade(TradeEvent trade, ICellContext context)

    {

        var cost = trade.ExecutedPrice * trade.Quantity;


        if (trade.Side == OrderSide.Buy)

        {

            if (_cash < cost)

            {

                context.Logger.LogWarning(

                    "⚠️ [{Client}] Insufficient cash for trade {TradeId}. Need {Cost:C}, have {Cash:C}",

                    _clientName, trade.TradeId, cost, _cash);

                return Task.CompletedTask;

            }


            _cash -= cost;

            UpdatePosition(trade.Symbol, trade.Quantity, trade.ExecutedPrice);

        }

        else // Sell

        {

            _cash += cost;

            UpdatePosition(trade.Symbol, -trade.Quantity, trade.ExecutedPrice);

        }


        var pos = _positions.GetValueOrDefault(trade.Symbol);


        context.Logger.LogInformation(

            "💼 [{Client}] TRADE CONFIRMED: {TradeId} | {Side} {Qty} {Symbol} @ {Price} | " +

            "Cash: {Cash:C} | Position: {PosQty} @ {PosAvg:C}",

            _clientName, trade.TradeId, trade.Side, trade.Quantity,

            trade.Symbol, trade.ExecutedPrice,

            _cash, pos?.Quantity ?? 0, pos?.AveragePrice ?? 0);


        return Task.CompletedTask;

    }


    private void UpdatePosition(string symbol, decimal quantityDelta, decimal price)

    {

        if (_positions.TryGetValue(symbol, out var existing))

        {

            var newQty = existing.Quantity + quantityDelta;

            if (newQty == 0)

            {

                _positions.Remove(symbol);

            }

            else

            {

                var newAvg = quantityDelta > 0

                    ? (existing.AveragePrice * existing.Quantity + price * quantityDelta) / newQty

                    : existing.AveragePrice;


                _positions[symbol] = new Position(symbol, newQty, Math.Round(newAvg, 4));

            }

        }

        else if (quantityDelta > 0)

        {

            _positions[symbol] = new Position(symbol, quantityDelta, price);

        }

    }


    /// <summary>

    /// Простая стратегия: если цена упала ниже порога — покупаем

    /// </summary>

    private async Task EvaluateStrategy(QuoteEvent quote, ICellContext context)

    {

        // Демо-стратегия: каждая 10-я котировка отправляет ордер

        _orderSequence++;

        if (_orderSequence % 10 != 0) return;


        // Покупаем только если есть деньги

        if (_cash < quote.Ask * 10) return;


        _orderSequence++;

        var order = new OrderEvent(

            EventId: $"order-{Guid.NewGuid():N}"[..16],

            Timestamp: DateTime.UtcNow,

            OrderId: $"O{_clientName}-{_orderSequence:D4}",

            ClientCellId: context.CellId,

            Symbol: quote.Symbol,

            Price: quote.Ask,

            Quantity: 10,

            Side: OrderSide.Buy,

            OrderType: OrderType.Limit,

            TargetCellId: _matchingEngineCellId);


        context.Logger.LogInformation(

            "📤 [{Client}] AUTO-ORDER: Buy {Qty} {Symbol} @ {Price}",

            _clientName, order.Quantity, order.Symbol, order.Price);


        await context.ExternalBus.SendToCellAsync(_matchingEngineCellId, order);

    }


    public Task OnStopAsync(ICellContext context)

    {

        context.Logger.LogInformation(

            "💼 [{Client}] Portfolio stopped. Cash: {Cash:C}, Positions: {Count}",

            _clientName, _cash, _positions.Count);


        foreach (var (symbol, pos) in _positions)

        {

            context.Logger.LogInformation(

                "📊 [{Client}] Final position: {Qty} {Symbol} @ {Avg:C}",

                _clientName, pos.Quantity, symbol, pos.AveragePrice);

        }


        return Task.CompletedTask;

    }

}

------------------------

Шаг 9: Обновляем Worker.cs — Трейдинговый Оркестратор

using FractalCell02.Core;

using FractalCell02.Core.Behaviors;

using FractalCell02.Core.Configuration;

using FractalCell02.Core.Interfaces;

using FractalCell02.Trading.Behaviors;

using FractalCell02.Trading.Events;

using Microsoft.Extensions.Hosting;

using Microsoft.Extensions.Logging;


namespace FractalCell02;


public class Worker : BackgroundService

{

    private readonly ILogger<Worker> _logger;

    private readonly IFractalEventHub _hub;

    private readonly ILoggerFactory _loggerFactory;

    private readonly List<IFractalCell> _cells = new();


    public Worker(

        ILogger<Worker> logger,

        IFractalEventHub hub,

        ILoggerFactory loggerFactory)

    {

        _logger = logger;

        _hub = hub;

        _loggerFactory = loggerFactory;

    }


    protected override async Task ExecuteAsync(CancellationToken stoppingToken)

    {

        _logger.LogInformation("🚀 Trading Orchestrator started");


        try

        {

            await InitializeTradingSystemAsync(stoppingToken);


            _logger.LogInformation("✅ Trading system initialized. Starting simulation...");


            // Даем системе время на запуск генератора котировок

            await Task.Delay(2000, stoppingToken);


            // Запускаем цикл отправки ордеров

            await RunOrderSimulationAsync(stoppingToken);

        }

        catch (OperationCanceledException)

        {

            _logger.LogInformation("👋 Orchestrator stopping");

        }

        catch (Exception ex)

        {

            _logger.LogError(ex, "❌ Critical orchestrator error");

        }

        finally

        {

            _logger.LogInformation("🏁 Orchestrator finished");

        }

    }


    private async Task InitializeTradingSystemAsync(CancellationToken ct)

    {

        _logger.LogInformation("🏗️ Building trading topology...");


        // ═══════════════════════════════════════════════

        // 1. Генератор котировок (Broadcasts QuoteEvents)

        // ═══════════════════════════════════════════════

        var quoteCell = await CreateCellAsync(

            cellId: "QuoteGen-AAPL",

            behavior: new QuoteGeneratorBehavior(

                symbol: "AAPL",

                startPrice: 185.00m,

                spread: 0.05m,

                intervalMs: 500),

            ct);


        // ═══════════════════════════════════════════════

        // 2. Matching Engine (Исполнитель ордеров)

        // ═══════════════════════════════════════════════

        var matchingEngineCell = await CreateCellAsync(

            cellId: "MatchingEngine-AAPL",

            behavior: new MatchingEngineBehavior(symbol: "AAPL"),

            ct);


        // ═══════════════════════════════════════════════

        // 3. Портфели клиентов

        // ═══════════════════════════════════════════════

        var portfolioA = await CreateCellAsync(

            cellId: "Portfolio-ClientA",

            behavior: new PortfolioBehavior(

                clientName: "Alice",

                initialCash: 100_000m,

                matchingEngineCellId: "MatchingEngine-AAPL"),

            ct);


        var portfolioB = await CreateCellAsync(

            cellId: "Portfolio-ClientB",

            behavior: new PortfolioBehavior(

                clientName: "Bob",

                initialCash: 250_000m,

                matchingEngineCellId: "MatchingEngine-AAPL"),

            ct);


        // Запускаем все ячейки

        _logger.LogInformation("▶️ Starting all trading cells...");

        foreach (var cell in _cells)

        {

            await cell.StartAsync(ct);

        }


        _logger.LogInformation(

            "✅ Trading topology: {Cells}",

            string.Join(" ↔ ", _cells.Select(c => c.CellId)));

    }


    /// <summary>

    /// Симуляция: оркестратор периодически отправляет ордера

    /// от имени клиентов в MatchingEngine

    /// </summary>

    private async Task RunOrderSimulationAsync(CancellationToken ct)

    {

        var random = new Random(42); // Детерминированный для воспроизводимости

        var orderNum = 0;


        while (!ct.IsCancellationRequested)

        {

            try

            {

                await Task.Delay(TimeSpan.FromSeconds(3), ct);


                orderNum++;

                var isBuy = random.Next(2) == 0;

                var clientCellId = orderNum % 2 == 0

                    ? "Portfolio-ClientA"

                    : "Portfolio-ClientB";

                var clientName = orderNum % 2 == 0 ? "Alice" : "Bob";


                // Лимитная цена: ~185 ± 2

                var limitPrice = Math.Round(183m + (decimal)random.NextDouble() * 4m, 2);

                var quantity = (random.Next(5) + 1) * 10; // 10, 20, 30, 40, 50


                var order = new OrderEvent(

                    EventId: $"order-{Guid.NewGuid():N}"[..16],

                    Timestamp: DateTime.UtcNow,

                    OrderId: $"SIM-{orderNum:D4}",

                    ClientCellId: clientCellId,

                    Symbol: "AAPL",

                    Price: limitPrice,

                    Quantity: quantity,

                    Side: isBuy ? OrderSide.Buy : OrderSide.Sell,

                    OrderType: OrderType.Limit,

                    TargetCellId: "MatchingEngine-AAPL");


                _logger.LogInformation(

                    "📤 [ORCH] {Client} places order: {Side} {Qty} AAPL @ {Price} (Limit)",

                    clientName, order.Side, order.Quantity, order.Price);


                // Отправляем ордер в MatchingEngine

                await _hub.PublishAsync("MatchingEngine-AAPL", order);

            }

            catch (OperationCanceledException)

            {

                break;

            }

            catch (Exception ex)

            {

                _logger.LogError(ex, "Error in order simulation");

            }

        }

    }


    private async Task<IFractalCell> CreateCellAsync(

        string cellId,

        ICellBehavior behavior,

        CancellationToken ct)

    {

        var config = new FractalCellConfiguration

        {

            CellId = cellId,

            BackgroundServiceCount = 1,

            InternalBusType = BusType.Channels,

            ExternalBusType = BusType.Channels,

            BusSettings = new BusSettings

            {

                Capacity = 5000,

                MaxParallelism = 2

            }

        };


        var cell = await FractalCellFactory.CreateAsync(

            config, _hub, _loggerFactory, behavior, ct);


        _cells.Add(cell);


        _logger.LogInformation("📦 Cell {CellId} created ({Behavior})",

            cellId, behavior.GetType().Name);


        return cell;

    }


    public override async Task StopAsync(CancellationToken cancellationToken)

    {

        _logger.LogInformation("🛑 Stopping all trading cells...");


        foreach (var cell in _cells)

        {

            await cell.StopAsync(cancellationToken);

        }


        _logger.LogInformation("✅ All trading cells stopped");

        await base.StopAsync(cancellationToken);

    }

}

----------------

Новые файлы (создать):

--------------

├── Trading/

│   ├── Events/

│   │   └── TradingEvents.cs         ← Шаг 5

│   └── Behaviors/

│       ├── QuoteGeneratorBehavior.cs ← Шаг 6

│       ├── MatchingEngineBehavior.cs ← Шаг 7

│       └── PortfolioBehavior.cs      ← Шаг 8

├── Core/

│   └── Behaviors/

│       └── ICellBehavior.cs          ← из предыдущего сообщения (Шаг 1)

-------------------

Обновлённые файлы (заменить):

├── Core/Templates/FractalCellTemplate.cs     ← из предыдущего сообщения (Шаг 2)

├── Core/FractalCellFactory.cs                ← из предыдущего сообщения (Шаг 4)

├── Implementations/Channels/ChannelFractalCell.cs ← из предыдущего сообщения (Шаг 3)

├── Worker.cs                                 ← Шаг 9 (выше)

------------------------------------------------

Неизменные файлы (оставить как есть):

-----------------------------------------------


├── Core/Interfaces/*                         ← без изменений

├── Core/Configuration/*                      ← без изменений

├── Core/Templates/ExternalBusTemplate.cs     ← без изменений

├── Core/Templates/InternalBusTemplate.cs     ← без изменений

├── Implementations/Channels/ChannelExternalBus.cs  ← без изменений

├── Implementations/Channels/ChannelInternalBus.cs  ← без изменений

├── Implementations/FractalEventHub.cs        ← без изменений

├── Model/FractalEvent.cs                     ← без изменений

├── Program.cs                                ← без изменений

---------------

T=0s    🚀 Система запускается

        📈 QuoteGen начинает генерировать AAPL @ ~185.00


T=0.5s  📈 [AAPL] Bid=184.95 Ask=185.05  → Broadcast → все получают

T=1.0s  📈 [AAPL] Bid=185.10 Ask=185.20  → Broadcast

T=1.5s  📈 [AAPL] Bid=184.85 Ask=184.95  → Broadcast


T=2.0s  📤 [ORCH] Alice places order: Buy 30 AAPL @ 184.90 (Limit)

        ⚙️ [ME] Order SIM-0001 added to book. Pending: 1

            (Ask=184.95 > 184.90 → не исполняется, ждёт)


T=2.5s  📈 [AAPL] Bid=184.70 Ask=184.80  → Broadcast

        ⚙️ [ME] Ask=184.80 ≤ 184.90 → EXECUTED!

        ✅ [ME] TRADE: T00001 | Buy 30 AAPL @ 184.90 → Portfolio-ClientA

        💼 [Alice] TRADE CONFIRMED: T00001 | Cash: 94,453.00 | Position: 30 @ 184.90


T=3.0s  📤 [ORCH] Bob places order: Sell 20 AAPL @ 185.50 (Limit)

        ⚙️ [ME] Order added to book (Bid < 185.50 → ждёт)


T=4.5s  📈 [AAPL] Bid=185.60 Ask=185.70

        ⚙️ [ME] Bid=185.60 ≥ 185.50 → EXECUTED!

        ✅ [ME] TRADE: T00002 | Sell 20 AAPL @ 185.50 → Portfolio-ClientB

        💼 [Bob] TRADE CONFIRMED: T00002 | Cash: 253,710.00


...и так далее, бесконечно живой рынок 🏛️

--------------


Комментариев нет:

Отправить комментарий