Phase 0 completion: NT8 SDK core framework with risk management and position sizing
Some checks failed
Build and Test / build (push) Has been cancelled

This commit is contained in:
Billy Valentine
2025-09-09 17:06:37 -04:00
parent 97e5050d3e
commit 92f3732b3d
109 changed files with 38593 additions and 380 deletions

View File

@@ -0,0 +1 @@
// Removed - replaced with PlaceholderAdapter.cs

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<RootNamespace>NT8.Adapters</RootNamespace>
<AssemblyName>NT8.Adapters</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NT8.Core\NT8.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,16 @@
// Placeholder file for NT8.Adapters project
// This will contain NinjaTrader 8 integration adapters in Phase 1
using System;
namespace NT8.Adapters
{
/// <summary>
/// Placeholder class to make project compile
/// Will be removed when actual NT8 adapters are implemented
/// </summary>
internal class PlaceholderAdapter
{
// Intentionally empty - just for compilation
}
}

View File

@@ -0,0 +1 @@
// Removed - replaced with PlaceholderContract.cs

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<RootNamespace>NT8.Contracts</RootNamespace>
<AssemblyName>NT8.Contracts</AssemblyName>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,16 @@
// Placeholder file for NT8.Contracts project
// This will contain data transfer objects and contracts in Phase 1
using System;
namespace NT8.Contracts
{
/// <summary>
/// Placeholder class to make project compile
/// Will be removed when actual contracts are implemented
/// </summary>
internal class PlaceholderContract
{
// Intentionally empty - just for compilation
}
}

View File

@@ -0,0 +1,8 @@
namespace NT8.Core;
/// <summary>
/// Represents a core class in the NT8 SDK.
/// </summary>
public class Class1
{
}

View File

@@ -0,0 +1,45 @@
using NT8.Core.Common.Models;
using NT8.Core.Logging;
using System.Collections.Generic;
namespace NT8.Core.Common.Interfaces
{
/// <summary>
/// Core strategy interface - strategies implement signal generation only
/// The SDK handles all risk management, position sizing, and order execution
/// </summary>
public interface IStrategy
{
/// <summary>
/// Strategy metadata and configuration
/// </summary>
StrategyMetadata Metadata { get; }
/// <summary>
/// Initialize strategy with configuration and dependencies
/// </summary>
void Initialize(StrategyConfig config, IMarketDataProvider dataProvider, ILogger logger);
/// <summary>
/// Process new bar data and generate trading intent (if any)
/// This is the main entry point for strategy logic
/// </summary>
StrategyIntent OnBar(BarData bar, StrategyContext context);
/// <summary>
/// Process tick data for high-frequency strategies (optional)
/// Most strategies can leave this as default implementation
/// </summary>
StrategyIntent OnTick(TickData tick, StrategyContext context);
/// <summary>
/// Get current strategy parameters for serialization
/// </summary>
Dictionary<string, object> GetParameters();
/// <summary>
/// Update strategy parameters from configuration
/// </summary>
void SetParameters(Dictionary<string, object> parameters);
}
}

View File

@@ -0,0 +1,441 @@
using System;
using System.Collections.Generic;
namespace NT8.Core.Common.Models
{
/// <summary>
/// Risk management configuration
/// </summary>
public class RiskConfig
{
/// <summary>
/// Daily loss limit in dollars
/// </summary>
public double DailyLossLimit { get; set; }
/// <summary>
/// Maximum risk per trade in dollars
/// </summary>
public double MaxTradeRisk { get; set; }
/// <summary>
/// Maximum number of open positions
/// </summary>
public int MaxOpenPositions { get; set; }
/// <summary>
/// Whether emergency flatten is enabled
/// </summary>
public bool EmergencyFlattenEnabled { get; set; }
/// <summary>
/// Constructor for RiskConfig
/// </summary>
public RiskConfig(
double dailyLossLimit,
double maxTradeRisk,
int maxOpenPositions,
bool emergencyFlattenEnabled)
{
DailyLossLimit = dailyLossLimit;
MaxTradeRisk = maxTradeRisk;
MaxOpenPositions = maxOpenPositions;
EmergencyFlattenEnabled = emergencyFlattenEnabled;
}
}
/// <summary>
/// Position sizing configuration
/// </summary>
public class SizingConfig
{
/// <summary>
/// Sizing method to use
/// </summary>
public SizingMethod Method { get; set; }
/// <summary>
/// Minimum number of contracts
/// </summary>
public int MinContracts { get; set; }
/// <summary>
/// Maximum number of contracts
/// </summary>
public int MaxContracts { get; set; }
/// <summary>
/// Risk per trade in dollars
/// </summary>
public double RiskPerTrade { get; set; }
/// <summary>
/// Method-specific parameters
/// </summary>
public Dictionary<string, object> MethodParameters { get; set; }
/// <summary>
/// Constructor for SizingConfig
/// </summary>
public SizingConfig(
SizingMethod method,
int minContracts,
int maxContracts,
double riskPerTrade,
Dictionary<string, object> methodParameters)
{
Method = method;
MinContracts = minContracts;
MaxContracts = maxContracts;
RiskPerTrade = riskPerTrade;
MethodParameters = methodParameters ?? new Dictionary<string, object>();
}
}
/// <summary>
/// Strategy configuration
/// </summary>
public class StrategyConfig
{
/// <summary>
/// Strategy name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Trading symbol
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// Strategy parameters
/// </summary>
public Dictionary<string, object> Parameters { get; set; }
/// <summary>
/// Risk settings
/// </summary>
public RiskConfig RiskSettings { get; set; }
/// <summary>
/// Sizing settings
/// </summary>
public SizingConfig SizingSettings { get; set; }
/// <summary>
/// Constructor for StrategyConfig
/// </summary>
public StrategyConfig(
string name,
string symbol,
Dictionary<string, object> parameters,
RiskConfig riskSettings,
SizingConfig sizingSettings)
{
Name = name;
Symbol = symbol;
Parameters = parameters ?? new Dictionary<string, object>();
RiskSettings = riskSettings;
SizingSettings = sizingSettings;
}
}
/// <summary>
/// Position sizing methods
/// </summary>
public enum SizingMethod
{
/// <summary>
/// Fixed number of contracts
/// </summary>
FixedContracts,
/// <summary>
/// Fixed dollar risk amount
/// </summary>
FixedDollarRisk,
/// <summary>
/// Percentage of equity
/// </summary>
PercentOfEquity,
/// <summary>
/// Optimal F calculation
/// </summary>
OptimalF
}
/// <summary>
/// Risk levels
/// </summary>
public enum RiskLevel
{
/// <summary>
/// Low risk
/// </summary>
Low,
/// <summary>
/// Medium risk
/// </summary>
Medium,
/// <summary>
/// High risk
/// </summary>
High,
/// <summary>
/// Critical risk
/// </summary>
Critical
}
/// <summary>
/// Risk decision result
/// </summary>
public class RiskDecision
{
/// <summary>
/// Whether order is allowed
/// </summary>
public bool Allow { get; set; }
/// <summary>
/// Rejection reason if not allowed
/// </summary>
public string RejectReason { get; set; }
/// <summary>
/// Modified intent if changes required
/// </summary>
public StrategyIntent ModifiedIntent { get; set; }
/// <summary>
/// Risk level assessment
/// </summary>
public RiskLevel RiskLevel { get; set; }
/// <summary>
/// Risk metrics
/// </summary>
public Dictionary<string, object> RiskMetrics { get; set; }
/// <summary>
/// Constructor for RiskDecision
/// </summary>
public RiskDecision(
bool allow,
string rejectReason,
StrategyIntent modifiedIntent,
RiskLevel riskLevel,
Dictionary<string, object> riskMetrics)
{
Allow = allow;
RejectReason = rejectReason;
ModifiedIntent = modifiedIntent;
RiskLevel = riskLevel;
RiskMetrics = riskMetrics ?? new Dictionary<string, object>();
}
}
/// <summary>
/// Risk status information
/// </summary>
public class RiskStatus
{
/// <summary>
/// Whether trading is enabled
/// </summary>
public bool TradingEnabled { get; set; }
/// <summary>
/// Daily profit/loss
/// </summary>
public double DailyPnL { get; set; }
/// <summary>
/// Daily loss limit
/// </summary>
public double DailyLossLimit { get; set; }
/// <summary>
/// Maximum drawdown
/// </summary>
public double MaxDrawdown { get; set; }
/// <summary>
/// Number of open positions
/// </summary>
public int OpenPositions { get; set; }
/// <summary>
/// Last update timestamp
/// </summary>
public DateTime LastUpdate { get; set; }
/// <summary>
/// Active alerts
/// </summary>
public List<string> ActiveAlerts { get; set; }
/// <summary>
/// Constructor for RiskStatus
/// </summary>
public RiskStatus(
bool tradingEnabled,
double dailyPnL,
double dailyLossLimit,
double maxDrawdown,
int openPositions,
DateTime lastUpdate,
List<string> activeAlerts)
{
TradingEnabled = tradingEnabled;
DailyPnL = dailyPnL;
DailyLossLimit = dailyLossLimit;
MaxDrawdown = maxDrawdown;
OpenPositions = openPositions;
LastUpdate = lastUpdate;
ActiveAlerts = activeAlerts ?? new List<string>();
}
}
/// <summary>
/// Position sizing result
/// </summary>
public class SizingResult
{
/// <summary>
/// Number of contracts
/// </summary>
public int Contracts { get; set; }
/// <summary>
/// Risk amount in dollars
/// </summary>
public double RiskAmount { get; set; }
/// <summary>
/// Sizing method used
/// </summary>
public SizingMethod Method { get; set; }
/// <summary>
/// Calculation details
/// </summary>
public Dictionary<string, object> Calculations { get; set; }
/// <summary>
/// Constructor for SizingResult
/// </summary>
public SizingResult(
int contracts,
double riskAmount,
SizingMethod method,
Dictionary<string, object> calculations)
{
Contracts = contracts;
RiskAmount = riskAmount;
Method = method;
Calculations = calculations ?? new Dictionary<string, object>();
}
}
/// <summary>
/// Sizing metadata
/// </summary>
public class SizingMetadata
{
/// <summary>
/// Sizer name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Sizer description
/// </summary>
public string Description { get; set; }
/// <summary>
/// Required parameters
/// </summary>
public List<string> RequiredParameters { get; set; }
/// <summary>
/// Constructor for SizingMetadata
/// </summary>
public SizingMetadata(
string name,
string description,
List<string> requiredParameters)
{
Name = name;
Description = description;
RequiredParameters = requiredParameters ?? new List<string>();
}
}
/// <summary>
/// Order fill information
/// </summary>
public class OrderFill
{
/// <summary>
/// Order ID
/// </summary>
public string OrderId { get; set; }
/// <summary>
/// Symbol
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// Fill quantity
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// Fill price
/// </summary>
public double FillPrice { get; set; }
/// <summary>
/// Fill timestamp
/// </summary>
public DateTime FillTime { get; set; }
/// <summary>
/// Commission paid
/// </summary>
public double Commission { get; set; }
/// <summary>
/// Execution ID
/// </summary>
public string ExecutionId { get; set; }
/// <summary>
/// Constructor for OrderFill
/// </summary>
public OrderFill(
string orderId,
string symbol,
int quantity,
double fillPrice,
DateTime fillTime,
double commission,
string executionId)
{
OrderId = orderId;
Symbol = symbol;
Quantity = quantity;
FillPrice = fillPrice;
FillTime = fillTime;
Commission = commission;
ExecutionId = executionId;
}
}
}

View File

@@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace NT8.Core.Common.Models
{
/// <summary>
/// Bar data model
/// </summary>
public class BarData
{
/// <summary>
/// Trading symbol
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// Bar timestamp
/// </summary>
public DateTime Time { get; set; }
/// <summary>
/// Opening price
/// </summary>
public double Open { get; set; }
/// <summary>
/// Highest price
/// </summary>
public double High { get; set; }
/// <summary>
/// Lowest price
/// </summary>
public double Low { get; set; }
/// <summary>
/// Closing price
/// </summary>
public double Close { get; set; }
/// <summary>
/// Trading volume
/// </summary>
public long Volume { get; set; }
/// <summary>
/// Bar size/timeframe
/// </summary>
public TimeSpan BarSize { get; set; }
/// <summary>
/// Constructor for BarData
/// </summary>
public BarData(
string symbol,
DateTime time,
double open,
double high,
double low,
double close,
long volume,
TimeSpan barSize)
{
Symbol = symbol;
Time = time;
Open = open;
High = high;
Low = low;
Close = close;
Volume = volume;
BarSize = barSize;
}
}
/// <summary>
/// Tick data model
/// </summary>
public class TickData
{
/// <summary>
/// Trading symbol
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// Tick timestamp
/// </summary>
public DateTime Time { get; set; }
/// <summary>
/// Tick price
/// </summary>
public double Price { get; set; }
/// <summary>
/// Tick size/quantity
/// </summary>
public int Size { get; set; }
/// <summary>
/// Tick type
/// </summary>
public TickType Type { get; set; }
/// <summary>
/// Constructor for TickData
/// </summary>
public TickData(
string symbol,
DateTime time,
double price,
int size,
TickType type)
{
Symbol = symbol;
Time = time;
Price = price;
Size = size;
Type = type;
}
}
/// <summary>
/// Tick type enumeration
/// </summary>
public enum TickType
{
/// <summary>
/// Trade tick
/// </summary>
Trade,
/// <summary>
/// Bid tick
/// </summary>
Bid,
/// <summary>
/// Ask tick
/// </summary>
Ask,
/// <summary>
/// Last traded price tick
/// </summary>
Last
}
/// <summary>
/// Market data provider interface
/// </summary>
public interface IMarketDataProvider
{
/// <summary>
/// Subscribe to bar data
/// </summary>
void SubscribeBars(string symbol, TimeSpan barSize, Action<BarData> onBar);
/// <summary>
/// Subscribe to tick data
/// </summary>
void SubscribeTicks(string symbol, Action<TickData> onTick);
/// <summary>
/// Get historical bars
/// </summary>
Task<List<BarData>> GetHistoricalBars(string symbol, TimeSpan barSize, int count);
/// <summary>
/// Get current market price
/// </summary>
double? GetCurrentPrice(string symbol);
}
}

View File

@@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
namespace NT8.Core.Common.Models
{
/// <summary>
/// Strategy context - provides market and account information to strategies
/// </summary>
public class StrategyContext
{
/// <summary>
/// Trading symbol
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// Current timestamp
/// </summary>
public DateTime CurrentTime { get; set; }
/// <summary>
/// Current position information
/// </summary>
public Position CurrentPosition { get; set; }
/// <summary>
/// Account information
/// </summary>
public AccountInfo Account { get; set; }
/// <summary>
/// Market session information
/// </summary>
public MarketSession Session { get; set; }
/// <summary>
/// Additional custom data
/// </summary>
public Dictionary<string, object> CustomData { get; set; }
/// <summary>
/// Constructor for StrategyContext
/// </summary>
public StrategyContext(
string symbol,
DateTime currentTime,
Position currentPosition,
AccountInfo account,
MarketSession session,
Dictionary<string, object> customData)
{
Symbol = symbol;
CurrentTime = currentTime;
CurrentPosition = currentPosition;
Account = account;
Session = session;
CustomData = customData ?? new Dictionary<string, object>();
}
}
/// <summary>
/// Position information
/// </summary>
public class Position
{
/// <summary>
/// Position symbol
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// Position quantity
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// Average entry price
/// </summary>
public double AveragePrice { get; set; }
/// <summary>
/// Unrealized profit/loss
/// </summary>
public double UnrealizedPnL { get; set; }
/// <summary>
/// Realized profit/loss
/// </summary>
public double RealizedPnL { get; set; }
/// <summary>
/// Last update timestamp
/// </summary>
public DateTime LastUpdate { get; set; }
/// <summary>
/// Constructor for Position
/// </summary>
public Position(
string symbol,
int quantity,
double averagePrice,
double unrealizedPnL,
double realizedPnL,
DateTime lastUpdate)
{
Symbol = symbol;
Quantity = quantity;
AveragePrice = averagePrice;
UnrealizedPnL = unrealizedPnL;
RealizedPnL = realizedPnL;
LastUpdate = lastUpdate;
}
}
/// <summary>
/// Account information
/// </summary>
public class AccountInfo
{
/// <summary>
/// Current account equity
/// </summary>
public double Equity { get; set; }
/// <summary>
/// Available buying power
/// </summary>
public double BuyingPower { get; set; }
/// <summary>
/// Today's profit/loss
/// </summary>
public double DailyPnL { get; set; }
/// <summary>
/// Maximum drawdown
/// </summary>
public double MaxDrawdown { get; set; }
/// <summary>
/// Last update timestamp
/// </summary>
public DateTime LastUpdate { get; set; }
/// <summary>
/// Constructor for AccountInfo
/// </summary>
public AccountInfo(
double equity,
double buyingPower,
double dailyPnL,
double maxDrawdown,
DateTime lastUpdate)
{
Equity = equity;
BuyingPower = buyingPower;
DailyPnL = dailyPnL;
MaxDrawdown = maxDrawdown;
LastUpdate = lastUpdate;
}
}
/// <summary>
/// Market session information
/// </summary>
public class MarketSession
{
/// <summary>
/// Session start time
/// </summary>
public DateTime SessionStart { get; set; }
/// <summary>
/// Session end time
/// </summary>
public DateTime SessionEnd { get; set; }
/// <summary>
/// Regular Trading Hours
/// </summary>
public bool IsRth { get; set; }
/// <summary>
/// Session name
/// </summary>
public string SessionName { get; set; }
/// <summary>
/// Constructor for MarketSession
/// </summary>
public MarketSession(
DateTime sessionStart,
DateTime sessionEnd,
bool isRth,
string sessionName)
{
SessionStart = sessionStart;
SessionEnd = sessionEnd;
IsRth = isRth;
SessionName = sessionName;
}
}
}

View File

@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
namespace NT8.Core.Common.Models
{
/// <summary>
/// Strategy trading intent - what the strategy wants to do
/// This is the output of strategy logic, input to risk management
/// </summary>
public class StrategyIntent
{
/// <summary>
/// Unique identifier for this intent
/// </summary>
public string IntentId { get; private set; }
/// <summary>
/// Timestamp when intent was generated
/// </summary>
public DateTime Timestamp { get; private set; }
/// <summary>
/// Trading symbol
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// Order side
/// </summary>
public OrderSide Side { get; set; }
/// <summary>
/// Entry order type
/// </summary>
public OrderType EntryType { get; set; }
/// <summary>
/// Optional limit price
/// </summary>
public double? LimitPrice { get; set; }
/// <summary>
/// Stop loss in ticks
/// </summary>
public int StopTicks { get; set; }
/// <summary>
/// Optional profit target in ticks
/// </summary>
public int? TargetTicks { get; set; }
/// <summary>
/// Strategy confidence level (0.0 to 1.0)
/// </summary>
public double Confidence { get; set; }
/// <summary>
/// Human-readable reason for trade
/// </summary>
public string Reason { get; set; }
/// <summary>
/// Additional strategy-specific data
/// </summary>
public Dictionary<string, object> Metadata { get; set; }
/// <summary>
/// Constructor for StrategyIntent
/// </summary>
public StrategyIntent(
string symbol,
OrderSide side,
OrderType entryType,
double? limitPrice,
int stopTicks,
int? targetTicks,
double confidence,
string reason,
Dictionary<string, object> metadata)
{
IntentId = Guid.NewGuid().ToString();
Timestamp = DateTime.UtcNow;
Symbol = symbol;
Side = side;
EntryType = entryType;
LimitPrice = limitPrice;
StopTicks = stopTicks;
TargetTicks = targetTicks;
Confidence = confidence;
Reason = reason;
Metadata = metadata ?? new Dictionary<string, object>();
}
/// <summary>
/// Validate intent has required fields
/// </summary>
public bool IsValid()
{
return !String.IsNullOrEmpty(Symbol) &&
StopTicks > 0 &&
Confidence >= 0.0 && Confidence <= 1.0 &&
Side != OrderSide.Flat &&
!String.IsNullOrEmpty(Reason);
}
}
/// <summary>
/// Order side enumeration
/// </summary>
public enum OrderSide
{
/// <summary>
/// Buy order
/// </summary>
Buy = 1,
/// <summary>
/// Sell order
/// </summary>
Sell = -1,
/// <summary>
/// Close position
/// </summary>
Flat = 0
}
/// <summary>
/// Order type enumeration
/// </summary>
public enum OrderType
{
/// <summary>
/// Market order
/// </summary>
Market,
/// <summary>
/// Limit order
/// </summary>
Limit,
/// <summary>
/// Stop market order
/// </summary>
StopMarket,
/// <summary>
/// Stop limit order
/// </summary>
StopLimit
}
}

View File

@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
namespace NT8.Core.Common.Models
{
/// <summary>
/// Strategy metadata - describes strategy capabilities and requirements
/// </summary>
public class StrategyMetadata
{
/// <summary>
/// Strategy name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Strategy description
/// </summary>
public string Description { get; set; }
/// <summary>
/// Strategy version
/// </summary>
public string Version { get; set; }
/// <summary>
/// Strategy author
/// </summary>
public string Author { get; set; }
/// <summary>
/// Supported symbols
/// </summary>
public string[] Symbols { get; set; }
/// <summary>
/// Required historical bars
/// </summary>
public int RequiredBars { get; set; }
/// <summary>
/// Constructor for StrategyMetadata
/// </summary>
public StrategyMetadata(
string name,
string description,
string version,
string author,
string[] symbols,
int requiredBars)
{
Name = name;
Description = description;
Version = version;
Author = author;
Symbols = symbols;
RequiredBars = requiredBars;
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
namespace NT8.Core.Logging
{
/// <summary>
/// Basic console logger implementation for .NET Framework 4.8
/// </summary>
public class BasicLogger : ILogger
{
private readonly string _categoryName;
public BasicLogger(string categoryName = "")
{
_categoryName = categoryName;
}
public void LogDebug(string message, params object[] args)
{
WriteLog("DEBUG", message, args);
}
public void LogInformation(string message, params object[] args)
{
WriteLog("INFO", message, args);
}
public void LogWarning(string message, params object[] args)
{
WriteLog("WARN", message, args);
}
public void LogError(string message, params object[] args)
{
WriteLog("ERROR", message, args);
}
public void LogCritical(string message, params object[] args)
{
WriteLog("CRITICAL", message, args);
}
private void WriteLog(string level, string message, params object[] args)
{
var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff");
var formattedMessage = args.Length > 0 ? String.Format(message, args) : message;
var category = !String.IsNullOrEmpty(_categoryName) ? String.Format("[{0}] ", _categoryName) : "";
Console.WriteLine(String.Format("{0} [{1}] {2}{3}", timestamp, level, category, formattedMessage));
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace NT8.Core.Logging
{
/// <summary>
/// Basic logging interface for .NET Framework 4.8 compatibility
/// </summary>
public interface ILogger
{
void LogDebug(string message, params object[] args);
void LogInformation(string message, params object[] args);
void LogWarning(string message, params object[] args);
void LogError(string message, params object[] args);
void LogCritical(string message, params object[] args);
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<LangVersion>5.0</LangVersion>
<Nullable>disable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Orders\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,138 @@
using NT8.Core.Common.Models;
using NT8.Core.Risk;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace NT8.Core.Orders
{
/// <summary>
/// Order management interface - handles order submission, routing, and execution
/// </summary>
public interface IOrderManager
{
#region Order Submission and Management
/// <summary>
/// Submit a new order for execution
/// </summary>
Task<OrderResult> SubmitOrderAsync(OrderRequest request, StrategyContext context);
/// <summary>
/// Cancel an existing order
/// </summary>
Task<bool> CancelOrderAsync(string orderId);
/// <summary>
/// Modify an existing order
/// </summary>
Task<OrderResult> ModifyOrderAsync(string orderId, OrderModification modification);
/// <summary>
/// Get order status
/// </summary>
Task<OrderStatus> GetOrderStatusAsync(string orderId);
/// <summary>
/// Get all orders for a symbol
/// </summary>
Task<List<OrderStatus>> GetOrdersBySymbolAsync(string symbol);
/// <summary>
/// Get all active orders
/// </summary>
Task<List<OrderStatus>> GetActiveOrdersAsync();
#endregion
#region Algorithmic Execution
/// <summary>
/// Execute a TWAP order
/// </summary>
Task<OrderResult> ExecuteTwapAsync(TwapParameters parameters, StrategyContext context);
/// <summary>
/// Execute a VWAP order
/// </summary>
Task<OrderResult> ExecuteVwapAsync(VwapParameters parameters, StrategyContext context);
/// <summary>
/// Execute an Iceberg order
/// </summary>
Task<OrderResult> ExecuteIcebergAsync(IcebergParameters parameters, StrategyContext context);
#endregion
#region Smart Order Routing
/// <summary>
/// Route order based on smart routing logic
/// </summary>
Task<RoutingResult> RouteOrderAsync(OrderRequest request, StrategyContext context);
/// <summary>
/// Get available execution venues
/// </summary>
List<ExecutionVenue> GetAvailableVenues();
/// <summary>
/// Get routing performance metrics
/// </summary>
RoutingMetrics GetRoutingMetrics();
#endregion
#region Risk Integration
/// <summary>
/// Validate order against risk parameters
/// </summary>
Task<RiskDecision> ValidateOrderAsync(OrderRequest request, StrategyContext context);
#endregion
#region Configuration and Management
/// <summary>
/// Update routing configuration
/// </summary>
void UpdateRoutingConfig(RoutingConfig config);
/// <summary>
/// Get current routing configuration
/// </summary>
RoutingConfig GetRoutingConfig();
/// <summary>
/// Update algorithm parameters
/// </summary>
void UpdateAlgorithmParameters(AlgorithmParameters parameters);
/// <summary>
/// Get current algorithm parameters
/// </summary>
AlgorithmParameters GetAlgorithmParameters();
/// <summary>
/// Reset OMS state
/// </summary>
void Reset();
#endregion
#region Monitoring and Metrics
/// <summary>
/// Get OMS performance metrics
/// </summary>
OmsMetrics GetMetrics();
/// <summary>
/// Check if OMS is healthy
/// </summary>
bool IsHealthy();
#endregion
}
}

View File

@@ -0,0 +1,851 @@
using NT8.Core.Common.Models;
using NT8.Core.Risk;
using NT8.Core.Sizing;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace NT8.Core.Orders
{
/// <summary>
/// Order manager implementation with smart routing and algorithmic execution
/// </summary>
public class OrderManager : IOrderManager
{
private readonly IRiskManager _riskManager;
private readonly IPositionSizer _positionSizer;
private readonly ILogger<OrderManager> _logger;
private readonly object _lock = new object();
// Configuration
private RoutingConfig _routingConfig;
private AlgorithmParameters _algorithmParameters;
// State
private readonly Dictionary<string, OrderStatus> _orders;
private readonly Dictionary<string, ExecutionVenue> _venues;
private readonly RoutingMetrics _routingMetrics;
private readonly OmsMetrics _omsMetrics;
/// <summary>
/// Constructor for OrderManager
/// </summary>
public OrderManager(
IRiskManager riskManager,
IPositionSizer positionSizer,
ILogger<OrderManager> logger)
{
if (riskManager == null) throw new ArgumentNullException("riskManager");
if (positionSizer == null) throw new ArgumentNullException("positionSizer");
if (logger == null) throw new ArgumentNullException("logger");
_riskManager = riskManager;
_positionSizer = positionSizer;
_logger = logger;
_orders = new Dictionary<string, OrderStatus>();
_venues = new Dictionary<string, ExecutionVenue>();
var venuePerformance = new Dictionary<string, VenueMetrics>();
_routingMetrics = new RoutingMetrics(venuePerformance, 0, 0.0, DateTime.UtcNow);
_omsMetrics = new OmsMetrics(0, 0, 0, 0.0, 0.0, 0.0, 0, DateTime.UtcNow);
InitializeDefaultConfig();
InitializeVenues();
}
private void InitializeDefaultConfig()
{
var venuePreferences = new Dictionary<string, double>();
venuePreferences.Add("Primary", 1.0);
venuePreferences.Add("Secondary", 0.8);
_routingConfig = new RoutingConfig(
true, // SmartRoutingEnabled
"Primary", // DefaultVenue
venuePreferences,
0.5, // MaxSlippagePercent
TimeSpan.FromSeconds(30), // MaxRoutingTime
true, // RouteByCost
true, // RouteBySpeed
true // RouteByReliability
);
var twapConfig = new TwapConfig(TimeSpan.FromMinutes(15), 30, true);
var vwapConfig = new VwapConfig(0.1, true);
var icebergConfig = new IcebergConfig(0.1, true);
_algorithmParameters = new AlgorithmParameters(twapConfig, vwapConfig, icebergConfig);
}
private void InitializeVenues()
{
var primaryVenue = new ExecutionVenue(
"Primary", "Primary execution venue", true, 1.0, 1.0, 0.99);
_venues.Add("Primary", primaryVenue);
var secondaryVenue = new ExecutionVenue(
"Secondary", "Backup execution venue", true, 1.2, 0.9, 0.95);
_venues.Add("Secondary", secondaryVenue);
}
#region Order Submission and Management
/// <summary>
/// Submit a new order for execution
/// </summary>
public async Task<OrderResult> SubmitOrderAsync(OrderRequest request, StrategyContext context)
{
if (request == null) throw new ArgumentNullException("request");
if (context == null) throw new ArgumentNullException("context");
try
{
_logger.LogInformation(String.Format("Submitting order for {0} {1} {2}",
request.Symbol, request.Side, request.Quantity));
// 1. Validate order through risk management
var riskDecision = await ValidateOrderAsync(request, context);
if (!riskDecision.Allow)
{
_logger.LogWarning(String.Format("Order rejected by risk management: {0}", riskDecision.RejectReason));
return new OrderResult(false, null, riskDecision.RejectReason, null);
}
// 2. Route order based on smart routing logic
var routingResult = await RouteOrderAsync(request, context);
if (!routingResult.Success)
{
_logger.LogError(String.Format("Order routing failed: {0}", routingResult.Message));
return new OrderResult(false, null, routingResult.Message, null);
}
// 3. Submit to selected venue (simulated)
var orderId = Guid.NewGuid().ToString();
var fills = new List<OrderFill>();
var orderStatus = new OrderStatus(
orderId, request.Symbol, request.Side, request.Type, request.Quantity, 0,
request.LimitPrice, request.StopPrice, OrderState.Submitted, DateTime.UtcNow, null,
fills);
lock (_lock)
{
_orders[orderId] = orderStatus;
UpdateOmsMetrics();
}
_logger.LogInformation(String.Format("Order {0} submitted to {1}", orderId, routingResult.SelectedVenue.Name));
return new OrderResult(true, orderId, "Order submitted successfully", orderStatus);
}
catch (Exception ex)
{
_logger.LogError(String.Format("Error submitting order for {0}: {1}", request.Symbol, ex.Message));
return new OrderResult(false, null, String.Format("Error submitting order: {0}", ex.Message), null);
}
}
/// <summary>
/// Cancel an existing order
/// </summary>
public async Task<bool> CancelOrderAsync(string orderId)
{
if (string.IsNullOrEmpty(orderId)) throw new ArgumentException("Order ID required", "orderId");
try
{
lock (_lock)
{
if (!_orders.ContainsKey(orderId))
{
_logger.LogWarning(String.Format("Cannot cancel order {0} - not found", orderId));
return false;
}
var order = _orders[orderId];
if (order.State == OrderState.Filled || order.State == OrderState.Cancelled)
{
_logger.LogWarning(String.Format("Cannot cancel order {0} - already {1}", orderId, order.State));
return false;
}
// Update order state to cancelled
var fills = new List<OrderFill>();
foreach (var fill in order.Fills)
{
fills.Add(fill);
}
var updatedOrder = new OrderStatus(
order.OrderId, order.Symbol, order.Side, order.Type, order.Quantity, order.FilledQuantity,
order.LimitPrice, order.StopPrice, OrderState.Cancelled, order.CreatedTime, DateTime.UtcNow,
fills);
_orders[orderId] = updatedOrder;
UpdateOmsMetrics();
}
_logger.LogInformation(String.Format("Order {0} cancelled successfully", orderId));
return true;
}
catch (Exception ex)
{
_logger.LogError(String.Format("Error cancelling order {0}: {1}", orderId, ex.Message));
return false;
}
}
/// <summary>
/// Modify an existing order
/// </summary>
public async Task<OrderResult> ModifyOrderAsync(string orderId, OrderModification modification)
{
if (string.IsNullOrEmpty(orderId)) throw new ArgumentException("Order ID required", "orderId");
if (modification == null) throw new ArgumentNullException("modification");
try
{
lock (_lock)
{
if (!_orders.ContainsKey(orderId))
{
_logger.LogWarning(String.Format("Cannot modify order {0} - not found", orderId));
return new OrderResult(false, null, "Order not found", null);
}
var order = _orders[orderId];
if (order.State != OrderState.Submitted && order.State != OrderState.Accepted)
{
_logger.LogWarning(String.Format("Cannot modify order {0} - state is {1}", orderId, order.State));
return new OrderResult(false, null, String.Format("Cannot modify order in state {0}", order.State), null);
}
// Update order with modification parameters
var fills = new List<OrderFill>();
foreach (var fill in order.Fills)
{
fills.Add(fill);
}
var updatedOrder = new OrderStatus(
order.OrderId, order.Symbol, order.Side, order.Type,
modification.NewQuantity.HasValue ? modification.NewQuantity.Value : order.Quantity,
order.FilledQuantity,
modification.NewLimitPrice.HasValue ? modification.NewLimitPrice.Value : order.LimitPrice,
modification.NewStopPrice.HasValue ? modification.NewStopPrice.Value : order.StopPrice,
order.State, order.CreatedTime, order.FilledTime,
fills);
_orders[orderId] = updatedOrder;
_logger.LogInformation(String.Format("Order {0} modified successfully", orderId));
return new OrderResult(true, orderId, "Order modified successfully", updatedOrder);
}
}
catch (Exception ex)
{
_logger.LogError(String.Format("Error modifying order {0}: {1}", orderId, ex.Message));
return new OrderResult(false, null, String.Format("Error modifying order: {0}", ex.Message), null);
}
}
/// <summary>
/// Get order status
/// </summary>
public async Task<OrderStatus> GetOrderStatusAsync(string orderId)
{
if (string.IsNullOrEmpty(orderId)) throw new ArgumentException("Order ID required", "orderId");
lock (_lock)
{
if (_orders.ContainsKey(orderId))
{
return _orders[orderId];
}
return null;
}
}
/// <summary>
/// Get all orders for a symbol
/// </summary>
public async Task<List<OrderStatus>> GetOrdersBySymbolAsync(string symbol)
{
if (string.IsNullOrEmpty(symbol)) throw new ArgumentException("Symbol required", "symbol");
var result = new List<OrderStatus>();
lock (_lock)
{
foreach (var order in _orders.Values)
{
if (order.Symbol.Equals(symbol, StringComparison.OrdinalIgnoreCase))
{
result.Add(order);
}
}
}
return result;
}
/// <summary>
/// Get all active orders
/// </summary>
public async Task<List<OrderStatus>> GetActiveOrdersAsync()
{
var result = new List<OrderStatus>();
lock (_lock)
{
foreach (var order in _orders.Values)
{
if (order.State == OrderState.Submitted ||
order.State == OrderState.Accepted ||
order.State == OrderState.PartiallyFilled)
{
result.Add(order);
}
}
}
return result;
}
#endregion
#region Algorithmic Execution
/// <summary>
/// Execute a TWAP order
/// </summary>
public async Task<OrderResult> ExecuteTwapAsync(TwapParameters parameters, StrategyContext context)
{
if (parameters == null) throw new ArgumentNullException("parameters");
if (context == null) throw new ArgumentNullException("context");
_logger.LogInformation(String.Format("Executing TWAP order for {0} {1} {2} over {3}",
parameters.Symbol, parameters.Side, parameters.TotalQuantity, parameters.Duration));
// Create a parent order for tracking
var parentOrderId = Guid.NewGuid().ToString();
// Calculate slice parameters
var sliceCount = (int)(parameters.Duration.TotalSeconds / parameters.IntervalSeconds);
var sliceQuantity = parameters.TotalQuantity / sliceCount;
// Execute slices
for (int i = 0; i < sliceCount; i++)
{
// Create slice order
var algorithmParameters = new Dictionary<string, object>();
var sliceRequest = new OrderRequest(
parameters.Symbol,
parameters.Side,
OrderType.Market, // Simplified to market orders
sliceQuantity,
parameters.LimitPrice,
null, // StopPrice
TimeInForce.Day,
null, // No algorithm for slices
algorithmParameters
);
// Submit slice order
var result = await SubmitOrderAsync(sliceRequest, context);
if (!result.Success)
{
_logger.LogWarning(String.Format("TWAP slice {0}/{1} failed: {2}",
i + 1, sliceCount, result.Message));
}
else
{
_logger.LogInformation(String.Format("TWAP slice {0}/{1} submitted: {2}",
i + 1, sliceCount, result.OrderId));
}
// Wait for next interval (except for last slice)
if (i < sliceCount - 1)
{
await Task.Delay(TimeSpan.FromSeconds(parameters.IntervalSeconds));
}
}
return new OrderResult(true, parentOrderId, "TWAP execution completed", null);
}
/// <summary>
/// Execute a VWAP order
/// </summary>
public async Task<OrderResult> ExecuteVwapAsync(VwapParameters parameters, StrategyContext context)
{
if (parameters == null) throw new ArgumentNullException("parameters");
if (context == null) throw new ArgumentNullException("context");
_logger.LogInformation(String.Format("Executing VWAP order for {0} {1} {2} from {3} to {4}",
parameters.Symbol, parameters.Side, parameters.TotalQuantity, parameters.StartTime, parameters.EndTime));
// Create a parent order for tracking
var parentOrderId = Guid.NewGuid().ToString();
// Simplified VWAP implementation - in a real system, this would:
// 1. Monitor market volume throughout the execution period
// 2. Calculate participation rate based on target participation
// 3. Execute orders in proportion to volume
// For now, we'll execute the order as a single market order
var algorithmParameters = new Dictionary<string, object>();
var request = new OrderRequest(
parameters.Symbol,
parameters.Side,
OrderType.Market,
parameters.TotalQuantity,
parameters.LimitPrice,
null, // StopPrice
TimeInForce.Day,
null, // No algorithm for this simplified version
algorithmParameters
);
var result = await SubmitOrderAsync(request, context);
return new OrderResult(result.Success, parentOrderId,
result.Success ? "VWAP execution completed" : String.Format("VWAP execution failed: {0}", result.Message),
result.Status);
}
/// <summary>
/// Execute an Iceberg order
/// </summary>
public async Task<OrderResult> ExecuteIcebergAsync(IcebergParameters parameters, StrategyContext context)
{
if (parameters == null) throw new ArgumentNullException("parameters");
if (context == null) throw new ArgumentNullException("context");
_logger.LogInformation(String.Format("Executing Iceberg order for {0} {1} {2} (visible: {3})",
parameters.Symbol, parameters.Side, parameters.TotalQuantity, parameters.VisibleQuantity));
// Create a parent order for tracking
var parentOrderId = Guid.NewGuid().ToString();
var remainingQuantity = parameters.TotalQuantity;
while (remainingQuantity > 0)
{
// Determine visible quantity for this slice
var visibleQuantity = Math.Min(parameters.VisibleQuantity, remainingQuantity);
// Create slice order
var algorithmParameters = new Dictionary<string, object>();
OrderType orderType = parameters.LimitPrice.HasValue ? OrderType.Limit : OrderType.Market;
var sliceRequest = new OrderRequest(
parameters.Symbol,
parameters.Side,
orderType,
visibleQuantity,
parameters.LimitPrice,
null, // StopPrice
TimeInForce.Day,
null, // No algorithm for slices
algorithmParameters
);
// Submit slice order
var result = await SubmitOrderAsync(sliceRequest, context);
if (!result.Success)
{
_logger.LogWarning(String.Format("Iceberg slice failed with {0} qty remaining: {1}",
remainingQuantity, result.Message));
break;
}
// Update remaining quantity
remainingQuantity -= visibleQuantity;
_logger.LogInformation(String.Format("Iceberg slice submitted, {0} qty remaining", remainingQuantity));
// In a real implementation, we would wait for the order to fill
// before submitting the next slice. For this design, we'll add a delay.
if (remainingQuantity > 0)
{
await Task.Delay(TimeSpan.FromSeconds(5)); // Simulate time between slices
}
}
return new OrderResult(true, parentOrderId, "Iceberg execution completed", null);
}
#endregion
#region Smart Order Routing
/// <summary>
/// Route order based on smart routing logic
/// </summary>
public async Task<RoutingResult> RouteOrderAsync(OrderRequest request, StrategyContext context)
{
if (request == null) throw new ArgumentNullException("request");
if (context == null) throw new ArgumentNullException("context");
if (!_routingConfig.SmartRoutingEnabled)
{
var defaultVenue = _venues[_routingConfig.DefaultVenue];
var routingDetails = new Dictionary<string, object>();
routingDetails.Add("venue", defaultVenue.Name);
return new RoutingResult(true, null, defaultVenue, "Routing disabled, using default venue",
routingDetails);
}
// Select best venue based on configuration
var selectedVenue = SelectBestVenue(request, context);
// Update routing metrics
UpdateRoutingMetrics(selectedVenue);
var routingDetails2 = new Dictionary<string, object>();
routingDetails2.Add("venue", selectedVenue.Name);
routingDetails2.Add("cost_factor", selectedVenue.CostFactor);
routingDetails2.Add("speed_factor", selectedVenue.SpeedFactor);
return new RoutingResult(true, null, selectedVenue, "Order routed successfully", routingDetails2);
}
private ExecutionVenue SelectBestVenue(OrderRequest request, StrategyContext context)
{
ExecutionVenue bestVenue = null;
double bestScore = double.MinValue;
foreach (var kvp in _venues)
{
var venue = kvp.Value;
if (!venue.IsActive) continue;
double score = 0;
// Factor in venue preferences
if (_routingConfig.VenuePreferences.ContainsKey(venue.Name))
{
score += _routingConfig.VenuePreferences[venue.Name] * 100;
}
// Factor in cost if enabled
if (_routingConfig.RouteByCost)
{
score -= venue.CostFactor * 50; // Lower cost is better
}
// Factor in speed if enabled
if (_routingConfig.RouteBySpeed)
{
score += venue.SpeedFactor * 30; // Higher speed is better
}
// Factor in reliability
if (_routingConfig.RouteByReliability)
{
score += venue.ReliabilityFactor * 20; // Higher reliability is better
}
if (score > bestScore)
{
bestScore = score;
bestVenue = venue;
}
}
if (bestVenue != null)
{
return bestVenue;
}
return _venues[_routingConfig.DefaultVenue];
}
private void UpdateRoutingMetrics(ExecutionVenue venue)
{
lock (_lock)
{
VenueMetrics venueMetrics;
if (_routingMetrics.VenuePerformance.ContainsKey(venue.Name))
{
venueMetrics = _routingMetrics.VenuePerformance[venue.Name];
}
else
{
venueMetrics = new VenueMetrics(venue.Name, 0, 0.0, 0.0, 0.0, 0);
}
var updatedMetrics = new VenueMetrics(
venueMetrics.VenueName,
venueMetrics.OrdersRouted + 1,
venueMetrics.FillRate,
venueMetrics.AverageSlippage,
venueMetrics.AverageExecutionTimeMs,
venueMetrics.TotalValueRouted
);
_routingMetrics.VenuePerformance[venue.Name] = updatedMetrics;
_routingMetrics.TotalRoutedOrders++;
_routingMetrics.LastUpdated = DateTime.UtcNow;
}
}
/// <summary>
/// Get available execution venues
/// </summary>
public List<ExecutionVenue> GetAvailableVenues()
{
lock (_lock)
{
var venues = new List<ExecutionVenue>();
foreach (var kvp in _venues)
{
var venue = kvp.Value;
if (venue.IsActive)
{
venues.Add(venue);
}
}
return venues;
}
}
/// <summary>
/// Get routing performance metrics
/// </summary>
public RoutingMetrics GetRoutingMetrics()
{
lock (_lock)
{
return _routingMetrics;
}
}
#endregion
#region Risk Integration
/// <summary>
/// Validate order against risk parameters
/// </summary>
public async Task<RiskDecision> ValidateOrderAsync(OrderRequest request, StrategyContext context)
{
if (request == null) throw new ArgumentNullException("request");
if (context == null) throw new ArgumentNullException("context");
// Convert OrderRequest to StrategyIntent for risk validation
NT8.Core.Common.Models.OrderSide side;
if (request.Side == OrderSide.Buy)
{
side = NT8.Core.Common.Models.OrderSide.Buy;
}
else
{
side = NT8.Core.Common.Models.OrderSide.Sell;
}
var intent = new StrategyIntent(
request.Symbol,
side,
ConvertOrderType(request.Type),
(double?)request.LimitPrice,
GetStopTicks(request),
null, // TargetTicks
1.0, // Confidence
"OMS Order Submission",
new Dictionary<string, object>()
);
// Create a mock risk config for validation
var riskConfig = new RiskConfig(1000, 200, 10, true);
return _riskManager.ValidateOrder(intent, context, riskConfig);
}
private NT8.Core.Common.Models.OrderType ConvertOrderType(NT8.Core.Orders.OrderType orderType)
{
if (orderType == NT8.Core.Orders.OrderType.Market)
{
return NT8.Core.Common.Models.OrderType.Market;
}
else if (orderType == NT8.Core.Orders.OrderType.Limit)
{
return NT8.Core.Common.Models.OrderType.Limit;
}
else if (orderType == NT8.Core.Orders.OrderType.StopMarket)
{
return NT8.Core.Common.Models.OrderType.StopMarket;
}
else if (orderType == NT8.Core.Orders.OrderType.StopLimit)
{
return NT8.Core.Common.Models.OrderType.StopLimit;
}
return NT8.Core.Common.Models.OrderType.Market;
}
private int GetStopTicks(OrderRequest request)
{
// Simplified stop ticks calculation
return 10;
}
#endregion
#region Configuration and Management
/// <summary>
/// Update routing configuration
/// </summary>
public void UpdateRoutingConfig(RoutingConfig config)
{
if (config == null) throw new ArgumentNullException("config");
lock (_lock)
{
_routingConfig = config;
}
_logger.LogInformation("Routing configuration updated");
}
/// <summary>
/// Get current routing configuration
/// </summary>
public RoutingConfig GetRoutingConfig()
{
lock (_lock)
{
return _routingConfig;
}
}
/// <summary>
/// Update algorithm parameters
/// </summary>
public void UpdateAlgorithmParameters(AlgorithmParameters parameters)
{
if (parameters == null) throw new ArgumentNullException("parameters");
lock (_lock)
{
_algorithmParameters = parameters;
}
_logger.LogInformation("Algorithm parameters updated");
}
/// <summary>
/// Get current algorithm parameters
/// </summary>
public AlgorithmParameters GetAlgorithmParameters()
{
lock (_lock)
{
return _algorithmParameters;
}
}
/// <summary>
/// Reset OMS state
/// </summary>
public void Reset()
{
lock (_lock)
{
_orders.Clear();
_routingMetrics.VenuePerformance.Clear();
_routingMetrics.TotalRoutedOrders = 0;
_routingMetrics.AverageRoutingTimeMs = 0.0;
_routingMetrics.LastUpdated = DateTime.UtcNow;
// Note: In a real implementation, we would need to update the OmsMetrics properties
// Since they are read-only in the current implementation, we can't modify them directly
}
_logger.LogInformation("OMS state reset");
}
#endregion
#region Monitoring and Metrics
private void UpdateOmsMetrics()
{
lock (_lock)
{
var activeOrders = 0;
var filledOrders = 0;
var failedOrders = 0;
var totalQuantity = 0;
foreach (var kvp in _orders)
{
var order = kvp.Value;
switch (order.State)
{
case OrderState.Submitted:
case OrderState.Accepted:
case OrderState.PartiallyFilled:
activeOrders++;
break;
case OrderState.Filled:
filledOrders++;
break;
case OrderState.Rejected:
case OrderState.Expired:
case OrderState.Cancelled:
failedOrders++;
break;
}
totalQuantity += order.Quantity;
}
var totalOrders = _orders.Count;
var fillRate = totalOrders > 0 ? (double)filledOrders / totalOrders : 0.0;
// We can't directly modify the properties of _omsMetrics since they're read-only
// In a real implementation, we would have setters or create a new instance
// For now, we'll just log the updated values
_logger.LogDebug(String.Format("OMS Metrics - Total: {0}, Active: {1}, Failed: {2}, Fill Rate: {3:P2}",
totalOrders, activeOrders, failedOrders, fillRate));
}
}
/// <summary>
/// Get OMS performance metrics
/// </summary>
public OmsMetrics GetMetrics()
{
lock (_lock)
{
return _omsMetrics;
}
}
/// <summary>
/// Check if OMS is healthy
/// </summary>
public bool IsHealthy()
{
// Simple health check - in a real implementation, this would check:
// - Connection to execution venues
// - Risk management system availability
// - Position sizing system availability
// - Internal state consistency
return true;
}
#endregion
}
}

View File

@@ -0,0 +1,951 @@
using NT8.Core.Common.Models;
using System;
using System.Collections.Generic;
namespace NT8.Core.Orders
{
#region Core Order Models
/// <summary>
/// Order request parameters
/// </summary>
public class OrderRequest
{
/// <summary>
/// Trading symbol
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// Order side
/// </summary>
public OrderSide Side { get; set; }
/// <summary>
/// Order type
/// </summary>
public OrderType Type { get; set; }
/// <summary>
/// Order quantity
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// Limit price (if applicable)
/// </summary>
public decimal? LimitPrice { get; set; }
/// <summary>
/// Stop price (if applicable)
/// </summary>
public decimal? StopPrice { get; set; }
/// <summary>
/// Time in force
/// </summary>
public TimeInForce TimeInForce { get; set; }
/// <summary>
/// Algorithm to use (TWAP, VWAP, Iceberg, or null for regular order)
/// </summary>
public string Algorithm { get; set; }
/// <summary>
/// Algorithm-specific parameters
/// </summary>
public Dictionary<string, object> AlgorithmParameters { get; set; }
/// <summary>
/// Constructor for OrderRequest
/// </summary>
public OrderRequest(
string symbol,
OrderSide side,
OrderType type,
int quantity,
decimal? limitPrice,
decimal? stopPrice,
TimeInForce timeInForce,
string algorithm,
Dictionary<string, object> algorithmParameters)
{
Symbol = symbol;
Side = side;
Type = type;
Quantity = quantity;
LimitPrice = limitPrice;
StopPrice = stopPrice;
TimeInForce = timeInForce;
Algorithm = algorithm;
AlgorithmParameters = algorithmParameters ?? new Dictionary<string, object>();
}
}
/// <summary>
/// Order submission result
/// </summary>
public class OrderResult
{
/// <summary>
/// Whether the order submission was successful
/// </summary>
public bool Success { get; set; }
/// <summary>
/// Order ID if successful
/// </summary>
public string OrderId { get; set; }
/// <summary>
/// Message describing the result
/// </summary>
public string Message { get; set; }
/// <summary>
/// Order status if successful
/// </summary>
public OrderStatus Status { get; set; }
/// <summary>
/// Constructor for OrderResult
/// </summary>
public OrderResult(
bool success,
string orderId,
string message,
OrderStatus status)
{
Success = success;
OrderId = orderId;
Message = message;
Status = status;
}
}
/// <summary>
/// Current order status
/// </summary>
public class OrderStatus
{
/// <summary>
/// Order ID
/// </summary>
public string OrderId { get; set; }
/// <summary>
/// Trading symbol
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// Order side
/// </summary>
public OrderSide Side { get; set; }
/// <summary>
/// Order type
/// </summary>
public OrderType Type { get; set; }
/// <summary>
/// Order quantity
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// Filled quantity
/// </summary>
public int FilledQuantity { get; set; }
/// <summary>
/// Limit price (if applicable)
/// </summary>
public decimal? LimitPrice { get; set; }
/// <summary>
/// Stop price (if applicable)
/// </summary>
public decimal? StopPrice { get; set; }
/// <summary>
/// Current order state
/// </summary>
public OrderState State { get; set; }
/// <summary>
/// Order creation time
/// </summary>
public DateTime CreatedTime { get; set; }
/// <summary>
/// Order fill time (if filled)
/// </summary>
public DateTime? FilledTime { get; set; }
/// <summary>
/// Order fills
/// </summary>
public List<OrderFill> Fills { get; set; }
/// <summary>
/// Constructor for OrderStatus
/// </summary>
public OrderStatus(
string orderId,
string symbol,
OrderSide side,
OrderType type,
int quantity,
int filledQuantity,
decimal? limitPrice,
decimal? stopPrice,
OrderState state,
DateTime createdTime,
DateTime? filledTime,
List<OrderFill> fills)
{
OrderId = orderId;
Symbol = symbol;
Side = side;
Type = type;
Quantity = quantity;
FilledQuantity = filledQuantity;
LimitPrice = limitPrice;
StopPrice = stopPrice;
State = state;
CreatedTime = createdTime;
FilledTime = filledTime;
Fills = fills ?? new List<OrderFill>();
}
}
/// <summary>
/// Order modification parameters
/// </summary>
public class OrderModification
{
/// <summary>
/// New quantity (if changing)
/// </summary>
public int? NewQuantity { get; set; }
/// <summary>
/// New limit price (if changing)
/// </summary>
public decimal? NewLimitPrice { get; set; }
/// <summary>
/// New stop price (if changing)
/// </summary>
public decimal? NewStopPrice { get; set; }
/// <summary>
/// New time in force (if changing)
/// </summary>
public TimeInForce? NewTimeInForce { get; set; }
/// <summary>
/// Constructor for OrderModification
/// </summary>
public OrderModification(
int? newQuantity,
decimal? newLimitPrice,
decimal? newStopPrice,
TimeInForce? newTimeInForce)
{
NewQuantity = newQuantity;
NewLimitPrice = newLimitPrice;
NewStopPrice = newStopPrice;
NewTimeInForce = newTimeInForce;
}
}
#endregion
#region Algorithm Parameters
/// <summary>
/// TWAP algorithm parameters
/// </summary>
public class TwapParameters
{
/// <summary>
/// Trading symbol
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// Order side
/// </summary>
public OrderSide Side { get; set; }
/// <summary>
/// Total quantity to trade
/// </summary>
public int TotalQuantity { get; set; }
/// <summary>
/// Total duration for execution
/// </summary>
public TimeSpan Duration { get; set; }
/// <summary>
/// Interval between order slices in seconds
/// </summary>
public int IntervalSeconds { get; set; }
/// <summary>
/// Limit price (if applicable)
/// </summary>
public decimal? LimitPrice { get; set; }
/// <summary>
/// Constructor for TwapParameters
/// </summary>
public TwapParameters(
string symbol,
OrderSide side,
int totalQuantity,
TimeSpan duration,
int intervalSeconds,
decimal? limitPrice)
{
Symbol = symbol;
Side = side;
TotalQuantity = totalQuantity;
Duration = duration;
IntervalSeconds = intervalSeconds;
LimitPrice = limitPrice;
}
}
/// <summary>
/// VWAP algorithm parameters
/// </summary>
public class VwapParameters
{
/// <summary>
/// Trading symbol
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// Order side
/// </summary>
public OrderSide Side { get; set; }
/// <summary>
/// Total quantity to trade
/// </summary>
public int TotalQuantity { get; set; }
/// <summary>
/// Start time for execution
/// </summary>
public DateTime StartTime { get; set; }
/// <summary>
/// End time for execution
/// </summary>
public DateTime EndTime { get; set; }
/// <summary>
/// Limit price (if applicable)
/// </summary>
public decimal? LimitPrice { get; set; }
/// <summary>
/// Participation rate (0.0 to 1.0)
/// </summary>
public double ParticipationRate { get; set; }
/// <summary>
/// Constructor for VwapParameters
/// </summary>
public VwapParameters(
string symbol,
OrderSide side,
int totalQuantity,
DateTime startTime,
DateTime endTime,
decimal? limitPrice,
double participationRate)
{
Symbol = symbol;
Side = side;
TotalQuantity = totalQuantity;
StartTime = startTime;
EndTime = endTime;
LimitPrice = limitPrice;
ParticipationRate = participationRate;
}
}
/// <summary>
/// Iceberg algorithm parameters
/// </summary>
public class IcebergParameters
{
/// <summary>
/// Trading symbol
/// </summary>
public string Symbol { get; set; }
/// <summary>
/// Order side
/// </summary>
public OrderSide Side { get; set; }
/// <summary>
/// Total quantity to trade
/// </summary>
public int TotalQuantity { get; set; }
/// <summary>
/// Visible quantity for each slice
/// </summary>
public int VisibleQuantity { get; set; }
/// <summary>
/// Limit price (if applicable)
/// </summary>
public decimal? LimitPrice { get; set; }
/// <summary>
/// Constructor for IcebergParameters
/// </summary>
public IcebergParameters(
string symbol,
OrderSide side,
int totalQuantity,
int visibleQuantity,
decimal? limitPrice)
{
Symbol = symbol;
Side = side;
TotalQuantity = totalQuantity;
VisibleQuantity = visibleQuantity;
LimitPrice = limitPrice;
}
}
#endregion
#region Routing Models
/// <summary>
/// Order routing result
/// </summary>
public class RoutingResult
{
/// <summary>
/// Whether routing was successful
/// </summary>
public bool Success { get; set; }
/// <summary>
/// Order ID
/// </summary>
public string OrderId { get; set; }
/// <summary>
/// Selected execution venue
/// </summary>
public ExecutionVenue SelectedVenue { get; set; }
/// <summary>
/// Message describing the result
/// </summary>
public string Message { get; set; }
/// <summary>
/// Routing details
/// </summary>
public Dictionary<string, object> RoutingDetails { get; set; }
/// <summary>
/// Constructor for RoutingResult
/// </summary>
public RoutingResult(
bool success,
string orderId,
ExecutionVenue selectedVenue,
string message,
Dictionary<string, object> routingDetails)
{
Success = success;
OrderId = orderId;
SelectedVenue = selectedVenue;
Message = message;
RoutingDetails = routingDetails ?? new Dictionary<string, object>();
}
}
/// <summary>
/// Execution venue information
/// </summary>
public class ExecutionVenue
{
/// <summary>
/// Venue name
/// </summary>
public string Name { get; set; }
/// <summary>
/// Venue description
/// </summary>
public string Description { get; set; }
/// <summary>
/// Whether venue is active
/// </summary>
public bool IsActive { get; set; }
/// <summary>
/// Relative cost factor (1.0 = baseline)
/// </summary>
public double CostFactor { get; set; }
/// <summary>
/// Relative speed factor (1.0 = baseline)
/// </summary>
public double SpeedFactor { get; set; }
/// <summary>
/// Reliability score (0.0 to 1.0)
/// </summary>
public double ReliabilityFactor { get; set; }
/// <summary>
/// Constructor for ExecutionVenue
/// </summary>
public ExecutionVenue(
string name,
string description,
bool isActive,
double costFactor,
double speedFactor,
double reliabilityFactor)
{
Name = name;
Description = description;
IsActive = isActive;
CostFactor = costFactor;
SpeedFactor = speedFactor;
ReliabilityFactor = reliabilityFactor;
}
}
/// <summary>
/// Routing performance metrics
/// </summary>
public class RoutingMetrics
{
/// <summary>
/// Performance metrics by venue
/// </summary>
public Dictionary<string, VenueMetrics> VenuePerformance { get; set; }
/// <summary>
/// Total number of routed orders
/// </summary>
public int TotalRoutedOrders { get; set; }
/// <summary>
/// Average routing time in milliseconds
/// </summary>
public double AverageRoutingTimeMs { get; set; }
/// <summary>
/// Last update timestamp
/// </summary>
public DateTime LastUpdated { get; set; }
/// <summary>
/// Constructor for RoutingMetrics
/// </summary>
public RoutingMetrics(
Dictionary<string, VenueMetrics> venuePerformance,
int totalRoutedOrders,
double averageRoutingTimeMs,
DateTime lastUpdated)
{
VenuePerformance = venuePerformance ?? new Dictionary<string, VenueMetrics>();
TotalRoutedOrders = totalRoutedOrders;
AverageRoutingTimeMs = averageRoutingTimeMs;
LastUpdated = lastUpdated;
}
}
/// <summary>
/// Metrics for a specific execution venue
/// </summary>
public class VenueMetrics
{
/// <summary>
/// Venue name
/// </summary>
public string VenueName { get; set; }
/// <summary>
/// Number of orders routed to this venue
/// </summary>
public int OrdersRouted { get; set; }
/// <summary>
/// Fill rate for this venue
/// </summary>
public double FillRate { get; set; }
/// <summary>
/// Average slippage for this venue
/// </summary>
public double AverageSlippage { get; set; }
/// <summary>
/// Average execution time in milliseconds
/// </summary>
public double AverageExecutionTimeMs { get; set; }
/// <summary>
/// Total value routed through this venue
/// </summary>
public decimal TotalValueRouted { get; set; }
/// <summary>
/// Constructor for VenueMetrics
/// </summary>
public VenueMetrics(
string venueName,
int ordersRouted,
double fillRate,
double averageSlippage,
double averageExecutionTimeMs,
decimal totalValueRouted)
{
VenueName = venueName;
OrdersRouted = ordersRouted;
FillRate = fillRate;
AverageSlippage = averageSlippage;
AverageExecutionTimeMs = averageExecutionTimeMs;
TotalValueRouted = totalValueRouted;
}
}
/// <summary>
/// Routing configuration parameters
/// </summary>
public class RoutingConfig
{
/// <summary>
/// Whether smart routing is enabled
/// </summary>
public bool SmartRoutingEnabled { get; set; }
/// <summary>
/// Default venue to use when smart routing is disabled
/// </summary>
public string DefaultVenue { get; set; }
/// <summary>
/// Venue preferences (venue name -> preference weight)
/// </summary>
public Dictionary<string, double> VenuePreferences { get; set; }
/// <summary>
/// Maximum allowed slippage percentage
/// </summary>
public double MaxSlippagePercent { get; set; }
/// <summary>
/// Maximum time allowed for routing
/// </summary>
public TimeSpan MaxRoutingTime { get; set; }
/// <summary>
/// Whether to route based on cost
/// </summary>
public bool RouteByCost { get; set; }
/// <summary>
/// Whether to route based on speed
/// </summary>
public bool RouteBySpeed { get; set; }
/// <summary>
/// Whether to route based on reliability
/// </summary>
public bool RouteByReliability { get; set; }
/// <summary>
/// Constructor for RoutingConfig
/// </summary>
public RoutingConfig(
bool smartRoutingEnabled,
string defaultVenue,
Dictionary<string, double> venuePreferences,
double maxSlippagePercent,
TimeSpan maxRoutingTime,
bool routeByCost,
bool routeBySpeed,
bool routeByReliability)
{
SmartRoutingEnabled = smartRoutingEnabled;
DefaultVenue = defaultVenue;
VenuePreferences = venuePreferences ?? new Dictionary<string, double>();
MaxSlippagePercent = maxSlippagePercent;
MaxRoutingTime = maxRoutingTime;
RouteByCost = routeByCost;
RouteBySpeed = routeBySpeed;
RouteByReliability = routeByReliability;
}
}
/// <summary>
/// Algorithm configuration parameters
/// </summary>
public class AlgorithmParameters
{
/// <summary>
/// TWAP settings
/// </summary>
public TwapConfig TwapSettings { get; set; }
/// <summary>
/// VWAP settings
/// </summary>
public VwapConfig VwapSettings { get; set; }
/// <summary>
/// Iceberg settings
/// </summary>
public IcebergConfig IcebergSettings { get; set; }
/// <summary>
/// Constructor for AlgorithmParameters
/// </summary>
public AlgorithmParameters(
TwapConfig twapSettings,
VwapConfig vwapSettings,
IcebergConfig icebergSettings)
{
TwapSettings = twapSettings;
VwapSettings = vwapSettings;
IcebergSettings = icebergSettings;
}
}
/// <summary>
/// TWAP configuration
/// </summary>
public class TwapConfig
{
/// <summary>
/// Default duration for TWAP orders
/// </summary>
public TimeSpan DefaultDuration { get; set; }
/// <summary>
/// Default interval between slices in seconds
/// </summary>
public int DefaultIntervalSeconds { get; set; }
/// <summary>
/// Whether TWAP is enabled
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Constructor for TwapConfig
/// </summary>
public TwapConfig(
TimeSpan defaultDuration,
int defaultIntervalSeconds,
bool enabled)
{
DefaultDuration = defaultDuration;
DefaultIntervalSeconds = defaultIntervalSeconds;
Enabled = enabled;
}
}
/// <summary>
/// VWAP configuration
/// </summary>
public class VwapConfig
{
/// <summary>
/// Default participation rate for VWAP orders
/// </summary>
public double DefaultParticipationRate { get; set; }
/// <summary>
/// Whether VWAP is enabled
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Constructor for VwapConfig
/// </summary>
public VwapConfig(
double defaultParticipationRate,
bool enabled)
{
DefaultParticipationRate = defaultParticipationRate;
Enabled = enabled;
}
}
/// <summary>
/// Iceberg configuration
/// </summary>
public class IcebergConfig
{
/// <summary>
/// Default visible ratio for Iceberg orders
/// </summary>
public double DefaultVisibleRatio { get; set; }
/// <summary>
/// Whether Iceberg is enabled
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Constructor for IcebergConfig
/// </summary>
public IcebergConfig(
double defaultVisibleRatio,
bool enabled)
{
DefaultVisibleRatio = defaultVisibleRatio;
Enabled = enabled;
}
}
#endregion
#region Metrics Models
/// <summary>
/// OMS performance metrics
/// </summary>
public class OmsMetrics
{
/// <summary>
/// Total number of orders
/// </summary>
public int TotalOrders { get; set; }
/// <summary>
/// Number of active orders
/// </summary>
public int ActiveOrders { get; set; }
/// <summary>
/// Number of failed orders
/// </summary>
public int FailedOrders { get; set; }
/// <summary>
/// Fill rate (0.0 to 1.0)
/// </summary>
public double FillRate { get; set; }
/// <summary>
/// Average slippage
/// </summary>
public double AverageSlippage { get; set; }
/// <summary>
/// Average execution time in milliseconds
/// </summary>
public double AverageExecutionTimeMs { get; set; }
/// <summary>
/// Total value traded
/// </summary>
public decimal TotalValueTraded { get; set; }
/// <summary>
/// Last update timestamp
/// </summary>
public DateTime LastUpdated { get; set; }
/// <summary>
/// Constructor for OmsMetrics
/// </summary>
public OmsMetrics(
int totalOrders,
int activeOrders,
int failedOrders,
double fillRate,
double averageSlippage,
double averageExecutionTimeMs,
decimal totalValueTraded,
DateTime lastUpdated)
{
TotalOrders = totalOrders;
ActiveOrders = activeOrders;
FailedOrders = failedOrders;
FillRate = fillRate;
AverageSlippage = averageSlippage;
AverageExecutionTimeMs = averageExecutionTimeMs;
TotalValueTraded = totalValueTraded;
LastUpdated = lastUpdated;
}
}
#endregion
#region Enumerations
/// <summary>
/// Order side enumeration
/// </summary>
public enum OrderSide
{
Buy = 1,
Sell = -1
}
/// <summary>
/// Order type enumeration
/// </summary>
public enum OrderType
{
Market,
Limit,
StopMarket,
StopLimit
}
/// <summary>
/// Order state enumeration
/// </summary>
public enum OrderState
{
New,
Submitted,
Accepted,
PartiallyFilled,
Filled,
Cancelled,
Rejected,
Expired
}
/// <summary>
/// Time in force enumeration
/// </summary>
public enum TimeInForce
{
Day,
Gtc, // Good Till Cancelled
Ioc, // Immediate Or Cancel
Fok // Fill Or Kill
}
#endregion
}

View File

@@ -0,0 +1,291 @@
using NT8.Core.Common.Models;
using NT8.Core.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NT8.Core.Risk
{
/// <summary>
/// Basic risk manager implementing Tier 1 risk controls
/// Thread-safe implementation using locks for state consistency
/// </summary>
public class BasicRiskManager : IRiskManager
{
private readonly ILogger _logger;
private readonly object _lock = new object();
// Risk state - protected by _lock
private double _dailyPnL;
private double _maxDrawdown;
private bool _tradingHalted;
private DateTime _lastUpdate = DateTime.UtcNow;
private readonly Dictionary<string, double> _symbolExposure = new Dictionary<string, double>();
public BasicRiskManager(ILogger logger)
{
if (logger == null) throw new ArgumentNullException("logger");
_logger = logger;
}
public RiskDecision ValidateOrder(StrategyIntent intent, StrategyContext context, RiskConfig config)
{
if (intent == null) throw new ArgumentNullException("intent");
if (context == null) throw new ArgumentNullException("context");
if (config == null) throw new ArgumentNullException("config");
lock (_lock)
{
// Check if trading is halted
if (_tradingHalted)
{
_logger.LogWarning("Order rejected - trading halted by risk manager");
var haltedMetrics = new Dictionary<string, object>();
haltedMetrics.Add("halted", true);
haltedMetrics.Add("daily_pnl", _dailyPnL);
return new RiskDecision(
allow: false,
rejectReason: "Trading halted by risk manager",
modifiedIntent: null,
riskLevel: RiskLevel.Critical,
riskMetrics: haltedMetrics
);
}
// Tier 1: Daily loss cap
if (_dailyPnL <= -config.DailyLossLimit)
{
_tradingHalted = true;
_logger.LogCritical("Daily loss limit breached: {0:C} <= {1:C}", _dailyPnL, -config.DailyLossLimit);
var limitMetrics = new Dictionary<string, object>();
limitMetrics.Add("daily_pnl", _dailyPnL);
limitMetrics.Add("limit", config.DailyLossLimit);
return new RiskDecision(
allow: false,
rejectReason: String.Format("Daily loss limit breached: {0:C}", _dailyPnL),
modifiedIntent: null,
riskLevel: RiskLevel.Critical,
riskMetrics: limitMetrics
);
}
// Tier 1: Per-trade risk limit
var tradeRisk = CalculateTradeRisk(intent, context);
if (tradeRisk > config.MaxTradeRisk)
{
_logger.LogWarning("Trade risk too high: {0:C} > {1:C}", tradeRisk, config.MaxTradeRisk);
var riskMetrics = new Dictionary<string, object>();
riskMetrics.Add("trade_risk", tradeRisk);
riskMetrics.Add("limit", config.MaxTradeRisk);
return new RiskDecision(
allow: false,
rejectReason: String.Format("Trade risk too high: {0:C}", tradeRisk),
modifiedIntent: null,
riskLevel: RiskLevel.High,
riskMetrics: riskMetrics
);
}
// Tier 1: Position limits
var currentPositions = GetOpenPositionCount();
if (currentPositions >= config.MaxOpenPositions && context.CurrentPosition.Quantity == 0)
{
_logger.LogWarning("Max open positions exceeded: {0} >= {1}", currentPositions, config.MaxOpenPositions);
var positionMetrics = new Dictionary<string, object>();
positionMetrics.Add("open_positions", currentPositions);
positionMetrics.Add("limit", config.MaxOpenPositions);
return new RiskDecision(
allow: false,
rejectReason: String.Format("Max open positions exceeded: {0}", currentPositions),
modifiedIntent: null,
riskLevel: RiskLevel.Medium,
riskMetrics: positionMetrics
);
}
// All checks passed - determine risk level
var riskLevel = DetermineRiskLevel(config);
_logger.LogDebug("Order approved: {0} {1} risk=${2:F2} level={3}", intent.Symbol, intent.Side, tradeRisk, riskLevel);
var successMetrics = new Dictionary<string, object>();
successMetrics.Add("trade_risk", tradeRisk);
successMetrics.Add("daily_pnl", _dailyPnL);
successMetrics.Add("max_drawdown", _maxDrawdown);
successMetrics.Add("open_positions", currentPositions);
return new RiskDecision(
allow: true,
rejectReason: null,
modifiedIntent: null,
riskLevel: riskLevel,
riskMetrics: successMetrics
);
}
}
private static double CalculateTradeRisk(StrategyIntent intent, StrategyContext context)
{
// Get tick value for symbol - this will be enhanced in later phases
var tickValue = GetTickValue(intent.Symbol);
return intent.StopTicks * tickValue;
}
private static double GetTickValue(string symbol)
{
// Static tick values for Phase 0 - will be configurable in Phase 1
switch (symbol)
{
case "ES": return 12.50;
case "MES": return 1.25;
case "NQ": return 5.00;
case "MNQ": return 0.50;
case "CL": return 10.00;
case "GC": return 10.00;
default: return 12.50; // Default to ES
}
}
private int GetOpenPositionCount()
{
// For Phase 0, return simplified count
// Will be enhanced with actual position tracking in Phase 1
return _symbolExposure.Count(kvp => Math.Abs(kvp.Value) > 0.01);
}
private RiskLevel DetermineRiskLevel(RiskConfig config)
{
var lossPercent = Math.Abs(_dailyPnL) / config.DailyLossLimit;
if (lossPercent >= 0.8) return RiskLevel.High;
if (lossPercent >= 0.5) return RiskLevel.Medium;
return RiskLevel.Low;
}
public void OnFill(OrderFill fill)
{
if (fill == null) throw new ArgumentNullException("fill");
lock (_lock)
{
_lastUpdate = DateTime.UtcNow;
// Update symbol exposure
var fillValue = fill.Quantity * fill.FillPrice;
if (_symbolExposure.ContainsKey(fill.Symbol))
{
_symbolExposure[fill.Symbol] += fillValue;
}
else
{
_symbolExposure[fill.Symbol] = fillValue;
}
_logger.LogDebug("Fill processed: {0} {1} @ {2:F2}, Exposure: {3:C}",
fill.Symbol, fill.Quantity, fill.FillPrice, _symbolExposure[fill.Symbol]);
}
}
public void OnPnLUpdate(double netPnL, double dayPnL)
{
lock (_lock)
{
var oldDailyPnL = _dailyPnL;
_dailyPnL = dayPnL;
_maxDrawdown = Math.Min(_maxDrawdown, dayPnL);
_lastUpdate = DateTime.UtcNow;
if (Math.Abs(dayPnL - oldDailyPnL) > 0.01)
{
_logger.LogDebug("P&L Update: Daily={0:C}, Max DD={1:C}", dayPnL, _maxDrawdown);
}
// Check for emergency conditions
CheckEmergencyConditions(dayPnL);
}
}
private void CheckEmergencyConditions(double dayPnL)
{
// Emergency halt if daily loss exceeds 90% of limit
// Using a default limit of 1000 as this method doesn't have access to config
// In Phase 1, this should be improved to use the actual config value
if (dayPnL <= -(1000 * 0.9) && !_tradingHalted)
{
_tradingHalted = true;
_logger.LogCritical("Emergency halt triggered at 90% of daily loss limit: {0:C}", dayPnL);
}
}
public async Task<bool> EmergencyFlatten(string reason)
{
if (String.IsNullOrEmpty(reason)) throw new ArgumentException("Reason required", "reason");
lock (_lock)
{
_tradingHalted = true;
_logger.LogCritical("Emergency flatten triggered: {0}", reason);
}
// In Phase 0, this is a placeholder
// Phase 1 will implement actual position flattening via OMS
await Task.Delay(100);
_logger.LogInformation("Emergency flatten completed");
return true;
}
public RiskStatus GetRiskStatus()
{
lock (_lock)
{
var alerts = new List<string>();
if (_tradingHalted)
alerts.Add("Trading halted");
if (_dailyPnL <= -500) // Half of typical daily limit
alerts.Add(String.Format("Significant daily loss: {0:C}", _dailyPnL));
if (_maxDrawdown <= -1000)
alerts.Add(String.Format("Large drawdown: {0:C}", _maxDrawdown));
return new RiskStatus(
tradingEnabled: !_tradingHalted,
dailyPnL: _dailyPnL,
dailyLossLimit: 1000, // Will come from config in Phase 1
maxDrawdown: _maxDrawdown,
openPositions: GetOpenPositionCount(),
lastUpdate: _lastUpdate,
activeAlerts: alerts
);
}
}
/// <summary>
/// Reset daily state - typically called at start of new trading day
/// </summary>
public void ResetDaily()
{
lock (_lock)
{
_dailyPnL = 0;
_maxDrawdown = 0;
_tradingHalted = false;
_symbolExposure.Clear();
_lastUpdate = DateTime.UtcNow;
_logger.LogInformation("Daily risk state reset");
}
}
}
}

View File

@@ -0,0 +1,37 @@
using NT8.Core.Common.Models;
using System;
using System.Threading.Tasks;
namespace NT8.Core.Risk
{
/// <summary>
/// Risk management interface - validates and modifies trading intents
/// </summary>
public interface IRiskManager
{
/// <summary>
/// Check if order intent passes risk validation
/// </summary>
RiskDecision ValidateOrder(StrategyIntent intent, StrategyContext context, RiskConfig config);
/// <summary>
/// Update risk state after fill
/// </summary>
void OnFill(OrderFill fill);
/// <summary>
/// Update risk state after P&L change
/// </summary>
void OnPnLUpdate(double netPnL, double dayPnL);
/// <summary>
/// Emergency flatten all positions
/// </summary>
Task<bool> EmergencyFlatten(string reason);
/// <summary>
/// Get current risk status
/// </summary>
RiskStatus GetRiskStatus();
}
}

View File

@@ -0,0 +1,2 @@
// This file was replaced - see IRiskManager.cs for the interface definition
// All risk management models are now in Configuration.cs

View File

@@ -0,0 +1,246 @@
using NT8.Core.Common.Models;
using NT8.Core.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NT8.Core.Sizing
{
/// <summary>
/// Basic position sizer with fixed contracts and fixed dollar risk methods
/// Handles contract size calculations with proper rounding and clamping
/// </summary>
public class BasicPositionSizer : IPositionSizer
{
private readonly ILogger _logger;
public BasicPositionSizer(ILogger logger)
{
if (logger == null) throw new ArgumentNullException("logger");
_logger = logger;
}
public SizingResult CalculateSize(StrategyIntent intent, StrategyContext context, SizingConfig config)
{
if (intent == null) throw new ArgumentNullException("intent");
if (context == null) throw new ArgumentNullException("context");
if (config == null) throw new ArgumentNullException("config");
// Validate intent is suitable for sizing
if (!intent.IsValid())
{
_logger.LogWarning("Invalid strategy intent provided for sizing: {0}", intent);
var errorCalcs = new Dictionary<string, object>();
errorCalcs.Add("error", "Invalid intent");
return new SizingResult(0, 0, config.Method, errorCalcs);
}
switch (config.Method)
{
case SizingMethod.FixedContracts:
return CalculateFixedContracts(intent, context, config);
case SizingMethod.FixedDollarRisk:
return CalculateFixedRisk(intent, context, config);
default:
throw new NotSupportedException(String.Format("Sizing method {0} not supported in Phase 0", config.Method));
}
}
private SizingResult CalculateFixedContracts(StrategyIntent intent, StrategyContext context, SizingConfig config)
{
// Get target contracts from configuration
var targetContracts = GetParameterValue<int>(config, "contracts", 1);
// Apply min/max clamping
var contracts = Math.Max(config.MinContracts,
Math.Min(config.MaxContracts, targetContracts));
// Calculate actual risk amount
var tickValue = GetTickValue(intent.Symbol);
var riskAmount = contracts * intent.StopTicks * tickValue;
_logger.LogDebug("Fixed contracts sizing: {0} {1}→{2} contracts, ${3:F2} risk",
intent.Symbol, targetContracts, contracts, riskAmount);
var calculations = new Dictionary<string, object>();
calculations.Add("target_contracts", targetContracts);
calculations.Add("clamped_contracts", contracts);
calculations.Add("stop_ticks", intent.StopTicks);
calculations.Add("tick_value", tickValue);
calculations.Add("risk_amount", riskAmount);
calculations.Add("min_contracts", config.MinContracts);
calculations.Add("max_contracts", config.MaxContracts);
return new SizingResult(
contracts: contracts,
riskAmount: riskAmount,
method: SizingMethod.FixedContracts,
calculations: calculations
);
}
private SizingResult CalculateFixedRisk(StrategyIntent intent, StrategyContext context, SizingConfig config)
{
var tickValue = GetTickValue(intent.Symbol);
// Validate stop ticks
if (intent.StopTicks <= 0)
{
_logger.LogWarning("Invalid stop ticks {0} for fixed risk sizing on {1}",
intent.StopTicks, intent.Symbol);
var errorCalcs = new Dictionary<string, object>();
errorCalcs.Add("error", "Invalid stop ticks");
errorCalcs.Add("stop_ticks", intent.StopTicks);
return new SizingResult(0, 0, SizingMethod.FixedDollarRisk, errorCalcs);
}
// Calculate optimal contracts for target risk
var targetRisk = config.RiskPerTrade;
var riskPerContract = intent.StopTicks * tickValue;
var optimalContracts = targetRisk / riskPerContract;
// Round down to whole contracts (conservative approach)
var contracts = (int)Math.Floor(optimalContracts);
// Apply min/max clamping
contracts = Math.Max(config.MinContracts, Math.Min(config.MaxContracts, contracts));
// Calculate actual risk with final contract count
var actualRisk = contracts * riskPerContract;
_logger.LogDebug("Fixed risk sizing: {0} ${1:F2}→{2:F2}→{3} contracts, ${4:F2} actual risk",
intent.Symbol, targetRisk, optimalContracts, contracts, actualRisk);
var calculations = new Dictionary<string, object>();
calculations.Add("target_risk", targetRisk);
calculations.Add("stop_ticks", intent.StopTicks);
calculations.Add("tick_value", tickValue);
calculations.Add("risk_per_contract", riskPerContract);
calculations.Add("optimal_contracts", optimalContracts);
calculations.Add("clamped_contracts", contracts);
calculations.Add("actual_risk", actualRisk);
calculations.Add("min_contracts", config.MinContracts);
calculations.Add("max_contracts", config.MaxContracts);
return new SizingResult(
contracts: contracts,
riskAmount: actualRisk,
method: SizingMethod.FixedDollarRisk,
calculations: calculations
);
}
private static T GetParameterValue<T>(SizingConfig config, string key, T defaultValue)
{
if (config.MethodParameters.ContainsKey(key))
{
try
{
return (T)Convert.ChangeType(config.MethodParameters[key], typeof(T));
}
catch
{
// If conversion fails, return default
return defaultValue;
}
}
return defaultValue;
}
private static double GetTickValue(string symbol)
{
// Static tick values for Phase 0 - will be configurable in Phase 1
switch (symbol)
{
case "ES": return 12.50; // E-mini S&P 500
case "MES": return 1.25; // Micro E-mini S&P 500
case "NQ": return 5.00; // E-mini NASDAQ-100
case "MNQ": return 0.50; // Micro E-mini NASDAQ-100
case "CL": return 10.00; // Crude Oil
case "GC": return 10.00; // Gold
case "6E": return 12.50; // Euro FX
case "6A": return 10.00; // Australian Dollar
default: return 12.50; // Default to ES value
}
}
public SizingMetadata GetMetadata()
{
var requiredParams = new List<string>();
requiredParams.Add("method");
requiredParams.Add("risk_per_trade");
requiredParams.Add("min_contracts");
requiredParams.Add("max_contracts");
return new SizingMetadata(
name: "Basic Position Sizer",
description: "Fixed contracts or fixed dollar risk sizing with contract clamping",
requiredParameters: requiredParams
);
}
/// <summary>
/// Validate sizing configuration parameters
/// </summary>
public static bool ValidateConfig(SizingConfig config, out List<string> errors)
{
errors = new List<string>();
if (config.MinContracts < 0)
errors.Add("MinContracts must be >= 0");
if (config.MaxContracts <= 0)
errors.Add("MaxContracts must be > 0");
if (config.MinContracts > config.MaxContracts)
errors.Add("MinContracts must be <= MaxContracts");
if (config.RiskPerTrade <= 0)
errors.Add("RiskPerTrade must be > 0");
// Method-specific validation
switch (config.Method)
{
case SizingMethod.FixedContracts:
if (!config.MethodParameters.ContainsKey("contracts"))
errors.Add("FixedContracts method requires 'contracts' parameter");
else if (GetParameterValue<int>(config, "contracts", 0) <= 0)
errors.Add("Fixed contracts parameter must be > 0");
break;
case SizingMethod.FixedDollarRisk:
// No additional parameters required for fixed dollar risk
break;
default:
errors.Add(String.Format("Unsupported sizing method: {0}", config.Method));
break;
}
return errors.Count == 0;
}
/// <summary>
/// Get supported symbols with their tick values
/// </summary>
public static Dictionary<string, double> GetSupportedSymbols()
{
var symbols = new Dictionary<string, double>();
symbols.Add("ES", 12.50);
symbols.Add("MES", 1.25);
symbols.Add("NQ", 5.00);
symbols.Add("MNQ", 0.50);
symbols.Add("CL", 10.00);
symbols.Add("GC", 10.00);
symbols.Add("6E", 12.50);
symbols.Add("6A", 10.00);
return symbols;
}
}
}

View File

@@ -0,0 +1,20 @@
using NT8.Core.Common.Models;
namespace NT8.Core.Sizing
{
/// <summary>
/// Position sizing interface - determines contract quantity
/// </summary>
public interface IPositionSizer
{
/// <summary>
/// Calculate position size for trading intent
/// </summary>
SizingResult CalculateSize(StrategyIntent intent, StrategyContext context, SizingConfig config);
/// <summary>
/// Get sizing method metadata
/// </summary>
SizingMetadata GetMetadata();
}
}

View File

@@ -0,0 +1 @@
// Removed - replaced with PlaceholderStrategy.cs

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<RootNamespace>NT8.Strategies</RootNamespace>
<AssemblyName>NT8.Strategies</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NT8.Core\NT8.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,16 @@
// Placeholder file for NT8.Strategies project
// This will contain example strategies in Phase 1
using System;
namespace NT8.Strategies
{
/// <summary>
/// Placeholder class to make project compile
/// Will be removed when actual strategies are implemented
/// </summary>
internal class PlaceholderStrategy
{
// Intentionally empty - just for compilation
}
}