Phase 0 completion: NT8 SDK core framework with risk management and position sizing
Some checks failed
Build and Test / build (push) Has been cancelled
Some checks failed
Build and Test / build (push) Has been cancelled
This commit is contained in:
1
src/NT8.Adapters/Class1.cs.bak
Normal file
1
src/NT8.Adapters/Class1.cs.bak
Normal file
@@ -0,0 +1 @@
|
||||
// Removed - replaced with PlaceholderAdapter.cs
|
||||
13
src/NT8.Adapters/NT8.Adapters.csproj
Normal file
13
src/NT8.Adapters/NT8.Adapters.csproj
Normal 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>
|
||||
16
src/NT8.Adapters/PlaceholderAdapter.cs
Normal file
16
src/NT8.Adapters/PlaceholderAdapter.cs
Normal 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
|
||||
}
|
||||
}
|
||||
1
src/NT8.Contracts/Class1.cs
Normal file
1
src/NT8.Contracts/Class1.cs
Normal file
@@ -0,0 +1 @@
|
||||
// Removed - replaced with PlaceholderContract.cs
|
||||
9
src/NT8.Contracts/NT8.Contracts.csproj
Normal file
9
src/NT8.Contracts/NT8.Contracts.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<RootNamespace>NT8.Contracts</RootNamespace>
|
||||
<AssemblyName>NT8.Contracts</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
16
src/NT8.Contracts/PlaceholderContract.cs
Normal file
16
src/NT8.Contracts/PlaceholderContract.cs
Normal 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
|
||||
}
|
||||
}
|
||||
8
src/NT8.Core/Class1.cs.bak
Normal file
8
src/NT8.Core/Class1.cs.bak
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace NT8.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a core class in the NT8 SDK.
|
||||
/// </summary>
|
||||
public class Class1
|
||||
{
|
||||
}
|
||||
45
src/NT8.Core/Common/Interfaces/IStrategy.cs
Normal file
45
src/NT8.Core/Common/Interfaces/IStrategy.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
441
src/NT8.Core/Common/Models/Configuration.cs
Normal file
441
src/NT8.Core/Common/Models/Configuration.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
175
src/NT8.Core/Common/Models/MarketData.cs
Normal file
175
src/NT8.Core/Common/Models/MarketData.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
204
src/NT8.Core/Common/Models/StrategyContext.cs
Normal file
204
src/NT8.Core/Common/Models/StrategyContext.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
153
src/NT8.Core/Common/Models/StrategyIntent.cs
Normal file
153
src/NT8.Core/Common/Models/StrategyIntent.cs
Normal 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
|
||||
}
|
||||
}
|
||||
60
src/NT8.Core/Common/Models/StrategyMetadata.cs
Normal file
60
src/NT8.Core/Common/Models/StrategyMetadata.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/NT8.Core/Logging/BasicLogger.cs
Normal file
51
src/NT8.Core/Logging/BasicLogger.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/NT8.Core/Logging/ILogger.cs
Normal file
16
src/NT8.Core/Logging/ILogger.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
18
src/NT8.Core/NT8.Core.csproj
Normal file
18
src/NT8.Core/NT8.Core.csproj
Normal 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>
|
||||
138
src/NT8.Core/Orders/IOrderManager.cs
Normal file
138
src/NT8.Core/Orders/IOrderManager.cs
Normal 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
|
||||
}
|
||||
}
|
||||
851
src/NT8.Core/Orders/OrderManager.cs
Normal file
851
src/NT8.Core/Orders/OrderManager.cs
Normal 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
|
||||
}
|
||||
}
|
||||
951
src/NT8.Core/Orders/OrderModels.cs
Normal file
951
src/NT8.Core/Orders/OrderModels.cs
Normal 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
|
||||
}
|
||||
291
src/NT8.Core/Risk/BasicRiskManager.cs
Normal file
291
src/NT8.Core/Risk/BasicRiskManager.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/NT8.Core/Risk/IRiskManager.cs
Normal file
37
src/NT8.Core/Risk/IRiskManager.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
2
src/NT8.Core/Risk/RiskManager.cs
Normal file
2
src/NT8.Core/Risk/RiskManager.cs
Normal 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
|
||||
246
src/NT8.Core/Sizing/BasicPositionSizer.cs
Normal file
246
src/NT8.Core/Sizing/BasicPositionSizer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/NT8.Core/Sizing/IPositionSizer.cs
Normal file
20
src/NT8.Core/Sizing/IPositionSizer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
1
src/NT8.Strategies/Class1.cs
Normal file
1
src/NT8.Strategies/Class1.cs
Normal file
@@ -0,0 +1 @@
|
||||
// Removed - replaced with PlaceholderStrategy.cs
|
||||
13
src/NT8.Strategies/NT8.Strategies.csproj
Normal file
13
src/NT8.Strategies/NT8.Strategies.csproj
Normal 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>
|
||||
16
src/NT8.Strategies/PlaceholderStrategy.cs
Normal file
16
src/NT8.Strategies/PlaceholderStrategy.cs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user