Add synthApi (#27)
* Add synthApi * Put confidence for Synth proba * Update the code * Update readme * Fix bootstraping * fix github build * Update the endpoints for scenario * Add scenario and update backtest modal * Update bot modal * Update interfaces for synth * add synth to backtest * Add Kelly criterion and better signal * Update signal confidence * update doc * save leaderboard and prediction * Update nswag to generate ApiClient in the correct path * Unify the trading modal * Save miner and prediction * Update messaging and block new signal until position not close when flipping off * Rename strategies to indicators * Update doc * Update chart + add signal name * Fix signal direction * Update docker webui * remove crypto npm * Clean
This commit is contained in:
@@ -103,7 +103,7 @@ Key Principles
|
||||
- Before creating new object or new method/function check if there a code that can be called
|
||||
- Most the time you will need to update multiple layer of code files. Make sure to reference all the method that you created when required
|
||||
- When you think its necessary update all the code from the database to the front end
|
||||
- Do not update ManagingApi.ts, user will always do it with nswag
|
||||
- Do not update ManagingApi.ts, once you made a change on the backend endpoint, execute the command to regenerate ManagingApi.ts on the frontend; cd src/Managing.Nswag && dotnet build
|
||||
- Do not reference new react library if a component already exist in mollecules or atoms
|
||||
|
||||
Follow the official Microsoft documentation and ASP.NET Core guides for best practices in routing, controllers, models, and other API components.
|
||||
|
||||
14
.github/workflows/caprover.yml
vendored
14
.github/workflows/caprover.yml
vendored
@@ -24,13 +24,13 @@ jobs:
|
||||
- name: Preset Image Name
|
||||
run: echo "IMAGE_URL=$(echo ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:$(echo ${{ github.sha }} | cut -c1-7) | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push Docker Image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./src/Managing.WebApp
|
||||
file: ./src/Managing.WebApp/Dockerfile-web-ui-dev
|
||||
push: true
|
||||
tags: ${{ env.IMAGE_URL }}
|
||||
# - name: Build and push Docker Image
|
||||
# uses: docker/build-push-action@v5
|
||||
# with:
|
||||
# context: ./src/Managing.WebApp
|
||||
# file: ./src/Managing.WebApp/Dockerfile-web-ui-dev
|
||||
# push: true
|
||||
# tags: ${{ env.IMAGE_URL }}
|
||||
|
||||
|
||||
# - name: Create deploy.tar
|
||||
|
||||
355
README-API.md
Normal file
355
README-API.md
Normal file
@@ -0,0 +1,355 @@
|
||||
# API Features
|
||||
|
||||
## User Management (`UserController`)
|
||||
|
||||
- **JWT Authentication**: Secure user authentication using Web3 signatures
|
||||
- **Profile Management**: Update agent name, avatar URL, and Telegram channel
|
||||
- **Current User Information**: Retrieve authenticated user details
|
||||
|
||||
### Authentication Flow
|
||||
1. Sign message with Web3 wallet
|
||||
2. Submit signed message to receive JWT token
|
||||
3. Use JWT token for all subsequent API calls
|
||||
|
||||
### Endpoints
|
||||
- `POST /User` - Create JWT token with Web3 signature
|
||||
- `GET /User` - Get current user information
|
||||
- `PUT /User/agent-name` - Update agent name
|
||||
- `PUT /User/avatar` - Update avatar URL
|
||||
- `PUT /User/telegram-channel` - Update Telegram channel
|
||||
|
||||
## Account Management (`AccountController`)
|
||||
|
||||
- **Account Creation**: Create trading accounts linked to user profiles
|
||||
- **Account Retrieval**: Get all accounts or specific account by name
|
||||
- **Balance Monitoring**: Retrieve real-time account balances
|
||||
- **GMX Integration**: Get claimable fees summary from GMX contracts
|
||||
- **Account Deletion**: Remove accounts from user profile
|
||||
|
||||
### Endpoints
|
||||
- `POST /Account` - Create new account
|
||||
- `GET /Account/accounts` - Get all user accounts
|
||||
- `GET /Account/balances` - Get account balances
|
||||
- `GET /Account` - Get specific account by name
|
||||
- `GET /Account/{name}/gmx-claimable-summary` - Get GMX claimable fees
|
||||
- `DELETE /Account` - Delete account by name
|
||||
|
||||
## Money Management (`MoneyManagementController`)
|
||||
|
||||
- **Strategy Creation**: Define risk management parameters (StopLoss, TakeProfit, position sizing)
|
||||
- **Strategy Updates**: Modify existing money management configurations
|
||||
- **Strategy Retrieval**: Access all strategies or specific strategy by name
|
||||
- **Strategy Deletion**: Remove unused money management configurations
|
||||
|
||||
### Endpoints
|
||||
- `POST /MoneyManagement` - Create or update money management strategy
|
||||
- `GET /MoneyManagement/moneymanagements` - Get all money management strategies
|
||||
- `GET /MoneyManagement` - Get specific money management by name
|
||||
- `DELETE /MoneyManagement` - Delete money management strategy
|
||||
|
||||
## Scenarios & Indicators (`ScenarioController`)
|
||||
|
||||
### Scenarios
|
||||
- **Build Scenarios**: Combine multiple indicators into trading strategies
|
||||
- **Update Scenarios**: Modify existing scenario configurations
|
||||
- **Scenario Management**: Create, retrieve, update, and delete scenarios
|
||||
- **Loopback Period**: Configure historical data lookback for scenario evaluation
|
||||
|
||||
### Indicators
|
||||
- **Indicator Creation**: Build custom technical indicators with specific parameters
|
||||
- **Indicator Updates**: Modify existing indicator configurations
|
||||
- **Comprehensive Parameters**: Support for period, multiplier, fast/slow periods, signal periods, etc.
|
||||
- **Indicator Types**: Support for various technical indicators (MACD, RSI, EMA, SuperTrend, etc.)
|
||||
|
||||
### Scenario Endpoints
|
||||
- `GET /Scenario` - Get all scenarios for user
|
||||
- `POST /Scenario` - Create new scenario
|
||||
- `PUT /Scenario` - Update existing scenario
|
||||
- `DELETE /Scenario` - Delete scenario by name
|
||||
|
||||
### Indicator Endpoints
|
||||
- `GET /Scenario/indicator` - Get all indicators for user
|
||||
- `POST /Scenario/indicator` - Create new indicator
|
||||
- `PUT /Scenario/indicator` - Update existing indicator
|
||||
- `DELETE /Scenario/indicator` - Delete indicator by name
|
||||
|
||||
### Available Indicators
|
||||
|
||||
| Indicator | Type | Description | Parameters |
|
||||
|----------------------|------|------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------|
|
||||
| RsiDivergence | Signal | Detects RSI divergence patterns where price and RSI move in opposite directions | Period (recommended: 4-6) |
|
||||
| RsiDivergenceConfirm | Signal | Enhanced RSI divergence that waits for confirmation before triggering signals | Period (recommended: 4-6) |
|
||||
| MacdCross | Signal | Triggers signals when MACD histogram crosses zero line | FastPeriods: 12, SlowPeriods: 26, SignalPeriods: 9 |
|
||||
| EmaCross | Signal | Triggers signals when price crosses EMA line | Period (recommended: 200) |
|
||||
| EmaTrend | Trend | Returns trend signals based on price position relative to EMA | Period (recommended: 200) |
|
||||
| SuperTrend | Signal | Triggers signals when price crosses SuperTrend indicator | Period: 10, Multiplier: 3 |
|
||||
| ChandelierExit | Signal | Exit strategy based on highest/lowest values over a period with ATR multiplier | Period: 22, Multiplier: 3 |
|
||||
| StochRsiTrend | Trend | Trend signals based on Stochastic RSI levels (>80% = Short, <20% = Long) | Period (recommended: 22) |
|
||||
| Stc | Signal | Schaff Trend Cycle - signals when crossing 75% (Short) or 25% (Long) levels | CyclePeriods, FastPeriods, SlowPeriods |
|
||||
| ThreeWhiteSoldiers | Signal | Candlestick pattern recognition for bullish reversal signals | Lookback Period: 3 |
|
||||
| LaggingStc | Signal | Enhanced STC with lagging mechanism for reduced false signals | CyclePeriods, FastPeriods, SlowPeriods |
|
||||
| SuperTrendCrossEma | Signal | Combined indicator using both SuperTrend and EMA crossovers | SuperTrend Period/Multiplier + EMA Period |
|
||||
| DualEmaCross | Signal | Dual EMA crossover system using fast and slow EMAs | FastPeriods, SlowPeriods |
|
||||
|
||||
#### Signal Types
|
||||
- **Signal**: Generates entry/exit signals for specific trading actions
|
||||
- **Trend**: Provides directional trend information for position bias
|
||||
- **Context**: Offers additional market context for decision making
|
||||
|
||||
#### Parameter Guidelines
|
||||
- **Period**: Number of candles for calculation (higher = smoother, lower = more responsive)
|
||||
- **Multiplier**: ATR multiplier for volatility-based indicators (higher = wider bands)
|
||||
- **Fast/Slow Periods**: For dual-line indicators, fast reacts quickly, slow provides stability
|
||||
- **Cycle Periods**: For oscillators, defines the cycle length for calculations
|
||||
|
||||
## Bot Management (`BotController`)
|
||||
|
||||
### Core Bot Operations
|
||||
- **Start Bots**: Deploy bots with comprehensive configuration
|
||||
- **Stop/Restart Bots**: Individual or bulk bot control
|
||||
- **Delete Bots**: Remove bots and their associated data
|
||||
- **Configuration Updates**: Real-time bot parameter updates without restart
|
||||
|
||||
### Advanced Bot Features
|
||||
- **Manual Trading**: Open/close positions manually
|
||||
- **Watch Mode**: Signal-only mode without actual trading
|
||||
- **User Ownership**: Security validation ensuring users only control their own bots
|
||||
- **Real-time Updates**: Live bot status and performance monitoring
|
||||
|
||||
### Flexible Configuration Options
|
||||
|
||||
**Scenario Configuration:**
|
||||
- **Saved Scenarios**: Use `ScenarioName` to reference scenarios saved in database
|
||||
- **Dynamic Scenarios**: Pass complete `Scenario` object directly without saving to database
|
||||
- Dynamic scenarios are useful for testing custom configurations or one-time strategies
|
||||
|
||||
**Money Management Configuration:**
|
||||
- **Saved Strategies**: Use `MoneyManagementName` to reference saved money management strategies
|
||||
- **Dynamic Strategies**: Pass complete `MoneyManagement` object directly without saving
|
||||
- Allows for custom risk parameters without cluttering the saved strategies list
|
||||
|
||||
### Endpoints
|
||||
- `POST /Bot/Start` - Start a new bot
|
||||
- `GET /Bot/Stop` - Stop specific bot
|
||||
- `GET /Bot/Restart` - Restart specific bot
|
||||
- `DELETE /Bot/Delete` - Delete bot
|
||||
- `POST /Bot/stop-all` - Stop all user bots
|
||||
- `POST /Bot/restart-all` - Restart all user bots
|
||||
- `GET /Bot/ToggleIsForWatching` - Toggle watch mode
|
||||
- `GET /Bot` - Get all active bots
|
||||
- `POST /Bot/OpenPosition` - Manually open position
|
||||
- `POST /Bot/ClosePosition` - Manually close position
|
||||
- `PUT /Bot/UpdateConfig` - Update bot configuration
|
||||
|
||||
### TradingBotConfig Documentation
|
||||
|
||||
The `TradingBotConfig` class defines all configuration parameters for trading bots. Below are the available properties:
|
||||
|
||||
#### Required Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `AccountName` | `string` | Name of the trading account to use |
|
||||
| `MoneyManagement` | `MoneyManagement` | Risk management strategy configuration |
|
||||
| `Ticker` | `Ticker` | Trading pair symbol (e.g., BTCUSDT) |
|
||||
| `Timeframe` | `Timeframe` | Candle timeframe for analysis |
|
||||
| `IsForWatchingOnly` | `bool` | If true, bot only sends signals without trading |
|
||||
| `BotTradingBalance` | `decimal` | Initial trading balance for the bot |
|
||||
| `BotType` | `BotType` | Type of trading bot behavior |
|
||||
| `IsForBacktest` | `bool` | Whether this config is for backtesting |
|
||||
| `CooldownPeriod` | `int` | Number of candles to wait before opening new position in same direction |
|
||||
| `MaxLossStreak` | `int` | Maximum consecutive losses before requiring opposite direction signal (0 = no limit) |
|
||||
| `FlipPosition` | `bool` | Whether the bot can flip positions |
|
||||
| `Name` | `string` | Unique identifier/name for the bot |
|
||||
| `FlipOnlyWhenInProfit` | `bool` | Only flip positions when current position is profitable (default: true) |
|
||||
|
||||
#### Optional Properties
|
||||
|
||||
| Property | Type | Description | Default |
|
||||
|----------|------|-------------|---------|
|
||||
| `Scenario` | `Scenario` | Scenario object with strategies (takes precedence over ScenarioName) | null |
|
||||
| `ScenarioName` | `string` | Name of scenario to load from database | null |
|
||||
| `MaxPositionTimeHours` | `decimal?` | Maximum hours a position can stay open before auto-close | null |
|
||||
| `CloseEarlyWhenProfitable` | `bool` | Close position early when profitable (requires MaxPositionTimeHours) | false |
|
||||
| `UseSynthApi` | `bool` | Enable Synth API for probabilistic price forecasts | false |
|
||||
| `UseForPositionSizing` | `bool` | Use Synth predictions for position sizing adjustments | true |
|
||||
| `UseForSignalFiltering` | `bool` | Use Synth predictions for signal filtering | true |
|
||||
| `UseForDynamicStopLoss` | `bool` | Use Synth predictions for dynamic stop-loss/take-profit | true |
|
||||
|
||||
#### Advanced Configuration Details
|
||||
|
||||
**Time-Based Position Management:**
|
||||
- When `MaxPositionTimeHours` is set, positions are automatically closed after the specified time
|
||||
- Only closes when position is in profit or at breakeven (never closes at a loss due to time)
|
||||
- `CloseEarlyWhenProfitable` allows immediate closure when profitable instead of waiting full duration
|
||||
|
||||
**Profit-Controlled Flipping:**
|
||||
- `FlipOnlyWhenInProfit` ensures safer trading by only flipping profitable positions
|
||||
- Helps prevent cascading losses in volatile markets
|
||||
|
||||
**Synth API Integration:**
|
||||
- `UseSynthApi` enables probabilistic price forecasting and risk assessment
|
||||
- Sub-properties control specific Synth features (position sizing, signal filtering, dynamic stops)
|
||||
- Provides AI-enhanced trading decisions when enabled
|
||||
|
||||
**Scenario Configuration:**
|
||||
- Either provide a `Scenario` object directly or use `ScenarioName` to load from database
|
||||
- Direct `Scenario` objects are useful for testing custom configurations without saving
|
||||
- `ScenarioName` is typically used for live trading with saved scenarios
|
||||
|
||||
**Money Management Configuration:**
|
||||
- Either provide a `MoneyManagement` object directly or use `MoneyManagementName` in the request
|
||||
- Direct objects allow for one-time custom risk parameters
|
||||
- Saved strategies via name reference are ideal for reusable configurations
|
||||
|
||||
### Bot Configuration Parameters
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|--------------------------|-------------------------------------------------------------------------------------------------------|---------|
|
||||
| MaxPositionTimeHours | Maximum time (in hours) a position can stay open. Closes only when in profit/breakeven. Null = disabled | null |
|
||||
| FlipOnlyWhenInProfit | Only flip positions when current position is profitable | true |
|
||||
| CooldownPeriod | Number of candles to wait before opening new position in same direction | 10 |
|
||||
| MaxLossStreak | Maximum consecutive losses before requiring opposite direction signal. 0 = no limit | 0 |
|
||||
| CloseEarlyWhenProfitable | Close positions early when profitable (requires MaxPositionTimeHours) | false |
|
||||
| BotTradingBalance | Initial trading balance for the bot | Required |
|
||||
|
||||
### Bot Types
|
||||
|
||||
The `BotType` enum in `TradingBotConfig` defines the following trading bot behaviors:
|
||||
|
||||
| Type | Description |
|
||||
|-------------|----------------------------------------------------------------------------------------|
|
||||
| SimpleBot | Basic bot implementation for simple trading strategies |
|
||||
| ScalpingBot | Opens positions and waits for cooldown period before opening new ones in same direction |
|
||||
| FlippingBot | Advanced bot that can flip positions when opposite signals are triggered |
|
||||
|
||||
#### Flipping Mode Configuration
|
||||
|
||||
The flipping behavior is controlled by several `TradingBotConfig` properties:
|
||||
|
||||
- **`BotType`**: Set to `FlippingBot` to enable position flipping capabilities
|
||||
- **`FlipPosition`**: Boolean flag that enables/disables position flipping (automatically set based on BotType)
|
||||
- **`FlipOnlyWhenInProfit`**: Safety feature that only allows flipping when current position is profitable (default: true)
|
||||
|
||||
#### How Flipping Works
|
||||
|
||||
**FlippingBot Behavior:**
|
||||
1. Opens initial position based on scenario signals
|
||||
2. Monitors for opposite direction signals from the same scenario
|
||||
3. When opposite signal occurs:
|
||||
- If `FlipOnlyWhenInProfit` is true: Only flips if current position is profitable
|
||||
- If `FlipOnlyWhenInProfit` is false: Flips regardless of profit status
|
||||
4. Closes current position and immediately opens new position in opposite direction
|
||||
5. Continues this cycle for the duration of the bot's operation
|
||||
|
||||
**ScalpingBot vs FlippingBot:**
|
||||
- **ScalpingBot**: Opens position → Waits for exit signal → Closes → Cooldown → Opens new position
|
||||
- **FlippingBot**: Opens position → Monitors for opposite signals → Flips immediately (no cooldown between flips)
|
||||
|
||||
This configuration allows for more aggressive trading strategies while maintaining risk management through the profit-controlled flipping mechanism.
|
||||
|
||||
## Backtesting (`BacktestController`)
|
||||
|
||||
### Backtest Operations
|
||||
- **Run Backtests**: Execute historical strategy testing with comprehensive parameters
|
||||
- **Retrieve Results**: Access backtest results with complete configuration details
|
||||
- **Delete Backtests**: Remove old backtest data
|
||||
- **Save/Load**: Optional saving of backtest results for future reference
|
||||
|
||||
### Enhanced Backtest Features
|
||||
- **Complete Configuration**: Backtests include full `TradingBotConfig` for easy bot deployment
|
||||
- **Advanced Parameters**: Support for all bot configuration parameters in backtests
|
||||
- **Date Range Testing**: Flexible start/end date selection
|
||||
- **Money Management Integration**: Test with specific money management strategies
|
||||
- **Watch Mode**: Run backtests without executing trades (signal analysis only)
|
||||
|
||||
### Flexible Configuration Options
|
||||
|
||||
**Scenario Configuration for Backtests:**
|
||||
- **Saved Scenarios**: Use `ScenarioName` to reference scenarios saved in database
|
||||
- **Dynamic Scenarios**: Pass complete `Scenario` object directly in the backtest request
|
||||
- Dynamic scenarios are perfect for testing custom indicator combinations without saving them
|
||||
- Allows rapid experimentation with different strategy configurations
|
||||
|
||||
**Money Management Configuration for Backtests:**
|
||||
- **Saved Strategies**: Use `MoneyManagementName` to reference saved money management strategies
|
||||
- **Dynamic Strategies**: Pass complete `MoneyManagement` object directly in the request
|
||||
- Enables testing various risk parameters without cluttering saved strategies
|
||||
- Ideal for optimization testing with different stop-loss/take-profit configurations
|
||||
|
||||
### Endpoints
|
||||
- `GET /Backtest` - Get all backtests for user
|
||||
- `GET /Backtest/{id}` - Get specific backtest by ID
|
||||
- `DELETE /Backtest` - Delete backtest by ID
|
||||
- `POST /Backtest/Run` - Run new backtest
|
||||
|
||||
### Backtest Parameters
|
||||
- **Exchange**: Target exchange for historical data
|
||||
- **Ticker**: Trading pair symbol
|
||||
- **Timeframe**: Candle timeframe (1m, 5m, 15m, 1h, 4h, 1d, etc.)
|
||||
- **Date Range**: Historical period for testing
|
||||
- **Initial Balance**: Starting capital for backtest
|
||||
- **Advanced Config**: All bot parameters (time limits, profit control, etc.)
|
||||
|
||||
### RunBacktestRequest Structure
|
||||
|
||||
The backtest request supports both saved and dynamic configurations:
|
||||
|
||||
```json
|
||||
{
|
||||
"Config": {
|
||||
"ScenarioName": "MySavedScenario", // OR pass Scenario object directly
|
||||
"Scenario": { /* full scenario object */ },
|
||||
"AccountName": "TestAccount",
|
||||
"Ticker": "BTCUSDT",
|
||||
"Timeframe": "OneHour",
|
||||
// ... other TradingBotConfig properties
|
||||
},
|
||||
"MoneyManagementName": "Conservative", // OR pass MoneyManagement object
|
||||
"MoneyManagement": { /* full money management object */ },
|
||||
"StartDate": "2024-01-01T00:00:00Z",
|
||||
"EndDate": "2024-02-01T00:00:00Z",
|
||||
"Balance": 10000,
|
||||
"Save": true
|
||||
}
|
||||
```
|
||||
|
||||
This flexibility allows for comprehensive strategy testing without requiring database saves for experimental configurations.
|
||||
|
||||
## Market Data & Analytics (`DataController`)
|
||||
|
||||
### Market Data
|
||||
- **Ticker Information**: Available trading pairs with logos and metadata
|
||||
- **Candle Data**: Historical OHLCV data for any timeframe
|
||||
- **Real-time Updates**: Live price feeds via SignalR
|
||||
|
||||
### Platform Analytics
|
||||
- **Strategies Statistics**: Platform-wide bot performance metrics
|
||||
- **Top Strategies**: Best performing strategies by ROI
|
||||
- **User Analytics**: Individual user strategy performance
|
||||
- **Platform Summary**: Comprehensive platform statistics with time filters
|
||||
- **Agent Balances**: Historical balance tracking for users
|
||||
- **Best Agents**: Leaderboard of top performing users
|
||||
|
||||
### Endpoints
|
||||
- `POST /Data/GetTickers` - Get available trading pairs with metadata
|
||||
- `GET /Data/Spotlight` - Get market spotlight overview
|
||||
- `GET /Data/GetCandles` - Get historical candle data
|
||||
- `GET /Data/GetStrategiesStatistics` - Get platform-wide strategy statistics
|
||||
- `GET /Data/GetTopStrategies` - Get top performing strategies
|
||||
- `GET /Data/GetUserStrategies` - Get user's strategy details
|
||||
- `GET /Data/GetUserStrategy` - Get specific user strategy
|
||||
- `GET /Data/GetPlatformSummary` - Get comprehensive platform summary
|
||||
- `GET /Data/GetAgentBalances` - Get agent balance history
|
||||
- `GET /Data/GetBestAgents` - Get best performing agents leaderboard
|
||||
|
||||
### Analytics Features
|
||||
- **Time Filters**: 24H, 3D, 1W, 1M, 1Y, Total
|
||||
- **Performance Metrics**: ROI, PnL, win rates, volume traded
|
||||
- **Caching**: Optimized response times with intelligent caching
|
||||
- **Real-time Updates**: Live performance tracking
|
||||
|
||||
### Spotlight Data
|
||||
- **Market Overview**: Comprehensive market analysis
|
||||
- **Strategy Performance**: Cross-strategy performance comparison
|
||||
- **Volume Analysis**: Trading volume statistics
|
||||
386
README.md
386
README.md
@@ -78,7 +78,7 @@ It contains bot management, backtesting, scenario management and money managemen
|
||||
|
||||
## Back-end
|
||||
|
||||
- .NET 7
|
||||
- .NET 8
|
||||
- [SignalR](https://dotnet.microsoft.com/en-us/apps/aspnet/signalr)
|
||||
- [Discord.Net](https://github.com/discord-net/Discord.Net)
|
||||
- [CryptoExchange.Net](https://github.com/JKorf/CryptoExchange.Net)
|
||||
@@ -88,79 +88,209 @@ It contains bot management, backtesting, scenario management and money managemen
|
||||
|
||||
---
|
||||
|
||||
# Features
|
||||
# API Features
|
||||
|
||||
## Privy
|
||||
## User Management (`UserController`)
|
||||
|
||||
Front-end required:
|
||||
- Sign message to get jwt
|
||||
- Delegate embedded address
|
||||
- Sign delegation
|
||||
- Send >10 USDc and 5$ of ETH for txn fees
|
||||
- Trigger to init address
|
||||
- **JWT Authentication**: Secure user authentication using Web3 signatures
|
||||
- **Profile Management**: Update agent name, avatar URL, and Telegram channel
|
||||
- **Current User Information**: Retrieve authenticated user details
|
||||
|
||||
Backend actions:
|
||||
- Approve GMX contracts addresses
|
||||
- Approve USDc contract address
|
||||
### Authentication Flow
|
||||
1. Sign message with Web3 wallet
|
||||
2. Submit signed message to receive JWT token
|
||||
3. Use JWT token for all subsequent API calls
|
||||
|
||||
### Endpoints
|
||||
- `POST /User` - Create JWT token with Web3 signature
|
||||
- `GET /User` - Get current user information
|
||||
- `PUT /User/agent-name` - Update agent name
|
||||
- `PUT /User/avatar` - Update avatar URL
|
||||
- `PUT /User/telegram-channel` - Update Telegram channel
|
||||
|
||||
## Money Management
|
||||
## Account Management (`AccountController`)
|
||||
|
||||
- Create a defined money management for a given timeframe (StopLoss, TakeProfit, Amount to risk)
|
||||
- Edit a money management configuration
|
||||
- Delete a configuration
|
||||
- **Account Creation**: Create trading accounts linked to user profiles
|
||||
- **Account Retrieval**: Get all accounts or specific account by name
|
||||
- **Balance Monitoring**: Retrieve real-time account balances
|
||||
- **GMX Integration**: Get claimable fees summary from GMX contracts
|
||||
- **Account Deletion**: Remove accounts from user profile
|
||||
|
||||
## Strategies
|
||||
### Endpoints
|
||||
- `POST /Account` - Create new account
|
||||
- `GET /Account/accounts` - Get all user accounts
|
||||
- `GET /Account/balances` - Get account balances
|
||||
- `GET /Account` - Get specific account by name
|
||||
- `GET /Account/{name}/gmx-claimable-summary` - Get GMX claimable fees
|
||||
- `DELETE /Account` - Delete account by name
|
||||
|
||||
- Build a strategy
|
||||
- Delete strategy
|
||||
## Money Management (`MoneyManagementController`)
|
||||
|
||||
Strategies availables :
|
||||
- **Strategy Creation**: Define risk management parameters (StopLoss, TakeProfit, position sizing)
|
||||
- **Strategy Updates**: Modify existing money management configurations
|
||||
- **Strategy Retrieval**: Access all strategies or specific strategy by name
|
||||
- **Strategy Deletion**: Remove unused money management configurations
|
||||
|
||||
| Strategy | Description | Recommended values |
|
||||
|----------------------|------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------|
|
||||
| ChandelierExit | Triggers a SHORT signal when the previous candle is above the ChandelierExit, and the last candle closes below the ChandelierExit. | Period: 22, Multiplier: 3 |
|
||||
| EMACross | Triggers a signal when the last candle crosses the EMA. | Period: 200 |
|
||||
| EMATrend | Returns a Trend signal SHORT when the last candle is below the EMA, and a Trend LONG signal when StochRSI < 20%. | Period: 200 |
|
||||
| MACDCross | Triggers a signal when EMAs cross. | FastPeriod: 12, SlowPeriods: 26, SignalPeriods: 9 |
|
||||
| RSIDivergenceConfirm | First, detects a divergence and then triggers a signal when the divergence is confirmed. | Period: 4 for 6 |
|
||||
| RSIDivergence | Triggers a signal when a divergence occurs on the period. | Period: 4 for 6 |
|
||||
| STC | Returns a signal SHORT when the previous STC > 75% and the current STC <= 75%. | Period: 22 |
|
||||
| StochRsiTrend | Returns a Trend signal SHORT when Stoch RSI > 80% and a Trend LONG signal when StochRSI < 20%. | Period: 22 |
|
||||
| SuperTrend | Triggers a SHORT signal when the previous candle is above the super trend, and the last candle closes below the super trend. | Period: 10, Multiplier: 3 |
|
||||
| ThreeWhiteSoldiers | Triggers a LONG signal when the Three White Soldiers pattern is identified. | Lookback Period: 3 |
|
||||
### Endpoints
|
||||
- `POST /MoneyManagement` - Create or update money management strategy
|
||||
- `GET /MoneyManagement/moneymanagements` - Get all money management strategies
|
||||
- `GET /MoneyManagement` - Get specific money management by name
|
||||
- `DELETE /MoneyManagement` - Delete money management strategy
|
||||
|
||||
## Scenarios
|
||||
## Scenarios & Indicators (`ScenarioController`)
|
||||
|
||||
- Build a scenario with multiple strategies
|
||||
- Delete a scenario
|
||||
### Scenarios
|
||||
- **Build Scenarios**: Combine multiple indicators into trading strategies
|
||||
- **Update Scenarios**: Modify existing scenario configurations
|
||||
- **Scenario Management**: Create, retrieve, update, and delete scenarios
|
||||
- **Loopback Period**: Configure historical data lookback for scenario evaluation
|
||||
|
||||
## Backtests
|
||||
### Indicators
|
||||
- **Indicator Creation**: Build custom technical indicators with specific parameters
|
||||
- **Indicator Updates**: Modify existing indicator configurations
|
||||
- **Comprehensive Parameters**: Support for period, multiplier, fast/slow periods, signal periods, etc.
|
||||
- **Indicator Types**: Support for various technical indicators (MACD, RSI, EMA, SuperTrend, etc.)
|
||||
|
||||
The backtest system works with multiple required parameters :
|
||||
### Scenario Endpoints
|
||||
- `GET /Scenario` - Get all scenarios for user
|
||||
- `POST /Scenario` - Create new scenario
|
||||
- `PUT /Scenario` - Update existing scenario
|
||||
- `DELETE /Scenario` - Delete scenario by name
|
||||
|
||||
- Exchange (Binance, Kraken, FTX)
|
||||
- Ticker (ADAUSDT, BTCUSDT, etc..)
|
||||
- Days : Since when did you want to start backtest. Should be a negative value
|
||||
- ScenarioName
|
||||
- Timeframe (OneDay, FifteenMinutes, etc..)
|
||||
- BotType (ScalpingBot or FlippingBot)
|
||||
- Initial balance
|
||||
- **Advanced parameters**: All bot configuration parameters (time limits, profit-controlled flipping, etc.)
|
||||
- **Smart bot deployment**: Deploy successful backtests as live bots with optimized settings
|
||||
- **Enhanced UI**: Wider modals with organized 2-column parameter layouts
|
||||
### Indicator Endpoints
|
||||
- `GET /Scenario/indicator` - Get all indicators for user
|
||||
- `POST /Scenario/indicator` - Create new indicator
|
||||
- `PUT /Scenario/indicator` - Update existing indicator
|
||||
- `DELETE /Scenario/indicator` - Delete indicator by name
|
||||
|
||||
## Bots
|
||||
### Available Indicators
|
||||
|
||||
- Create and run a bot
|
||||
- Stop / Restart a bot
|
||||
- Delete a bot
|
||||
- Stop all bots
|
||||
- Set bot to watch only (send signal to discord instead of opening a new position)
|
||||
- **Time-based position management**: Automatically close positions after maximum time limit (only when in profit/breakeven)
|
||||
- **Advanced position flipping**: Control whether positions flip only when current position is profitable
|
||||
- **Real-time configuration updates**: Update bot settings without restarting
|
||||
- **Enhanced money management**: Smart money management selection with optimized settings from backtests
|
||||
| Indicator | Type | Description | Parameters |
|
||||
|----------------------|------|------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------|
|
||||
| RsiDivergence | Signal | Detects RSI divergence patterns where price and RSI move in opposite directions | Period (recommended: 4-6) |
|
||||
| RsiDivergenceConfirm | Signal | Enhanced RSI divergence that waits for confirmation before triggering signals | Period (recommended: 4-6) |
|
||||
| MacdCross | Signal | Triggers signals when MACD histogram crosses zero line | FastPeriods: 12, SlowPeriods: 26, SignalPeriods: 9 |
|
||||
| EmaCross | Signal | Triggers signals when price crosses EMA line | Period (recommended: 200) |
|
||||
| EmaTrend | Trend | Returns trend signals based on price position relative to EMA | Period (recommended: 200) |
|
||||
| SuperTrend | Signal | Triggers signals when price crosses SuperTrend indicator | Period: 10, Multiplier: 3 |
|
||||
| ChandelierExit | Signal | Exit strategy based on highest/lowest values over a period with ATR multiplier | Period: 22, Multiplier: 3 |
|
||||
| StochRsiTrend | Trend | Trend signals based on Stochastic RSI levels (>80% = Short, <20% = Long) | Period (recommended: 22) |
|
||||
| Stc | Signal | Schaff Trend Cycle - signals when crossing 75% (Short) or 25% (Long) levels | CyclePeriods, FastPeriods, SlowPeriods |
|
||||
| ThreeWhiteSoldiers | Signal | Candlestick pattern recognition for bullish reversal signals | Lookback Period: 3 |
|
||||
| LaggingStc | Signal | Enhanced STC with lagging mechanism for reduced false signals | CyclePeriods, FastPeriods, SlowPeriods |
|
||||
| SuperTrendCrossEma | Signal | Combined indicator using both SuperTrend and EMA crossovers | SuperTrend Period/Multiplier + EMA Period |
|
||||
| DualEmaCross | Signal | Dual EMA crossover system using fast and slow EMAs | FastPeriods, SlowPeriods |
|
||||
|
||||
#### Signal Types
|
||||
- **Signal**: Generates entry/exit signals for specific trading actions
|
||||
- **Trend**: Provides directional trend information for position bias
|
||||
- **Context**: Offers additional market context for decision making
|
||||
|
||||
#### Parameter Guidelines
|
||||
- **Period**: Number of candles for calculation (higher = smoother, lower = more responsive)
|
||||
- **Multiplier**: ATR multiplier for volatility-based indicators (higher = wider bands)
|
||||
- **Fast/Slow Periods**: For dual-line indicators, fast reacts quickly, slow provides stability
|
||||
- **Cycle Periods**: For oscillators, defines the cycle length for calculations
|
||||
|
||||
## Bot Management (`BotController`)
|
||||
|
||||
### Core Bot Operations
|
||||
- **Start Bots**: Deploy bots with comprehensive configuration
|
||||
- **Stop/Restart Bots**: Individual or bulk bot control
|
||||
- **Delete Bots**: Remove bots and their associated data
|
||||
- **Configuration Updates**: Real-time bot parameter updates without restart
|
||||
|
||||
### Advanced Bot Features
|
||||
- **Manual Trading**: Open/close positions manually
|
||||
- **Watch Mode**: Signal-only mode without actual trading
|
||||
- **User Ownership**: Security validation ensuring users only control their own bots
|
||||
- **Real-time Updates**: Live bot status and performance monitoring
|
||||
|
||||
### Flexible Configuration Options
|
||||
|
||||
**Scenario Configuration:**
|
||||
- **Saved Scenarios**: Use `ScenarioName` to reference scenarios saved in database
|
||||
- **Dynamic Scenarios**: Pass complete `Scenario` object directly without saving to database
|
||||
- Dynamic scenarios are useful for testing custom configurations or one-time strategies
|
||||
|
||||
**Money Management Configuration:**
|
||||
- **Saved Strategies**: Use `MoneyManagementName` to reference saved money management strategies
|
||||
- **Dynamic Strategies**: Pass complete `MoneyManagement` object directly without saving
|
||||
- Allows for custom risk parameters without cluttering the saved strategies list
|
||||
|
||||
### Endpoints
|
||||
- `POST /Bot/Start` - Start a new bot
|
||||
- `GET /Bot/Stop` - Stop specific bot
|
||||
- `GET /Bot/Restart` - Restart specific bot
|
||||
- `DELETE /Bot/Delete` - Delete bot
|
||||
- `POST /Bot/stop-all` - Stop all user bots
|
||||
- `POST /Bot/restart-all` - Restart all user bots
|
||||
- `GET /Bot/ToggleIsForWatching` - Toggle watch mode
|
||||
- `GET /Bot` - Get all active bots
|
||||
- `POST /Bot/OpenPosition` - Manually open position
|
||||
- `POST /Bot/ClosePosition` - Manually close position
|
||||
- `PUT /Bot/UpdateConfig` - Update bot configuration
|
||||
|
||||
### TradingBotConfig Documentation
|
||||
|
||||
The `TradingBotConfig` class defines all configuration parameters for trading bots. Below are the available properties:
|
||||
|
||||
#### Required Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `AccountName` | `string` | Name of the trading account to use |
|
||||
| `MoneyManagement` | `MoneyManagement` | Risk management strategy configuration |
|
||||
| `Ticker` | `Ticker` | Trading pair symbol (e.g., BTCUSDT) |
|
||||
| `Timeframe` | `Timeframe` | Candle timeframe for analysis |
|
||||
| `IsForWatchingOnly` | `bool` | If true, bot only sends signals without trading |
|
||||
| `BotTradingBalance` | `decimal` | Initial trading balance for the bot |
|
||||
| `BotType` | `BotType` | Type of trading bot behavior |
|
||||
| `IsForBacktest` | `bool` | Whether this config is for backtesting |
|
||||
| `CooldownPeriod` | `int` | Number of candles to wait before opening new position in same direction |
|
||||
| `MaxLossStreak` | `int` | Maximum consecutive losses before requiring opposite direction signal (0 = no limit) |
|
||||
| `FlipPosition` | `bool` | Whether the bot can flip positions |
|
||||
| `Name` | `string` | Unique identifier/name for the bot |
|
||||
| `FlipOnlyWhenInProfit` | `bool` | Only flip positions when current position is profitable (default: true) |
|
||||
|
||||
#### Optional Properties
|
||||
|
||||
| Property | Type | Description | Default |
|
||||
|----------|------|-------------|---------|
|
||||
| `Scenario` | `Scenario` | Scenario object with strategies (takes precedence over ScenarioName) | null |
|
||||
| `ScenarioName` | `string` | Name of scenario to load from database | null |
|
||||
| `MaxPositionTimeHours` | `decimal?` | Maximum hours a position can stay open before auto-close | null |
|
||||
| `CloseEarlyWhenProfitable` | `bool` | Close position early when profitable (requires MaxPositionTimeHours) | false |
|
||||
| `UseSynthApi` | `bool` | Enable Synth API for probabilistic price forecasts | false |
|
||||
| `UseForPositionSizing` | `bool` | Use Synth predictions for position sizing adjustments | true |
|
||||
| `UseForSignalFiltering` | `bool` | Use Synth predictions for signal filtering | true |
|
||||
| `UseForDynamicStopLoss` | `bool` | Use Synth predictions for dynamic stop-loss/take-profit | true |
|
||||
|
||||
#### Advanced Configuration Details
|
||||
|
||||
**Time-Based Position Management:**
|
||||
- When `MaxPositionTimeHours` is set, positions are automatically closed after the specified time
|
||||
- Only closes when position is in profit or at breakeven (never closes at a loss due to time)
|
||||
- `CloseEarlyWhenProfitable` allows immediate closure when profitable instead of waiting full duration
|
||||
|
||||
**Profit-Controlled Flipping:**
|
||||
- `FlipOnlyWhenInProfit` ensures safer trading by only flipping profitable positions
|
||||
- Helps prevent cascading losses in volatile markets
|
||||
|
||||
**Synth API Integration:**
|
||||
- `UseSynthApi` enables probabilistic price forecasting and risk assessment
|
||||
- Sub-properties control specific Synth features (position sizing, signal filtering, dynamic stops)
|
||||
- Provides AI-enhanced trading decisions when enabled
|
||||
|
||||
**Scenario Configuration:**
|
||||
- Either provide a `Scenario` object directly or use `ScenarioName` to load from database
|
||||
- Direct `Scenario` objects are useful for testing custom configurations without saving
|
||||
- `ScenarioName` is typically used for live trading with saved scenarios
|
||||
|
||||
**Money Management Configuration:**
|
||||
- Either provide a `MoneyManagement` object directly or use `MoneyManagementName` in the request
|
||||
- Direct objects allow for one-time custom risk parameters
|
||||
- Saved strategies via name reference are ideal for reusable configurations
|
||||
|
||||
### Bot Configuration Parameters
|
||||
|
||||
@@ -170,13 +300,149 @@ The backtest system works with multiple required parameters :
|
||||
| FlipOnlyWhenInProfit | Only flip positions when current position is profitable | true |
|
||||
| CooldownPeriod | Number of candles to wait before opening new position in same direction | 10 |
|
||||
| MaxLossStreak | Maximum consecutive losses before requiring opposite direction signal. 0 = no limit | 0 |
|
||||
| CloseEarlyWhenProfitable | Close positions early when profitable (requires MaxPositionTimeHours) | false |
|
||||
| BotTradingBalance | Initial trading balance for the bot | Required |
|
||||
|
||||
Bot types availables :
|
||||
### Bot Types
|
||||
|
||||
The `BotType` enum in `TradingBotConfig` defines the following trading bot behaviors:
|
||||
|
||||
| Type | Description |
|
||||
|-------------|----------------------------------------------------------------------------------------|
|
||||
| ScalpingBot | This bot will open position and wait before opening a new one |
|
||||
| FlippingBot | The flipping bot flipping the position only when a strategy trigger an opposite signal |
|
||||
| SimpleBot | Basic bot implementation for simple trading strategies |
|
||||
| ScalpingBot | Opens positions and waits for cooldown period before opening new ones in same direction |
|
||||
| FlippingBot | Advanced bot that can flip positions when opposite signals are triggered |
|
||||
|
||||
#### Flipping Mode Configuration
|
||||
|
||||
The flipping behavior is controlled by several `TradingBotConfig` properties:
|
||||
|
||||
- **`BotType`**: Set to `FlippingBot` to enable position flipping capabilities
|
||||
- **`FlipPosition`**: Boolean flag that enables/disables position flipping (automatically set based on BotType)
|
||||
- **`FlipOnlyWhenInProfit`**: Safety feature that only allows flipping when current position is profitable (default: true)
|
||||
|
||||
#### How Flipping Works
|
||||
|
||||
**FlippingBot Behavior:**
|
||||
1. Opens initial position based on scenario signals
|
||||
2. Monitors for opposite direction signals from the same scenario
|
||||
3. When opposite signal occurs:
|
||||
- If `FlipOnlyWhenInProfit` is true: Only flips if current position is profitable
|
||||
- If `FlipOnlyWhenInProfit` is false: Flips regardless of profit status
|
||||
4. Closes current position and immediately opens new position in opposite direction
|
||||
5. Continues this cycle for the duration of the bot's operation
|
||||
|
||||
**ScalpingBot vs FlippingBot:**
|
||||
- **ScalpingBot**: Opens position → Waits for exit signal → Closes → Cooldown → Opens new position
|
||||
- **FlippingBot**: Opens position → Monitors for opposite signals → Flips immediately (no cooldown between flips)
|
||||
|
||||
This configuration allows for more aggressive trading strategies while maintaining risk management through the profit-controlled flipping mechanism.
|
||||
|
||||
## Backtesting (`BacktestController`)
|
||||
|
||||
### Backtest Operations
|
||||
- **Run Backtests**: Execute historical strategy testing with comprehensive parameters
|
||||
- **Retrieve Results**: Access backtest results with complete configuration details
|
||||
- **Delete Backtests**: Remove old backtest data
|
||||
- **Save/Load**: Optional saving of backtest results for future reference
|
||||
|
||||
### Enhanced Backtest Features
|
||||
- **Complete Configuration**: Backtests include full `TradingBotConfig` for easy bot deployment
|
||||
- **Advanced Parameters**: Support for all bot configuration parameters in backtests
|
||||
- **Date Range Testing**: Flexible start/end date selection
|
||||
- **Money Management Integration**: Test with specific money management strategies
|
||||
- **Watch Mode**: Run backtests without executing trades (signal analysis only)
|
||||
|
||||
### Flexible Configuration Options
|
||||
|
||||
**Scenario Configuration for Backtests:**
|
||||
- **Saved Scenarios**: Use `ScenarioName` to reference scenarios saved in database
|
||||
- **Dynamic Scenarios**: Pass complete `Scenario` object directly in the backtest request
|
||||
- Dynamic scenarios are perfect for testing custom indicator combinations without saving them
|
||||
- Allows rapid experimentation with different strategy configurations
|
||||
|
||||
**Money Management Configuration for Backtests:**
|
||||
- **Saved Strategies**: Use `MoneyManagementName` to reference saved money management strategies
|
||||
- **Dynamic Strategies**: Pass complete `MoneyManagement` object directly in the request
|
||||
- Enables testing various risk parameters without cluttering saved strategies
|
||||
- Ideal for optimization testing with different stop-loss/take-profit configurations
|
||||
|
||||
### Endpoints
|
||||
- `GET /Backtest` - Get all backtests for user
|
||||
- `GET /Backtest/{id}` - Get specific backtest by ID
|
||||
- `DELETE /Backtest` - Delete backtest by ID
|
||||
- `POST /Backtest/Run` - Run new backtest
|
||||
|
||||
### Backtest Parameters
|
||||
- **Exchange**: Target exchange for historical data
|
||||
- **Ticker**: Trading pair symbol
|
||||
- **Timeframe**: Candle timeframe (1m, 5m, 15m, 1h, 4h, 1d, etc.)
|
||||
- **Date Range**: Historical period for testing
|
||||
- **Initial Balance**: Starting capital for backtest
|
||||
- **Advanced Config**: All bot parameters (time limits, profit control, etc.)
|
||||
|
||||
### RunBacktestRequest Structure
|
||||
|
||||
The backtest request supports both saved and dynamic configurations:
|
||||
|
||||
```json
|
||||
{
|
||||
"Config": {
|
||||
"ScenarioName": "MySavedScenario", // OR pass Scenario object directly
|
||||
"Scenario": { /* full scenario object */ },
|
||||
"AccountName": "TestAccount",
|
||||
"Ticker": "BTCUSDT",
|
||||
"Timeframe": "OneHour",
|
||||
// ... other TradingBotConfig properties
|
||||
},
|
||||
"MoneyManagementName": "Conservative", // OR pass MoneyManagement object
|
||||
"MoneyManagement": { /* full money management object */ },
|
||||
"StartDate": "2024-01-01T00:00:00Z",
|
||||
"EndDate": "2024-02-01T00:00:00Z",
|
||||
"Balance": 10000,
|
||||
"Save": true
|
||||
}
|
||||
```
|
||||
|
||||
This flexibility allows for comprehensive strategy testing without requiring database saves for experimental configurations.
|
||||
|
||||
## Market Data & Analytics (`DataController`)
|
||||
|
||||
### Market Data
|
||||
- **Ticker Information**: Available trading pairs with logos and metadata
|
||||
- **Candle Data**: Historical OHLCV data for any timeframe
|
||||
- **Real-time Updates**: Live price feeds via SignalR
|
||||
|
||||
### Platform Analytics
|
||||
- **Strategies Statistics**: Platform-wide bot performance metrics
|
||||
- **Top Strategies**: Best performing strategies by ROI
|
||||
- **User Analytics**: Individual user strategy performance
|
||||
- **Platform Summary**: Comprehensive platform statistics with time filters
|
||||
- **Agent Balances**: Historical balance tracking for users
|
||||
- **Best Agents**: Leaderboard of top performing users
|
||||
|
||||
### Endpoints
|
||||
- `POST /Data/GetTickers` - Get available trading pairs with metadata
|
||||
- `GET /Data/Spotlight` - Get market spotlight overview
|
||||
- `GET /Data/GetCandles` - Get historical candle data
|
||||
- `GET /Data/GetStrategiesStatistics` - Get platform-wide strategy statistics
|
||||
- `GET /Data/GetTopStrategies` - Get top performing strategies
|
||||
- `GET /Data/GetUserStrategies` - Get user's strategy details
|
||||
- `GET /Data/GetUserStrategy` - Get specific user strategy
|
||||
- `GET /Data/GetPlatformSummary` - Get comprehensive platform summary
|
||||
- `GET /Data/GetAgentBalances` - Get agent balance history
|
||||
- `GET /Data/GetBestAgents` - Get best performing agents leaderboard
|
||||
|
||||
### Analytics Features
|
||||
- **Time Filters**: 24H, 3D, 1W, 1M, 1Y, Total
|
||||
- **Performance Metrics**: ROI, PnL, win rates, volume traded
|
||||
- **Caching**: Optimized response times with intelligent caching
|
||||
- **Real-time Updates**: Live performance tracking
|
||||
|
||||
### Spotlight Data
|
||||
- **Market Overview**: Comprehensive market analysis
|
||||
- **Strategy Performance**: Cross-strategy performance comparison
|
||||
- **Volume Analysis**: Trading volume statistics
|
||||
|
||||
## Privy Integration
|
||||
|
||||
|
||||
494
SYNTH_API_INTEGRATION.md
Normal file
494
SYNTH_API_INTEGRATION.md
Normal file
@@ -0,0 +1,494 @@
|
||||
# Synth API Integration - Technical Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The Synth API integration provides probabilistic price forecasting capabilities for trading signal validation using AI-powered predictions from Mode Network's Synth Subnet. The system employs a sophisticated **probability-weighted confidence calculation** that prioritizes actual trading probabilities over theoretical position sizing metrics.
|
||||
|
||||
## Implementation Architecture
|
||||
|
||||
### Service Overview
|
||||
|
||||
The Synth API integration is implemented through the `SynthPredictionService` class, which serves as the central orchestrator for all probabilistic forecasting operations. The service provides MongoDB-backed caching, comprehensive signal validation, and real-time risk assessment capabilities.
|
||||
|
||||
#### Key Dependencies
|
||||
- **ISynthApiClient**: Handles communication with Mode Network's Synth Subnet API
|
||||
- **ISynthRepository**: Manages MongoDB persistence for prediction caching and leaderboard data
|
||||
- **ILogger**: Provides detailed logging for debugging and monitoring
|
||||
|
||||
#### Supported Assets
|
||||
Currently supports BTC and ETH with extensible architecture for additional cryptocurrencies.
|
||||
|
||||
### Timeframe-Specific Configuration
|
||||
|
||||
The system automatically optimizes API calls based on trading timeframes due to Synth API limitations (only supports 5-minute and 24-hour increments):
|
||||
|
||||
**Short timeframes (1m, 5m, 15m, 30m):**
|
||||
- Uses 5-minute time increments with 4-hour prediction horizons
|
||||
- Updates cache every 2 minutes for rapid market changes
|
||||
|
||||
**Medium timeframes (1h):**
|
||||
- Uses 5-minute time increments with 12-hour prediction horizons
|
||||
- Updates cache every 5 minutes for balanced responsiveness
|
||||
|
||||
**Long timeframes (4h, 1d):**
|
||||
- Uses 24-hour time increments with 48-hour prediction horizons
|
||||
- Updates cache every 15 minutes for stability
|
||||
|
||||
### Caching Strategy
|
||||
|
||||
#### Two-Tier MongoDB Caching System
|
||||
|
||||
**1. Leaderboard Caching**
|
||||
- Caches top miner rankings to avoid repeated API calls
|
||||
- Separate caching for live vs backtest scenarios
|
||||
- Uses structured cache keys incorporating asset, time increment, and scenario type
|
||||
|
||||
**2. Individual Prediction Caching**
|
||||
- Stores detailed price path predictions per miner
|
||||
- Enables partial cache hits (fetch only missing miners)
|
||||
- Automatic cache invalidation based on timeframe-specific durations
|
||||
|
||||
#### Intelligent Cache Management
|
||||
The system optimizes performance by identifying which miner predictions are already cached and fetching only the missing data, then combining cached and fresh predictions for complete analysis.
|
||||
|
||||
### Signal Validation Workflow
|
||||
|
||||
#### Primary Validation Method: `ValidateSignalAsync`
|
||||
|
||||
**Input Processing:**
|
||||
1. Extracts money management settings (SL/TP percentages)
|
||||
2. Calculates dynamic price thresholds based on position direction
|
||||
3. Determines appropriate time horizon (24 hours for signal validation)
|
||||
|
||||
**Price Threshold Calculation:**
|
||||
For LONG positions, the system calculates stop loss prices below current price and take profit prices above. For SHORT positions, this is reversed - stop loss prices above current price and take profit prices below.
|
||||
|
||||
**Probability Analysis:**
|
||||
- Retrieves cached or fresh predictions from top 10 miners
|
||||
- Calculates probability of reaching each price threshold
|
||||
- Analyzes directional movement based on position type
|
||||
|
||||
**Comprehensive Scoring:**
|
||||
- Calculates Expected Monetary Value (EMV)
|
||||
- Applies Kelly Criterion analysis
|
||||
- Computes Expected Utility Theory metrics
|
||||
- Generates confidence score using multi-factor algorithm
|
||||
|
||||
### Multi-Factor Confidence Scoring
|
||||
|
||||
#### Three-Component Scoring System
|
||||
|
||||
**1. Configuration-Aware Scoring (50% weight)**
|
||||
- Adverse probability pressure analysis
|
||||
- Kelly fraction alignment with risk tolerance
|
||||
- Risk aversion compatibility assessment
|
||||
- Configuration consistency bonuses
|
||||
|
||||
**2. Threshold Alignment Scoring (30% weight)**
|
||||
- Kelly minimum/maximum threshold compliance
|
||||
- Favorable probability threshold achievement
|
||||
- Kelly multiplier philosophy alignment
|
||||
|
||||
**3. Probability Scoring (20% weight)**
|
||||
- Take Profit probability assessment
|
||||
- Stop Loss risk evaluation
|
||||
- TP/SL ratio analysis
|
||||
- Win/Loss ratio consideration
|
||||
- Probability dominance bonuses
|
||||
|
||||
#### Conditional Pass-Through System
|
||||
|
||||
For signals exceeding adverse probability thresholds, the system evaluates **8 redeeming qualities**:
|
||||
|
||||
1. Significant Kelly edge (>2x minimum threshold AND >5%)
|
||||
2. Meaningful Expected Monetary Value (>$100)
|
||||
3. Excellent TP/SL ratio (≥2.0)
|
||||
4. Positive Expected Utility (>1.0)
|
||||
5. TP probability dominance (TP > SL × 1.5)
|
||||
6. Moderate threshold breach (SL/Threshold ≤ 1.25)
|
||||
7. Strong Kelly assessment indicators
|
||||
8. Favorable Win/Loss ratio (≥2.0)
|
||||
|
||||
**Pass-through requires 75% of factors to be positive**, resulting in constrained LOW confidence.
|
||||
|
||||
### Risk Assessment Implementation
|
||||
|
||||
#### Position Risk Assessment: `AssessPositionRiskAsync`
|
||||
|
||||
**Pre-Position Risk Check:**
|
||||
- Estimates liquidation price based on money management
|
||||
- Calculates 24-hour liquidation probability
|
||||
- Blocks positions exceeding `MaxLiquidationProbability` (default 10%)
|
||||
|
||||
#### Ongoing Position Monitoring: `MonitorPositionRiskAsync`
|
||||
|
||||
**Real-time Risk Monitoring:**
|
||||
- 6-hour liquidation probability assessment
|
||||
- Warning threshold at 20% risk
|
||||
- Auto-close threshold at 50% risk (if dynamic SL enabled)
|
||||
- Generates actionable risk notifications
|
||||
|
||||
### Backtest vs Live Trading Support
|
||||
|
||||
#### Historical Data Integration
|
||||
For backtesting scenarios, the system retrieves historical leaderboard data from 30 minutes before the signal date and fetches corresponding historical predictions. This ensures accurate simulation of what information would have been available at the time of the original signal.
|
||||
|
||||
#### Cache Separation
|
||||
- Separate cache keys for backtest vs live scenarios
|
||||
- Historical leaderboard and prediction storage
|
||||
- Automatic fallback to current data if historical unavailable
|
||||
|
||||
### Probability Calculation Engine
|
||||
|
||||
#### Path Aggregation Algorithm
|
||||
The probability calculation engine aggregates simulation paths from all selected miners and analyzes each path for target price crossings within the specified time horizon. The system performs directional analysis based on position type (LONG/SHORT) and calculates the final probability as the percentage of paths that cross the target price.
|
||||
|
||||
**Process:**
|
||||
1. Aggregates simulation paths from all selected miners
|
||||
2. Analyzes each path for target price crossings within time horizon
|
||||
3. Directional analysis based on position type (LONG/SHORT)
|
||||
4. Calculates probability as: (Paths Crossing Target) / (Total Paths)
|
||||
|
||||
### Performance Optimization Features
|
||||
|
||||
#### Intelligent API Usage
|
||||
- Partial cache hits reduce API calls by up to 90%
|
||||
- Timeframe-specific configurations minimize unnecessary data
|
||||
- Batch processing for multiple threshold calculations
|
||||
|
||||
#### Memory Management
|
||||
- MongoDB persistence prevents RAM saturation
|
||||
- Automatic cleanup of expired cache data
|
||||
- Efficient data structures for large prediction datasets
|
||||
|
||||
#### Error Handling & Resilience
|
||||
- Graceful degradation on API failures
|
||||
- Fallback confidence scoring when predictions unavailable
|
||||
- Comprehensive error logging with context preservation
|
||||
|
||||
## Core Calculation Methodology
|
||||
|
||||
### Confidence Score Calculation
|
||||
|
||||
The system uses a weighted composite scoring approach:
|
||||
|
||||
**Standard Signals (within adverse threshold):**
|
||||
```
|
||||
Confidence Score = (ConfigurationScore × 50%) + (ThresholdAlignmentScore × 30%) + (ProbabilityScore × 20%)
|
||||
```
|
||||
|
||||
**Over-Threshold Signals (conditional pass-through):**
|
||||
```
|
||||
Constrained Score = (KellyScore × 25%) + (TpSlScore × 25%) + (EMVScore × 20%) + (UtilityScore × 15%) + (RiskPenalty × 15%)
|
||||
Final Score = min(Constrained Score × 0.75, LowThreshold + 0.05)
|
||||
```
|
||||
|
||||
### Sigmoid Scoring Functions
|
||||
|
||||
All scoring components use continuous sigmoid functions for smooth transitions:
|
||||
|
||||
**MapToScore (higher is better):**
|
||||
```
|
||||
Score = 1 / (1 + e^(-steepness × (value - midpoint)))
|
||||
```
|
||||
|
||||
**MapToInverseScore (lower is better):**
|
||||
```
|
||||
Score = 1 / (1 + e^(steepness × (value - midpoint)))
|
||||
```
|
||||
|
||||
## Risk Management Parameters
|
||||
|
||||
### Core Signal Filtering Parameters
|
||||
|
||||
| Parameter | Range | Default | Purpose |
|
||||
|-----------|-------|---------|---------|
|
||||
| `AdverseProbabilityThreshold` | 5% - 50% | 20% | Maximum acceptable Stop Loss probability |
|
||||
| `FavorableProbabilityThreshold` | 10% - 70% | 30% | Minimum required Take Profit probability |
|
||||
| `SignalValidationTimeHorizonHours` | 1 - 168 | 24 | Time horizon for probability calculations |
|
||||
|
||||
### Expected Utility Theory Parameters
|
||||
|
||||
| Parameter | Range | Default | Purpose |
|
||||
|-----------|-------|---------|---------|
|
||||
| `RiskAversion` | 0.1 - 5.0 | 1.0 | Portfolio utility risk aversion coefficient |
|
||||
| `UseExpectedUtility` | boolean | true | Enable Expected Utility calculations |
|
||||
|
||||
### Kelly Criterion Parameters
|
||||
|
||||
| Parameter | Range | Default | Purpose |
|
||||
|-----------|-------|---------|---------|
|
||||
| `KellyMinimumThreshold` | 0.5% - 10% | 1% | Minimum Kelly fraction for favorable signals |
|
||||
| `KellyMaximumCap` | 5% - 50% | 25% | Maximum Kelly fraction (safety cap) |
|
||||
| `KellyFractionalMultiplier` | 10% - 100% | 100% | Kelly sizing multiplier (fractional Kelly) |
|
||||
| `UseKellyCriterion` | boolean | true | Enable Kelly position sizing recommendations |
|
||||
|
||||
### Position Management Parameters
|
||||
|
||||
| Parameter | Range | Default | Purpose |
|
||||
|-----------|-------|---------|---------|
|
||||
| `MaxLiquidationProbability` | 5% - 30% | 10% | Maximum acceptable liquidation risk |
|
||||
| `PositionMonitoringTimeHorizonHours` | 1 - 48 | 6 | Monitoring frequency for open positions |
|
||||
| `PositionWarningThreshold` | 10% - 40% | 20% | Risk level for position warnings |
|
||||
| `PositionAutoCloseThreshold` | 30% - 80% | 50% | Risk level for automatic position closure |
|
||||
|
||||
## Risk Tolerance Profiles
|
||||
|
||||
### Conservative Profile
|
||||
```
|
||||
AdverseProbabilityThreshold = 15% // Stricter blocking threshold
|
||||
FavorableProbabilityThreshold = 40% // Higher TP requirements
|
||||
RiskAversion = 2.0 // More risk-averse utility calculation
|
||||
KellyMinimumThreshold = 2% // Higher Kelly minimum
|
||||
KellyMaximumCap = 15% // Lower Kelly maximum
|
||||
KellyFractionalMultiplier = 50% // Half-Kelly sizing
|
||||
MaxLiquidationProbability = 8% // Stricter position risk
|
||||
PositionWarningThreshold = 15% // Earlier warnings
|
||||
PositionAutoCloseThreshold = 35% // Earlier auto-close
|
||||
```
|
||||
|
||||
**Confidence Thresholds:** High ≥80%, Medium ≥60%, Low ≥40%
|
||||
|
||||
### Moderate Profile (Default)
|
||||
```
|
||||
AdverseProbabilityThreshold = 20% // Balanced blocking threshold
|
||||
FavorableProbabilityThreshold = 30% // Reasonable TP requirements
|
||||
RiskAversion = 1.0 // Neutral utility calculation
|
||||
KellyMinimumThreshold = 1% // Standard Kelly minimum
|
||||
KellyMaximumCap = 25% // Standard Kelly maximum
|
||||
KellyFractionalMultiplier = 100% // Full Kelly sizing
|
||||
MaxLiquidationProbability = 10% // Standard position risk
|
||||
PositionWarningThreshold = 20% // Standard warnings
|
||||
PositionAutoCloseThreshold = 50% // Standard auto-close
|
||||
```
|
||||
|
||||
**Confidence Thresholds:** High ≥75%, Medium ≥55%, Low ≥35%
|
||||
|
||||
### Aggressive Profile
|
||||
```
|
||||
AdverseProbabilityThreshold = 30% // Permissive blocking threshold
|
||||
FavorableProbabilityThreshold = 25% // Lower TP requirements
|
||||
RiskAversion = 0.5 // Risk-seeking utility calculation
|
||||
KellyMinimumThreshold = 0.5% // Lower Kelly minimum
|
||||
KellyMaximumCap = 40% // Higher Kelly maximum
|
||||
KellyFractionalMultiplier = 100% // Full Kelly sizing
|
||||
MaxLiquidationProbability = 15% // Higher position risk tolerance
|
||||
PositionWarningThreshold = 30% // Later warnings
|
||||
PositionAutoCloseThreshold = 70% // Later auto-close
|
||||
```
|
||||
|
||||
**Confidence Thresholds:** High ≥70%, Medium ≥45%, Low ≥25%
|
||||
|
||||
## Detailed Scoring Algorithms
|
||||
|
||||
### 1. Configuration-Aware Scoring (50% Weight)
|
||||
|
||||
**Adverse Probability Pressure (30% of ConfigScore):**
|
||||
- Conservative: Penalizes at 40% of threshold, steepness 6.0
|
||||
- Moderate: Penalizes at 60% of threshold, steepness 4.0
|
||||
- Aggressive: Penalizes at 80% of threshold, steepness 3.0
|
||||
|
||||
**Kelly Alignment Scoring (25% of ConfigScore):**
|
||||
- Conservative: Smooth curves for Kelly 2%-8%, gradual penalty >15%
|
||||
- Moderate: Broad scoring range, penalty for <1% or >15%
|
||||
- Aggressive: Rewards higher Kelly, less penalty for extremes
|
||||
|
||||
**Risk Aversion Alignment (25% of ConfigScore):**
|
||||
- Conservative: Lenient utility requirements (midpoint -0.2, steepness 3.0)
|
||||
- Moderate: Balanced utility assessment (midpoint 0.0, steepness 5.0)
|
||||
- Aggressive: Higher utility demands (midpoint 0.2, steepness 8.0)
|
||||
|
||||
**Configuration Consistency Bonus (20% of ConfigScore):**
|
||||
- Conservative: Bonus for SL <12%, Kelly <6%, TP/SL >1.5
|
||||
- Moderate: Bonus for SL <16%, Kelly >3%, TP/SL >1.5
|
||||
- Aggressive: Bonus for Kelly >8%, TP/SL >2.0, positive EMV
|
||||
|
||||
### 2. Threshold Alignment Scoring (30% Weight)
|
||||
|
||||
**Kelly Threshold Compliance (50% of ThresholdScore):**
|
||||
- Kelly Minimum Score: Sigmoid scoring if below minimum threshold
|
||||
- Kelly Maximum Score: Inverse sigmoid penalty if above maximum cap
|
||||
|
||||
**Favorable Probability Threshold (30% of ThresholdScore):**
|
||||
- Direct comparison to FavorableProbabilityThreshold
|
||||
- Sigmoid scoring for partial compliance
|
||||
|
||||
**Kelly Multiplier Philosophy Alignment (20% of ThresholdScore):**
|
||||
- Fractional Kelly (<100%): Rewards lower SL probability (midpoint 15%, steepness 8.0)
|
||||
- Full Kelly (≥100%): Rewards higher TP/SL ratio (midpoint 1.8, steepness 5.0)
|
||||
|
||||
### 3. Probability Scoring (20% Weight)
|
||||
|
||||
**Take Profit Probability (30% of ProbScore):**
|
||||
- Midpoint: 60% TP probability
|
||||
- Steepness: 4.0 for sensitivity around realistic ranges
|
||||
|
||||
**Stop Loss Risk Assessment (30% of ProbScore):**
|
||||
- Uses 75% of adverse threshold as reference point
|
||||
- Inverse scoring (lower SL probability = higher score)
|
||||
- Steepness: 8.0 for sharp penalty above threshold
|
||||
|
||||
**TP/SL Ratio Assessment (25% of ProbScore):**
|
||||
- Midpoint: 1.5 (realistic good ratio)
|
||||
- Steepness: 3.0 for balanced sensitivity
|
||||
|
||||
**Win/Loss Ratio (10% of ProbScore):**
|
||||
- Midpoint: 1.5 (favorable ratio)
|
||||
- Steepness: 3.0
|
||||
|
||||
**Probability Dominance Bonus (5% of ProbScore):**
|
||||
- TP/SL ratio scaled by factor of 2
|
||||
- Binary scoring for extreme cases
|
||||
|
||||
## Conditional Pass-Through System
|
||||
|
||||
### Redeeming Qualities Assessment (8 Factors)
|
||||
|
||||
Signals exceeding the adverse probability threshold can still receive LOW confidence if meeting **75% of these criteria:**
|
||||
|
||||
1. **Significant Kelly Edge:** Kelly > (2 × KellyMinimumThreshold) AND Kelly > 5%
|
||||
2. **Meaningful Expected Value:** EMV > $100
|
||||
3. **Excellent TP/SL Ratio:** TP/SL ≥ 2.0
|
||||
4. **Positive Expected Utility:** ExpectedUtility > 1.0
|
||||
5. **TP Probability Dominance:** TP > (SL × 1.5)
|
||||
6. **Moderate Threshold Breach:** SL/Threshold ≤ 1.25
|
||||
7. **Strong Kelly Assessment:** Contains "Strong", "Exceptional", or "Extraordinary"
|
||||
8. **Favorable Win/Loss Ratio:** Win/Loss ≥ 2.0
|
||||
|
||||
### Constrained Scoring for Pass-Through
|
||||
|
||||
**Component Weights:**
|
||||
- Kelly Score: 25% (capped at 0.8)
|
||||
- TP/SL Score: 25% (capped at 0.7)
|
||||
- EMV Score: 20% (binary: >0 = 0.6, ≤0 = 0.2)
|
||||
- Utility Score: 15% (binary: >0 = 0.5, ≤0 = 0.2)
|
||||
- Risk Penalty: 15% (inverse sigmoid on threshold overage)
|
||||
|
||||
**Final Score Calculation:**
|
||||
```
|
||||
Constrained Score × 0.75 penalty, capped at (LowThreshold + 0.05)
|
||||
```
|
||||
|
||||
## API Time Horizon Configuration
|
||||
|
||||
### Timeframe-Based Settings
|
||||
|
||||
**Short Timeframes (1m, 5m, 15m, 30m):**
|
||||
- Time Increment: 300 seconds (5 minutes)
|
||||
- Default Horizon: 14,400 seconds (4 hours)
|
||||
- Cache Duration: 2 minutes
|
||||
|
||||
**Medium Timeframes (1h):**
|
||||
- Time Increment: 300 seconds (5 minutes)
|
||||
- Default Horizon: 43,200 seconds (12 hours)
|
||||
- Cache Duration: 5 minutes
|
||||
|
||||
**Long Timeframes (4h, 1d):**
|
||||
- Time Increment: 86,400 seconds (24 hours)
|
||||
- Default Horizon: 172,800 seconds (48 hours)
|
||||
- Cache Duration: 15 minutes
|
||||
|
||||
### Price Threshold Calculations
|
||||
|
||||
**LONG Positions:**
|
||||
```
|
||||
Stop Loss Price = Current Price × (1 - StopLossPercentage)
|
||||
Take Profit Price = Current Price × (1 + TakeProfitPercentage)
|
||||
```
|
||||
|
||||
**SHORT Positions:**
|
||||
```
|
||||
Stop Loss Price = Current Price × (1 + StopLossPercentage)
|
||||
Take Profit Price = Current Price × (1 - TakeProfitPercentage)
|
||||
```
|
||||
|
||||
### Probability Calculation Method
|
||||
|
||||
The system aggregates simulation paths from top-performing miners and calculates the percentage of paths that cross the target price within the specified time horizon:
|
||||
|
||||
```
|
||||
Probability = (Paths Crossing Target) / (Total Simulation Paths)
|
||||
```
|
||||
|
||||
**Path Analysis:**
|
||||
- Uses top 10 miners by default (configurable)
|
||||
- Aggregates all simulation paths from selected miners
|
||||
- Directional analysis based on position type (LONG/SHORT)
|
||||
- Time-bounded evaluation within specified horizon
|
||||
|
||||
## Parameter Optimization Guidelines
|
||||
|
||||
### For Different Market Conditions
|
||||
|
||||
**High Volatility Markets:**
|
||||
- Increase AdverseProbabilityThreshold (25-35%)
|
||||
- Decrease FavorableProbabilityThreshold (20-25%)
|
||||
- Lower KellyMaximumCap (15-20%)
|
||||
- Shorter SignalValidationTimeHorizonHours (12-18)
|
||||
|
||||
**Low Volatility Markets:**
|
||||
- Decrease AdverseProbabilityThreshold (15-20%)
|
||||
- Increase FavorableProbabilityThreshold (35-45%)
|
||||
- Higher KellyMaximumCap (25-35%)
|
||||
- Longer SignalValidationTimeHorizonHours (24-48)
|
||||
|
||||
**Trending Markets:**
|
||||
- Use Aggressive profile
|
||||
- Lower RiskAversion (0.5-0.8)
|
||||
- Higher KellyFractionalMultiplier (85-100%)
|
||||
|
||||
**Ranging Markets:**
|
||||
- Use Conservative profile
|
||||
- Higher RiskAversion (1.5-2.5)
|
||||
- Lower KellyFractionalMultiplier (50-75%)
|
||||
|
||||
### Money Management Integration Impact
|
||||
|
||||
**Tight SL/TP Ranges (< 2%):**
|
||||
- Use Aggressive thresholds (30-35% adverse)
|
||||
- Higher probability granularity
|
||||
- More frequent MEDIUM/LOW confidence levels
|
||||
|
||||
**Standard SL/TP Ranges (2-5%):**
|
||||
- Use Moderate thresholds (20-25% adverse)
|
||||
- Balanced confidence distribution
|
||||
- Optimal for most trading strategies
|
||||
|
||||
**Wide SL/TP Ranges (> 5%):**
|
||||
- Use Conservative thresholds (15-20% adverse)
|
||||
- Lower probability values
|
||||
- More reliable confidence signals
|
||||
|
||||
## Performance Monitoring
|
||||
|
||||
### Key Metrics to Track
|
||||
|
||||
**Confidence Distribution:**
|
||||
- Target: 30% High, 40% Medium, 25% Low, 5% None
|
||||
- Monitor for uniform distribution (indicates poor calibration)
|
||||
|
||||
**Kelly Capping Frequency:**
|
||||
- Track percentage of signals with capped Kelly fractions
|
||||
- High capping (>50%) may indicate overly aggressive signals
|
||||
|
||||
**Expected Utility Trends:**
|
||||
- Monitor portfolio-level utility accumulation
|
||||
- Negative trends indicate poor risk management
|
||||
|
||||
**Pass-Through Analysis:**
|
||||
- Track conditional pass-through frequency
|
||||
- Monitor success rate of LOW confidence signals
|
||||
|
||||
### Calibration Validation
|
||||
|
||||
**Probability Accuracy:**
|
||||
- Compare predicted vs. actual outcome frequencies
|
||||
- Calibrate thresholds based on historical performance
|
||||
|
||||
**Confidence Effectiveness:**
|
||||
- Track win rates by confidence level
|
||||
- Validate that High > Medium > Low > None in performance
|
||||
|
||||
**Risk Assessment Accuracy:**
|
||||
- Monitor false positive/negative rates for liquidation predictions
|
||||
- Adjust MaxLiquidationProbability based on actual risk events
|
||||
@@ -1,9 +1,12 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Api.Models.Requests;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Hubs;
|
||||
using Managing.Domain.Backtests;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Strategies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
@@ -123,7 +126,7 @@ public class BacktestController : BaseController
|
||||
return BadRequest("Either scenario name or scenario object is required");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(request.MoneyManagementName) && request.MoneyManagement == null)
|
||||
if (string.IsNullOrEmpty(request.Config.MoneyManagementName) && request.Config.MoneyManagement == null)
|
||||
{
|
||||
return BadRequest("Either money management name or money management object is required");
|
||||
}
|
||||
@@ -136,26 +139,58 @@ public class BacktestController : BaseController
|
||||
|
||||
// Get money management
|
||||
MoneyManagement moneyManagement;
|
||||
if (!string.IsNullOrEmpty(request.MoneyManagementName))
|
||||
if (!string.IsNullOrEmpty(request.Config.MoneyManagementName))
|
||||
{
|
||||
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
|
||||
moneyManagement =
|
||||
await _moneyManagementService.GetMoneyMangement(user, request.Config.MoneyManagementName);
|
||||
if (moneyManagement == null)
|
||||
return BadRequest("Money management not found");
|
||||
}
|
||||
else
|
||||
{
|
||||
moneyManagement = request.MoneyManagement;
|
||||
moneyManagement = Map(request.Config.MoneyManagement);
|
||||
moneyManagement?.FormatPercentage();
|
||||
}
|
||||
|
||||
// Update config with money management - TradingBot will handle scenario loading
|
||||
// Handle scenario - either from ScenarioRequest or ScenarioName
|
||||
Scenario scenario = null;
|
||||
if (request.Config.Scenario != null)
|
||||
{
|
||||
// Convert ScenarioRequest to Scenario domain object
|
||||
scenario = new Scenario(request.Config.Scenario.Name, request.Config.Scenario.LoopbackPeriod)
|
||||
{
|
||||
User = user
|
||||
};
|
||||
|
||||
// Convert IndicatorRequest objects to Indicator domain objects
|
||||
foreach (var indicatorRequest in request.Config.Scenario.Indicators)
|
||||
{
|
||||
var indicator = new Indicator(indicatorRequest.Name, indicatorRequest.Type)
|
||||
{
|
||||
SignalType = indicatorRequest.SignalType,
|
||||
MinimumHistory = indicatorRequest.MinimumHistory,
|
||||
Period = indicatorRequest.Period,
|
||||
FastPeriods = indicatorRequest.FastPeriods,
|
||||
SlowPeriods = indicatorRequest.SlowPeriods,
|
||||
SignalPeriods = indicatorRequest.SignalPeriods,
|
||||
Multiplier = indicatorRequest.Multiplier,
|
||||
SmoothPeriods = indicatorRequest.SmoothPeriods,
|
||||
StochPeriods = indicatorRequest.StochPeriods,
|
||||
CyclePeriods = indicatorRequest.CyclePeriods,
|
||||
User = user
|
||||
};
|
||||
scenario.AddIndicator(indicator);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert TradingBotConfigRequest to TradingBotConfig for backtest
|
||||
var backtestConfig = new TradingBotConfig
|
||||
{
|
||||
AccountName = request.Config.AccountName,
|
||||
MoneyManagement = moneyManagement,
|
||||
Ticker = request.Config.Ticker,
|
||||
ScenarioName = request.Config.ScenarioName,
|
||||
Scenario = request.Config.Scenario,
|
||||
Scenario = scenario, // Use the converted scenario object
|
||||
Timeframe = request.Config.Timeframe,
|
||||
IsForWatchingOnly = request.WatchOnly,
|
||||
BotTradingBalance = request.Balance,
|
||||
@@ -165,10 +200,14 @@ public class BacktestController : BaseController
|
||||
MaxLossStreak = request.Config.MaxLossStreak,
|
||||
MaxPositionTimeHours = request.Config.MaxPositionTimeHours,
|
||||
FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit,
|
||||
FlipPosition = request.Config.FlipPosition,
|
||||
FlipPosition = request.Config.BotType == BotType.FlippingBot, // Computed based on BotType
|
||||
Name = request.Config.Name ??
|
||||
$"Backtest-{request.Config.ScenarioName ?? request.Config.Scenario?.Name ?? "Custom"}-{DateTime.UtcNow:yyyyMMdd-HHmmss}",
|
||||
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable,
|
||||
UseSynthApi = request.Config.UseSynthApi,
|
||||
UseForPositionSizing = request.Config.UseForPositionSizing,
|
||||
UseForSignalFiltering = request.Config.UseForSignalFiltering,
|
||||
UseForDynamicStopLoss = request.Config.UseForDynamicStopLoss
|
||||
};
|
||||
|
||||
switch (request.Config.BotType)
|
||||
@@ -208,6 +247,18 @@ public class BacktestController : BaseController
|
||||
await _hubContext.Clients.All.SendAsync("BacktestsSubscription", backtesting);
|
||||
}
|
||||
}
|
||||
|
||||
public MoneyManagement Map(MoneyManagementRequest moneyManagementRequest)
|
||||
{
|
||||
return new MoneyManagement
|
||||
{
|
||||
Name = moneyManagementRequest.Name,
|
||||
StopLoss = moneyManagementRequest.StopLoss,
|
||||
TakeProfit = moneyManagementRequest.TakeProfit,
|
||||
Leverage = moneyManagementRequest.Leverage,
|
||||
Timeframe = moneyManagementRequest.Timeframe
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -216,9 +267,9 @@ public class BacktestController : BaseController
|
||||
public class RunBacktestRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The trading bot configuration to use for the backtest
|
||||
/// The trading bot configuration request to use for the backtest
|
||||
/// </summary>
|
||||
public TradingBotConfig Config { get; set; }
|
||||
public TradingBotConfigRequest Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The start date for the backtest
|
||||
@@ -244,14 +295,4 @@ public class RunBacktestRequest
|
||||
/// Whether to save the backtest results
|
||||
/// </summary>
|
||||
public bool Save { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the money management to use (optional if MoneyManagement is provided)
|
||||
/// </summary>
|
||||
public string? MoneyManagementName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The money management details (optional if MoneyManagementName is provided)
|
||||
/// </summary>
|
||||
public MoneyManagement? MoneyManagement { get; set; }
|
||||
}
|
||||
@@ -30,6 +30,9 @@ public abstract class BaseController : ControllerBase
|
||||
|
||||
throw new Exception("User not found for this token");
|
||||
}
|
||||
|
||||
throw new Exception("Not identity assigned to this token");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Managing.Api.Models.Requests;
|
||||
using Managing.Api.Models.Responses;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
@@ -7,6 +7,8 @@ using Managing.Application.ManageBot.Commands;
|
||||
using Managing.Common;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Trades;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@@ -117,24 +119,37 @@ public class BotController : BaseController
|
||||
return Forbid("You don't have permission to start a bot with this account");
|
||||
}
|
||||
|
||||
// Trigger error if money management is not provided
|
||||
if (string.IsNullOrEmpty(request.MoneyManagementName) && request.Config.MoneyManagement == null)
|
||||
// Validate that either money management name or object is provided
|
||||
if (string.IsNullOrEmpty(request.Config.MoneyManagementName) && request.Config.MoneyManagement == null)
|
||||
{
|
||||
return BadRequest("Money management name or money management object is required");
|
||||
return BadRequest("Either money management name or money management object is required");
|
||||
}
|
||||
|
||||
var user = await GetUser();
|
||||
|
||||
// Get money management if name is provided
|
||||
MoneyManagement moneyManagement = request.Config.MoneyManagement;
|
||||
if (!string.IsNullOrEmpty(request.MoneyManagementName))
|
||||
|
||||
// Get money management - either by name lookup or use provided object
|
||||
MoneyManagement moneyManagement;
|
||||
if (!string.IsNullOrEmpty(request.Config.MoneyManagementName))
|
||||
{
|
||||
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
|
||||
moneyManagement =
|
||||
await _moneyManagementService.GetMoneyMangement(user, request.Config.MoneyManagementName);
|
||||
if (moneyManagement == null)
|
||||
{
|
||||
return BadRequest("Money management not found");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
moneyManagement = Map(request.Config.MoneyManagement);
|
||||
// Format percentage values if using custom money management
|
||||
moneyManagement?.FormatPercentage();
|
||||
|
||||
// Ensure user is set for custom money management
|
||||
if (moneyManagement != null)
|
||||
{
|
||||
moneyManagement.User = user;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate initialTradingBalance
|
||||
if (request.Config.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
|
||||
@@ -167,13 +182,45 @@ public class BotController : BaseController
|
||||
return BadRequest("CloseEarlyWhenProfitable can only be enabled when MaxPositionTimeHours is set");
|
||||
}
|
||||
|
||||
// Update the config with final money management
|
||||
// Handle scenario - either from ScenarioRequest or ScenarioName
|
||||
Scenario scenario = null;
|
||||
if (request.Config.Scenario != null)
|
||||
{
|
||||
// Convert ScenarioRequest to Scenario domain object
|
||||
scenario = new Scenario(request.Config.Scenario.Name, request.Config.Scenario.LoopbackPeriod)
|
||||
{
|
||||
User = user
|
||||
};
|
||||
|
||||
// Convert IndicatorRequest objects to Indicator domain objects
|
||||
foreach (var indicatorRequest in request.Config.Scenario.Indicators)
|
||||
{
|
||||
var indicator = new Indicator(indicatorRequest.Name, indicatorRequest.Type)
|
||||
{
|
||||
SignalType = indicatorRequest.SignalType,
|
||||
MinimumHistory = indicatorRequest.MinimumHistory,
|
||||
Period = indicatorRequest.Period,
|
||||
FastPeriods = indicatorRequest.FastPeriods,
|
||||
SlowPeriods = indicatorRequest.SlowPeriods,
|
||||
SignalPeriods = indicatorRequest.SignalPeriods,
|
||||
Multiplier = indicatorRequest.Multiplier,
|
||||
SmoothPeriods = indicatorRequest.SmoothPeriods,
|
||||
StochPeriods = indicatorRequest.StochPeriods,
|
||||
CyclePeriods = indicatorRequest.CyclePeriods,
|
||||
User = user
|
||||
};
|
||||
scenario.AddIndicator(indicator);
|
||||
}
|
||||
}
|
||||
|
||||
// Map the request to the full TradingBotConfig
|
||||
var config = new TradingBotConfig
|
||||
{
|
||||
AccountName = request.Config.AccountName,
|
||||
MoneyManagement = moneyManagement,
|
||||
Ticker = request.Config.Ticker,
|
||||
ScenarioName = request.Config.ScenarioName,
|
||||
Scenario = scenario, // Use the converted scenario object
|
||||
ScenarioName = request.Config.ScenarioName, // Fallback to scenario name if scenario object not provided
|
||||
Timeframe = request.Config.Timeframe,
|
||||
IsForWatchingOnly = request.Config.IsForWatchingOnly,
|
||||
BotTradingBalance = request.Config.BotTradingBalance,
|
||||
@@ -182,10 +229,15 @@ public class BotController : BaseController
|
||||
MaxLossStreak = request.Config.MaxLossStreak,
|
||||
MaxPositionTimeHours = request.Config.MaxPositionTimeHours,
|
||||
FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit,
|
||||
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable,
|
||||
UseSynthApi = request.Config.UseSynthApi,
|
||||
UseForPositionSizing = request.Config.UseForPositionSizing,
|
||||
UseForSignalFiltering = request.Config.UseForSignalFiltering,
|
||||
UseForDynamicStopLoss = request.Config.UseForDynamicStopLoss,
|
||||
// Set computed/default properties
|
||||
IsForBacktest = false,
|
||||
FlipPosition = request.Config.BotType == BotType.FlippingBot,
|
||||
Name = request.Config.Name,
|
||||
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable
|
||||
Name = request.Config.Name
|
||||
};
|
||||
|
||||
var result = await _mediator.Send(new StartBotCommand(config, request.Config.Name, user));
|
||||
@@ -200,6 +252,7 @@ public class BotController : BaseController
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Stops a bot specified by type and name.
|
||||
/// </summary>
|
||||
@@ -614,7 +667,7 @@ public class BotController : BaseController
|
||||
// Get the existing bot to ensure it exists and get current config
|
||||
var bots = _botService.GetActiveBots();
|
||||
var existingBot = bots.FirstOrDefault(b => b.Identifier == request.Identifier);
|
||||
|
||||
|
||||
if (existingBot == null)
|
||||
{
|
||||
return NotFound($"Bot with identifier '{request.Identifier}' not found");
|
||||
@@ -630,9 +683,9 @@ public class BotController : BaseController
|
||||
}
|
||||
|
||||
// If the bot name is being changed, check for conflicts
|
||||
var isNameChanging = !string.IsNullOrEmpty(request.Config.Name) &&
|
||||
var isNameChanging = !string.IsNullOrEmpty(request.Config.Name) &&
|
||||
request.Config.Name != request.Identifier;
|
||||
|
||||
|
||||
if (isNameChanging)
|
||||
{
|
||||
// Check if new name already exists
|
||||
@@ -643,31 +696,36 @@ public class BotController : BaseController
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the money management if provided
|
||||
if (request.Config.MoneyManagement != null)
|
||||
// Validate and get the money management
|
||||
MoneyManagement moneyManagement = null;
|
||||
if (!string.IsNullOrEmpty(request.MoneyManagementName))
|
||||
{
|
||||
// Check if the money management belongs to the user
|
||||
var userMoneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.Config.MoneyManagement.Name);
|
||||
if (userMoneyManagement != null && userMoneyManagement.User?.Name != user.Name)
|
||||
{
|
||||
return Forbid("You don't have permission to use this money management");
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(request.MoneyManagementName))
|
||||
{
|
||||
// If MoneyManagement is null but MoneyManagementName is provided, load it
|
||||
var moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
|
||||
// Load money management by name
|
||||
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
|
||||
if (moneyManagement == null)
|
||||
{
|
||||
return BadRequest($"Money management '{request.MoneyManagementName}' not found");
|
||||
}
|
||||
|
||||
|
||||
if (moneyManagement.User?.Name != user.Name)
|
||||
{
|
||||
return Forbid("You don't have permission to use this money management");
|
||||
}
|
||||
|
||||
request.Config.MoneyManagement = moneyManagement;
|
||||
}
|
||||
else if (request.MoneyManagement != null)
|
||||
{
|
||||
// Use provided money management object
|
||||
moneyManagement = request.MoneyManagement;
|
||||
// Format percentage values if using custom money management
|
||||
moneyManagement.FormatPercentage();
|
||||
|
||||
// Ensure user is set for custom money management
|
||||
moneyManagement.User = user;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use existing bot's money management if no new one is provided
|
||||
moneyManagement = existingBot.Config.MoneyManagement;
|
||||
}
|
||||
|
||||
// Validate CloseEarlyWhenProfitable requires MaxPositionTimeHours
|
||||
@@ -676,27 +734,85 @@ public class BotController : BaseController
|
||||
return BadRequest("CloseEarlyWhenProfitable requires MaxPositionTimeHours to be set");
|
||||
}
|
||||
|
||||
// Handle scenario - either from ScenarioRequest or ScenarioName
|
||||
Scenario scenarioForUpdate = null;
|
||||
if (request.Config.Scenario != null)
|
||||
{
|
||||
// Convert ScenarioRequest to Scenario domain object
|
||||
scenarioForUpdate = new Scenario(request.Config.Scenario.Name, request.Config.Scenario.LoopbackPeriod)
|
||||
{
|
||||
User = user
|
||||
};
|
||||
|
||||
// Convert IndicatorRequest objects to Indicator domain objects
|
||||
foreach (var indicatorRequest in request.Config.Scenario.Indicators)
|
||||
{
|
||||
var indicator = new Indicator(indicatorRequest.Name, indicatorRequest.Type)
|
||||
{
|
||||
SignalType = indicatorRequest.SignalType,
|
||||
MinimumHistory = indicatorRequest.MinimumHistory,
|
||||
Period = indicatorRequest.Period,
|
||||
FastPeriods = indicatorRequest.FastPeriods,
|
||||
SlowPeriods = indicatorRequest.SlowPeriods,
|
||||
SignalPeriods = indicatorRequest.SignalPeriods,
|
||||
Multiplier = indicatorRequest.Multiplier,
|
||||
SmoothPeriods = indicatorRequest.SmoothPeriods,
|
||||
StochPeriods = indicatorRequest.StochPeriods,
|
||||
CyclePeriods = indicatorRequest.CyclePeriods,
|
||||
User = user
|
||||
};
|
||||
scenarioForUpdate.AddIndicator(indicator);
|
||||
}
|
||||
}
|
||||
|
||||
// Map the request to the full TradingBotConfig
|
||||
var updatedConfig = new TradingBotConfig
|
||||
{
|
||||
AccountName = request.Config.AccountName,
|
||||
MoneyManagement = moneyManagement,
|
||||
Ticker = request.Config.Ticker,
|
||||
Scenario = scenarioForUpdate, // Use the converted scenario object
|
||||
ScenarioName = request.Config.ScenarioName, // Fallback to scenario name if scenario object not provided
|
||||
Timeframe = request.Config.Timeframe,
|
||||
IsForWatchingOnly = request.Config.IsForWatchingOnly,
|
||||
BotTradingBalance = request.Config.BotTradingBalance,
|
||||
BotType = request.Config.BotType,
|
||||
CooldownPeriod = request.Config.CooldownPeriod,
|
||||
MaxLossStreak = request.Config.MaxLossStreak,
|
||||
MaxPositionTimeHours = request.Config.MaxPositionTimeHours,
|
||||
FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit,
|
||||
CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable,
|
||||
UseSynthApi = request.Config.UseSynthApi,
|
||||
UseForPositionSizing = request.Config.UseForPositionSizing,
|
||||
UseForSignalFiltering = request.Config.UseForSignalFiltering,
|
||||
UseForDynamicStopLoss = request.Config.UseForDynamicStopLoss,
|
||||
// Set computed/default properties
|
||||
IsForBacktest = false,
|
||||
FlipPosition = request.Config.BotType == BotType.FlippingBot,
|
||||
Name = request.Config.Name
|
||||
};
|
||||
|
||||
// Update the bot configuration using the enhanced method
|
||||
var success = await _botService.UpdateBotConfiguration(request.Identifier, request.Config);
|
||||
|
||||
var success = await _botService.UpdateBotConfiguration(request.Identifier, updatedConfig);
|
||||
|
||||
if (success)
|
||||
{
|
||||
var finalBotName = isNameChanging ? request.Config.Name : request.Identifier;
|
||||
|
||||
|
||||
await _hubContext.Clients.All.SendAsync("SendNotification",
|
||||
$"Bot {finalBotName} configuration updated successfully by {user.Name}." +
|
||||
(isNameChanging ? $" (renamed from {request.Identifier})" : ""), "Info");
|
||||
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
|
||||
return Ok(isNameChanging
|
||||
|
||||
return Ok(isNameChanging
|
||||
? $"Bot configuration updated successfully and renamed to '{request.Config.Name}'"
|
||||
: "Bot configuration updated successfully");
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest("Failed to update bot configuration. " +
|
||||
(isNameChanging ? "The new name might already be in use." : ""));
|
||||
(isNameChanging ? "The new name might already be in use." : ""));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -705,6 +821,18 @@ public class BotController : BaseController
|
||||
return StatusCode(500, $"Error updating bot configuration: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public MoneyManagement Map(MoneyManagementRequest moneyManagementRequest)
|
||||
{
|
||||
return new MoneyManagement
|
||||
{
|
||||
Name = moneyManagementRequest.Name,
|
||||
StopLoss = moneyManagementRequest.StopLoss,
|
||||
TakeProfit = moneyManagementRequest.TakeProfit,
|
||||
Leverage = moneyManagementRequest.Leverage,
|
||||
Timeframe = moneyManagementRequest.Timeframe
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -745,35 +873,7 @@ public class ClosePositionRequest
|
||||
public class StartBotRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The trading bot configuration
|
||||
/// The trading bot configuration request with primary properties
|
||||
/// </summary>
|
||||
public TradingBotConfig Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional money management name (if not included in Config.MoneyManagement)
|
||||
/// </summary>
|
||||
public string? MoneyManagementName { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request model for updating bot configuration
|
||||
/// </summary>
|
||||
public class UpdateBotConfigRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The unique identifier of the bot to update
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Identifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The new trading bot configuration
|
||||
/// </summary>
|
||||
[Required]
|
||||
public TradingBotConfig Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional: Money management name to load if Config.MoneyManagement is null
|
||||
/// </summary>
|
||||
public string? MoneyManagementName { get; set; }
|
||||
public TradingBotConfigRequest Config { get; set; }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Api.Models.Responses;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Strategies;
|
||||
@@ -39,10 +40,12 @@ public class ScenarioController : BaseController
|
||||
/// </summary>
|
||||
/// <returns>A list of scenarios.</returns>
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<Scenario>>> GetScenarios()
|
||||
public async Task<ActionResult<IEnumerable<ScenarioViewModel>>> GetScenarios()
|
||||
{
|
||||
var user = await GetUser();
|
||||
return Ok(_scenarioService.GetScenariosByUser(user));
|
||||
var scenarios = _scenarioService.GetScenariosByUser(user);
|
||||
var scenarioViewModels = scenarios.Select(MapToScenarioViewModel);
|
||||
return Ok(scenarioViewModels);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,11 +55,13 @@ public class ScenarioController : BaseController
|
||||
/// <param name="strategies">A list of strategy names to include in the scenario.</param>
|
||||
/// <returns>The created scenario.</returns>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<Scenario>> CreateScenario(string name, List<string> strategies,
|
||||
public async Task<ActionResult<ScenarioViewModel>> CreateScenario(string name, List<string> strategies,
|
||||
int? loopbackPeriod = null)
|
||||
{
|
||||
var user = await GetUser();
|
||||
return Ok(_scenarioService.CreateScenarioForUser(user, name, strategies, loopbackPeriod));
|
||||
var scenario = _scenarioService.CreateScenarioForUser(user, name, strategies, loopbackPeriod);
|
||||
var scenarioViewModel = MapToScenarioViewModel(scenario);
|
||||
return Ok(scenarioViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -85,10 +90,12 @@ public class ScenarioController : BaseController
|
||||
/// <returns>A list of strategies.</returns>
|
||||
[HttpGet]
|
||||
[Route("indicator")]
|
||||
public async Task<ActionResult<IEnumerable<Indicator>>> GetIndicators()
|
||||
public async Task<ActionResult<IEnumerable<IndicatorViewModel>>> GetIndicators()
|
||||
{
|
||||
var user = await GetUser();
|
||||
return Ok(_scenarioService.GetIndicatorsByUser(user));
|
||||
var indicators = _scenarioService.GetIndicatorsByUser(user);
|
||||
var indicatorViewModels = indicators.Select(MapToIndicatorViewModel);
|
||||
return Ok(indicatorViewModels);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -107,7 +114,7 @@ public class ScenarioController : BaseController
|
||||
/// <returns>The created indicator.</returns>
|
||||
[HttpPost]
|
||||
[Route("indicator")]
|
||||
public async Task<ActionResult<Indicator>> CreateIndicator(
|
||||
public async Task<ActionResult<IndicatorViewModel>> CreateIndicator(
|
||||
IndicatorType indicatorType,
|
||||
string name,
|
||||
int? period = null,
|
||||
@@ -120,7 +127,7 @@ public class ScenarioController : BaseController
|
||||
int? cyclePeriods = null)
|
||||
{
|
||||
var user = await GetUser();
|
||||
return Ok(_scenarioService.CreateIndicatorForUser(
|
||||
var indicator = _scenarioService.CreateIndicatorForUser(
|
||||
user,
|
||||
indicatorType,
|
||||
name,
|
||||
@@ -131,7 +138,9 @@ public class ScenarioController : BaseController
|
||||
multiplier,
|
||||
stochPeriods,
|
||||
smoothPeriods,
|
||||
cyclePeriods));
|
||||
cyclePeriods);
|
||||
var indicatorViewModel = MapToIndicatorViewModel(indicator);
|
||||
return Ok(indicatorViewModel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -176,4 +185,35 @@ public class ScenarioController : BaseController
|
||||
smoothPeriods,
|
||||
cyclePeriods));
|
||||
}
|
||||
|
||||
private static ScenarioViewModel MapToScenarioViewModel(Scenario scenario)
|
||||
{
|
||||
return new ScenarioViewModel
|
||||
{
|
||||
Name = scenario.Name,
|
||||
LoopbackPeriod = scenario.LoopbackPeriod,
|
||||
UserName = scenario.User?.Name,
|
||||
Indicators = scenario.Indicators?.Select(MapToIndicatorViewModel).ToList() ?? new List<IndicatorViewModel>()
|
||||
};
|
||||
}
|
||||
|
||||
private static IndicatorViewModel MapToIndicatorViewModel(Indicator indicator)
|
||||
{
|
||||
return new IndicatorViewModel
|
||||
{
|
||||
Name = indicator.Name,
|
||||
Type = indicator.Type,
|
||||
SignalType = indicator.SignalType,
|
||||
MinimumHistory = indicator.MinimumHistory,
|
||||
Period = indicator.Period,
|
||||
FastPeriods = indicator.FastPeriods,
|
||||
SlowPeriods = indicator.SlowPeriods,
|
||||
SignalPeriods = indicator.SignalPeriods,
|
||||
Multiplier = indicator.Multiplier,
|
||||
SmoothPeriods = indicator.SmoothPeriods,
|
||||
StochPeriods = indicator.StochPeriods,
|
||||
CyclePeriods = indicator.CyclePeriods,
|
||||
UserName = indicator.User?.Name
|
||||
};
|
||||
}
|
||||
}
|
||||
73
src/Managing.Api/Models/Requests/IndicatorRequest.cs
Normal file
73
src/Managing.Api/Models/Requests/IndicatorRequest.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Api.Models.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Request model for indicator configuration without user information
|
||||
/// </summary>
|
||||
public class IndicatorRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the indicator
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of indicator
|
||||
/// </summary>
|
||||
[Required]
|
||||
public IndicatorType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The signal type for this indicator
|
||||
/// </summary>
|
||||
[Required]
|
||||
public SignalType SignalType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum history required for this indicator
|
||||
/// </summary>
|
||||
public int MinimumHistory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Period parameter for the indicator
|
||||
/// </summary>
|
||||
public int? Period { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Fast periods parameter for indicators like MACD
|
||||
/// </summary>
|
||||
public int? FastPeriods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Slow periods parameter for indicators like MACD
|
||||
/// </summary>
|
||||
public int? SlowPeriods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signal periods parameter for indicators like MACD
|
||||
/// </summary>
|
||||
public int? SignalPeriods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier parameter for indicators like SuperTrend
|
||||
/// </summary>
|
||||
public double? Multiplier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Smooth periods parameter
|
||||
/// </summary>
|
||||
public int? SmoothPeriods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stochastic periods parameter
|
||||
/// </summary>
|
||||
public int? StochPeriods { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cycle periods parameter
|
||||
/// </summary>
|
||||
public int? CyclePeriods { get; set; }
|
||||
}
|
||||
13
src/Managing.Api/Models/Requests/MoneyManagementRequest.cs
Normal file
13
src/Managing.Api/Models/Requests/MoneyManagementRequest.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Managing.Common;
|
||||
|
||||
namespace Managing.Api.Models.Requests;
|
||||
|
||||
public class MoneyManagementRequest
|
||||
{
|
||||
[Required] public string Name { get; set; }
|
||||
[Required] public Enums.Timeframe Timeframe { get; set; }
|
||||
[Required] public decimal StopLoss { get; set; }
|
||||
[Required] public decimal TakeProfit { get; set; }
|
||||
[Required] public decimal Leverage { get; set; }
|
||||
}
|
||||
@@ -1,15 +1,49 @@
|
||||
using static Managing.Common.Enums;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
|
||||
namespace Managing.Api.Models.Requests
|
||||
namespace Managing.Api.Models.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Request model for running a backtest
|
||||
/// </summary>
|
||||
public class RunBacktestRequest
|
||||
{
|
||||
public class RunBacktestRequest
|
||||
{
|
||||
public TradingExchanges Exchange { get; set; }
|
||||
public BotType BotType { get; set; }
|
||||
public Ticker Ticker { get; set; }
|
||||
public Timeframe Timeframe { get; set; }
|
||||
public RiskLevel RiskLevel { get; set; }
|
||||
public bool WatchOnly { get; set; }
|
||||
public int Days { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// The trading bot configuration request to use for the backtest
|
||||
/// </summary>
|
||||
public TradingBotConfigRequest Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The start date for the backtest
|
||||
/// </summary>
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The end date for the backtest
|
||||
/// </summary>
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The starting balance for the backtest
|
||||
/// </summary>
|
||||
public decimal Balance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to only watch the backtest without executing trades
|
||||
/// </summary>
|
||||
public bool WatchOnly { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to save the backtest results
|
||||
/// </summary>
|
||||
public bool Save { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the money management to use (optional if MoneyManagement is provided)
|
||||
/// </summary>
|
||||
public string? MoneyManagementName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The money management details (optional if MoneyManagementName is provided)
|
||||
/// </summary>
|
||||
public MoneyManagement? MoneyManagement { get; set; }
|
||||
}
|
||||
|
||||
26
src/Managing.Api/Models/Requests/ScenarioRequest.cs
Normal file
26
src/Managing.Api/Models/Requests/ScenarioRequest.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Managing.Api.Models.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Request model for scenario configuration without user information
|
||||
/// </summary>
|
||||
public class ScenarioRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the scenario
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of indicator configurations for this scenario
|
||||
/// </summary>
|
||||
[Required]
|
||||
public List<IndicatorRequest> Indicators { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The loopback period for the scenario
|
||||
/// </summary>
|
||||
public int? LoopbackPeriod { get; set; }
|
||||
}
|
||||
@@ -1,31 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Api.Models.Requests
|
||||
{
|
||||
/// <summary>
|
||||
/// Request model for starting a bot
|
||||
/// </summary>
|
||||
public class StartBotRequest
|
||||
{
|
||||
[Required] public BotType BotType { get; set; }
|
||||
[Required] public string BotName { get; set; }
|
||||
[Required] public Ticker Ticker { get; set; }
|
||||
[Required] public Timeframe Timeframe { get; set; }
|
||||
[Required] public bool IsForWatchOnly { get; set; }
|
||||
[Required] public string Scenario { get; set; }
|
||||
[Required] public string AccountName { get; set; }
|
||||
[Required] public string MoneyManagementName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initial trading balance in USD for the bot
|
||||
/// The trading bot configuration request with primary properties
|
||||
/// </summary>
|
||||
[Required]
|
||||
[Range(10.00, double.MaxValue, ErrorMessage = "Initial trading balance must be greater than ten")]
|
||||
public decimal InitialTradingBalance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cooldown period in minutes between trades
|
||||
/// </summary>
|
||||
[Required]
|
||||
[Range(1, 1440, ErrorMessage = "Cooldown period must be between 1 and 1440 minutes (24 hours)")]
|
||||
public decimal CooldownPeriod { get; set; } = 1; // Default to 1 minute if not specified
|
||||
public TradingBotConfigRequest Config { get; set; }
|
||||
}
|
||||
}
|
||||
119
src/Managing.Api/Models/Requests/TradingBotConfigRequest.cs
Normal file
119
src/Managing.Api/Models/Requests/TradingBotConfigRequest.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Api.Models.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Simplified trading bot configuration request with only primary properties
|
||||
/// </summary>
|
||||
public class TradingBotConfigRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The account name to use for trading
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string AccountName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ticker/symbol to trade
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Ticker Ticker { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The timeframe for trading decisions
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Timeframe Timeframe { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this bot is for watching only (no actual trading)
|
||||
/// </summary>
|
||||
[Required]
|
||||
public bool IsForWatchingOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The initial trading balance for the bot
|
||||
/// </summary>
|
||||
[Required]
|
||||
public decimal BotTradingBalance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of bot (SimpleBot, ScalpingBot, FlippingBot)
|
||||
/// </summary>
|
||||
[Required]
|
||||
public BotType BotType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name/identifier for this bot
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cooldown period between trades (in candles)
|
||||
/// </summary>
|
||||
[Required]
|
||||
public int CooldownPeriod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum consecutive losses before stopping the bot
|
||||
/// </summary>
|
||||
[Required]
|
||||
public int MaxLossStreak { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The scenario configuration (takes precedence over ScenarioName)
|
||||
/// </summary>
|
||||
public ScenarioRequest? Scenario { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The scenario name to load from database (only used when Scenario is not provided)
|
||||
/// </summary>
|
||||
public string? ScenarioName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The money management name to load from database (only used when MoneyManagement is not provided)
|
||||
/// </summary>
|
||||
public string? MoneyManagementName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The money management object to use for the bot
|
||||
/// </summary>
|
||||
public MoneyManagementRequest? MoneyManagement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum time in hours that a position can remain open before being automatically closed
|
||||
/// </summary>
|
||||
public decimal? MaxPositionTimeHours { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to close positions early when they become profitable
|
||||
/// </summary>
|
||||
public bool CloseEarlyWhenProfitable { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to only flip positions when the current position is in profit
|
||||
/// </summary>
|
||||
public bool FlipOnlyWhenInProfit { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Synth API for predictions and risk assessment
|
||||
/// </summary>
|
||||
public bool UseSynthApi { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Synth predictions for position sizing adjustments
|
||||
/// </summary>
|
||||
public bool UseForPositionSizing { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Synth predictions for signal filtering
|
||||
/// </summary>
|
||||
public bool UseForSignalFiltering { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Synth predictions for dynamic stop-loss/take-profit adjustments
|
||||
/// </summary>
|
||||
public bool UseForDynamicStopLoss { get; set; } = true;
|
||||
}
|
||||
32
src/Managing.Api/Models/Requests/UpdateBotConfigRequest.cs
Normal file
32
src/Managing.Api/Models/Requests/UpdateBotConfigRequest.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
|
||||
namespace Managing.Api.Models.Requests;
|
||||
|
||||
/// <summary>
|
||||
/// Request model for updating bot configuration
|
||||
/// </summary>
|
||||
public class UpdateBotConfigRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The unique identifier of the bot to update
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Identifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The new trading bot configuration request
|
||||
/// </summary>
|
||||
[Required]
|
||||
public TradingBotConfigRequest Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional: Money management name to load from database (if MoneyManagement object is not provided)
|
||||
/// </summary>
|
||||
public string? MoneyManagementName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional: Money management object for custom configurations (takes precedence over MoneyManagementName)
|
||||
/// </summary>
|
||||
public MoneyManagement? MoneyManagement { get; set; }
|
||||
}
|
||||
31
src/Managing.Api/Models/Responses/IndicatorViewModel.cs
Normal file
31
src/Managing.Api/Models/Responses/IndicatorViewModel.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Api.Models.Responses;
|
||||
|
||||
public class IndicatorViewModel
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public IndicatorType Type { get; set; }
|
||||
|
||||
[Required]
|
||||
public SignalType SignalType { get; set; }
|
||||
|
||||
[Required]
|
||||
public int MinimumHistory { get; set; }
|
||||
|
||||
public int? Period { get; set; }
|
||||
public int? FastPeriods { get; set; }
|
||||
public int? SlowPeriods { get; set; }
|
||||
public int? SignalPeriods { get; set; }
|
||||
public double? Multiplier { get; set; }
|
||||
public int? SmoothPeriods { get; set; }
|
||||
public int? StochPeriods { get; set; }
|
||||
public int? CyclePeriods { get; set; }
|
||||
|
||||
[Required]
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
}
|
||||
17
src/Managing.Api/Models/Responses/ScenarioViewModel.cs
Normal file
17
src/Managing.Api/Models/Responses/ScenarioViewModel.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Managing.Api.Models.Responses;
|
||||
|
||||
public class ScenarioViewModel
|
||||
{
|
||||
[Required]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public List<IndicatorViewModel> Indicators { get; set; } = new();
|
||||
|
||||
public int? LoopbackPeriod { get; set; }
|
||||
|
||||
[Required]
|
||||
public string UserName { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using Managing.Domain.Synth.Models;
|
||||
|
||||
namespace Managing.Application.Abstractions.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Repository interface for Synth-related data operations
|
||||
/// Provides MongoDB persistence for leaderboard and individual predictions data
|
||||
/// </summary>
|
||||
public interface ISynthRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets cached leaderboard data by cache key
|
||||
/// </summary>
|
||||
/// <param name="cacheKey">The cache key to search for</param>
|
||||
/// <returns>Cached leaderboard data if found, null otherwise</returns>
|
||||
Task<SynthMinersLeaderboard?> GetLeaderboardAsync(string cacheKey);
|
||||
|
||||
/// <summary>
|
||||
/// Saves leaderboard data to MongoDB
|
||||
/// </summary>
|
||||
/// <param name="leaderboard">The leaderboard data to save</param>
|
||||
Task SaveLeaderboardAsync(SynthMinersLeaderboard leaderboard);
|
||||
|
||||
/// <summary>
|
||||
/// Gets individual cached prediction data by asset, parameters, and miner UIDs
|
||||
/// </summary>
|
||||
/// <param name="asset">Asset symbol</param>
|
||||
/// <param name="timeIncrement">Time increment in seconds</param>
|
||||
/// <param name="timeLength">Time length in seconds</param>
|
||||
/// <param name="minerUids">List of miner UIDs to get predictions for</param>
|
||||
/// <param name="isBacktest">Whether this is backtest data</param>
|
||||
/// <param name="signalDate">Signal date for backtest data</param>
|
||||
/// <returns>List of cached individual predictions</returns>
|
||||
Task<List<SynthPrediction>> GetIndividualPredictionsAsync(
|
||||
string asset,
|
||||
int timeIncrement,
|
||||
int timeLength,
|
||||
List<int> minerUids,
|
||||
bool isBacktest,
|
||||
DateTime? signalDate);
|
||||
|
||||
/// <summary>
|
||||
/// Saves individual prediction data to MongoDB
|
||||
/// </summary>
|
||||
/// <param name="prediction">The individual prediction data to save</param>
|
||||
Task SaveIndividualPredictionAsync(SynthPrediction prediction);
|
||||
|
||||
/// <summary>
|
||||
/// Saves multiple individual predictions to MongoDB in batch
|
||||
/// </summary>
|
||||
/// <param name="predictions">The list of individual predictions to save</param>
|
||||
Task SaveIndividualPredictionsAsync(List<SynthPrediction> predictions);
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up old cached data beyond the retention period
|
||||
/// </summary>
|
||||
/// <param name="retentionDays">Number of days to retain data</param>
|
||||
Task CleanupOldDataAsync(int retentionDays = 30);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using Managing.Domain.Synth.Models;
|
||||
|
||||
namespace Managing.Application.Abstractions.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for communicating with the Synth API
|
||||
/// </summary>
|
||||
public interface ISynthApiClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Fetches the current leaderboard from Synth API
|
||||
/// </summary>
|
||||
/// <param name="config">Synth configuration containing API key and settings</param>
|
||||
/// <returns>List of miners with their rankings and stats</returns>
|
||||
Task<List<MinerInfo>> GetLeaderboardAsync(SynthConfiguration config);
|
||||
|
||||
/// <summary>
|
||||
/// Fetches historical leaderboard data from Synth API for a specific time range
|
||||
/// </summary>
|
||||
/// <param name="startTime">Start time for historical data (ISO 8601 format)</param>
|
||||
/// <param name="endTime">End time for historical data (ISO 8601 format)</param>
|
||||
/// <param name="config">Synth configuration containing API key and settings</param>
|
||||
/// <returns>List of miners with their historical rankings and stats</returns>
|
||||
Task<List<MinerInfo>> GetHistoricalLeaderboardAsync(DateTime startTime, DateTime endTime, SynthConfiguration config);
|
||||
|
||||
/// <summary>
|
||||
/// Fetches latest predictions from specified miners
|
||||
/// </summary>
|
||||
/// <param name="minerUids">List of miner UIDs to get predictions from</param>
|
||||
/// <param name="asset">Asset symbol (e.g., "BTC", "ETH")</param>
|
||||
/// <param name="timeIncrement">Time interval in seconds between each prediction point</param>
|
||||
/// <param name="timeLength">Total prediction time length in seconds</param>
|
||||
/// <param name="config">Synth configuration containing API key and settings</param>
|
||||
/// <returns>List of predictions from the specified miners</returns>
|
||||
Task<List<MinerPrediction>> GetMinerPredictionsAsync(
|
||||
List<int> minerUids,
|
||||
string asset,
|
||||
int timeIncrement,
|
||||
int timeLength,
|
||||
SynthConfiguration config);
|
||||
|
||||
/// <summary>
|
||||
/// Fetches historical predictions from specified miners for a specific time point
|
||||
/// </summary>
|
||||
/// <param name="minerUids">List of miner UIDs to get predictions from</param>
|
||||
/// <param name="asset">Asset symbol (e.g., "BTC", "ETH")</param>
|
||||
/// <param name="startTime">Start time for historical predictions (when the prediction was made)</param>
|
||||
/// <param name="timeIncrement">Time interval in seconds between each prediction point</param>
|
||||
/// <param name="timeLength">Total prediction time length in seconds</param>
|
||||
/// <param name="config">Synth configuration containing API key and settings</param>
|
||||
/// <returns>List of historical predictions from the specified miners</returns>
|
||||
Task<List<MinerPrediction>> GetHistoricalMinerPredictionsAsync(
|
||||
List<int> minerUids,
|
||||
string asset,
|
||||
DateTime startTime,
|
||||
int timeIncrement,
|
||||
int timeLength,
|
||||
SynthConfiguration config);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Synth.Models;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Abstractions.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service interface for Synth prediction business logic and probability calculations
|
||||
/// </summary>
|
||||
public interface ISynthPredictionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the probability of price reaching a target within a specified time horizon
|
||||
/// </summary>
|
||||
/// <param name="asset">Asset symbol (e.g., "BTC", "ETH")</param>
|
||||
/// <param name="currentPrice">Current market price</param>
|
||||
/// <param name="targetPrice">Target price to reach</param>
|
||||
/// <param name="timeHorizonSeconds">Time horizon in seconds</param>
|
||||
/// <param name="isLongPosition">True for long positions (liquidation when price drops), false for short positions (liquidation when price rises)</param>
|
||||
/// <param name="config">Synth configuration for this operation</param>
|
||||
/// <returns>Probability as a decimal between 0.0 and 1.0</returns>
|
||||
Task<decimal> GetProbabilityOfTargetPriceAsync(
|
||||
string asset,
|
||||
decimal currentPrice,
|
||||
decimal targetPrice,
|
||||
int timeHorizonSeconds,
|
||||
bool isLongPosition,
|
||||
SynthConfiguration config);
|
||||
|
||||
/// <summary>
|
||||
/// Gets probabilities for multiple price thresholds at once
|
||||
/// </summary>
|
||||
/// <param name="asset">Asset symbol</param>
|
||||
/// <param name="currentPrice">Current market price</param>
|
||||
/// <param name="priceThresholds">Dictionary of threshold names to prices</param>
|
||||
/// <param name="timeHorizonSeconds">Time horizon in seconds</param>
|
||||
/// <param name="isLongPosition">True for long positions, false for short positions</param>
|
||||
/// <param name="config">Synth configuration for this operation</param>
|
||||
/// <param name="isBacktest">Parameter for backtest</param>
|
||||
/// <param name="signalDate">Signal date</param>
|
||||
/// <returns>Dictionary of threshold names to probabilities</returns>
|
||||
Task<Dictionary<string, decimal>> GetMultipleThresholdProbabilitiesAsync(
|
||||
string asset,
|
||||
decimal currentPrice,
|
||||
Dictionary<string, decimal> priceThresholds,
|
||||
int timeHorizonSeconds,
|
||||
bool isLongPosition,
|
||||
SynthConfiguration config,
|
||||
bool isBacktest,
|
||||
DateTime signalDate);
|
||||
|
||||
/// <summary>
|
||||
/// Clears cached predictions (useful for testing or forced refresh)
|
||||
/// </summary>
|
||||
void ClearCache();
|
||||
|
||||
/// <summary>
|
||||
/// Clears cached predictions from MongoDB asynchronously
|
||||
/// </summary>
|
||||
Task ClearCacheAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Validates a trading signal using Synth predictions to check for adverse price movements
|
||||
/// </summary>
|
||||
/// <param name="signal">The trading signal containing ticker, direction, candle data, and other context</param>
|
||||
/// <param name="currentPrice">Current market price (required)</param>
|
||||
/// <param name="botConfig">Bot configuration with Synth settings</param>
|
||||
/// <param name="isBacktest">Whether this is a backtest</param>
|
||||
/// <param name="customThresholds">Custom probability thresholds for decision-making. If null, uses default thresholds.</param>
|
||||
/// <returns>Comprehensive signal validation result including confidence, probabilities, and risk analysis</returns>
|
||||
Task<SignalValidationResult> ValidateSignalAsync(Signal signal, decimal currentPrice,
|
||||
TradingBotConfig botConfig, bool isBacktest, Dictionary<string, decimal> customThresholds = null);
|
||||
|
||||
/// <summary>
|
||||
/// Performs risk assessment before opening a position
|
||||
/// </summary>
|
||||
/// <param name="ticker">Trading ticker</param>
|
||||
/// <param name="direction">Position direction</param>
|
||||
/// <param name="currentPrice">Current market price</param>
|
||||
/// <param name="botConfig">Bot configuration with Synth settings</param>
|
||||
/// <param name="isBacktest">Whether this is a backtest</param>
|
||||
/// <returns>True if position should be allowed, false if blocked</returns>
|
||||
Task<bool> AssessPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice,
|
||||
TradingBotConfig botConfig, bool isBacktest);
|
||||
|
||||
/// <summary>
|
||||
/// Monitors liquidation risk for an open position
|
||||
/// </summary>
|
||||
/// <param name="ticker">Trading ticker</param>
|
||||
/// <param name="direction">Position direction</param>
|
||||
/// <param name="currentPrice">Current market price</param>
|
||||
/// <param name="liquidationPrice">Position liquidation price</param>
|
||||
/// <param name="positionIdentifier">Position identifier for logging</param>
|
||||
/// <param name="botConfig">Bot configuration with Synth settings</param>
|
||||
/// <returns>Risk assessment result</returns>
|
||||
Task<SynthRiskResult> MonitorPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice,
|
||||
decimal liquidationPrice, string positionIdentifier, TradingBotConfig botConfig);
|
||||
|
||||
/// <summary>
|
||||
/// Estimates liquidation price based on money management settings
|
||||
/// </summary>
|
||||
/// <param name="currentPrice">Current market price</param>
|
||||
/// <param name="direction">Position direction</param>
|
||||
/// <param name="moneyManagement">Money management settings</param>
|
||||
/// <returns>Estimated liquidation price</returns>
|
||||
decimal EstimateLiquidationPrice(decimal currentPrice, TradeDirection direction, MoneyManagement moneyManagement);
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Synth.Models;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Evm.Models.Privy;
|
||||
using static Managing.Common.Enums;
|
||||
@@ -37,4 +39,15 @@ public interface ITradingService
|
||||
void UpdateStrategy(Indicator indicator);
|
||||
Task<IEnumerable<Position>> GetBrokerPositions(Account account);
|
||||
Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress);
|
||||
|
||||
// Synth API integration methods
|
||||
Task<SignalValidationResult> ValidateSynthSignalAsync(Signal signal, decimal currentPrice,
|
||||
TradingBotConfig botConfig,
|
||||
bool isBacktest);
|
||||
|
||||
Task<bool> AssessSynthPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice,
|
||||
TradingBotConfig botConfig, bool isBacktest);
|
||||
|
||||
Task<SynthRiskResult> MonitorSynthPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice,
|
||||
decimal liquidationPrice, string positionIdentifier, TradingBotConfig botConfig);
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace Managing.Application.Abstractions;
|
||||
|
||||
public interface IBotService
|
||||
{
|
||||
void SaveOrUpdateBotBackup(User user, string identifier, BotType botType, BotStatus status, string data);
|
||||
void SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, string data);
|
||||
void AddSimpleBotToCache(IBot bot);
|
||||
void AddTradingBotToCache(ITradingBot bot);
|
||||
List<ITradingBot> GetActiveBots();
|
||||
@@ -21,7 +21,7 @@ public interface IBotService
|
||||
/// <param name="config">The trading bot configuration</param>
|
||||
/// <returns>ITradingBot instance</returns>
|
||||
ITradingBot CreateTradingBot(TradingBotConfig config);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a trading bot for backtesting using the unified TradingBot class
|
||||
/// </summary>
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Managing.Application.Abstractions
|
||||
{
|
||||
IEnumerable<Scenario> GetScenarios();
|
||||
Scenario CreateScenario(string name, List<string> strategies, int? loopbackPeriod = 1);
|
||||
IEnumerable<Indicator> GetStrategies();
|
||||
IEnumerable<Indicator> GetIndicators();
|
||||
bool DeleteStrategy(string name);
|
||||
bool DeleteScenario(string name);
|
||||
Scenario GetScenario(string name);
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace Managing.Application.Abstractions
|
||||
{
|
||||
TradingBotConfig Config { get; set; }
|
||||
Account Account { get; set; }
|
||||
HashSet<IIndicator> Indicators { get; set; }
|
||||
FixedSizeQueue<Candle> OptimizedCandles { get; set; }
|
||||
HashSet<Candle> Candles { get; set; }
|
||||
HashSet<Signal> Signals { get; set; }
|
||||
@@ -25,14 +24,12 @@ namespace Managing.Application.Abstractions
|
||||
DateTime PreloadSince { get; set; }
|
||||
int PreloadedCandlesCount { get; set; }
|
||||
decimal Fee { get; set; }
|
||||
Scenario Scenario { get; set; }
|
||||
|
||||
Task Run();
|
||||
Task ToggleIsForWatchOnly();
|
||||
int GetWinRate();
|
||||
decimal GetProfitAndLoss();
|
||||
decimal GetTotalFees();
|
||||
void LoadIndicators(IEnumerable<IIndicator> indicators);
|
||||
void LoadScenario(string scenarioName);
|
||||
void LoadScenario(Scenario scenario);
|
||||
void UpdateIndicatorsValues();
|
||||
|
||||
@@ -183,19 +183,44 @@ namespace Managing.Application.Backtesting
|
||||
throw new Exception("No candle to backtest");
|
||||
}
|
||||
|
||||
var totalCandles = candles.Count;
|
||||
var currentCandle = 0;
|
||||
var lastLoggedPercentage = 0;
|
||||
|
||||
_logger.LogInformation("Starting backtest with {TotalCandles} candles for {Ticker} on {Timeframe}",
|
||||
totalCandles, config.Ticker, config.Timeframe);
|
||||
|
||||
bot.WalletBalances.Add(candles.FirstOrDefault().Date, config.BotTradingBalance);
|
||||
|
||||
foreach (var candle in candles)
|
||||
{
|
||||
bot.OptimizedCandles.Enqueue(candle);
|
||||
bot.Candles.Add(candle);
|
||||
bot.Run();
|
||||
|
||||
currentCandle++;
|
||||
|
||||
// Log progress every 10% or every 1000 candles, whichever comes first
|
||||
var currentPercentage = (int)((double)currentCandle / totalCandles * 100);
|
||||
var shouldLog = currentPercentage >= lastLoggedPercentage + 10 ||
|
||||
currentCandle % 1000 == 0 ||
|
||||
currentCandle == totalCandles;
|
||||
|
||||
if (shouldLog && currentPercentage > lastLoggedPercentage)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"Backtest progress: {CurrentCandle}/{TotalCandles} ({Percentage}%) - Processing candle from {CandleDate}",
|
||||
currentCandle, totalCandles, currentPercentage, candle.Date.ToString("yyyy-MM-dd HH:mm"));
|
||||
lastLoggedPercentage = currentPercentage;
|
||||
}
|
||||
}
|
||||
|
||||
bot.Candles = new HashSet<Candle>(candles);
|
||||
bot.UpdateIndicatorsValues();
|
||||
_logger.LogInformation("Backtest processing completed. Calculating final results...");
|
||||
|
||||
var strategies = _scenarioService.GetStrategies();
|
||||
var strategiesValues = GetStrategiesValues(strategies, candles);
|
||||
bot.Candles = new HashSet<Candle>(candles);
|
||||
// bot.UpdateIndicatorsValues();
|
||||
|
||||
var indicatorsValues = GetIndicatorsValues(bot.Config.Scenario.Indicators, candles);
|
||||
|
||||
var finalPnl = bot.GetProfitAndLoss();
|
||||
var winRate = bot.GetWinRate();
|
||||
@@ -230,7 +255,7 @@ namespace Managing.Application.Backtesting
|
||||
WalletBalances = bot.WalletBalances.ToList(),
|
||||
Statistics = stats,
|
||||
OptimizedMoneyManagement = optimizedMoneyManagement,
|
||||
StrategiesValues = AggregateValues(strategiesValues, bot.IndicatorsValues),
|
||||
IndicatorsValues = AggregateValues(indicatorsValues, bot.IndicatorsValues),
|
||||
Score = score
|
||||
};
|
||||
|
||||
@@ -238,14 +263,14 @@ namespace Managing.Application.Backtesting
|
||||
}
|
||||
|
||||
private Dictionary<IndicatorType, IndicatorsResultBase> AggregateValues(
|
||||
Dictionary<IndicatorType, IndicatorsResultBase> strategiesValues,
|
||||
Dictionary<IndicatorType, IndicatorsResultBase> indicatorsValues,
|
||||
Dictionary<IndicatorType, IndicatorsResultBase> botStrategiesValues)
|
||||
{
|
||||
// Foreach strategy type, only retrieve the values where the strategy is not present already in the bot
|
||||
// Then, add the values to the bot values
|
||||
|
||||
var result = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||
foreach (var strategy in strategiesValues)
|
||||
foreach (var indicator in indicatorsValues)
|
||||
{
|
||||
// if (!botStrategiesValues.ContainsKey(strategy.Key))
|
||||
// {
|
||||
@@ -255,29 +280,29 @@ namespace Managing.Application.Backtesting
|
||||
// result[strategy.Key] = botStrategiesValues[strategy.Key];
|
||||
// }
|
||||
|
||||
result[strategy.Key] = strategy.Value;
|
||||
result[indicator.Key] = indicator.Value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Dictionary<IndicatorType, IndicatorsResultBase> GetStrategiesValues(IEnumerable<Indicator> strategies,
|
||||
private Dictionary<IndicatorType, IndicatorsResultBase> GetIndicatorsValues(List<Indicator> indicators,
|
||||
List<Candle> candles)
|
||||
{
|
||||
var strategiesValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||
var indicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||
var fixedCandles = new FixedSizeQueue<Candle>(10000);
|
||||
foreach (var candle in candles)
|
||||
{
|
||||
fixedCandles.Enqueue(candle);
|
||||
}
|
||||
|
||||
foreach (var strategy in strategies)
|
||||
foreach (var indicator in indicators)
|
||||
{
|
||||
try
|
||||
{
|
||||
var s = ScenarioHelpers.BuildIndicator(strategy, 10000);
|
||||
var s = ScenarioHelpers.BuildIndicator(indicator, 10000);
|
||||
s.Candles = fixedCandles;
|
||||
strategiesValues[strategy.Type] = s.GetStrategyValues();
|
||||
indicatorsValues[indicator.Type] = s.GetIndicatorValues();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -285,7 +310,7 @@ namespace Managing.Application.Backtesting
|
||||
}
|
||||
}
|
||||
|
||||
return strategiesValues;
|
||||
return indicatorsValues;
|
||||
}
|
||||
|
||||
public bool DeleteBacktest(string id)
|
||||
|
||||
@@ -3,7 +3,6 @@ using Managing.Domain.Bots;
|
||||
using Managing.Domain.Workflows;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Bots
|
||||
{
|
||||
@@ -44,7 +43,7 @@ namespace Managing.Application.Bots
|
||||
public override void SaveBackup()
|
||||
{
|
||||
var data = JsonConvert.SerializeObject(_workflow);
|
||||
_botService.SaveOrUpdateBotBackup(User, Identifier, BotType.SimpleBot, Status, data);
|
||||
_botService.SaveOrUpdateBotBackup(User, Identifier, Status, data);
|
||||
}
|
||||
|
||||
public override void LoadBackup(BotBackup backup)
|
||||
|
||||
@@ -7,7 +7,6 @@ using Managing.Core.FixedSizedQueue;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Shared.Helpers;
|
||||
using Managing.Domain.Strategies;
|
||||
@@ -41,8 +40,6 @@ public class TradingBot : Bot, ITradingBot
|
||||
public DateTime PreloadSince { get; set; }
|
||||
public int PreloadedCandlesCount { get; set; }
|
||||
public decimal Fee { get; set; }
|
||||
public Scenario Scenario { get; set; }
|
||||
|
||||
|
||||
public TradingBot(
|
||||
IExchangeService exchangeService,
|
||||
@@ -132,6 +129,9 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
public void LoadScenario(string scenarioName)
|
||||
{
|
||||
if (Config.Scenario != null)
|
||||
return;
|
||||
|
||||
var scenario = TradingService.GetScenarioByName(scenarioName);
|
||||
if (scenario == null)
|
||||
{
|
||||
@@ -140,7 +140,6 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
else
|
||||
{
|
||||
Scenario = scenario;
|
||||
LoadIndicators(ScenarioHelpers.GetIndicatorsFromScenario(scenario));
|
||||
}
|
||||
}
|
||||
@@ -154,11 +153,15 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
else
|
||||
{
|
||||
Scenario = scenario;
|
||||
LoadIndicators(ScenarioHelpers.GetIndicatorsFromScenario(scenario));
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadIndicators(Scenario scenario)
|
||||
{
|
||||
LoadIndicators(ScenarioHelpers.GetIndicatorsFromScenario(scenario));
|
||||
}
|
||||
|
||||
public void LoadIndicators(IEnumerable<IIndicator> indicators)
|
||||
{
|
||||
foreach (var strategy in indicators)
|
||||
@@ -209,7 +212,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
|
||||
UpdateWalletBalances();
|
||||
if (OptimizedCandles.Count % 100 == 0) // Log every 10th execution
|
||||
if (!Config.IsForBacktest) // Log every 10th execution
|
||||
{
|
||||
Logger.LogInformation($"Candle date : {OptimizedCandles.Last().Date:u}");
|
||||
Logger.LogInformation($"Signals : {Signals.Count}");
|
||||
@@ -223,7 +226,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
foreach (var strategy in Indicators)
|
||||
{
|
||||
IndicatorsValues[strategy.Type] = ((Indicator)strategy).GetStrategyValues();
|
||||
IndicatorsValues[strategy.Type] = ((Indicator)strategy).GetIndicatorValues();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +263,10 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
private async Task UpdateSignals(FixedSizeQueue<Candle> candles)
|
||||
{
|
||||
var signal = TradingBox.GetSignal(candles.ToHashSet(), Indicators, Signals, Scenario.LoopbackPeriod);
|
||||
// If position open and not flipped, do not update signals
|
||||
if (!Config.FlipPosition && Positions.Any(p => !p.IsFinished())) return;
|
||||
|
||||
var signal = TradingBox.GetSignal(candles.ToHashSet(), Indicators, Signals, Config.Scenario.LoopbackPeriod);
|
||||
if (signal == null) return;
|
||||
|
||||
signal.User = Account.User;
|
||||
@@ -272,11 +278,39 @@ public class TradingBot : Bot, ITradingBot
|
||||
if (Config.IsForWatchingOnly || (ExecutionCount < 1 && !Config.IsForBacktest))
|
||||
signal.Status = SignalStatus.Expired;
|
||||
|
||||
Signals.Add(signal);
|
||||
|
||||
var signalText = $"{Config.ScenarioName} trigger a signal. Signal told you " +
|
||||
$"to {signal.Direction} {Config.Ticker} on {Config.Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}";
|
||||
|
||||
|
||||
// Apply Synth-based signal filtering if enabled
|
||||
if (Config.UseSynthApi)
|
||||
{
|
||||
var currentPrice = Config.IsForBacktest
|
||||
? OptimizedCandles.Last().Close
|
||||
: ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
|
||||
|
||||
var signalValidationResult = TradingService.ValidateSynthSignalAsync(signal, currentPrice, Config,
|
||||
Config.IsForBacktest).GetAwaiter().GetResult();
|
||||
|
||||
if (signalValidationResult.Confidence == Confidence.None ||
|
||||
signalValidationResult.Confidence == Confidence.Low ||
|
||||
signalValidationResult.IsBlocked)
|
||||
{
|
||||
signal.Status = SignalStatus.Expired;
|
||||
await LogInformation(
|
||||
$"🚫 **Synth Signal Filter** - Signal {signal.Identifier} blocked by Synth risk assessment. Context : {signalValidationResult.ValidationContext}");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
signal.SetConfidence(signalValidationResult.Confidence);
|
||||
signalText +=
|
||||
$" and Synth risk assessment passed. Context : {signalValidationResult.ValidationContext}";
|
||||
}
|
||||
}
|
||||
|
||||
Signals.Add(signal);
|
||||
|
||||
Logger.LogInformation(signalText);
|
||||
|
||||
if (Config.IsForWatchingOnly && !Config.IsForBacktest && ExecutionCount > 0)
|
||||
@@ -326,7 +360,8 @@ public class TradingBot : Bot, ITradingBot
|
||||
date: position.Open.Date,
|
||||
exchange: Account.Exchange,
|
||||
indicatorType: IndicatorType.Stc, // Use a valid strategy type for recreated signals
|
||||
signalType: SignalType.Signal
|
||||
signalType: SignalType.Signal,
|
||||
indicatorName: "RecreatedSignal"
|
||||
);
|
||||
|
||||
// Since Signal identifier is auto-generated, we need to update our position
|
||||
@@ -414,8 +449,6 @@ public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInformation($"📊 **Position Update**\nUpdating position: `{positionForSignal.SignalIdentifier}`");
|
||||
|
||||
var position = Config.IsForBacktest
|
||||
? positionForSignal
|
||||
: TradingService.GetPositionByIdentifier(positionForSignal.Identifier);
|
||||
@@ -624,6 +657,38 @@ public class TradingBot : Bot, ITradingBot
|
||||
await OpenPosition(signal);
|
||||
}
|
||||
}
|
||||
|
||||
// Synth-based position monitoring for liquidation risk
|
||||
if (Config.UseSynthApi && !Config.IsForBacktest &&
|
||||
positionForSignal.Status == PositionStatus.Filled)
|
||||
{
|
||||
var currentPrice = ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
|
||||
var riskResult = await TradingService.MonitorSynthPositionRiskAsync(
|
||||
Config.Ticker,
|
||||
positionForSignal.OriginDirection,
|
||||
currentPrice,
|
||||
positionForSignal.StopLoss.Price,
|
||||
positionForSignal.Identifier,
|
||||
Config);
|
||||
|
||||
if (riskResult != null && riskResult.ShouldWarn && !string.IsNullOrEmpty(riskResult.WarningMessage))
|
||||
{
|
||||
await LogWarning(riskResult.WarningMessage);
|
||||
}
|
||||
|
||||
if (riskResult.ShouldAutoClose && !string.IsNullOrEmpty(riskResult.EmergencyMessage))
|
||||
{
|
||||
await LogWarning(riskResult.EmergencyMessage);
|
||||
|
||||
var signalForAutoClose =
|
||||
Signals.FirstOrDefault(s => s.Identifier == positionForSignal.SignalIdentifier);
|
||||
if (signalForAutoClose != null)
|
||||
{
|
||||
await CloseTrade(signalForAutoClose, positionForSignal, positionForSignal.StopLoss,
|
||||
currentPrice, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -784,6 +849,20 @@ public class TradingBot : Bot, ITradingBot
|
||||
return false;
|
||||
}
|
||||
|
||||
// Synth-based pre-trade risk assessment
|
||||
if (Config.UseSynthApi)
|
||||
{
|
||||
var currentPrice = Config.IsForBacktest
|
||||
? OptimizedCandles.Last().Close
|
||||
: ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
|
||||
|
||||
if (!(await TradingService.AssessSynthPositionRiskAsync(Config.Ticker, signal.Direction, currentPrice,
|
||||
Config, Config.IsForBacktest)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check cooldown period and loss streak
|
||||
return await CheckCooldownPeriod(signal) && await CheckLossStreak(signal);
|
||||
}
|
||||
@@ -1030,7 +1109,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
// Add PnL (could be positive or negative)
|
||||
Config.BotTradingBalance += position.ProfitAndLoss.Realized;
|
||||
|
||||
|
||||
Logger.LogInformation(
|
||||
$"💰 **Balance Updated**\nNew bot trading balance: `${Config.BotTradingBalance:F2}`");
|
||||
}
|
||||
@@ -1150,7 +1229,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
public decimal GetTotalFees()
|
||||
{
|
||||
decimal totalFees = 0;
|
||||
|
||||
|
||||
foreach (var position in Positions.Where(p => p.Open.Price > 0 && p.Open.Quantity > 0))
|
||||
{
|
||||
totalFees += CalculatePositionFees(position);
|
||||
@@ -1167,22 +1246,22 @@ public class TradingBot : Bot, ITradingBot
|
||||
private decimal CalculatePositionFees(Position position)
|
||||
{
|
||||
decimal fees = 0;
|
||||
|
||||
|
||||
// Calculate position size in USD (leverage is already included in quantity calculation)
|
||||
var positionSizeUsd = position.Open.Price * position.Open.Quantity;
|
||||
|
||||
|
||||
// UI Fee: 0.1% of position size paid BOTH on opening AND closing
|
||||
var uiFeeRate = 0.001m; // 0.1%
|
||||
var uiFeeOpen = positionSizeUsd * uiFeeRate; // Fee paid on opening
|
||||
var uiFeeClose = positionSizeUsd * uiFeeRate; // Fee paid on closing
|
||||
var totalUiFees = uiFeeOpen + uiFeeClose; // Total: 0.2% of position size
|
||||
var uiFeeOpen = positionSizeUsd * uiFeeRate; // Fee paid on opening
|
||||
var uiFeeClose = positionSizeUsd * uiFeeRate; // Fee paid on closing
|
||||
var totalUiFees = uiFeeOpen + uiFeeClose; // Total: 0.2% of position size
|
||||
fees += totalUiFees;
|
||||
|
||||
|
||||
// Network Fee: $0.50 for opening position only
|
||||
// Closing is handled by oracle, so no network fee for closing
|
||||
var networkFeeForOpening = 0.50m;
|
||||
fees += networkFeeForOpening;
|
||||
|
||||
|
||||
return fees;
|
||||
}
|
||||
|
||||
@@ -1236,53 +1315,29 @@ public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
var data = new TradingBotBackup
|
||||
{
|
||||
Name = Name,
|
||||
BotType = Config.BotType,
|
||||
Config = Config,
|
||||
Signals = Signals,
|
||||
Positions = Positions,
|
||||
Timeframe = Config.Timeframe,
|
||||
Ticker = Config.Ticker,
|
||||
ScenarioName = Config.ScenarioName,
|
||||
AccountName = Config.AccountName,
|
||||
IsForWatchingOnly = Config.IsForWatchingOnly,
|
||||
WalletBalances = WalletBalances,
|
||||
MoneyManagement = Config.MoneyManagement,
|
||||
BotTradingBalance = Config.BotTradingBalance,
|
||||
StartupTime = StartupTime,
|
||||
CooldownPeriod = Config.CooldownPeriod,
|
||||
MaxLossStreak = Config.MaxLossStreak,
|
||||
MaxPositionTimeHours = Config.MaxPositionTimeHours ?? 0m,
|
||||
FlipOnlyWhenInProfit = Config.FlipOnlyWhenInProfit,
|
||||
CloseEarlyWhenProfitable = Config.CloseEarlyWhenProfitable,
|
||||
StartupTime = StartupTime
|
||||
};
|
||||
BotService.SaveOrUpdateBotBackup(User, Identifier, Config.BotType, Status, JsonConvert.SerializeObject(data));
|
||||
BotService.SaveOrUpdateBotBackup(User, Identifier, Status, JsonConvert.SerializeObject(data));
|
||||
}
|
||||
|
||||
public override void LoadBackup(BotBackup backup)
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<TradingBotBackup>(backup.Data);
|
||||
Config = new TradingBotConfig
|
||||
{
|
||||
AccountName = data.AccountName,
|
||||
MoneyManagement = data.MoneyManagement,
|
||||
Ticker = data.Ticker,
|
||||
ScenarioName = data.ScenarioName,
|
||||
Timeframe = data.Timeframe,
|
||||
IsForBacktest = false, // Always false when loading from backup
|
||||
IsForWatchingOnly = data.IsForWatchingOnly,
|
||||
BotTradingBalance = data.BotTradingBalance,
|
||||
BotType = data.BotType,
|
||||
CooldownPeriod = data.CooldownPeriod,
|
||||
MaxLossStreak = data.MaxLossStreak,
|
||||
MaxPositionTimeHours = data.MaxPositionTimeHours == 0m ? null : data.MaxPositionTimeHours,
|
||||
FlipOnlyWhenInProfit = data.FlipOnlyWhenInProfit,
|
||||
CloseEarlyWhenProfitable = data.CloseEarlyWhenProfitable,
|
||||
Name = data.Name
|
||||
};
|
||||
|
||||
Signals = data.Signals;
|
||||
Positions = data.Positions;
|
||||
WalletBalances = data.WalletBalances;
|
||||
// Load the configuration directly
|
||||
Config = data.Config;
|
||||
|
||||
// Ensure IsForBacktest is always false when loading from backup
|
||||
Config.IsForBacktest = false;
|
||||
|
||||
// Load runtime state
|
||||
Signals = data.Signals ?? new HashSet<Signal>();
|
||||
Positions = data.Positions ?? new List<Position>();
|
||||
WalletBalances = data.WalletBalances ?? new Dictionary<DateTime, decimal>();
|
||||
PreloadSince = data.StartupTime;
|
||||
Identifier = backup.Identifier;
|
||||
User = backup.User;
|
||||
@@ -1307,7 +1362,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
// Create a fake signal for manual position opening
|
||||
var signal = new Signal(Config.Ticker, direction, Confidence.Low, lastCandle, lastCandle.Date,
|
||||
TradingExchanges.GmxV2,
|
||||
IndicatorType.Stc, SignalType.Signal);
|
||||
IndicatorType.Stc, SignalType.Signal, "Manual Signal");
|
||||
signal.Status = SignalStatus.WaitingForPosition; // Ensure status is correct
|
||||
signal.User = Account.User; // Assign user
|
||||
|
||||
@@ -1433,7 +1488,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
|
||||
// If scenario changed, reload it
|
||||
var currentScenario = Scenario?.Name;
|
||||
var currentScenario = Config.Scenario?.Name;
|
||||
if (Config.ScenarioName != currentScenario)
|
||||
{
|
||||
LoadScenario(Config.ScenarioName);
|
||||
@@ -1485,29 +1540,36 @@ public class TradingBot : Bot, ITradingBot
|
||||
FlipOnlyWhenInProfit = Config.FlipOnlyWhenInProfit,
|
||||
FlipPosition = Config.FlipPosition,
|
||||
Name = Config.Name,
|
||||
CloseEarlyWhenProfitable = Config.CloseEarlyWhenProfitable
|
||||
CloseEarlyWhenProfitable = Config.CloseEarlyWhenProfitable,
|
||||
UseSynthApi = Config.UseSynthApi,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class TradingBotBackup
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public BotType BotType { get; set; }
|
||||
/// <summary>
|
||||
/// The complete trading bot configuration
|
||||
/// </summary>
|
||||
public TradingBotConfig Config { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Runtime state: Active signals for the bot
|
||||
/// </summary>
|
||||
public HashSet<Signal> Signals { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Runtime state: Open and closed positions for the bot
|
||||
/// </summary>
|
||||
public List<Position> Positions { get; set; }
|
||||
public Timeframe Timeframe { get; set; }
|
||||
public Ticker Ticker { get; set; }
|
||||
public string ScenarioName { get; set; }
|
||||
public string AccountName { get; set; }
|
||||
public bool IsForWatchingOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Runtime state: Historical wallet balances over time
|
||||
/// </summary>
|
||||
public Dictionary<DateTime, decimal> WalletBalances { get; set; }
|
||||
public MoneyManagement MoneyManagement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Runtime state: When the bot was started
|
||||
/// </summary>
|
||||
public DateTime StartupTime { get; set; }
|
||||
public decimal BotTradingBalance { get; set; }
|
||||
public int CooldownPeriod { get; set; }
|
||||
public int MaxLossStreak { get; set; }
|
||||
public decimal MaxPositionTimeHours { get; set; }
|
||||
public bool FlipOnlyWhenInProfit { get; set; }
|
||||
public bool CloseEarlyWhenProfitable { get; set; }
|
||||
}
|
||||
@@ -45,7 +45,7 @@ namespace Managing.Application.ManageBot
|
||||
return _botRepository.GetBots().FirstOrDefault(b => b.Identifier == identifier);
|
||||
}
|
||||
|
||||
public void SaveOrUpdateBotBackup(User user, string identifier, BotType botType, BotStatus status, string data)
|
||||
public void SaveOrUpdateBotBackup(User user, string identifier, BotStatus status, string data)
|
||||
{
|
||||
var backup = GetBotBackup(identifier);
|
||||
|
||||
@@ -62,7 +62,6 @@ namespace Managing.Application.ManageBot
|
||||
LastStatus = status,
|
||||
User = user,
|
||||
Identifier = identifier,
|
||||
BotType = botType,
|
||||
Data = data
|
||||
};
|
||||
|
||||
@@ -118,40 +117,29 @@ namespace Managing.Application.ManageBot
|
||||
object bot = null;
|
||||
Task botTask = null;
|
||||
|
||||
switch (backupBot.BotType)
|
||||
var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
|
||||
|
||||
// Get the config directly from the backup
|
||||
var scalpingConfig = scalpingBotData.Config;
|
||||
|
||||
// Ensure the money management is properly loaded from database if needed
|
||||
if (scalpingConfig.MoneyManagement != null &&
|
||||
!string.IsNullOrEmpty(scalpingConfig.MoneyManagement.Name))
|
||||
{
|
||||
case BotType.ScalpingBot:
|
||||
case BotType.FlippingBot:
|
||||
var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
|
||||
var scalpingMoneyManagement =
|
||||
_moneyManagementService.GetMoneyMangement(scalpingBotData.MoneyManagement.Name).Result;
|
||||
|
||||
// Create config from backup data
|
||||
var scalpingConfig = new TradingBotConfig
|
||||
{
|
||||
AccountName = scalpingBotData.AccountName,
|
||||
MoneyManagement = scalpingMoneyManagement,
|
||||
Ticker = scalpingBotData.Ticker,
|
||||
ScenarioName = scalpingBotData.ScenarioName,
|
||||
Timeframe = scalpingBotData.Timeframe,
|
||||
IsForWatchingOnly = scalpingBotData.IsForWatchingOnly,
|
||||
BotTradingBalance = scalpingBotData.BotTradingBalance,
|
||||
BotType = scalpingBotData.BotType,
|
||||
Name = scalpingBotData.Name,
|
||||
CooldownPeriod = scalpingBotData.CooldownPeriod,
|
||||
MaxLossStreak = scalpingBotData.MaxLossStreak,
|
||||
MaxPositionTimeHours = scalpingBotData.MaxPositionTimeHours == 0m ? null : scalpingBotData.MaxPositionTimeHours,
|
||||
FlipOnlyWhenInProfit = scalpingBotData.FlipOnlyWhenInProfit,
|
||||
IsForBacktest = false,
|
||||
FlipPosition = false,
|
||||
CloseEarlyWhenProfitable = scalpingBotData.CloseEarlyWhenProfitable
|
||||
};
|
||||
|
||||
bot = CreateTradingBot(scalpingConfig);
|
||||
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
||||
break;
|
||||
var moneyManagement = _moneyManagementService
|
||||
.GetMoneyMangement(scalpingConfig.MoneyManagement.Name).Result;
|
||||
if (moneyManagement != null)
|
||||
{
|
||||
scalpingConfig.MoneyManagement = moneyManagement;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure critical properties are set correctly for restored bots
|
||||
scalpingConfig.IsForBacktest = false;
|
||||
|
||||
bot = CreateTradingBot(scalpingConfig);
|
||||
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
||||
|
||||
if (bot != null && botTask != null)
|
||||
{
|
||||
var botWrapper = new BotTaskWrapper(botTask, bot.GetType(), bot);
|
||||
@@ -258,14 +246,14 @@ namespace Managing.Application.ManageBot
|
||||
|
||||
// Update the bot configuration first
|
||||
var updateResult = await tradingBot.UpdateConfiguration(newConfig, allowNameChange: true);
|
||||
|
||||
|
||||
if (updateResult)
|
||||
{
|
||||
// Update the dictionary key
|
||||
if (_botTasks.TryRemove(identifier, out var removedWrapper))
|
||||
{
|
||||
_botTasks.TryAdd(newConfig.Name, removedWrapper);
|
||||
|
||||
|
||||
// Update the backup with the new identifier
|
||||
if (!newConfig.IsForBacktest)
|
||||
{
|
||||
@@ -275,7 +263,7 @@ namespace Managing.Application.ManageBot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return updateResult;
|
||||
}
|
||||
else
|
||||
@@ -288,7 +276,6 @@ namespace Managing.Application.ManageBot
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ITradingBot CreateTradingBot(TradingBotConfig config)
|
||||
{
|
||||
|
||||
@@ -52,7 +52,9 @@ namespace Managing.Application.ManageBot
|
||||
|
||||
var usdcBalance = account.Balances.FirstOrDefault(b => b.TokenName == Ticker.USDC.ToString());
|
||||
|
||||
if (usdcBalance == null || usdcBalance.Value < request.Config.BotTradingBalance)
|
||||
if (usdcBalance == null ||
|
||||
usdcBalance.Value < Constants.GMX.Config.MinimumPositionAmount ||
|
||||
usdcBalance.Value < request.Config.BotTradingBalance)
|
||||
{
|
||||
throw new Exception($"Account {request.Config.AccountName} has no USDC balance or not enough balance");
|
||||
}
|
||||
@@ -64,12 +66,14 @@ namespace Managing.Application.ManageBot
|
||||
MoneyManagement = request.Config.MoneyManagement,
|
||||
Ticker = request.Config.Ticker,
|
||||
ScenarioName = request.Config.ScenarioName,
|
||||
Scenario = request.Config.Scenario,
|
||||
Timeframe = request.Config.Timeframe,
|
||||
IsForWatchingOnly = request.Config.IsForWatchingOnly,
|
||||
BotTradingBalance = request.Config.BotTradingBalance,
|
||||
BotType = request.Config.BotType,
|
||||
IsForBacktest = request.Config.IsForBacktest,
|
||||
CooldownPeriod = request.Config.CooldownPeriod > 0 ? request.Config.CooldownPeriod : 1, // Default to 1 if not set
|
||||
CooldownPeriod =
|
||||
request.Config.CooldownPeriod > 0 ? request.Config.CooldownPeriod : 1, // Default to 1 if not set
|
||||
MaxLossStreak = request.Config.MaxLossStreak,
|
||||
MaxPositionTimeHours = request.Config.MaxPositionTimeHours, // Properly handle nullable value
|
||||
FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit,
|
||||
@@ -85,15 +89,15 @@ namespace Managing.Application.ManageBot
|
||||
bot.User = request.User;
|
||||
_botService.AddSimpleBotToCache(bot);
|
||||
return bot.GetStatus();
|
||||
|
||||
|
||||
case BotType.ScalpingBot:
|
||||
case BotType.FlippingBot:
|
||||
var tradingBot = _botFactory.CreateTradingBot(configToUse);
|
||||
tradingBot.User = request.User;
|
||||
|
||||
|
||||
// Log the configuration being used
|
||||
await LogBotConfigurationAsync(tradingBot, $"{configToUse.BotType} created");
|
||||
|
||||
|
||||
_botService.AddTradingBotToCache(tradingBot);
|
||||
return tradingBot.GetStatus();
|
||||
}
|
||||
@@ -112,16 +116,16 @@ namespace Managing.Application.ManageBot
|
||||
{
|
||||
var config = bot.GetConfiguration();
|
||||
var logMessage = $"{context} - Bot: {config.Name}, " +
|
||||
$"Type: {config.BotType}, " +
|
||||
$"Account: {config.AccountName}, " +
|
||||
$"Ticker: {config.Ticker}, " +
|
||||
$"Balance: {config.BotTradingBalance}, " +
|
||||
$"MaxTime: {config.MaxPositionTimeHours?.ToString() ?? "Disabled"}, " +
|
||||
$"FlipOnlyProfit: {config.FlipOnlyWhenInProfit}, " +
|
||||
$"FlipPosition: {config.FlipPosition}, " +
|
||||
$"Cooldown: {config.CooldownPeriod}, " +
|
||||
$"MaxLoss: {config.MaxLossStreak}";
|
||||
|
||||
$"Type: {config.BotType}, " +
|
||||
$"Account: {config.AccountName}, " +
|
||||
$"Ticker: {config.Ticker}, " +
|
||||
$"Balance: {config.BotTradingBalance}, " +
|
||||
$"MaxTime: {config.MaxPositionTimeHours?.ToString() ?? "Disabled"}, " +
|
||||
$"FlipOnlyProfit: {config.FlipOnlyWhenInProfit}, " +
|
||||
$"FlipPosition: {config.FlipPosition}, " +
|
||||
$"Cooldown: {config.CooldownPeriod}, " +
|
||||
$"MaxLoss: {config.MaxLossStreak}";
|
||||
|
||||
// Log through the bot's logger (this will use the bot's logging mechanism)
|
||||
// For now, we'll just add a comment that this could be enhanced with actual logging
|
||||
// Console.WriteLine(logMessage); // Could be replaced with proper logging
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace Managing.Application.Scenarios
|
||||
return _tradingService.GetScenarioByName(name);
|
||||
}
|
||||
|
||||
public IEnumerable<Indicator> GetStrategies()
|
||||
public IEnumerable<Indicator> GetIndicators()
|
||||
{
|
||||
return _tradingService.GetStrategies();
|
||||
}
|
||||
|
||||
324
src/Managing.Application/Synth/SynthApiClient.cs
Normal file
324
src/Managing.Application/Synth/SynthApiClient.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
using System.Text.Json;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.Synth.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Managing.Application.Synth;
|
||||
|
||||
/// <summary>
|
||||
/// Client for communicating with the Synth API
|
||||
/// </summary>
|
||||
public class SynthApiClient : ISynthApiClient, IDisposable
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<SynthApiClient> _logger;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
// Private configuration - should come from app settings or environment variables
|
||||
private readonly string _apiKey;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public SynthApiClient(HttpClient httpClient, ILogger<SynthApiClient> logger)
|
||||
{
|
||||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
// TODO: These should come from IConfiguration or environment variables
|
||||
_apiKey = Environment.GetEnvironmentVariable("SYNTH_API_KEY") ??
|
||||
"bfd2a078b412452af2e01ca74b2a7045d4ae411a85943342";
|
||||
_baseUrl = Environment.GetEnvironmentVariable("SYNTH_BASE_URL") ?? "https://api.synthdata.co";
|
||||
|
||||
// Configure HttpClient once
|
||||
ConfigureHttpClient();
|
||||
|
||||
// Configure JSON options
|
||||
_jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the HTTP client with API settings
|
||||
/// </summary>
|
||||
private void ConfigureHttpClient()
|
||||
{
|
||||
// Validate API configuration
|
||||
if (string.IsNullOrEmpty(_apiKey) || string.IsNullOrEmpty(_baseUrl))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Synth API configuration is missing. Please set SYNTH_API_KEY and SYNTH_BASE_URL environment variables.");
|
||||
}
|
||||
|
||||
// Set base address and authorization
|
||||
_httpClient.BaseAddress = new Uri(_baseUrl);
|
||||
_httpClient.DefaultRequestHeaders.Clear();
|
||||
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Apikey {_apiKey}");
|
||||
_httpClient.Timeout = TimeSpan.FromSeconds(30);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the current leaderboard from Synth API
|
||||
/// </summary>
|
||||
public async Task<List<MinerInfo>> GetLeaderboardAsync(SynthConfiguration config)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("🔍 **Synth API** - Fetching leaderboard");
|
||||
|
||||
var response = await _httpClient.GetAsync("/leaderboard/latest");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
$"Synth API leaderboard request failed: {response.StatusCode} - {response.ReasonPhrase}");
|
||||
return new List<MinerInfo>();
|
||||
}
|
||||
|
||||
var jsonContent = await response.Content.ReadAsStringAsync();
|
||||
var miners = JsonSerializer.Deserialize<List<MinerInfo>>(jsonContent, _jsonOptions);
|
||||
|
||||
_logger.LogInformation($"📊 **Synth API** - Retrieved {miners?.Count ?? 0} miners from leaderboard");
|
||||
|
||||
return miners ?? new List<MinerInfo>();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "HTTP error while fetching Synth leaderboard");
|
||||
return new List<MinerInfo>();
|
||||
}
|
||||
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
|
||||
{
|
||||
_logger.LogError(ex, "Timeout while fetching Synth leaderboard");
|
||||
return new List<MinerInfo>();
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
_logger.LogError(ex, "JSON deserialization error while parsing Synth leaderboard");
|
||||
return new List<MinerInfo>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error while fetching Synth leaderboard");
|
||||
return new List<MinerInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches historical leaderboard data from Synth API for a specific time range
|
||||
/// </summary>
|
||||
public async Task<List<MinerInfo>> GetHistoricalLeaderboardAsync(DateTime startTime, DateTime endTime, SynthConfiguration config)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Format dates to ISO 8601 format as required by the API
|
||||
var startTimeStr = Uri.EscapeDataString(startTime.ToString("yyyy-MM-ddTHH:mm:ssZ"));
|
||||
var endTimeStr = Uri.EscapeDataString(endTime.ToString("yyyy-MM-ddTHH:mm:ssZ"));
|
||||
|
||||
var url = $"/leaderboard/historical?start_time={startTimeStr}&end_time={endTimeStr}";
|
||||
|
||||
_logger.LogInformation($"🔍 **Synth API** - Fetching historical leaderboard from {startTime:yyyy-MM-dd HH:mm} to {endTime:yyyy-MM-dd HH:mm}");
|
||||
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
$"Synth API historical leaderboard request failed: {response.StatusCode} - {response.ReasonPhrase}");
|
||||
return new List<MinerInfo>();
|
||||
}
|
||||
|
||||
var jsonContent = await response.Content.ReadAsStringAsync();
|
||||
var miners = JsonSerializer.Deserialize<List<MinerInfo>>(jsonContent, _jsonOptions);
|
||||
|
||||
_logger.LogInformation($"📊 **Synth API** - Retrieved {miners?.Count ?? 0} miners from historical leaderboard");
|
||||
|
||||
return miners ?? new List<MinerInfo>();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "HTTP error while fetching Synth historical leaderboard");
|
||||
return new List<MinerInfo>();
|
||||
}
|
||||
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
|
||||
{
|
||||
_logger.LogError(ex, "Timeout while fetching Synth historical leaderboard");
|
||||
return new List<MinerInfo>();
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
_logger.LogError(ex, "JSON deserialization error while parsing Synth historical leaderboard");
|
||||
return new List<MinerInfo>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error while fetching Synth historical leaderboard");
|
||||
return new List<MinerInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches latest predictions from specified miners
|
||||
/// </summary>
|
||||
public async Task<List<MinerPrediction>> GetMinerPredictionsAsync(
|
||||
List<int> minerUids,
|
||||
string asset,
|
||||
int timeIncrement,
|
||||
int timeLength,
|
||||
SynthConfiguration config)
|
||||
{
|
||||
if (minerUids == null || !minerUids.Any())
|
||||
{
|
||||
_logger.LogWarning("No miner UIDs provided for prediction request");
|
||||
return new List<MinerPrediction>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Build URL with proper array formatting for miner parameter
|
||||
var queryParams = new List<string>
|
||||
{
|
||||
$"asset={Uri.EscapeDataString(asset)}",
|
||||
$"time_increment={timeIncrement}",
|
||||
$"time_length={timeLength}"
|
||||
};
|
||||
|
||||
// Add each miner UID as a separate parameter (standard array query parameter format)
|
||||
foreach (var minerUid in minerUids)
|
||||
{
|
||||
queryParams.Add($"miner={minerUid}");
|
||||
}
|
||||
|
||||
var url = $"/prediction/latest?{string.Join("&", queryParams)}";
|
||||
|
||||
_logger.LogInformation(
|
||||
$"🔮 **Synth API** - Fetching predictions for {minerUids.Count} miners, asset: {asset}, time: {timeLength}s");
|
||||
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
$"Synth API predictions request failed: {response.StatusCode} - {response.ReasonPhrase}");
|
||||
return new List<MinerPrediction>();
|
||||
}
|
||||
|
||||
var jsonContent = await response.Content.ReadAsStringAsync();
|
||||
var predictions = JsonSerializer.Deserialize<List<MinerPrediction>>(jsonContent, _jsonOptions);
|
||||
|
||||
var totalPaths = predictions?.Sum(p => p.NumSimulations) ?? 0;
|
||||
_logger.LogInformation(
|
||||
$"📈 **Synth API** - Retrieved {predictions?.Count ?? 0} predictions with {totalPaths} total simulation paths");
|
||||
|
||||
return predictions ?? new List<MinerPrediction>();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, $"HTTP error while fetching Synth predictions for {asset}");
|
||||
return new List<MinerPrediction>();
|
||||
}
|
||||
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
|
||||
{
|
||||
_logger.LogError(ex, $"Timeout while fetching Synth predictions for {asset}");
|
||||
return new List<MinerPrediction>();
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
_logger.LogError(ex, $"JSON deserialization error while parsing Synth predictions for {asset}");
|
||||
return new List<MinerPrediction>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Unexpected error while fetching Synth predictions for {asset}");
|
||||
return new List<MinerPrediction>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches historical predictions from specified miners for a specific time point
|
||||
/// </summary>
|
||||
public async Task<List<MinerPrediction>> GetHistoricalMinerPredictionsAsync(
|
||||
List<int> minerUids,
|
||||
string asset,
|
||||
DateTime startTime,
|
||||
int timeIncrement,
|
||||
int timeLength,
|
||||
SynthConfiguration config)
|
||||
{
|
||||
if (minerUids == null || !minerUids.Any())
|
||||
{
|
||||
_logger.LogWarning("No miner UIDs provided for historical prediction request");
|
||||
return new List<MinerPrediction>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Format start time to ISO 8601 format as required by the API
|
||||
var startTimeStr = Uri.EscapeDataString(startTime.ToString("yyyy-MM-ddTHH:mm:ssZ"));
|
||||
|
||||
// Build URL with proper array formatting for miner parameter
|
||||
var queryParams = new List<string>
|
||||
{
|
||||
$"asset={Uri.EscapeDataString(asset)}",
|
||||
$"start_time={startTimeStr}",
|
||||
$"time_increment={timeIncrement}",
|
||||
$"time_length={timeLength}"
|
||||
};
|
||||
|
||||
// Add each miner UID as a separate parameter (standard array query parameter format)
|
||||
foreach (var minerUid in minerUids)
|
||||
{
|
||||
queryParams.Add($"miner={minerUid}");
|
||||
}
|
||||
|
||||
var url = $"/prediction/historical?{string.Join("&", queryParams)}";
|
||||
|
||||
_logger.LogInformation(
|
||||
$"🔮 **Synth API** - Fetching historical predictions for {minerUids.Count} miners, asset: {asset}, time: {startTime:yyyy-MM-dd HH:mm}, duration: {timeLength}s");
|
||||
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
$"Synth API historical predictions request failed: {response.StatusCode} - {response.ReasonPhrase}");
|
||||
return new List<MinerPrediction>();
|
||||
}
|
||||
|
||||
var jsonContent = await response.Content.ReadAsStringAsync();
|
||||
var predictions = JsonSerializer.Deserialize<List<MinerPrediction>>(jsonContent, _jsonOptions);
|
||||
|
||||
var totalPaths = predictions?.Sum(p => p.NumSimulations) ?? 0;
|
||||
_logger.LogInformation(
|
||||
$"📈 **Synth API** - Retrieved {predictions?.Count ?? 0} historical predictions with {totalPaths} total simulation paths");
|
||||
|
||||
return predictions ?? new List<MinerPrediction>();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, $"HTTP error while fetching Synth historical predictions for {asset}");
|
||||
return new List<MinerPrediction>();
|
||||
}
|
||||
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
|
||||
{
|
||||
_logger.LogError(ex, $"Timeout while fetching Synth historical predictions for {asset}");
|
||||
return new List<MinerPrediction>();
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
_logger.LogError(ex, $"JSON deserialization error while parsing Synth historical predictions for {asset}");
|
||||
return new List<MinerPrediction>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Unexpected error while fetching Synth historical predictions for {asset}");
|
||||
return new List<MinerPrediction>();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_httpClient?.Dispose();
|
||||
}
|
||||
}
|
||||
169
src/Managing.Application/Synth/SynthConfigurationHelper.cs
Normal file
169
src/Managing.Application/Synth/SynthConfigurationHelper.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using Managing.Domain.Synth.Models;
|
||||
|
||||
namespace Managing.Application.Synth;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for creating and configuring Synth API integration
|
||||
/// </summary>
|
||||
public static class SynthConfigurationHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a default Synth configuration for live trading
|
||||
/// </summary>
|
||||
/// <returns>A configured SynthConfiguration instance</returns>
|
||||
public static SynthConfiguration CreateLiveTradingConfig()
|
||||
{
|
||||
return new SynthConfiguration
|
||||
{
|
||||
IsEnabled = true,
|
||||
TopMinersCount = 10,
|
||||
TimeIncrement = 300, // 5 minutes
|
||||
DefaultTimeLength = 86400, // 24 hours
|
||||
MaxLiquidationProbability = 0.10m, // 10% max risk
|
||||
PredictionCacheDurationMinutes = 5,
|
||||
UseForPositionSizing = true,
|
||||
UseForSignalFiltering = true,
|
||||
UseForDynamicStopLoss = true
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a conservative Synth configuration with lower risk tolerances
|
||||
/// </summary>
|
||||
/// <returns>A conservative SynthConfiguration instance</returns>
|
||||
public static SynthConfiguration CreateConservativeConfig()
|
||||
{
|
||||
return new SynthConfiguration
|
||||
{
|
||||
IsEnabled = true,
|
||||
TopMinersCount = 10,
|
||||
TimeIncrement = 300, // 5 minutes
|
||||
DefaultTimeLength = 86400, // 24 hours
|
||||
MaxLiquidationProbability = 0.05m, // 5% max risk (more conservative)
|
||||
PredictionCacheDurationMinutes = 3, // More frequent updates
|
||||
UseForPositionSizing = true,
|
||||
UseForSignalFiltering = true,
|
||||
UseForDynamicStopLoss = true
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an aggressive Synth configuration with higher risk tolerances
|
||||
/// </summary>
|
||||
/// <returns>An aggressive SynthConfiguration instance</returns>
|
||||
public static SynthConfiguration CreateAggressiveConfig()
|
||||
{
|
||||
return new SynthConfiguration
|
||||
{
|
||||
IsEnabled = true,
|
||||
TopMinersCount = 15, // More miners for broader consensus
|
||||
TimeIncrement = 300, // 5 minutes
|
||||
DefaultTimeLength = 86400, // 24 hours
|
||||
MaxLiquidationProbability = 0.15m, // 15% max risk (more aggressive)
|
||||
PredictionCacheDurationMinutes = 7, // Less frequent updates to reduce API calls
|
||||
UseForPositionSizing = true,
|
||||
UseForSignalFiltering = false, // Don't filter signals in aggressive mode
|
||||
UseForDynamicStopLoss = true
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a disabled Synth configuration (bot will operate without Synth predictions)
|
||||
/// </summary>
|
||||
/// <returns>A disabled SynthConfiguration instance</returns>
|
||||
public static SynthConfiguration CreateDisabledConfig()
|
||||
{
|
||||
return new SynthConfiguration
|
||||
{
|
||||
IsEnabled = false,
|
||||
TopMinersCount = 10,
|
||||
TimeIncrement = 300,
|
||||
DefaultTimeLength = 86400,
|
||||
MaxLiquidationProbability = 0.10m,
|
||||
PredictionCacheDurationMinutes = 5,
|
||||
UseForPositionSizing = false,
|
||||
UseForSignalFiltering = false,
|
||||
UseForDynamicStopLoss = false
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Synth configuration optimized for backtesting (disabled)
|
||||
/// </summary>
|
||||
/// <returns>A backtesting-optimized SynthConfiguration instance</returns>
|
||||
public static SynthConfiguration CreateBacktestConfig()
|
||||
{
|
||||
// Synth predictions are not available for historical data, so always disabled for backtests
|
||||
return CreateDisabledConfig();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates and provides suggestions for improving a Synth configuration
|
||||
/// </summary>
|
||||
/// <param name="config">The configuration to validate</param>
|
||||
/// <returns>List of validation messages and suggestions</returns>
|
||||
public static List<string> ValidateConfiguration(SynthConfiguration config)
|
||||
{
|
||||
var messages = new List<string>();
|
||||
|
||||
if (config == null)
|
||||
{
|
||||
messages.Add("❌ Configuration is null");
|
||||
return messages;
|
||||
}
|
||||
|
||||
if (!config.IsEnabled)
|
||||
{
|
||||
messages.Add("ℹ️ Synth API is disabled - bot will operate without predictions");
|
||||
return messages;
|
||||
}
|
||||
|
||||
if (config.TopMinersCount <= 0)
|
||||
{
|
||||
messages.Add("❌ TopMinersCount must be greater than 0");
|
||||
}
|
||||
else if (config.TopMinersCount > 20)
|
||||
{
|
||||
messages.Add("⚠️ TopMinersCount > 20 may result in slower performance and higher API usage");
|
||||
}
|
||||
|
||||
if (config.TimeIncrement <= 0)
|
||||
{
|
||||
messages.Add("❌ TimeIncrement must be greater than 0");
|
||||
}
|
||||
|
||||
if (config.DefaultTimeLength <= 0)
|
||||
{
|
||||
messages.Add("❌ DefaultTimeLength must be greater than 0");
|
||||
}
|
||||
|
||||
if (config.MaxLiquidationProbability < 0 || config.MaxLiquidationProbability > 1)
|
||||
{
|
||||
messages.Add("❌ MaxLiquidationProbability must be between 0 and 1");
|
||||
}
|
||||
else if (config.MaxLiquidationProbability < 0.02m)
|
||||
{
|
||||
messages.Add("⚠️ MaxLiquidationProbability < 2% is very conservative and may block many trades");
|
||||
}
|
||||
else if (config.MaxLiquidationProbability > 0.20m)
|
||||
{
|
||||
messages.Add("⚠️ MaxLiquidationProbability > 20% is very aggressive and may increase risk");
|
||||
}
|
||||
|
||||
if (config.PredictionCacheDurationMinutes <= 0)
|
||||
{
|
||||
messages.Add("❌ PredictionCacheDurationMinutes must be greater than 0");
|
||||
}
|
||||
else if (config.PredictionCacheDurationMinutes < 1)
|
||||
{
|
||||
messages.Add("⚠️ Cache duration < 1 minute may result in excessive API calls");
|
||||
}
|
||||
|
||||
if (messages.Count == 0)
|
||||
{
|
||||
messages.Add("✅ Configuration appears valid");
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
1194
src/Managing.Application/Synth/SynthPredictionService.cs
Normal file
1194
src/Managing.Application/Synth/SynthPredictionService.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,12 @@
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Shared.Helpers;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Synth.Models;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Infrastructure.Evm.Models.Privy;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -22,6 +24,7 @@ public class TradingService : ITradingService
|
||||
private readonly IStatisticRepository _statisticRepository;
|
||||
private readonly IEvmManager _evmManager;
|
||||
private readonly ILogger<TradingService> _logger;
|
||||
private readonly ISynthPredictionService _synthPredictionService;
|
||||
|
||||
public TradingService(
|
||||
ITradingRepository tradingRepository,
|
||||
@@ -31,7 +34,8 @@ public class TradingService : ITradingService
|
||||
ICacheService cacheService,
|
||||
IMessengerService messengerService,
|
||||
IStatisticRepository statisticRepository,
|
||||
IEvmManager evmManager)
|
||||
IEvmManager evmManager,
|
||||
ISynthPredictionService synthPredictionService)
|
||||
{
|
||||
_tradingRepository = tradingRepository;
|
||||
_exchangeService = exchangeService;
|
||||
@@ -41,6 +45,7 @@ public class TradingService : ITradingService
|
||||
_messengerService = messengerService;
|
||||
_statisticRepository = statisticRepository;
|
||||
_evmManager = evmManager;
|
||||
_synthPredictionService = synthPredictionService;
|
||||
}
|
||||
|
||||
public void DeleteScenario(string name)
|
||||
@@ -397,4 +402,25 @@ public class TradingService : ITradingService
|
||||
return new PrivyInitAddressResponse { Success = false, Error = ex.Message };
|
||||
}
|
||||
}
|
||||
|
||||
// Synth API integration methods
|
||||
public async Task<SignalValidationResult> ValidateSynthSignalAsync(Signal signal, decimal currentPrice,
|
||||
TradingBotConfig botConfig, bool isBacktest)
|
||||
{
|
||||
return await _synthPredictionService.ValidateSignalAsync(signal, currentPrice, botConfig, isBacktest);
|
||||
}
|
||||
|
||||
public async Task<bool> AssessSynthPositionRiskAsync(Ticker ticker, TradeDirection direction, decimal currentPrice,
|
||||
TradingBotConfig botConfig, bool isBacktest)
|
||||
{
|
||||
return await _synthPredictionService.AssessPositionRiskAsync(ticker, direction, currentPrice,
|
||||
botConfig, isBacktest);
|
||||
}
|
||||
|
||||
public async Task<SynthRiskResult> MonitorSynthPositionRiskAsync(Ticker ticker, TradeDirection direction,
|
||||
decimal currentPrice, decimal liquidationPrice, string positionIdentifier, TradingBotConfig botConfig)
|
||||
{
|
||||
return await _synthPredictionService.MonitorPositionRiskAsync(ticker, direction, currentPrice, liquidationPrice,
|
||||
positionIdentifier, botConfig);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ using Managing.Application.MoneyManagements;
|
||||
using Managing.Application.Scenarios;
|
||||
using Managing.Application.Shared;
|
||||
using Managing.Application.Shared.Behaviours;
|
||||
using Managing.Application.Synth;
|
||||
using Managing.Application.Trading;
|
||||
using Managing.Application.Trading.Commands;
|
||||
using Managing.Application.Users;
|
||||
@@ -95,6 +96,8 @@ public static class ApiBootstrap
|
||||
services.AddTransient<IPrivyService, PrivyService>();
|
||||
services.AddTransient<IWeb3ProxyService, Web3ProxyService>();
|
||||
services.AddTransient<IWebhookService, WebhookService>();
|
||||
services.AddTransient<ISynthPredictionService, SynthPredictionService>();
|
||||
services.AddTransient<ISynthApiClient, SynthApiClient>();
|
||||
|
||||
return services;
|
||||
}
|
||||
@@ -133,6 +136,7 @@ public static class ApiBootstrap
|
||||
services.AddTransient<IBotRepository, BotRepository>();
|
||||
services.AddTransient<IWorkerRepository, WorkerRepository>();
|
||||
services.AddTransient<IAgentBalanceRepository, AgentBalanceRepository>();
|
||||
services.AddTransient<ISynthRepository, SynthRepository>();
|
||||
|
||||
// Cache
|
||||
services.AddDistributedMemoryCache();
|
||||
|
||||
@@ -12,6 +12,7 @@ using Managing.Application.ManageBot;
|
||||
using Managing.Application.MoneyManagements;
|
||||
using Managing.Application.Scenarios;
|
||||
using Managing.Application.Shared;
|
||||
using Managing.Application.Synth;
|
||||
using Managing.Application.Trading;
|
||||
using Managing.Application.Trading.Commands;
|
||||
using Managing.Application.Users;
|
||||
@@ -66,6 +67,7 @@ public static class WorkersBootstrap
|
||||
services.AddSingleton<ISettingsService, SettingsService>();
|
||||
services.AddSingleton<IBacktester, Backtester>();
|
||||
services.AddSingleton<IBotService, BotService>();
|
||||
services.AddSingleton<ISynthPredictionService, SynthPredictionService>();
|
||||
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
|
||||
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
|
||||
|
||||
@@ -111,6 +113,7 @@ public static class WorkersBootstrap
|
||||
services.AddTransient<IBacktestRepository, BacktestRepository>();
|
||||
services.AddTransient<IBotRepository, BotRepository>();
|
||||
services.AddTransient<IUserRepository, UserRepository>();
|
||||
services.AddTransient<ISynthRepository, SynthRepository>();
|
||||
|
||||
// Cache
|
||||
services.AddDistributedMemoryCache();
|
||||
@@ -126,6 +129,7 @@ public static class WorkersBootstrap
|
||||
services.AddSingleton<IBinanceSocketClient, BinanceSocketClient>();
|
||||
services.AddSingleton<IKrakenSocketClient, KrakenSocketClient>();
|
||||
services.AddSingleton<IPrivyService, PrivyService>();
|
||||
services.AddSingleton<ISynthApiClient, SynthApiClient>();
|
||||
|
||||
// Web3Proxy Configuration
|
||||
services.Configure<Web3ProxySettings>(configuration.GetSection("Web3Proxy"));
|
||||
|
||||
@@ -405,4 +405,14 @@ public static class Enums
|
||||
Position,
|
||||
MoneyManagement
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Risk tolerance levels for trading strategies
|
||||
/// </summary>
|
||||
public enum RiskToleranceLevel
|
||||
{
|
||||
Conservative = 1,
|
||||
Moderate = 2,
|
||||
Aggressive = 3
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,26 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Managing.Core.FixedSizedQueue;
|
||||
|
||||
public class FixedSizeQueue<T> : Queue<T>
|
||||
{
|
||||
private readonly int _maxSize;
|
||||
|
||||
/// <summary>
|
||||
/// Parameterless constructor for serialization support
|
||||
/// </summary>
|
||||
public FixedSizeQueue() : this(500) // Default size
|
||||
{
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public FixedSizeQueue(int maxSize) => _maxSize = maxSize;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum size of the queue (for serialization)
|
||||
/// </summary>
|
||||
public int MaxSize => _maxSize;
|
||||
|
||||
public new void Enqueue(T item)
|
||||
{
|
||||
while (Count >= _maxSize) Dequeue();
|
||||
|
||||
@@ -24,7 +24,7 @@ public class Backtest
|
||||
Signals = signals;
|
||||
Candles = candles;
|
||||
WalletBalances = new List<KeyValuePair<DateTime, decimal>>();
|
||||
StrategiesValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||
IndicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||
|
||||
// Initialize start and end dates if candles are provided
|
||||
if (candles != null && candles.Count > 0)
|
||||
@@ -55,7 +55,7 @@ public class Backtest
|
||||
[Required] public List<KeyValuePair<DateTime, decimal>> WalletBalances { get; set; }
|
||||
[Required] public MoneyManagement OptimizedMoneyManagement { get; set; }
|
||||
[Required] public User User { get; set; }
|
||||
[Required] public Dictionary<IndicatorType, IndicatorsResultBase> StrategiesValues { get; set; }
|
||||
[Required] public Dictionary<IndicatorType, IndicatorsResultBase> IndicatorsValues { get; set; }
|
||||
[Required] public double Score { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace Managing.Domain.Bots;
|
||||
|
||||
public class BotBackup
|
||||
{
|
||||
public BotType BotType { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
public User User { get; set; }
|
||||
public string Data { get; set; }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Risk;
|
||||
using Managing.Domain.Scenarios;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -20,6 +21,13 @@ public class TradingBotConfig
|
||||
[Required] public bool FlipPosition { get; set; }
|
||||
[Required] public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Risk management configuration for advanced probabilistic analysis and position sizing.
|
||||
/// Contains all configurable parameters for Expected Utility Theory, Kelly Criterion, and probability thresholds.
|
||||
/// If null, default risk management settings will be used.
|
||||
/// </summary>
|
||||
public RiskManagement RiskManagement { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The scenario object containing all strategies. When provided, this takes precedence over ScenarioName.
|
||||
/// This allows running backtests without requiring scenarios to be saved in the database.
|
||||
@@ -52,4 +60,27 @@ public class TradingBotConfig
|
||||
/// </summary>
|
||||
[Required]
|
||||
public bool FlipOnlyWhenInProfit { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Synth API for probabilistic price forecasts and risk assessment.
|
||||
/// When true, the bot will use Synth predictions for signal filtering, position risk assessment, and position monitoring.
|
||||
/// When false, the bot operates in traditional mode without Synth predictions.
|
||||
/// The actual Synth configuration is managed centrally in SynthPredictionService.
|
||||
/// </summary>
|
||||
public bool UseSynthApi { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Synth predictions for position sizing adjustments and risk assessment
|
||||
/// </summary>
|
||||
public bool UseForPositionSizing { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Synth predictions for signal filtering
|
||||
/// </summary>
|
||||
public bool UseForSignalFiltering { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Synth predictions for dynamic stop-loss/take-profit adjustments
|
||||
/// </summary>
|
||||
public bool UseForDynamicStopLoss { get; set; } = true;
|
||||
}
|
||||
192
src/Managing.Domain/Risk/RiskManagement.cs
Normal file
192
src/Managing.Domain/Risk/RiskManagement.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Managing.Common;
|
||||
|
||||
namespace Managing.Domain.Risk;
|
||||
|
||||
/// <summary>
|
||||
/// Risk management configuration for trading bots
|
||||
/// Contains all configurable risk parameters for probabilistic analysis and position sizing
|
||||
/// </summary>
|
||||
public class RiskManagement
|
||||
{
|
||||
/// <summary>
|
||||
/// Threshold for adverse probability in signal validation (default: 20%)
|
||||
/// Signals with SL probability above this threshold may be filtered out
|
||||
/// Range: 0.05 (5%) to 0.50 (50%)
|
||||
/// </summary>
|
||||
[Range(0.05, 0.50)]
|
||||
[Required]
|
||||
public decimal AdverseProbabilityThreshold { get; set; } = 0.20m;
|
||||
|
||||
/// <summary>
|
||||
/// Threshold for favorable probability in signal validation (default: 30%)
|
||||
/// Used for additional signal filtering and confidence assessment
|
||||
/// Range: 0.10 (10%) to 0.70 (70%)
|
||||
/// </summary>
|
||||
[Range(0.10, 0.70)]
|
||||
[Required]
|
||||
public decimal FavorableProbabilityThreshold { get; set; } = 0.30m;
|
||||
|
||||
/// <summary>
|
||||
/// Risk aversion parameter for Expected Utility calculations (default: 1.0)
|
||||
/// Higher values = more risk-averse behavior in utility calculations
|
||||
/// Range: 0.1 (risk-seeking) to 5.0 (highly risk-averse)
|
||||
/// </summary>
|
||||
[Range(0.1, 5.0)]
|
||||
[Required]
|
||||
public decimal RiskAversion { get; set; } = 1.0m;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum Kelly Criterion fraction to consider a trade favorable (default: 1%)
|
||||
/// Trades with Kelly fraction below this threshold are considered unfavorable
|
||||
/// Range: 0.5% to 10%
|
||||
/// </summary>
|
||||
[Range(0.005, 0.10)]
|
||||
[Required]
|
||||
public decimal KellyMinimumThreshold { get; set; } = 0.01m;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum Kelly Criterion fraction cap for practical risk management (default: 25%)
|
||||
/// Prevents over-allocation even when Kelly suggests higher percentages
|
||||
/// Range: 5% to 50%
|
||||
/// </summary>
|
||||
[Range(0.05, 0.50)]
|
||||
[Required]
|
||||
public decimal KellyMaximumCap { get; set; } = 0.25m;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum acceptable liquidation probability for position risk assessment (default: 10%)
|
||||
/// Positions with higher liquidation risk may be blocked or reduced
|
||||
/// Range: 5% to 30%
|
||||
/// </summary>
|
||||
[Range(0.05, 0.30)]
|
||||
[Required]
|
||||
public decimal MaxLiquidationProbability { get; set; } = 0.10m;
|
||||
|
||||
/// <summary>
|
||||
/// Time horizon in hours for signal validation analysis (default: 24 hours)
|
||||
/// Longer horizons provide more stable predictions but less responsive signals
|
||||
/// Range: 1 hour to 168 hours (1 week)
|
||||
/// </summary>
|
||||
[Range(1, 168)]
|
||||
[Required]
|
||||
public int SignalValidationTimeHorizonHours { get; set; } = 24;
|
||||
|
||||
/// <summary>
|
||||
/// Time horizon in hours for position risk monitoring (default: 6 hours)
|
||||
/// Shorter horizons for more frequent risk updates on open positions
|
||||
/// Range: 1 hour to 48 hours
|
||||
/// </summary>
|
||||
[Range(1, 48)]
|
||||
[Required]
|
||||
public int PositionMonitoringTimeHorizonHours { get; set; } = 6;
|
||||
|
||||
/// <summary>
|
||||
/// Probability threshold for issuing position risk warnings (default: 20%)
|
||||
/// Positions exceeding this liquidation risk will trigger warnings
|
||||
/// Range: 10% to 40%
|
||||
/// </summary>
|
||||
[Range(0.10, 0.40)]
|
||||
[Required]
|
||||
public decimal PositionWarningThreshold { get; set; } = 0.20m;
|
||||
|
||||
/// <summary>
|
||||
/// Probability threshold for automatic position closure (default: 50%)
|
||||
/// Positions exceeding this liquidation risk will be automatically closed
|
||||
/// Range: 30% to 80%
|
||||
/// </summary>
|
||||
[Range(0.30, 0.80)]
|
||||
[Required]
|
||||
public decimal PositionAutoCloseThreshold { get; set; } = 0.50m;
|
||||
|
||||
/// <summary>
|
||||
/// Fractional Kelly multiplier for conservative position sizing (default: 1.0)
|
||||
/// Values less than 1.0 implement fractional Kelly (e.g., 0.5 = half-Kelly)
|
||||
/// Range: 0.1 to 1.0
|
||||
/// </summary>
|
||||
[Range(0.1, 1.0)]
|
||||
[Required]
|
||||
public decimal KellyFractionalMultiplier { get; set; } = 1.0m;
|
||||
|
||||
/// <summary>
|
||||
/// Risk tolerance level affecting overall risk calculations
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Enums.RiskToleranceLevel RiskTolerance { get; set; } = Enums.RiskToleranceLevel.Moderate;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Expected Utility Theory for decision making
|
||||
/// </summary>
|
||||
[Required]
|
||||
public bool UseExpectedUtility { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Kelly Criterion for position sizing recommendations
|
||||
/// </summary>
|
||||
[Required]
|
||||
public bool UseKellyCriterion { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Validates that the risk management configuration is coherent
|
||||
/// </summary>
|
||||
/// <returns>True if configuration is valid, false otherwise</returns>
|
||||
public bool IsConfigurationValid()
|
||||
{
|
||||
// Ensure favorable threshold is higher than adverse threshold
|
||||
if (FavorableProbabilityThreshold <= AdverseProbabilityThreshold)
|
||||
return false;
|
||||
|
||||
// Ensure Kelly minimum is less than maximum
|
||||
if (KellyMinimumThreshold >= KellyMaximumCap)
|
||||
return false;
|
||||
|
||||
// Ensure warning threshold is less than auto-close threshold
|
||||
if (PositionWarningThreshold >= PositionAutoCloseThreshold)
|
||||
return false;
|
||||
|
||||
// Ensure signal validation horizon is longer than position monitoring
|
||||
if (SignalValidationTimeHorizonHours < PositionMonitoringTimeHorizonHours)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a preset configuration based on risk tolerance level
|
||||
/// </summary>
|
||||
/// <param name="tolerance">Risk tolerance level</param>
|
||||
/// <returns>Configured RiskManagement instance</returns>
|
||||
public static RiskManagement GetPresetConfiguration(Enums.RiskToleranceLevel tolerance)
|
||||
{
|
||||
return tolerance switch
|
||||
{
|
||||
Enums.RiskToleranceLevel.Conservative => new RiskManagement
|
||||
{
|
||||
AdverseProbabilityThreshold = 0.15m,
|
||||
FavorableProbabilityThreshold = 0.40m,
|
||||
RiskAversion = 2.0m,
|
||||
KellyMinimumThreshold = 0.02m,
|
||||
KellyMaximumCap = 0.15m,
|
||||
MaxLiquidationProbability = 0.08m,
|
||||
PositionWarningThreshold = 0.15m,
|
||||
PositionAutoCloseThreshold = 0.35m,
|
||||
KellyFractionalMultiplier = 0.5m,
|
||||
RiskTolerance = tolerance
|
||||
},
|
||||
Enums.RiskToleranceLevel.Aggressive => new RiskManagement
|
||||
{
|
||||
AdverseProbabilityThreshold = 0.30m,
|
||||
FavorableProbabilityThreshold = 0.25m,
|
||||
RiskAversion = 0.5m,
|
||||
KellyMinimumThreshold = 0.005m,
|
||||
KellyMaximumCap = 0.40m,
|
||||
MaxLiquidationProbability = 0.15m,
|
||||
PositionWarningThreshold = 0.30m,
|
||||
PositionAutoCloseThreshold = 0.70m,
|
||||
KellyFractionalMultiplier = 1.0m,
|
||||
RiskTolerance = tolerance
|
||||
},
|
||||
_ => new RiskManagement { RiskTolerance = tolerance } // Moderate (default values)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -108,15 +108,21 @@ public static class TradingBox
|
||||
}
|
||||
}
|
||||
|
||||
// Keep only the latest signal per indicator to avoid count mismatch
|
||||
var latestSignalsPerIndicator = signalOnCandles
|
||||
.GroupBy(s => s.IndicatorName)
|
||||
.Select(g => g.OrderByDescending(s => s.Date).First())
|
||||
.ToHashSet();
|
||||
|
||||
// Remove the restrictive requirement that ALL strategies must produce signals
|
||||
// Instead, let ComputeSignals handle the logic based on what we have
|
||||
if (!signalOnCandles.Any())
|
||||
if (!latestSignalsPerIndicator.Any())
|
||||
{
|
||||
return null; // No signals from any strategy
|
||||
}
|
||||
|
||||
var data = newCandles.First();
|
||||
return ComputeSignals(strategies, signalOnCandles, MiscExtensions.ParseEnum<Ticker>(data.Ticker),
|
||||
return ComputeSignals(strategies, latestSignalsPerIndicator, MiscExtensions.ParseEnum<Ticker>(data.Ticker),
|
||||
data.Timeframe, config);
|
||||
}
|
||||
|
||||
@@ -136,51 +142,88 @@ public static class TradingBox
|
||||
}
|
||||
|
||||
// Check if all strategies produced signals - this is required for composite signals
|
||||
if (signalOnCandles.Count != strategies.Count)
|
||||
var strategyNames = strategies.Select(s => s.Name).ToHashSet();
|
||||
var signalIndicatorNames = signalOnCandles.Select(s => s.IndicatorName).ToHashSet();
|
||||
|
||||
if (!strategyNames.SetEquals(signalIndicatorNames))
|
||||
{
|
||||
// Not all strategies produced signals - composite signal requires all strategies to contribute
|
||||
return null;
|
||||
}
|
||||
|
||||
// Group signals by type for analysis
|
||||
var signalStrategies = signalOnCandles.Where(s => s.SignalType == SignalType.Signal).ToList();
|
||||
var trendStrategies = signalOnCandles.Where(s => s.SignalType == SignalType.Trend).ToList();
|
||||
var contextStrategies = signalOnCandles.Where(s => s.SignalType == SignalType.Context).ToList();
|
||||
var signals = signalOnCandles.Where(s => s.SignalType == SignalType.Signal).ToList();
|
||||
var trendSignals = signalOnCandles.Where(s => s.SignalType == SignalType.Trend).ToList();
|
||||
var contextSignals = signalOnCandles.Where(s => s.SignalType == SignalType.Context).ToList();
|
||||
|
||||
// Context validation - evaluates market conditions based on confidence levels
|
||||
if (!ValidateContextStrategies(strategies, contextStrategies, config))
|
||||
if (!ValidateContextStrategies(strategies, contextSignals, config))
|
||||
{
|
||||
return null; // Context strategies are blocking the trade
|
||||
}
|
||||
|
||||
// Trend analysis - evaluate overall market direction
|
||||
var trendDirection = EvaluateTrendDirection(trendStrategies, config);
|
||||
// Check for 100% agreement across ALL signals (no threshold voting)
|
||||
var allDirectionalSignals = signalOnCandles
|
||||
.Where(s => s.Direction != TradeDirection.None && s.SignalType != SignalType.Context).ToList();
|
||||
|
||||
// Signal analysis - evaluate entry signals
|
||||
var signalDirection = EvaluateSignalDirection(signalStrategies, config);
|
||||
if (!allDirectionalSignals.Any())
|
||||
{
|
||||
return null; // No directional signals available
|
||||
}
|
||||
|
||||
// Determine final direction and confidence
|
||||
var (finalDirection, confidence) =
|
||||
DetermineFinalSignal(signalDirection, trendDirection, signalStrategies, trendStrategies, config);
|
||||
// Require 100% agreement - all signals must have the same direction
|
||||
var lastSignalDirection = allDirectionalSignals.Last().Direction;
|
||||
if (!allDirectionalSignals.All(s => s.Direction == lastSignalDirection))
|
||||
{
|
||||
return null; // Signals are not in complete agreement
|
||||
}
|
||||
|
||||
if (finalDirection == TradeDirection.None || confidence < config.MinimumConfidence)
|
||||
var finalDirection = lastSignalDirection;
|
||||
|
||||
// Calculate confidence based on the average confidence of all signals
|
||||
var averageConfidence = CalculateAverageConfidence(allDirectionalSignals);
|
||||
|
||||
if (finalDirection == TradeDirection.None || averageConfidence < config.MinimumConfidence)
|
||||
{
|
||||
return null; // No valid signal or below minimum confidence
|
||||
}
|
||||
|
||||
// Create composite signal
|
||||
var lastSignal = signalStrategies.LastOrDefault() ??
|
||||
trendStrategies.LastOrDefault() ?? contextStrategies.LastOrDefault();
|
||||
var lastSignal = signals.LastOrDefault() ??
|
||||
trendSignals.LastOrDefault() ?? contextSignals.LastOrDefault();
|
||||
|
||||
return new Signal(
|
||||
ticker,
|
||||
finalDirection,
|
||||
confidence,
|
||||
averageConfidence,
|
||||
lastSignal?.Candle,
|
||||
lastSignal?.Date ?? DateTime.UtcNow,
|
||||
lastSignal?.Exchange ?? config.DefaultExchange,
|
||||
IndicatorType.Composite,
|
||||
SignalType.Signal);
|
||||
SignalType.Signal, "Aggregated");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the average confidence level from a list of signals
|
||||
/// </summary>
|
||||
private static Confidence CalculateAverageConfidence(List<Signal> signals)
|
||||
{
|
||||
if (!signals.Any())
|
||||
{
|
||||
return Confidence.None;
|
||||
}
|
||||
|
||||
// Convert confidence enum to numeric values for averaging
|
||||
var confidenceValues = signals.Select(s => (int)s.Confidence).ToList();
|
||||
var averageValue = confidenceValues.Average();
|
||||
|
||||
// Round to nearest confidence level
|
||||
var roundedValue = Math.Round(averageValue);
|
||||
|
||||
// Ensure the value is within valid confidence enum range
|
||||
roundedValue = Math.Max(0, Math.Min(3, roundedValue));
|
||||
|
||||
return (Confidence)(int)roundedValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -73,7 +73,7 @@ public class StDevContext : Indicator
|
||||
}
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
var test = new IndicatorsResultBase()
|
||||
{
|
||||
@@ -119,7 +119,7 @@ public class StDevContext : Indicator
|
||||
candleSignal,
|
||||
candleSignal.Date,
|
||||
candleSignal.Exchange,
|
||||
Type, SignalType);
|
||||
Type, SignalType, Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Managing.Domain.Strategies
|
||||
FixedSizeQueue<Candle> Candles { get; set; }
|
||||
|
||||
List<Signal> Run();
|
||||
IndicatorsResultBase GetStrategyValues();
|
||||
IndicatorsResultBase GetIndicatorValues();
|
||||
void UpdateCandles(HashSet<Candle> newCandles);
|
||||
string GetName();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
using Managing.Core.FixedSizedQueue;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Scenarios;
|
||||
@@ -19,7 +20,7 @@ namespace Managing.Domain.Strategies
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
[JsonIgnore] public FixedSizeQueue<Candle> Candles { get; set; }
|
||||
[JsonIgnore] [IgnoreDataMember] public FixedSizeQueue<Candle> Candles { get; set; }
|
||||
public IndicatorType Type { get; set; }
|
||||
public SignalType SignalType { get; set; }
|
||||
public int MinimumHistory { get; set; }
|
||||
@@ -38,7 +39,7 @@ namespace Managing.Domain.Strategies
|
||||
return new List<Signal>();
|
||||
}
|
||||
|
||||
public virtual IndicatorsResultBase GetStrategyValues()
|
||||
public virtual IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
return new IndicatorsResultBase();
|
||||
}
|
||||
|
||||
@@ -21,9 +21,11 @@ namespace Managing.Domain.Strategies
|
||||
[Required] public IndicatorType IndicatorType { get; set; }
|
||||
[Required] public SignalType SignalType { get; set; }
|
||||
public User User { get; set; }
|
||||
[Required] public string IndicatorName { get; set; }
|
||||
|
||||
public Signal(Ticker ticker, TradeDirection direction, Confidence confidence, Candle candle, DateTime date,
|
||||
TradingExchanges exchange, IndicatorType indicatorType, SignalType signalType, User user = null)
|
||||
TradingExchanges exchange, IndicatorType indicatorType, SignalType signalType, string indicatorName,
|
||||
User user = null)
|
||||
{
|
||||
Direction = direction;
|
||||
Confidence = confidence;
|
||||
@@ -34,10 +36,11 @@ namespace Managing.Domain.Strategies
|
||||
Status = SignalStatus.WaitingForPosition;
|
||||
IndicatorType = indicatorType;
|
||||
User = user;
|
||||
IndicatorName = indicatorName;
|
||||
SignalType = signalType;
|
||||
|
||||
Identifier =
|
||||
$"{IndicatorType}-{direction}-{ticker}-{candle?.Close.ToString(CultureInfo.InvariantCulture)}-{date:yyyyMMdd-HHmmss}";
|
||||
SignalType = signalType;
|
||||
$"{indicatorName}-{indicatorType}-{direction}-{ticker}-{candle?.Close.ToString(CultureInfo.InvariantCulture)}-{date:yyyyMMdd-HHmmss}";
|
||||
}
|
||||
|
||||
public void SetConfidence(Confidence confidence)
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ChandelierExitIndicator : Indicator
|
||||
}
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
return new IndicatorsResultBase()
|
||||
{
|
||||
@@ -113,7 +113,8 @@ public class ChandelierExitIndicator : Indicator
|
||||
candleSignal,
|
||||
candleSignal.Date,
|
||||
candleSignal.Exchange,
|
||||
Type, SignalType);
|
||||
Type, SignalType,
|
||||
Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
|
||||
@@ -19,7 +19,7 @@ public class DualEmaCrossIndicator : EmaBaseIndicator
|
||||
MinimumHistory = Math.Max(fastPeriod, slowPeriod) * 2;
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
return new IndicatorsResultBase()
|
||||
{
|
||||
@@ -104,7 +104,7 @@ public class DualEmaCrossIndicator : EmaBaseIndicator
|
||||
private void AddSignal(CandleDualEma candleSignal, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence,
|
||||
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType);
|
||||
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
|
||||
@@ -16,7 +16,7 @@ public class EmaCrossIndicator : EmaBaseIndicator
|
||||
Period = period;
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
return new IndicatorsResultBase()
|
||||
{
|
||||
@@ -68,7 +68,7 @@ public class EmaCrossIndicator : EmaBaseIndicator
|
||||
private void AddSignal(CandleEma candleSignal, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence,
|
||||
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType);
|
||||
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
|
||||
@@ -89,7 +89,7 @@ public class LaggingSTC : Indicator
|
||||
}
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
var stc = Candles.GetStc(FastPeriods.Value, FastPeriods.Value, SlowPeriods.Value).ToList();
|
||||
return new IndicatorsResultBase
|
||||
@@ -130,7 +130,8 @@ public class LaggingSTC : Indicator
|
||||
candleSignal,
|
||||
candleSignal.Date,
|
||||
candleSignal.Exchange,
|
||||
Type, SignalType);
|
||||
Type, SignalType,
|
||||
Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
|
||||
@@ -59,7 +59,7 @@ public class MacdCrossIndicator : Indicator
|
||||
}
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
return new IndicatorsResultBase()
|
||||
{
|
||||
@@ -96,7 +96,7 @@ public class MacdCrossIndicator : Indicator
|
||||
Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence,
|
||||
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType);
|
||||
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
|
||||
@@ -49,7 +49,7 @@ public class RsiDivergenceConfirmIndicator : Indicator
|
||||
}
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
return new IndicatorsResultBase()
|
||||
{
|
||||
@@ -233,7 +233,7 @@ public class RsiDivergenceConfirmIndicator : Indicator
|
||||
private void AddSignal(CandleRsi candleSignal, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence,
|
||||
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType);
|
||||
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
|
||||
@@ -52,7 +52,7 @@ public class RsiDivergenceIndicator : Indicator
|
||||
}
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
return new IndicatorsResultBase()
|
||||
{
|
||||
@@ -206,7 +206,7 @@ public class RsiDivergenceIndicator : Indicator
|
||||
private void AddSignal(CandleRsi candleSignal, TradeDirection direction)
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, Confidence.Low,
|
||||
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType);
|
||||
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name);
|
||||
|
||||
if (Signals.Count(s => s.Identifier == signal.Identifier) < 1)
|
||||
{
|
||||
|
||||
@@ -64,7 +64,7 @@ public class StcIndicator : Indicator
|
||||
}
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
if (FastPeriods != null && SlowPeriods != null)
|
||||
{
|
||||
@@ -110,7 +110,8 @@ public class StcIndicator : Indicator
|
||||
candleSignal,
|
||||
candleSignal.Date,
|
||||
candleSignal.Exchange,
|
||||
Type, SignalType);
|
||||
Type, SignalType,
|
||||
Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
|
||||
@@ -157,7 +157,7 @@ public class SuperTrendCrossEma : Indicator
|
||||
return superTrends;
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
return new IndicatorsResultBase()
|
||||
{
|
||||
@@ -171,7 +171,7 @@ public class SuperTrendCrossEma : Indicator
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence,
|
||||
candleSignal, candleSignal.Date,
|
||||
candleSignal.Exchange, Type, SignalType);
|
||||
candleSignal.Exchange, Type, SignalType, Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
|
||||
@@ -61,7 +61,7 @@ public class SuperTrendIndicator : Indicator
|
||||
}
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
return new IndicatorsResultBase()
|
||||
{
|
||||
@@ -99,7 +99,7 @@ public class SuperTrendIndicator : Indicator
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence,
|
||||
candleSignal, candleSignal.Date,
|
||||
candleSignal.Exchange, Type, SignalType);
|
||||
candleSignal.Exchange, Type, SignalType, Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Managing.Domain.Strategies.Signals
|
||||
}
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class EmaTrendIndicator : EmaBaseIndicator
|
||||
}
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
return new IndicatorsResultBase()
|
||||
{
|
||||
@@ -65,7 +65,7 @@ public class EmaTrendIndicator : EmaBaseIndicator
|
||||
public void AddSignal(CandleEma candleSignal, TradeDirection direction, Confidence confidence)
|
||||
{
|
||||
var signal = new Signal(MiscExtensions.ParseEnum<Ticker>(candleSignal.Ticker), direction, confidence,
|
||||
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType);
|
||||
candleSignal, candleSignal.Date, candleSignal.Exchange, Type, SignalType, Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
|
||||
@@ -65,7 +65,7 @@ public class StochRsiTrendIndicator : Indicator
|
||||
}
|
||||
}
|
||||
|
||||
public override IndicatorsResultBase GetStrategyValues()
|
||||
public override IndicatorsResultBase GetIndicatorValues()
|
||||
{
|
||||
return new IndicatorsResultBase()
|
||||
{
|
||||
@@ -108,7 +108,8 @@ public class StochRsiTrendIndicator : Indicator
|
||||
candleSignal.Date,
|
||||
candleSignal.Exchange,
|
||||
Type,
|
||||
SignalType);
|
||||
SignalType,
|
||||
Name);
|
||||
if (!Signals.Any(s => s.Identifier == signal.Identifier))
|
||||
{
|
||||
Signals.AddItem(signal);
|
||||
|
||||
36
src/Managing.Domain/Synth/Models/MinerInfo.cs
Normal file
36
src/Managing.Domain/Synth/Models/MinerInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Managing.Domain.Synth.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a miner on the Synth API leaderboard
|
||||
/// </summary>
|
||||
public class MinerInfo
|
||||
{
|
||||
[JsonPropertyName("coldkey")]
|
||||
public string Coldkey { get; set; }
|
||||
|
||||
[JsonPropertyName("emission")]
|
||||
public decimal Emission { get; set; }
|
||||
|
||||
[JsonPropertyName("incentive")]
|
||||
public decimal Incentive { get; set; }
|
||||
|
||||
[JsonPropertyName("neuron_uid")]
|
||||
public int NeuronUid { get; set; }
|
||||
|
||||
[JsonPropertyName("pruning_score")]
|
||||
public decimal PruningScore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Rank value from API (decimal representing the ranking score)
|
||||
/// </summary>
|
||||
[JsonPropertyName("rank")]
|
||||
public decimal Rank { get; set; }
|
||||
|
||||
[JsonPropertyName("stake")]
|
||||
public decimal Stake { get; set; }
|
||||
|
||||
[JsonPropertyName("updated_at")]
|
||||
public string UpdatedAt { get; set; }
|
||||
}
|
||||
32
src/Managing.Domain/Synth/Models/MinerPrediction.cs
Normal file
32
src/Managing.Domain/Synth/Models/MinerPrediction.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Managing.Domain.Synth.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the prediction data from a single miner
|
||||
/// Contains multiple simulated price paths and the miner's information
|
||||
/// </summary>
|
||||
public class MinerPrediction
|
||||
{
|
||||
public string Asset { get; set; }
|
||||
[JsonPropertyName("miner_uid")] public int MinerUid { get; set; }
|
||||
[JsonPropertyName("num_simulations")] public int NumSimulations { get; set; }
|
||||
public List<List<PricePoint>> Prediction { get; set; } = new();
|
||||
[JsonPropertyName("start_time")] public string StartTime { get; set; }
|
||||
[JsonPropertyName("time_increment")] public int TimeIncrement { get; set; }
|
||||
[JsonPropertyName("time_length")] public int TimeLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Complete miner information including rank, stake, incentive, etc.
|
||||
/// This is populated after fetching predictions by mapping MinerUid to MinerInfo.NeuronUid
|
||||
/// </summary>
|
||||
public MinerInfo? MinerInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts the StartTime string to DateTime for easier manipulation
|
||||
/// </summary>
|
||||
public DateTime GetStartDateTime()
|
||||
{
|
||||
return DateTime.TryParse(StartTime, out var dateTime) ? dateTime : DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
18
src/Managing.Domain/Synth/Models/PricePoint.cs
Normal file
18
src/Managing.Domain/Synth/Models/PricePoint.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Managing.Domain.Synth.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a price at a specific time within a simulated path
|
||||
/// </summary>
|
||||
public class PricePoint
|
||||
{
|
||||
public decimal Price { get; set; }
|
||||
public string Time { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts the Time string to DateTime for easier manipulation
|
||||
/// </summary>
|
||||
public DateTime GetDateTime()
|
||||
{
|
||||
return DateTime.TryParse(Time, out var dateTime) ? dateTime : DateTime.MinValue;
|
||||
}
|
||||
}
|
||||
403
src/Managing.Domain/Synth/Models/SignalValidationResult.cs
Normal file
403
src/Managing.Domain/Synth/Models/SignalValidationResult.cs
Normal file
@@ -0,0 +1,403 @@
|
||||
using Managing.Domain.Risk;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Synth.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Result of Synth signal validation containing comprehensive analysis data
|
||||
/// </summary>
|
||||
public class SignalValidationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Overall confidence level of the signal based on TP vs SL probability analysis
|
||||
/// </summary>
|
||||
public Confidence Confidence { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Raw stop loss probability (0.0 to 1.0)
|
||||
/// </summary>
|
||||
public decimal StopLossProbability { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Raw take profit probability (0.0 to 1.0)
|
||||
/// </summary>
|
||||
public decimal TakeProfitProbability { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Calculated ratio of Take Profit Probability / Stop Loss Probability
|
||||
/// Higher values indicate more favorable risk/reward
|
||||
/// </summary>
|
||||
public decimal TpSlRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the signal should be blocked based on risk analysis
|
||||
/// True when confidence is None or adverse probability is too high
|
||||
/// </summary>
|
||||
public bool IsBlocked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Threshold used for adverse probability evaluation
|
||||
/// </summary>
|
||||
public decimal AdverseProbabilityThreshold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional context information about the validation
|
||||
/// </summary>
|
||||
public string ValidationContext { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Time horizon used for the probability calculations (in seconds)
|
||||
/// </summary>
|
||||
public int TimeHorizonSeconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether custom thresholds were used in the analysis
|
||||
/// </summary>
|
||||
public bool UsedCustomThresholds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Monetary gain if take profit is reached (positive value)
|
||||
/// </summary>
|
||||
public decimal TakeProfitGain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Monetary loss if stop loss is hit (positive value representing loss amount)
|
||||
/// </summary>
|
||||
public decimal StopLossLoss { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Expected Monetary Value: (TP_Gain * TP_Prob) - (SL_Loss * SL_Prob)
|
||||
/// Positive values indicate favorable expected outcomes
|
||||
/// </summary>
|
||||
public decimal ExpectedMonetaryValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Expected Utility using logarithmic utility function for risk-adjusted decision making
|
||||
/// Higher values indicate more desirable risk-adjusted outcomes
|
||||
/// </summary>
|
||||
public decimal ExpectedUtility { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Risk-adjusted return ratio (Expected Utility / Risk)
|
||||
/// Higher values indicate better risk-adjusted opportunities
|
||||
/// </summary>
|
||||
public decimal UtilityRiskRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Kelly Criterion fraction - optimal percentage of capital to allocate (0.0 to 1.0)
|
||||
/// Based on Kelly formula: f* = (bp - q) / b, where b = payoff ratio, p = win probability, q = loss probability
|
||||
/// Values above 0.25 (25%) are typically capped for practical risk management
|
||||
/// </summary>
|
||||
public decimal KellyFraction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Capped Kelly Fraction for practical risk management (typically max 25% of capital)
|
||||
/// </summary>
|
||||
public decimal KellyCappedFraction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Win/Loss ratio used in Kelly calculation (TakeProfitGain / StopLossLoss)
|
||||
/// </summary>
|
||||
public decimal WinLossRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Kelly Criterion assessment indicating the quality of the opportunity
|
||||
/// </summary>
|
||||
public string KellyAssessment { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Risk tolerance level affecting overall risk calculations
|
||||
/// </summary>
|
||||
public RiskToleranceLevel RiskTolerance { get; set; } = RiskToleranceLevel.Moderate;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Expected Utility Theory for decision making
|
||||
/// </summary>
|
||||
public bool UseExpectedUtility { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Kelly Criterion for position sizing recommendations
|
||||
/// </summary>
|
||||
public bool UseKellyCriterion { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Trading balance used for utility calculations (from TradingBotConfig.BotTradingBalance)
|
||||
/// Represents the actual capital allocated to this trading bot
|
||||
/// </summary>
|
||||
public decimal TradingBalance { get; private set; } = 10000m;
|
||||
|
||||
/// <summary>
|
||||
/// Risk aversion parameter used for utility calculations (configured from RiskManagement)
|
||||
/// </summary>
|
||||
public decimal ConfiguredRiskAversion { get; private set; } = 1.0m;
|
||||
|
||||
/// <summary>
|
||||
/// Calculates Expected Monetary Value and Expected Utility using configured risk parameters
|
||||
/// </summary>
|
||||
/// <param name="tradingBalance">Actual trading balance allocated to the bot</param>
|
||||
/// <param name="riskConfig">Complete risk management configuration</param>
|
||||
public void CalculateExpectedMetrics(decimal tradingBalance, RiskManagement riskConfig)
|
||||
{
|
||||
// Store configured values for reference
|
||||
TradingBalance = tradingBalance;
|
||||
ConfiguredRiskAversion = riskConfig.RiskAversion;
|
||||
|
||||
// Calculate Expected Monetary Value
|
||||
// EMV = (TP_Gain * TP_Prob) - (SL_Loss * SL_Prob)
|
||||
ExpectedMonetaryValue = (TakeProfitGain * TakeProfitProbability) - (StopLossLoss * StopLossProbability);
|
||||
|
||||
// Calculate Expected Utility using logarithmic utility function
|
||||
// This accounts for diminishing marginal utility and risk aversion
|
||||
ExpectedUtility = CalculateLogarithmicExpectedUtility();
|
||||
|
||||
// Calculate utility-to-risk ratio for ranking opportunities
|
||||
var totalRisk = StopLossLoss > 0 ? StopLossLoss : 1m; // Avoid division by zero
|
||||
UtilityRiskRatio = ExpectedUtility / totalRisk;
|
||||
|
||||
// Calculate Kelly Criterion for optimal position sizing using full risk config
|
||||
CalculateKellyCriterion(riskConfig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates Expected Utility using logarithmic utility function
|
||||
/// U(x) = ln(tradingBalance + x) for gains, ln(tradingBalance - x) for losses
|
||||
/// Uses the actual trading balance and configured risk aversion
|
||||
/// </summary>
|
||||
/// <returns>Expected utility value</returns>
|
||||
private decimal CalculateLogarithmicExpectedUtility()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Use actual trading balance and configured risk aversion
|
||||
var baseCapital = TradingBalance > 0 ? TradingBalance : 10000m;
|
||||
var riskAversion = ConfiguredRiskAversion > 0 ? ConfiguredRiskAversion : 1.0m;
|
||||
|
||||
// Calculate utility of TP outcome: U(tradingBalance + gain)
|
||||
var tpOutcome = baseCapital + TakeProfitGain;
|
||||
var tpUtility = tpOutcome > 0 ? (decimal)Math.Log((double)tpOutcome) / riskAversion : decimal.MinValue;
|
||||
|
||||
// Calculate utility of SL outcome: U(tradingBalance - loss)
|
||||
var slOutcome = baseCapital - StopLossLoss;
|
||||
var slUtility = slOutcome > 0 ? (decimal)Math.Log((double)slOutcome) / riskAversion : decimal.MinValue;
|
||||
|
||||
// Calculate utility of no-change outcome (neither TP nor SL hit)
|
||||
var noChangeProb = Math.Max(0m, 1m - TakeProfitProbability - StopLossProbability);
|
||||
var noChangeUtility = (decimal)Math.Log((double)baseCapital) / riskAversion;
|
||||
|
||||
// Expected Utility = Sum of (Utility * Probability) for all outcomes
|
||||
var expectedUtility = (tpUtility * TakeProfitProbability) +
|
||||
(slUtility * StopLossProbability) +
|
||||
(noChangeUtility * noChangeProb);
|
||||
|
||||
return expectedUtility;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Return conservative utility value on calculation errors
|
||||
return decimal.MinValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates Kelly Criterion for optimal position sizing
|
||||
/// Kelly Formula: f* = (bp - q) / b
|
||||
/// Where: b = payoff ratio (win/loss), p = win probability, q = loss probability
|
||||
/// </summary>
|
||||
/// <param name="riskConfig">Complete risk management configuration</param>
|
||||
private void CalculateKellyCriterion(RiskManagement riskConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Calculate Win/Loss Ratio (b in Kelly formula)
|
||||
WinLossRatio = StopLossLoss > 0 ? TakeProfitGain / StopLossLoss : 0m;
|
||||
|
||||
// Handle edge cases
|
||||
if (WinLossRatio <= 0 || TakeProfitProbability <= 0)
|
||||
{
|
||||
KellyFraction = 0m;
|
||||
KellyCappedFraction = 0m;
|
||||
KellyAssessment = "No Position - Unfavorable risk/reward ratio";
|
||||
return;
|
||||
}
|
||||
|
||||
// Kelly Formula: f* = (bp - q) / b
|
||||
// Where:
|
||||
// b = WinLossRatio (TakeProfitGain / StopLossLoss)
|
||||
// p = TakeProfitProbability
|
||||
// q = StopLossProbability
|
||||
var numerator = (WinLossRatio * TakeProfitProbability) - StopLossProbability;
|
||||
var kellyFraction = numerator / WinLossRatio;
|
||||
|
||||
// Ensure Kelly fraction is not negative (would indicate unfavorable bet)
|
||||
KellyFraction = Math.Max(0m, kellyFraction);
|
||||
|
||||
// Apply fractional Kelly multiplier
|
||||
KellyFraction *= riskConfig.KellyFractionalMultiplier;
|
||||
|
||||
// Apply practical cap for risk management
|
||||
KellyCappedFraction = Math.Min(KellyFraction, riskConfig.KellyMaximumCap);
|
||||
|
||||
// Generate Kelly assessment using the configured threshold
|
||||
KellyAssessment = GenerateKellyAssessment(riskConfig);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Safe defaults on calculation errors
|
||||
KellyFraction = 0m;
|
||||
KellyCappedFraction = 0m;
|
||||
WinLossRatio = 0m;
|
||||
KellyAssessment = "Calculation Error - No position recommended";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a descriptive assessment of the Kelly Criterion result
|
||||
/// </summary>
|
||||
/// <param name="riskConfig">Risk management configuration containing Kelly thresholds</param>
|
||||
/// <returns>Human-readable Kelly assessment</returns>
|
||||
private string GenerateKellyAssessment(RiskManagement riskConfig)
|
||||
{
|
||||
if (KellyFraction <= 0)
|
||||
return "No Position - Negative or zero Kelly fraction";
|
||||
|
||||
if (KellyFraction < riskConfig.KellyMinimumThreshold)
|
||||
return $"Below Threshold - Kelly {KellyFraction:P2} < {riskConfig.KellyMinimumThreshold:P2} minimum";
|
||||
|
||||
if (KellyFraction < 0.05m) // 1-5%
|
||||
return "Small Position - Low but positive edge";
|
||||
|
||||
if (KellyFraction < 0.10m) // 5-10%
|
||||
return "Moderate Position - Reasonable edge";
|
||||
|
||||
if (KellyFraction < 0.25m) // 10-25%
|
||||
return "Large Position - Strong edge detected";
|
||||
|
||||
if (KellyFraction < 0.50m) // 25-50%
|
||||
return "Very Large Position - Exceptional edge (CAPPED for safety)";
|
||||
|
||||
// Above 50%
|
||||
return "Extreme Position - Extraordinary edge (HEAVILY CAPPED for safety)";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets detailed Kelly Criterion analysis including fractional betting recommendations
|
||||
/// </summary>
|
||||
/// <param name="totalCapital">Total available capital for position sizing</param>
|
||||
/// <returns>Detailed Kelly analysis with dollar amounts</returns>
|
||||
public string GetDetailedKellyAnalysis(decimal totalCapital = 100000m)
|
||||
{
|
||||
var recommendedAmount = KellyCappedFraction * totalCapital;
|
||||
var uncappedAmount = KellyFraction * totalCapital;
|
||||
|
||||
var analysis = $"Kelly Analysis:\n" +
|
||||
$"• Win/Loss Ratio: {WinLossRatio:F2}:1\n" +
|
||||
$"• Optimal Kelly %: {KellyFraction:P2}\n" +
|
||||
$"• Capped Kelly %: {KellyCappedFraction:P2}\n" +
|
||||
$"• Recommended Amount: ${recommendedAmount:N0}\n";
|
||||
|
||||
if (KellyFraction > KellyCappedFraction)
|
||||
{
|
||||
analysis += $"• Uncapped Amount: ${uncappedAmount:N0} (RISK WARNING)\n";
|
||||
}
|
||||
|
||||
analysis += $"• Assessment: {KellyAssessment}";
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates fractional Kelly betting for more conservative position sizing
|
||||
/// </summary>
|
||||
/// <param name="fraction">Fraction of Kelly to use (e.g., 0.5 for half-Kelly)</param>
|
||||
/// <returns>Fractional Kelly allocation percentage</returns>
|
||||
public decimal GetFractionalKelly(decimal fraction = 0.5m)
|
||||
{
|
||||
if (fraction < 0 || fraction > 1) fraction = 0.5m; // Default to half-Kelly
|
||||
return KellyFraction * fraction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates if the Kelly Criterion suggests this is a profitable opportunity
|
||||
/// </summary>
|
||||
/// <param name="riskConfig">Risk management configuration containing Kelly thresholds</param>
|
||||
/// <returns>True if Kelly fraction is above the configured threshold</returns>
|
||||
public bool IsKellyFavorable(RiskManagement riskConfig)
|
||||
{
|
||||
return KellyFraction > riskConfig.KellyMinimumThreshold;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alternative utility calculation using square root utility (less risk-averse than logarithmic)
|
||||
/// Uses the actual trading balance from the bot configuration
|
||||
/// </summary>
|
||||
/// <returns>Expected utility using square root function</returns>
|
||||
public decimal CalculateSquareRootExpectedUtility()
|
||||
{
|
||||
try
|
||||
{
|
||||
var baseCapital = TradingBalance > 0 ? TradingBalance : 10000m;
|
||||
|
||||
// Square root utility: U(x) = sqrt(x)
|
||||
var tpOutcome = baseCapital + TakeProfitGain;
|
||||
var tpUtility = tpOutcome > 0 ? (decimal)Math.Sqrt((double)tpOutcome) : 0m;
|
||||
|
||||
var slOutcome = Math.Max(0m, baseCapital - StopLossLoss);
|
||||
var slUtility = (decimal)Math.Sqrt((double)slOutcome);
|
||||
|
||||
var noChangeProb = Math.Max(0m, 1m - TakeProfitProbability - StopLossProbability);
|
||||
var noChangeUtility = (decimal)Math.Sqrt((double)baseCapital);
|
||||
|
||||
return (tpUtility * TakeProfitProbability) +
|
||||
(slUtility * StopLossProbability) +
|
||||
(noChangeUtility * noChangeProb);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return 0m;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a risk assessment based on Expected Utility Theory
|
||||
/// </summary>
|
||||
/// <returns>Descriptive risk assessment</returns>
|
||||
public string GetUtilityRiskAssessment()
|
||||
{
|
||||
if (ExpectedMonetaryValue > 0 && ExpectedUtility > 0)
|
||||
return "Favorable - Positive expected value and utility";
|
||||
|
||||
if (ExpectedMonetaryValue > 0 && ExpectedUtility <= 0)
|
||||
return "Cautious - Positive expected value but negative risk-adjusted utility";
|
||||
|
||||
if (ExpectedMonetaryValue <= 0 && ExpectedUtility > 0)
|
||||
return "Risk-Seeking - Negative expected value but positive utility (unusual)";
|
||||
|
||||
return "Unfavorable - Negative expected value and utility";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a result indicating Synth is disabled
|
||||
/// </summary>
|
||||
public static SignalValidationResult CreateDisabledResult(Confidence originalConfidence)
|
||||
{
|
||||
return new SignalValidationResult
|
||||
{
|
||||
Confidence = originalConfidence,
|
||||
IsBlocked = false,
|
||||
ValidationContext = "Synth API disabled"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a result for error scenarios
|
||||
/// </summary>
|
||||
public static SignalValidationResult CreateErrorResult(Confidence fallbackConfidence, string errorContext)
|
||||
{
|
||||
return new SignalValidationResult
|
||||
{
|
||||
Confidence = fallbackConfidence,
|
||||
IsBlocked = false,
|
||||
ValidationContext = $"Error in validation: {errorContext}"
|
||||
};
|
||||
}
|
||||
}
|
||||
64
src/Managing.Domain/Synth/Models/SynthConfiguration.cs
Normal file
64
src/Managing.Domain/Synth/Models/SynthConfiguration.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
namespace Managing.Domain.Synth.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration settings for Synth API integration
|
||||
/// </summary>
|
||||
public class SynthConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to enable Synth API integration
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Number of top miners to fetch predictions from (default: 10)
|
||||
/// </summary>
|
||||
public int TopMinersCount { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Time increment in seconds for predictions (default: 300 = 5 minutes)
|
||||
/// </summary>
|
||||
public int TimeIncrement { get; set; } = 300;
|
||||
|
||||
/// <summary>
|
||||
/// Default time length in seconds for predictions (default: 86400 = 24 hours)
|
||||
/// </summary>
|
||||
public int DefaultTimeLength { get; set; } = 86400;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum acceptable liquidation probability threshold (0.0 to 1.0)
|
||||
/// If liquidation probability exceeds this, position opening may be blocked
|
||||
/// </summary>
|
||||
public decimal MaxLiquidationProbability { get; set; } = 0.10m; // 10%
|
||||
|
||||
/// <summary>
|
||||
/// Cache duration for predictions in minutes (default: 5 minutes)
|
||||
/// </summary>
|
||||
public int PredictionCacheDurationMinutes { get; set; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Synth predictions for position sizing adjustments
|
||||
/// </summary>
|
||||
public bool UseForPositionSizing { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Synth predictions for signal filtering
|
||||
/// </summary>
|
||||
public bool UseForSignalFiltering { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use Synth predictions for dynamic stop-loss/take-profit adjustments
|
||||
/// </summary>
|
||||
public bool UseForDynamicStopLoss { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Validates the configuration
|
||||
/// </summary>
|
||||
public bool IsValid()
|
||||
{
|
||||
return !IsEnabled || (TopMinersCount > 0 &&
|
||||
TimeIncrement > 0 &&
|
||||
DefaultTimeLength > 0 &&
|
||||
MaxLiquidationProbability >= 0 && MaxLiquidationProbability <= 1);
|
||||
}
|
||||
}
|
||||
57
src/Managing.Domain/Synth/Models/SynthMinersLeaderboard.cs
Normal file
57
src/Managing.Domain/Synth/Models/SynthMinersLeaderboard.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
namespace Managing.Domain.Synth.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a cached leaderboard entry for Synth miners
|
||||
/// Used for MongoDB persistence to avoid repeated API calls
|
||||
/// </summary>
|
||||
public class SynthMinersLeaderboard
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for this leaderboard entry
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Asset symbol (e.g., "BTC", "ETH")
|
||||
/// </summary>
|
||||
public string Asset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time increment used for this leaderboard data
|
||||
/// </summary>
|
||||
public int TimeIncrement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signal date for which this leaderboard was retrieved (for backtests)
|
||||
/// Null for live trading data
|
||||
/// </summary>
|
||||
public DateTime? SignalDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is backtest data or live data
|
||||
/// </summary>
|
||||
public bool IsBacktest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of miners in the leaderboard
|
||||
/// </summary>
|
||||
public List<MinerInfo> Miners { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// When this leaderboard data was created/stored
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Generates a cache key for this leaderboard entry
|
||||
/// </summary>
|
||||
public string GetCacheKey()
|
||||
{
|
||||
var key = $"{Asset}_{TimeIncrement}";
|
||||
if (IsBacktest && SignalDate.HasValue)
|
||||
{
|
||||
key += $"_backtest_{SignalDate.Value:yyyy-MM-dd-HH}";
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
||||
72
src/Managing.Domain/Synth/Models/SynthMinersPredictions.cs
Normal file
72
src/Managing.Domain/Synth/Models/SynthMinersPredictions.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
namespace Managing.Domain.Synth.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents cached prediction data from Synth miners
|
||||
/// Used for MongoDB persistence to avoid repeated API calls
|
||||
/// </summary>
|
||||
public class SynthMinersPredictions
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for this predictions entry
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Asset symbol (e.g., "BTC", "ETH")
|
||||
/// </summary>
|
||||
public string Asset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time increment used for these predictions
|
||||
/// </summary>
|
||||
public int TimeIncrement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time length (horizon) for these predictions in seconds
|
||||
/// </summary>
|
||||
public int TimeLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signal date for which these predictions were retrieved (for backtests)
|
||||
/// Null for live trading data
|
||||
/// </summary>
|
||||
public DateTime? SignalDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is backtest data or live data
|
||||
/// </summary>
|
||||
public bool IsBacktest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of miner UIDs these predictions are from
|
||||
/// </summary>
|
||||
public List<int> MinerUids { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The actual prediction data from miners
|
||||
/// </summary>
|
||||
public List<MinerPrediction> Predictions { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// When this prediction data was fetched from the API
|
||||
/// </summary>
|
||||
public DateTime FetchedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When this prediction data was created/stored
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Generates a cache key for this predictions entry
|
||||
/// </summary>
|
||||
public string GetCacheKey()
|
||||
{
|
||||
var key = $"{Asset}_{TimeIncrement}_{TimeLength}";
|
||||
if (IsBacktest && SignalDate.HasValue)
|
||||
{
|
||||
key += $"_backtest_{SignalDate.Value:yyyy-MM-dd-HH}";
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
||||
67
src/Managing.Domain/Synth/Models/SynthPrediction.cs
Normal file
67
src/Managing.Domain/Synth/Models/SynthPrediction.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
namespace Managing.Domain.Synth.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents cached prediction data from a single Synth miner
|
||||
/// Used for MongoDB persistence to avoid repeated API calls and reduce document size
|
||||
/// </summary>
|
||||
public class SynthPrediction
|
||||
{
|
||||
/// <summary>
|
||||
/// Unique identifier for this prediction entry
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Asset symbol (e.g., "BTC", "ETH")
|
||||
/// </summary>
|
||||
public string Asset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Miner UID that provided this prediction
|
||||
/// </summary>
|
||||
public int MinerUid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time increment used for this prediction
|
||||
/// </summary>
|
||||
public int TimeIncrement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time length (horizon) for this prediction in seconds
|
||||
/// </summary>
|
||||
public int TimeLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signal date for which this prediction was retrieved (for backtests)
|
||||
/// Null for live trading data
|
||||
/// </summary>
|
||||
public DateTime? SignalDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is backtest data or live data
|
||||
/// </summary>
|
||||
public bool IsBacktest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The actual prediction data from the miner
|
||||
/// </summary>
|
||||
public MinerPrediction Prediction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When this prediction data was created/stored
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Generates a cache key for this prediction entry
|
||||
/// </summary>
|
||||
public string GetCacheKey()
|
||||
{
|
||||
var key = $"{Asset}_{TimeIncrement}_{TimeLength}_{MinerUid}";
|
||||
if (IsBacktest && SignalDate.HasValue)
|
||||
{
|
||||
key += $"_backtest_{SignalDate.Value:yyyy-MM-dd-HH}";
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
||||
13
src/Managing.Domain/Synth/Models/SynthRiskResult.cs
Normal file
13
src/Managing.Domain/Synth/Models/SynthRiskResult.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Managing.Domain.Synth.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Result of Synth risk monitoring
|
||||
/// </summary>
|
||||
public class SynthRiskResult
|
||||
{
|
||||
public decimal LiquidationProbability { get; set; }
|
||||
public bool ShouldWarn { get; set; }
|
||||
public bool ShouldAutoClose { get; set; }
|
||||
public string WarningMessage { get; set; }
|
||||
public string EmergencyMessage { get; set; }
|
||||
}
|
||||
@@ -18,5 +18,6 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
|
||||
public IndicatorType Type { get; set; }
|
||||
public SignalType SignalType { get; set; }
|
||||
public UserDto User { get; set; }
|
||||
public string IndicatorName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Managing.Infrastructure.Databases.MongoDb.Attributes;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Configurations;
|
||||
|
||||
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// MongoDB DTO for storing Synth miners leaderboard data
|
||||
/// </summary>
|
||||
[BsonCollection("SynthMinersLeaderboard")]
|
||||
public class SynthMinersLeaderboardDto : Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Asset symbol (e.g., "BTC", "ETH")
|
||||
/// </summary>
|
||||
public string Asset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time increment used for this leaderboard data
|
||||
/// </summary>
|
||||
public int TimeIncrement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signal date for which this leaderboard was retrieved (for backtests)
|
||||
/// Null for live trading data
|
||||
/// </summary>
|
||||
public DateTime? SignalDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is backtest data or live data
|
||||
/// </summary>
|
||||
public bool IsBacktest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Serialized JSON of miners list
|
||||
/// </summary>
|
||||
public string MinersData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cache key for quick lookup
|
||||
/// </summary>
|
||||
public string CacheKey { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Managing.Infrastructure.Databases.MongoDb.Attributes;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Configurations;
|
||||
|
||||
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// MongoDB DTO for storing Synth miners predictions data
|
||||
/// </summary>
|
||||
[BsonCollection("SynthMinersPredictions")]
|
||||
public class SynthMinersPredictionsDto : Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Asset symbol (e.g., "BTC", "ETH")
|
||||
/// </summary>
|
||||
public string Asset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time increment used for these predictions
|
||||
/// </summary>
|
||||
public int TimeIncrement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time length (horizon) for these predictions in seconds
|
||||
/// </summary>
|
||||
public int TimeLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signal date for which these predictions were retrieved (for backtests)
|
||||
/// Null for live trading data
|
||||
/// </summary>
|
||||
public DateTime? SignalDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is backtest data or live data
|
||||
/// </summary>
|
||||
public bool IsBacktest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Serialized JSON of miner UIDs list
|
||||
/// </summary>
|
||||
public string MinerUidsData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Serialized JSON of predictions data
|
||||
/// </summary>
|
||||
public string PredictionsData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When this prediction data was fetched from the API
|
||||
/// </summary>
|
||||
public DateTime FetchedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cache key for quick lookup
|
||||
/// </summary>
|
||||
public string CacheKey { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Managing.Infrastructure.Databases.MongoDb.Attributes;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Configurations;
|
||||
|
||||
namespace Managing.Infrastructure.Databases.MongoDb.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// MongoDB DTO for storing individual Synth miner prediction data
|
||||
/// </summary>
|
||||
[BsonCollection("SynthPredictions")]
|
||||
public class SynthPredictionDto : Document
|
||||
{
|
||||
/// <summary>
|
||||
/// Asset symbol (e.g., "BTC", "ETH")
|
||||
/// </summary>
|
||||
public string Asset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Miner UID that provided this prediction
|
||||
/// </summary>
|
||||
public int MinerUid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time increment used for this prediction
|
||||
/// </summary>
|
||||
public int TimeIncrement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time length (horizon) for this prediction in seconds
|
||||
/// </summary>
|
||||
public int TimeLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signal date for which this prediction was retrieved (for backtests)
|
||||
/// Null for live trading data
|
||||
/// </summary>
|
||||
public DateTime? SignalDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is backtest data or live data
|
||||
/// </summary>
|
||||
public bool IsBacktest { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Serialized JSON of the prediction data
|
||||
/// </summary>
|
||||
public string PredictionData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cache key for quick lookup
|
||||
/// </summary>
|
||||
public string CacheKey { get; set; }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Managing.Domain.Accounts;
|
||||
using System.Text.Json;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Backtests;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
@@ -6,6 +7,7 @@ using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Synth.Models;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Users;
|
||||
using Managing.Domain.Workers;
|
||||
@@ -371,7 +373,8 @@ public static class MongoMappers
|
||||
Status = signal.Status,
|
||||
Timeframe = signal.Timeframe,
|
||||
Type = signal.IndicatorType,
|
||||
User = signal.User != null ? Map(signal.User) : null
|
||||
User = signal.User != null ? Map(signal.User) : null,
|
||||
IndicatorName = signal.IndicatorName
|
||||
};
|
||||
}
|
||||
|
||||
@@ -387,6 +390,7 @@ public static class MongoMappers
|
||||
TradingExchanges.Binance, //TODO FIXME When the signal status is modified from controller
|
||||
bSignal.Type,
|
||||
bSignal.SignalType,
|
||||
bSignal.IndicatorName,
|
||||
bSignal.User != null ? Map(bSignal.User) : null)
|
||||
{
|
||||
Status = bSignal.Status
|
||||
@@ -744,7 +748,6 @@ public static class MongoMappers
|
||||
{
|
||||
User = Map(bot.User),
|
||||
Identifier = bot.Identifier,
|
||||
BotType = bot.BotType,
|
||||
Data = bot.Data,
|
||||
LastStatus = bot.LastStatus
|
||||
};
|
||||
@@ -758,7 +761,6 @@ public static class MongoMappers
|
||||
{
|
||||
User = Map(b.User),
|
||||
Identifier = b.Identifier,
|
||||
BotType = b.BotType,
|
||||
Data = b.Data,
|
||||
LastStatus = b.LastStatus
|
||||
};
|
||||
@@ -792,4 +794,143 @@ public static class MongoMappers
|
||||
Direction = fundingRate.Direction
|
||||
};
|
||||
}
|
||||
|
||||
#region Synth
|
||||
|
||||
/// <summary>
|
||||
/// Maps domain SynthMinersLeaderboard to MongoDB DTO
|
||||
/// </summary>
|
||||
internal static SynthMinersLeaderboardDto Map(SynthMinersLeaderboard leaderboard)
|
||||
{
|
||||
if (leaderboard == null) return null;
|
||||
|
||||
return new SynthMinersLeaderboardDto
|
||||
{
|
||||
Asset = leaderboard.Asset,
|
||||
TimeIncrement = leaderboard.TimeIncrement,
|
||||
SignalDate = leaderboard.SignalDate,
|
||||
IsBacktest = leaderboard.IsBacktest,
|
||||
MinersData = JsonSerializer.Serialize(leaderboard.Miners),
|
||||
CacheKey = leaderboard.GetCacheKey()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps MongoDB DTO to domain SynthMinersLeaderboard
|
||||
/// </summary>
|
||||
internal static SynthMinersLeaderboard Map(SynthMinersLeaderboardDto dto)
|
||||
{
|
||||
if (dto == null) return null;
|
||||
|
||||
var miners = string.IsNullOrEmpty(dto.MinersData)
|
||||
? new List<MinerInfo>()
|
||||
: JsonSerializer.Deserialize<List<MinerInfo>>(dto.MinersData) ?? new List<MinerInfo>();
|
||||
|
||||
return new SynthMinersLeaderboard
|
||||
{
|
||||
Id = dto.Id.ToString(),
|
||||
Asset = dto.Asset,
|
||||
TimeIncrement = dto.TimeIncrement,
|
||||
SignalDate = dto.SignalDate,
|
||||
IsBacktest = dto.IsBacktest,
|
||||
Miners = miners,
|
||||
CreatedAt = dto.CreatedAt
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps domain SynthMinersPredictions to MongoDB DTO
|
||||
/// </summary>
|
||||
internal static SynthMinersPredictionsDto Map(SynthMinersPredictions predictions)
|
||||
{
|
||||
if (predictions == null) return null;
|
||||
|
||||
return new SynthMinersPredictionsDto
|
||||
{
|
||||
Asset = predictions.Asset,
|
||||
TimeIncrement = predictions.TimeIncrement,
|
||||
TimeLength = predictions.TimeLength,
|
||||
SignalDate = predictions.SignalDate,
|
||||
IsBacktest = predictions.IsBacktest,
|
||||
MinerUidsData = JsonSerializer.Serialize(predictions.MinerUids),
|
||||
PredictionsData = JsonSerializer.Serialize(predictions.Predictions),
|
||||
CacheKey = predictions.GetCacheKey()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps MongoDB DTO to domain SynthMinersPredictions
|
||||
/// </summary>
|
||||
internal static SynthMinersPredictions Map(SynthMinersPredictionsDto dto)
|
||||
{
|
||||
if (dto == null) return null;
|
||||
|
||||
var minerUids = string.IsNullOrEmpty(dto.MinerUidsData)
|
||||
? new List<int>()
|
||||
: JsonSerializer.Deserialize<List<int>>(dto.MinerUidsData) ?? new List<int>();
|
||||
|
||||
var predictions = string.IsNullOrEmpty(dto.PredictionsData)
|
||||
? new List<MinerPrediction>()
|
||||
: JsonSerializer.Deserialize<List<MinerPrediction>>(dto.PredictionsData) ?? new List<MinerPrediction>();
|
||||
|
||||
return new SynthMinersPredictions
|
||||
{
|
||||
Id = dto.Id.ToString(),
|
||||
Asset = dto.Asset,
|
||||
TimeIncrement = dto.TimeIncrement,
|
||||
TimeLength = dto.TimeLength,
|
||||
SignalDate = dto.SignalDate,
|
||||
IsBacktest = dto.IsBacktest,
|
||||
MinerUids = minerUids,
|
||||
Predictions = predictions,
|
||||
CreatedAt = dto.CreatedAt
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps domain SynthPrediction to MongoDB DTO
|
||||
/// </summary>
|
||||
internal static SynthPredictionDto Map(SynthPrediction prediction)
|
||||
{
|
||||
if (prediction == null) return null;
|
||||
|
||||
return new SynthPredictionDto
|
||||
{
|
||||
Asset = prediction.Asset,
|
||||
MinerUid = prediction.MinerUid,
|
||||
TimeIncrement = prediction.TimeIncrement,
|
||||
TimeLength = prediction.TimeLength,
|
||||
SignalDate = prediction.SignalDate,
|
||||
IsBacktest = prediction.IsBacktest,
|
||||
PredictionData = JsonSerializer.Serialize(prediction.Prediction),
|
||||
CacheKey = prediction.GetCacheKey()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps MongoDB DTO to domain SynthPrediction
|
||||
/// </summary>
|
||||
internal static SynthPrediction Map(SynthPredictionDto dto)
|
||||
{
|
||||
if (dto == null) return null;
|
||||
|
||||
var prediction = string.IsNullOrEmpty(dto.PredictionData)
|
||||
? null
|
||||
: JsonSerializer.Deserialize<MinerPrediction>(dto.PredictionData);
|
||||
|
||||
return new SynthPrediction
|
||||
{
|
||||
Id = dto.Id.ToString(),
|
||||
Asset = dto.Asset,
|
||||
MinerUid = dto.MinerUid,
|
||||
TimeIncrement = dto.TimeIncrement,
|
||||
TimeLength = dto.TimeLength,
|
||||
SignalDate = dto.SignalDate,
|
||||
IsBacktest = dto.IsBacktest,
|
||||
Prediction = prediction,
|
||||
CreatedAt = dto.CreatedAt
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
223
src/Managing.Infrastructure.Database/SynthRepository.cs
Normal file
223
src/Managing.Infrastructure.Database/SynthRepository.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Domain.Synth.Models;
|
||||
using Managing.Infrastructure.Databases.MongoDb;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Abstractions;
|
||||
using Managing.Infrastructure.Databases.MongoDb.Collections;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Managing.Infrastructure.Databases;
|
||||
|
||||
/// <summary>
|
||||
/// Repository implementation for Synth-related data operations using MongoDB
|
||||
/// Provides persistence for leaderboard and individual predictions data
|
||||
/// </summary>
|
||||
public class SynthRepository : ISynthRepository
|
||||
{
|
||||
private readonly IMongoRepository<SynthMinersLeaderboardDto> _leaderboardRepository;
|
||||
private readonly IMongoRepository<SynthPredictionDto> _individualPredictionsRepository;
|
||||
private readonly ILogger<SynthRepository> _logger;
|
||||
|
||||
public SynthRepository(
|
||||
IMongoRepository<SynthMinersLeaderboardDto> leaderboardRepository,
|
||||
IMongoRepository<SynthPredictionDto> individualPredictionsRepository,
|
||||
ILogger<SynthRepository> logger)
|
||||
{
|
||||
_leaderboardRepository = leaderboardRepository;
|
||||
_individualPredictionsRepository = individualPredictionsRepository;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets cached leaderboard data by cache key
|
||||
/// </summary>
|
||||
public async Task<SynthMinersLeaderboard?> GetLeaderboardAsync(string cacheKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dto = await _leaderboardRepository.FindOneAsync(x => x.CacheKey == cacheKey);
|
||||
|
||||
if (dto == null)
|
||||
{
|
||||
_logger.LogDebug($"🔍 **Synth Cache** - No leaderboard cache found for key: {cacheKey}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = MongoMappers.Map(dto);
|
||||
_logger.LogDebug($"📦 **Synth Cache** - Retrieved leaderboard from MongoDB for key: {cacheKey}");
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error retrieving leaderboard cache for key: {cacheKey}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves leaderboard data to MongoDB
|
||||
/// </summary>
|
||||
public async Task SaveLeaderboardAsync(SynthMinersLeaderboard leaderboard)
|
||||
{
|
||||
try
|
||||
{
|
||||
leaderboard.CreatedAt = DateTime.UtcNow;
|
||||
var dto = MongoMappers.Map(leaderboard);
|
||||
|
||||
// Check if we already have this cache key and update instead of inserting
|
||||
var existing = await _leaderboardRepository.FindOneAsync(x => x.CacheKey == dto.CacheKey);
|
||||
if (existing != null)
|
||||
{
|
||||
dto.Id = existing.Id;
|
||||
_leaderboardRepository.Update(dto);
|
||||
_logger.LogDebug($"💾 **Synth Cache** - Updated leaderboard in MongoDB for key: {dto.CacheKey}");
|
||||
}
|
||||
else
|
||||
{
|
||||
await _leaderboardRepository.InsertOneAsync(dto);
|
||||
_logger.LogDebug($"💾 **Synth Cache** - Saved new leaderboard to MongoDB for key: {dto.CacheKey}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error saving leaderboard cache for key: {leaderboard.GetCacheKey()}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets individual cached prediction data by asset, parameters, and miner UIDs
|
||||
/// </summary>
|
||||
public async Task<List<SynthPrediction>> GetIndividualPredictionsAsync(
|
||||
string asset,
|
||||
int timeIncrement,
|
||||
int timeLength,
|
||||
List<int> minerUids,
|
||||
bool isBacktest,
|
||||
DateTime? signalDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
var results = new List<SynthPrediction>();
|
||||
|
||||
foreach (var minerUid in minerUids)
|
||||
{
|
||||
// Build cache key for individual prediction
|
||||
var cacheKey = $"{asset}_{timeIncrement}_{timeLength}_{minerUid}";
|
||||
if (isBacktest && signalDate.HasValue)
|
||||
{
|
||||
cacheKey += $"_backtest_{signalDate.Value:yyyy-MM-dd-HH}";
|
||||
}
|
||||
|
||||
var dto = await _individualPredictionsRepository.FindOneAsync(x => x.CacheKey == cacheKey);
|
||||
|
||||
if (dto != null)
|
||||
{
|
||||
var prediction = MongoMappers.Map(dto);
|
||||
if (prediction != null)
|
||||
{
|
||||
results.Add(prediction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (results.Any())
|
||||
{
|
||||
_logger.LogDebug($"📦 **Synth Individual Cache** - Retrieved {results.Count}/{minerUids.Count} individual predictions for {asset}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug($"🔍 **Synth Individual Cache** - No individual predictions found for {asset}");
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error retrieving individual predictions cache for asset: {asset}");
|
||||
return new List<SynthPrediction>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves individual prediction data to MongoDB
|
||||
/// </summary>
|
||||
public async Task SaveIndividualPredictionAsync(SynthPrediction prediction)
|
||||
{
|
||||
try
|
||||
{
|
||||
prediction.CreatedAt = DateTime.UtcNow;
|
||||
var dto = MongoMappers.Map(prediction);
|
||||
|
||||
// Check if we already have this cache key and update instead of inserting
|
||||
var existing = await _individualPredictionsRepository.FindOneAsync(x => x.CacheKey == dto.CacheKey);
|
||||
if (existing != null)
|
||||
{
|
||||
dto.Id = existing.Id;
|
||||
_individualPredictionsRepository.Update(dto);
|
||||
_logger.LogDebug($"💾 **Synth Individual Cache** - Updated individual prediction for miner {prediction.MinerUid}");
|
||||
}
|
||||
else
|
||||
{
|
||||
await _individualPredictionsRepository.InsertOneAsync(dto);
|
||||
_logger.LogDebug($"💾 **Synth Individual Cache** - Saved new individual prediction for miner {prediction.MinerUid}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error saving individual prediction cache for miner {prediction.MinerUid}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves multiple individual predictions to MongoDB in batch
|
||||
/// </summary>
|
||||
public async Task SaveIndividualPredictionsAsync(List<SynthPrediction> predictions)
|
||||
{
|
||||
if (!predictions.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var saveTasks = new List<Task>();
|
||||
|
||||
foreach (var prediction in predictions)
|
||||
{
|
||||
// Save each prediction individually to handle potential conflicts
|
||||
saveTasks.Add(SaveIndividualPredictionAsync(prediction));
|
||||
}
|
||||
|
||||
await Task.WhenAll(saveTasks);
|
||||
|
||||
_logger.LogInformation($"💾 **Synth Individual Cache** - Successfully saved {predictions.Count} individual predictions to MongoDB");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error saving batch of {predictions.Count} individual predictions");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up old cached data beyond the retention period
|
||||
/// </summary>
|
||||
public async Task CleanupOldDataAsync(int retentionDays = 30)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cutoffDate = DateTime.UtcNow.AddDays(-retentionDays);
|
||||
|
||||
// Clean up old leaderboard data
|
||||
await _leaderboardRepository.DeleteManyAsync(x => x.CreatedAt < cutoffDate);
|
||||
|
||||
// Clean up old individual predictions data
|
||||
await _individualPredictionsRepository.DeleteManyAsync(x => x.CreatedAt < cutoffDate);
|
||||
|
||||
_logger.LogInformation($"🧹 **Synth Cache** - Cleaned up old Synth cache data older than {retentionDays} days");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error during cleanup of old Synth cache data");
|
||||
}
|
||||
}
|
||||
}
|
||||
540
src/Managing.Infrastructure.Tests/SynthPredictionTests.cs
Normal file
540
src/Managing.Infrastructure.Tests/SynthPredictionTests.cs
Normal file
@@ -0,0 +1,540 @@
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Application.Synth;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Risk;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Synth.Models;
|
||||
using Managing.Domain.Users;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Infrastructure.Tests;
|
||||
|
||||
public class SynthPredictionTests
|
||||
{
|
||||
private readonly ITestOutputHelper _testOutputHelper;
|
||||
|
||||
public SynthPredictionTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_testOutputHelper = testOutputHelper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create a test signal with realistic candle data
|
||||
/// </summary>
|
||||
private static Signal CreateTestSignal(Ticker ticker, TradeDirection direction, decimal price,
|
||||
DateTime? date = null)
|
||||
{
|
||||
var signalDate = date ?? DateTime.UtcNow;
|
||||
var candle = new Candle
|
||||
{
|
||||
Date = signalDate,
|
||||
Open = price * 0.999m,
|
||||
High = price * 1.001m,
|
||||
Low = price * 0.998m,
|
||||
Close = price,
|
||||
BaseVolume = 1000m,
|
||||
QuoteVolume = price * 1000m
|
||||
};
|
||||
|
||||
return new Signal(
|
||||
ticker: ticker,
|
||||
direction: direction,
|
||||
confidence: Confidence.Medium, // Will be updated by validation
|
||||
candle: candle,
|
||||
date: signalDate,
|
||||
exchange: TradingExchanges.GmxV2,
|
||||
indicatorType: IndicatorType.Stc,
|
||||
signalType: SignalType.Signal,
|
||||
indicatorName: "TestIndicator",
|
||||
user: new User { Name = "TestUser" }
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetProbabilityOfTargetPriceAsync_ShouldReturnValidProbability_ForBTC_RealAPI()
|
||||
{
|
||||
// Arrange - Static values for testing
|
||||
const decimal currentBtcPrice = 102000m; // Current BTC price at $102k
|
||||
const decimal takeProfitPrice = currentBtcPrice * 1.02m; // 2% TP = $104,040
|
||||
const decimal stopLossPrice = currentBtcPrice * 0.99m; // 1% SL = $100,980
|
||||
const int timeHorizonHours = 24; // 24 hour forecast
|
||||
|
||||
Console.WriteLine($"🚀 Starting Synth API Test for BTC at ${currentBtcPrice:N0}");
|
||||
|
||||
// Create real API client and service
|
||||
var httpClient = new HttpClient();
|
||||
var logger = new TestLogger<SynthPredictionService>();
|
||||
var synthApiClient = new SynthApiClient(httpClient, new TestLogger<SynthApiClient>());
|
||||
var mockSynthRepository = new Mock<ISynthRepository>();
|
||||
var synthPredictionService = new SynthPredictionService(synthApiClient, mockSynthRepository.Object, logger);
|
||||
|
||||
// Create configuration for enabled Synth API
|
||||
var config = new SynthConfiguration
|
||||
{
|
||||
IsEnabled = true,
|
||||
TopMinersCount = 5, // Use fewer miners for faster testing
|
||||
TimeIncrement = 300, // 5 minutes (supported by Synth API)
|
||||
DefaultTimeLength = timeHorizonHours * 3600, // 24 hours in seconds
|
||||
MaxLiquidationProbability = 0.10m,
|
||||
PredictionCacheDurationMinutes = 1 // Short cache for testing
|
||||
};
|
||||
|
||||
// Act & Assert - Test Take Profit probability (upward movement for LONG)
|
||||
try
|
||||
{
|
||||
Console.WriteLine("🔍 Fetching Take Profit probability from Synth API...");
|
||||
|
||||
var takeProfitProbability = await synthPredictionService.GetProbabilityOfTargetPriceAsync(
|
||||
asset: "BTC",
|
||||
currentPrice: currentBtcPrice,
|
||||
targetPrice: takeProfitPrice,
|
||||
timeHorizonSeconds: timeHorizonHours * 3600,
|
||||
isLongPosition: false, // For TP, we want upward movement (opposite of liquidation direction)
|
||||
config: config);
|
||||
|
||||
Console.WriteLine($"🎯 Take Profit Analysis (2% gain):");
|
||||
Console.WriteLine($"Current Price: ${currentBtcPrice:N0}");
|
||||
Console.WriteLine($"Target Price: ${takeProfitPrice:N0}");
|
||||
Console.WriteLine($"Probability: {takeProfitProbability:P2}");
|
||||
|
||||
Assert.True(takeProfitProbability >= 0m && takeProfitProbability <= 1m,
|
||||
"Take profit probability should be between 0 and 1");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Take Profit test failed: {ex.Message}");
|
||||
Console.WriteLine("⚠️ Skipping Take Profit test due to API issue");
|
||||
}
|
||||
|
||||
// Act & Assert - Test Stop Loss probability (downward movement for LONG)
|
||||
try
|
||||
{
|
||||
Console.WriteLine("\n🔍 Fetching Stop Loss probability from Synth API...");
|
||||
|
||||
var stopLossProbability = await synthPredictionService.GetProbabilityOfTargetPriceAsync(
|
||||
asset: "BTC",
|
||||
currentPrice: currentBtcPrice,
|
||||
targetPrice: stopLossPrice,
|
||||
timeHorizonSeconds: timeHorizonHours * 3600,
|
||||
isLongPosition: true, // For SL in long position, we check downward movement
|
||||
config: config);
|
||||
|
||||
Console.WriteLine($"🛑 Stop Loss Analysis (1% loss):");
|
||||
Console.WriteLine($"Current Price: ${currentBtcPrice:N0}");
|
||||
Console.WriteLine($"Stop Loss Price: ${stopLossPrice:N0}");
|
||||
Console.WriteLine($"Liquidation Risk: {stopLossProbability:P2}");
|
||||
|
||||
Assert.True(stopLossProbability >= 0m && stopLossProbability <= 1m,
|
||||
"Stop loss probability should be between 0 and 1");
|
||||
|
||||
// Risk assessment - typical risk thresholds
|
||||
if (stopLossProbability > 0.20m)
|
||||
{
|
||||
Console.WriteLine("⚠️ HIGH RISK: Liquidation probability exceeds 20%");
|
||||
}
|
||||
else if (stopLossProbability > 0.10m)
|
||||
{
|
||||
Console.WriteLine("⚡ MODERATE RISK: Liquidation probability between 10-20%");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("✅ LOW RISK: Liquidation probability below 10%");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Stop Loss test failed: {ex.Message}");
|
||||
Console.WriteLine("⚠️ Skipping Stop Loss test due to API issue");
|
||||
}
|
||||
|
||||
Console.WriteLine($"\n📊 Money Management Summary:");
|
||||
Console.WriteLine($"Position: LONG BTC");
|
||||
Console.WriteLine($"Entry: ${currentBtcPrice:N0}");
|
||||
Console.WriteLine($"Take Profit: ${takeProfitPrice:N0} (+2.00%)");
|
||||
Console.WriteLine($"Stop Loss: ${stopLossPrice:N0} (-1.00%)");
|
||||
Console.WriteLine($"Risk/Reward Ratio: 1:2");
|
||||
Console.WriteLine($"Time Horizon: {timeHorizonHours} hours");
|
||||
Console.WriteLine("🏁 Test completed!");
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateSignalAsync_ShouldUseCustomThresholds_ForSignalFiltering_RealAPI()
|
||||
{
|
||||
// Arrange - Static values for custom threshold testing
|
||||
const decimal currentBtcPrice = 107300m; // Current BTC price at $105,700
|
||||
|
||||
Console.WriteLine($"🔧 Starting RiskManagement Configuration Test for BTC at ${currentBtcPrice:N0}");
|
||||
|
||||
// Create real API client and service
|
||||
var httpClient = new HttpClient();
|
||||
var logger = new TestLogger<SynthPredictionService>();
|
||||
var synthApiClient = new SynthApiClient(httpClient, new TestLogger<SynthApiClient>());
|
||||
var mockSynthRepository = new Mock<ISynthRepository>();
|
||||
var synthPredictionService = new SynthPredictionService(synthApiClient, mockSynthRepository.Object, logger);
|
||||
|
||||
// Define test scenarios for both LONG and SHORT signals
|
||||
var signalDirections = new[]
|
||||
{
|
||||
new { Direction = TradeDirection.Long, Name = "LONG" },
|
||||
new { Direction = TradeDirection.Short, Name = "SHORT" }
|
||||
};
|
||||
|
||||
// Define RiskManagement configurations to test
|
||||
var riskConfigs = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
Name = "Default (Moderate)",
|
||||
RiskConfig = new RiskManagement
|
||||
{
|
||||
AdverseProbabilityThreshold = 0.25m, // 25% - balanced threshold
|
||||
FavorableProbabilityThreshold = 0.30m, // 30% - reasonable expectation
|
||||
RiskAversion = 1.5m, // Moderate risk aversion
|
||||
KellyMinimumThreshold = 0.02m, // 2% - practical minimum
|
||||
KellyMaximumCap = 0.20m, // 20% - reasonable maximum
|
||||
KellyFractionalMultiplier = 0.75m, // 75% of Kelly (conservative)
|
||||
RiskTolerance = RiskToleranceLevel.Moderate
|
||||
}
|
||||
},
|
||||
new
|
||||
{
|
||||
Name = "Conservative",
|
||||
RiskConfig = new RiskManagement
|
||||
{
|
||||
AdverseProbabilityThreshold = 0.20m, // 20% - stricter threshold
|
||||
FavorableProbabilityThreshold = 0.40m, // 40% - higher TP expectation
|
||||
RiskAversion = 2.0m, // Higher risk aversion
|
||||
KellyMinimumThreshold = 0.03m, // 3% - higher minimum
|
||||
KellyMaximumCap = 0.15m, // 15% - lower maximum
|
||||
KellyFractionalMultiplier = 0.50m, // 50% of Kelly (very conservative)
|
||||
RiskTolerance = RiskToleranceLevel.Conservative
|
||||
}
|
||||
},
|
||||
new
|
||||
{
|
||||
Name = "Aggressive",
|
||||
RiskConfig = new RiskManagement
|
||||
{
|
||||
AdverseProbabilityThreshold = 0.35m, // 35% - more permissive
|
||||
FavorableProbabilityThreshold = 0.25m, // 25% - lower TP barrier
|
||||
RiskAversion = 1.0m, // Lower risk aversion
|
||||
KellyMinimumThreshold = 0.01m, // 1% - lower minimum
|
||||
KellyMaximumCap = 0.30m, // 30% - higher maximum
|
||||
KellyFractionalMultiplier = 1.0m, // 100% of Kelly (full Kelly)
|
||||
RiskTolerance = RiskToleranceLevel.Aggressive
|
||||
}
|
||||
},
|
||||
new
|
||||
{
|
||||
Name = "Moderate-Plus",
|
||||
RiskConfig = new RiskManagement
|
||||
{
|
||||
AdverseProbabilityThreshold = 0.30m, // 30% - slightly more permissive
|
||||
FavorableProbabilityThreshold = 0.35m, // 35% - balanced expectation
|
||||
RiskAversion = 1.2m, // Slightly less risk-averse
|
||||
KellyMinimumThreshold = 0.015m, // 1.5% - practical minimum
|
||||
KellyMaximumCap = 0.25m, // 25% - reasonable maximum
|
||||
KellyFractionalMultiplier = 0.85m, // 85% of Kelly
|
||||
RiskTolerance = RiskToleranceLevel.Moderate
|
||||
}
|
||||
},
|
||||
new
|
||||
{
|
||||
Name = "Risk-Focused",
|
||||
RiskConfig = new RiskManagement
|
||||
{
|
||||
AdverseProbabilityThreshold = 0.18m, // 18% - tight risk control
|
||||
FavorableProbabilityThreshold = 0.45m, // 45% - high TP requirement
|
||||
RiskAversion = 2.5m, // High risk aversion
|
||||
KellyMinimumThreshold = 0.025m, // 2.5% - higher minimum
|
||||
KellyMaximumCap = 0.12m, // 12% - very conservative maximum
|
||||
KellyFractionalMultiplier = 0.40m, // 40% of Kelly (very conservative)
|
||||
RiskTolerance = RiskToleranceLevel.Conservative
|
||||
}
|
||||
},
|
||||
new
|
||||
{
|
||||
Name = "Ultra-Conservative",
|
||||
RiskConfig = new RiskManagement
|
||||
{
|
||||
AdverseProbabilityThreshold = 0.16m, // 16% - very strict threshold (should trigger some LOWs)
|
||||
FavorableProbabilityThreshold = 0.60m, // 60% - very high TP requirement
|
||||
RiskAversion = 3.5m, // Very high risk aversion
|
||||
KellyMinimumThreshold = 0.04m, // 4% - high minimum barrier
|
||||
KellyMaximumCap = 0.08m, // 8% - very low maximum (forces heavy capping)
|
||||
KellyFractionalMultiplier = 0.25m, // 25% of Kelly (ultra conservative)
|
||||
RiskTolerance = RiskToleranceLevel.Conservative
|
||||
}
|
||||
},
|
||||
new
|
||||
{
|
||||
Name =
|
||||
"Paranoid-Blocking",
|
||||
RiskConfig = new RiskManagement
|
||||
{
|
||||
AdverseProbabilityThreshold = 0.12m, // 12% - very strict (should block 22-25% SL signals)
|
||||
FavorableProbabilityThreshold = 0.60m, // 60% - very high TP requirement
|
||||
RiskAversion = 4.0m, // Extremely high risk aversion
|
||||
KellyMinimumThreshold = 0.05m, // 5% - very high minimum
|
||||
KellyMaximumCap = 0.06m, // 6% - extremely conservative maximum
|
||||
KellyFractionalMultiplier = 0.15m, // 15% of Kelly (extremely conservative)
|
||||
RiskTolerance = RiskToleranceLevel.Conservative,
|
||||
SignalValidationTimeHorizonHours = 24
|
||||
}
|
||||
},
|
||||
new
|
||||
{
|
||||
Name = "Extreme-Blocking",
|
||||
RiskConfig = new RiskManagement
|
||||
{
|
||||
AdverseProbabilityThreshold = 0.08m, // 8% - extremely strict (will block 22-25% SL signals)
|
||||
FavorableProbabilityThreshold = 0.70m, // 70% - extremely high TP requirement
|
||||
RiskAversion = 5.0m, // Maximum risk aversion
|
||||
KellyMinimumThreshold = 0.08m, // 8% - very high minimum
|
||||
KellyMaximumCap = 0.05m, // 5% - extremely small maximum
|
||||
KellyFractionalMultiplier = 0.10m, // 10% of Kelly (ultra-conservative)
|
||||
RiskTolerance = RiskToleranceLevel.Conservative,
|
||||
SignalValidationTimeHorizonHours = 24
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Store results for summary
|
||||
var testResults =
|
||||
new Dictionary<string, Dictionary<string, SignalValidationResult>>();
|
||||
|
||||
// Test each RiskManagement configuration with both LONG and SHORT signals
|
||||
foreach (var configTest in riskConfigs)
|
||||
{
|
||||
Console.WriteLine($"\n📊 Testing {configTest.Name})");
|
||||
testResults[configTest.Name] =
|
||||
new Dictionary<string, SignalValidationResult>();
|
||||
|
||||
// Create bot configuration with the specific RiskManagement
|
||||
var botConfig = new TradingBotConfig
|
||||
{
|
||||
BotTradingBalance = 50000m, // $50k trading balance for realistic utility calculations
|
||||
Timeframe = Timeframe.FifteenMinutes,
|
||||
UseSynthApi = true,
|
||||
UseForSignalFiltering = true,
|
||||
UseForPositionSizing = true,
|
||||
UseForDynamicStopLoss = false,
|
||||
RiskManagement = configTest.RiskConfig, // Use the specific risk configuration
|
||||
MoneyManagement = new MoneyManagement
|
||||
{
|
||||
Name = "Test Money Management",
|
||||
StopLoss = 0.02m, // 2% stop loss
|
||||
TakeProfit = 0.022m, // 4% take profit (1:2 risk/reward ratio)
|
||||
Leverage = 10m,
|
||||
Timeframe = Timeframe.FifteenMinutes
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var signal in signalDirections)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($" 🎯 {signal.Name} Signal Test");
|
||||
|
||||
// Create a test signal for this direction
|
||||
var testSignal = CreateTestSignal(Ticker.BTC, signal.Direction, currentBtcPrice);
|
||||
|
||||
var result = await synthPredictionService.ValidateSignalAsync(
|
||||
signal: testSignal,
|
||||
currentPrice: currentBtcPrice,
|
||||
botConfig: botConfig,
|
||||
isBacktest: false,
|
||||
customThresholds: null); // No custom thresholds - use RiskManagement config
|
||||
|
||||
testResults[configTest.Name][signal.Name] = result;
|
||||
|
||||
Console.WriteLine($" 🎯 Confidence: {result.Confidence}");
|
||||
Console.WriteLine(
|
||||
$" 📊 SL Risk: {result.StopLossProbability:P2} | TP Prob: {result.TakeProfitProbability:P2}");
|
||||
Console.WriteLine(
|
||||
$" 🎲 TP/SL Ratio: {result.TpSlRatio:F2}x | Win/Loss: {result.WinLossRatio:F2}:1");
|
||||
Console.WriteLine($" 💰 Expected Value: ${result.ExpectedMonetaryValue:F2}");
|
||||
Console.WriteLine($" 🧮 Expected Utility: {result.ExpectedUtility:F4}");
|
||||
Console.WriteLine(
|
||||
$" 🎯 Kelly: {result.KellyFraction:P2} (Capped: {result.KellyCappedFraction:P2})");
|
||||
Console.WriteLine($" 📊 Kelly Assessment: {result.KellyAssessment}");
|
||||
Console.WriteLine($" ✅ Kelly Favorable: {result.IsKellyFavorable(configTest.RiskConfig)}");
|
||||
Console.WriteLine($" 🚫 Blocked: {result.IsBlocked}");
|
||||
|
||||
// Debug: Show actual probability values and threshold comparison
|
||||
var adverseThreshold = configTest.RiskConfig.AdverseProbabilityThreshold;
|
||||
Console.WriteLine(
|
||||
$" 🔍 DEBUG - SL: {result.StopLossProbability:F4} | TP: {result.TakeProfitProbability:F4} | Threshold: {adverseThreshold:F4}");
|
||||
Console.WriteLine(
|
||||
$" 🔍 DEBUG - SL > Threshold: {result.StopLossProbability > adverseThreshold} | TP > SL: {result.TakeProfitProbability > result.StopLossProbability}");
|
||||
|
||||
// Assert that the method works with RiskManagement configuration
|
||||
Assert.True(Enum.IsDefined(typeof(Confidence), result.Confidence),
|
||||
$"{configTest.Name} - {signal.Name} signal should return a valid Confidence level");
|
||||
|
||||
// Assert that Kelly calculations were performed
|
||||
Assert.True(result.KellyFraction >= 0, "Kelly fraction should be non-negative");
|
||||
Assert.True(result.KellyCappedFraction >= 0, "Capped Kelly fraction should be non-negative");
|
||||
|
||||
// Assert that Expected Utility calculations were performed
|
||||
Assert.True(result.TradingBalance > 0, "Trading balance should be set from bot config");
|
||||
Assert.Equal(botConfig.BotTradingBalance, result.TradingBalance);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($" ❌ {signal.Name} signal test failed: {ex.Message}");
|
||||
// Create a fallback result for error cases
|
||||
testResults[configTest.Name][signal.Name] = new SignalValidationResult
|
||||
{
|
||||
Confidence = Confidence.High, // Default to high confidence on error
|
||||
ValidationContext = $"Error: {ex.Message}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Display comprehensive results summary
|
||||
Console.WriteLine($"\n📈 Comprehensive RiskManagement Configuration Test Summary:");
|
||||
Console.WriteLine($"Asset: BTC | Price: ${currentBtcPrice:N0} | Trading Balance: ${50000:N0}");
|
||||
Console.WriteLine($"Stop Loss: 2.0% | Take Profit: 4.0% | Risk/Reward Ratio: 1:2.0");
|
||||
|
||||
_testOutputHelper.WriteLine($"\n🎯 Results Matrix:");
|
||||
_testOutputHelper.WriteLine(
|
||||
$"{"Configuration",-20} {"LONG Confidence",-15} {"LONG Kelly",-12} {"SHORT Confidence",-16} {"SHORT Kelly",-12}");
|
||||
_testOutputHelper.WriteLine(new string('-', 85));
|
||||
|
||||
foreach (var configTest in riskConfigs)
|
||||
{
|
||||
var longResult = testResults[configTest.Name].GetValueOrDefault("LONG");
|
||||
var shortResult = testResults[configTest.Name].GetValueOrDefault("SHORT");
|
||||
|
||||
var longConf = longResult?.Confidence ?? Confidence.None;
|
||||
var shortConf = shortResult?.Confidence ?? Confidence.None;
|
||||
var longKelly = longResult?.KellyCappedFraction ?? 0m;
|
||||
var shortKelly = shortResult?.KellyCappedFraction ?? 0m;
|
||||
|
||||
_testOutputHelper.WriteLine(
|
||||
$"{configTest.Name,-20} {GetConfidenceDisplay(longConf),-15} {longKelly,-12:P1} {GetConfidenceDisplay(shortConf),-16} {shortKelly,-12:P1}");
|
||||
}
|
||||
|
||||
// Display detailed ValidationContext for each configuration and direction
|
||||
Console.WriteLine($"\n📊 Detailed Analysis Results:");
|
||||
Console.WriteLine(new string('=', 120));
|
||||
|
||||
foreach (var configTest in riskConfigs)
|
||||
{
|
||||
Console.WriteLine($"\n🔧 {configTest.Name}");
|
||||
Console.WriteLine(new string('-', 80));
|
||||
|
||||
var longResult = testResults[configTest.Name].GetValueOrDefault("LONG");
|
||||
var shortResult = testResults[configTest.Name].GetValueOrDefault("SHORT");
|
||||
|
||||
if (longResult != null)
|
||||
{
|
||||
Console.WriteLine($"📈 LONG Signal Analysis:");
|
||||
Console.WriteLine($" Context: {longResult.ValidationContext ?? "N/A"}");
|
||||
Console.WriteLine(
|
||||
$" Confidence: {GetConfidenceDisplay(longResult.Confidence)} | Blocked: {longResult.IsBlocked}");
|
||||
Console.WriteLine(
|
||||
$" Kelly Assessment: {longResult.KellyAssessment} | Kelly Favorable: {longResult.IsKellyFavorable(configTest.RiskConfig)}");
|
||||
if (longResult.TradingBalance > 0)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$" Trading Balance: ${longResult.TradingBalance:N0} | Risk Assessment: {longResult.GetUtilityRiskAssessment()}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"📈 LONG Signal Analysis: ERROR - No result available");
|
||||
}
|
||||
|
||||
Console.WriteLine(); // Empty line for separation
|
||||
|
||||
if (shortResult != null)
|
||||
{
|
||||
Console.WriteLine($"📉 SHORT Signal Analysis:");
|
||||
Console.WriteLine($" Context: {shortResult.ValidationContext ?? "N/A"}");
|
||||
Console.WriteLine(
|
||||
$" Confidence: {GetConfidenceDisplay(shortResult.Confidence)} | Blocked: {shortResult.IsBlocked}");
|
||||
Console.WriteLine(
|
||||
$" Kelly Assessment: {shortResult.KellyAssessment} | Kelly Favorable: {shortResult.IsKellyFavorable(configTest.RiskConfig)}");
|
||||
if (shortResult.TradingBalance > 0)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$" Trading Balance: ${shortResult.TradingBalance:N0} | Risk Assessment: {shortResult.GetUtilityRiskAssessment()}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"📉 SHORT Signal Analysis: ERROR - No result available");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"\n📊 Risk Configuration Analysis:");
|
||||
Console.WriteLine($"• Default: Balanced 20% adverse threshold, 1% Kelly minimum");
|
||||
Console.WriteLine($"• Conservative: Strict 15% adverse, 2% Kelly min, half-Kelly multiplier");
|
||||
Console.WriteLine($"• Aggressive: Permissive 30% adverse, 0.5% Kelly min, full Kelly");
|
||||
Console.WriteLine($"• Custom Permissive: Very permissive 35% adverse, low barriers");
|
||||
Console.WriteLine($"• Custom Strict: Very strict 10% adverse, high barriers, conservative sizing");
|
||||
|
||||
Console.WriteLine($"\n💡 Key Insights:");
|
||||
Console.WriteLine($"• Conservative configs should block more signals (lower confidence)");
|
||||
Console.WriteLine($"• Aggressive configs should allow more signals (higher confidence)");
|
||||
Console.WriteLine($"• Kelly fractions should vary based on risk tolerance settings");
|
||||
Console.WriteLine($"• Expected Utility should reflect trading balance and risk aversion");
|
||||
|
||||
// Verify that we have results for all configurations and directions
|
||||
foreach (var configTest in riskConfigs)
|
||||
{
|
||||
foreach (var signal in signalDirections)
|
||||
{
|
||||
Assert.True(testResults.ContainsKey(configTest.Name) &&
|
||||
testResults[configTest.Name].ContainsKey(signal.Name),
|
||||
$"Should have test result for {configTest.Name} - {signal.Name}");
|
||||
|
||||
var result = testResults[configTest.Name][signal.Name];
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.TradingBalance > 0, "Trading balance should be populated from bot config");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("🏁 Comprehensive RiskManagement Configuration Test completed!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to display confidence levels with emojis
|
||||
/// </summary>
|
||||
/// <param name="confidence">Confidence level</param>
|
||||
/// <returns>Formatted confidence display</returns>
|
||||
private static string GetConfidenceDisplay(Confidence confidence)
|
||||
{
|
||||
return confidence switch
|
||||
{
|
||||
Confidence.High => "🟢 HIGH",
|
||||
Confidence.Medium => "🟡 MEDIUM",
|
||||
Confidence.Low => "🟠 LOW",
|
||||
Confidence.None => "🔴 NONE",
|
||||
_ => "❓ UNKNOWN"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Simple test logger implementation
|
||||
public class TestLogger<T> : ILogger<T>
|
||||
{
|
||||
public IDisposable BeginScope<TState>(TState state) => null;
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
|
||||
Func<TState, Exception, string> formatter)
|
||||
{
|
||||
// Silent logger for tests - output goes to Console.WriteLine
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,25 @@ using NSwag.CodeGeneration.TypeScript;
|
||||
|
||||
var document = OpenApiDocument.FromUrlAsync(("http://localhost:5000/swagger/v1/swagger.json")).Result;
|
||||
|
||||
// Get the solution directory by going up from the current executable location
|
||||
var currentDirectory = Directory.GetCurrentDirectory();
|
||||
var solutionDirectory = currentDirectory;
|
||||
|
||||
// Navigate up until we find the src directory or reach a reasonable limit
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
if (Directory.Exists(Path.Combine(solutionDirectory, "src")))
|
||||
break;
|
||||
|
||||
var parent = Directory.GetParent(solutionDirectory);
|
||||
if (parent == null)
|
||||
break;
|
||||
solutionDirectory = parent.FullName;
|
||||
}
|
||||
|
||||
var targetDirectory = Path.Combine(solutionDirectory, "src", "Managing.WebApp", "src", "generated");
|
||||
Directory.CreateDirectory(targetDirectory); // Ensure the directory exists
|
||||
|
||||
var settings = new TypeScriptClientGeneratorSettings
|
||||
{
|
||||
ClassName = "{controller}Client",
|
||||
@@ -30,7 +49,27 @@ var settings = new TypeScriptClientGeneratorSettings
|
||||
|
||||
var generatorApiClient = new TypeScriptClientGenerator(document, settings);
|
||||
var codeApiClient = generatorApiClient.GenerateFile();
|
||||
File.WriteAllText("ManagingApi.ts", codeApiClient);
|
||||
|
||||
// Add the necessary imports after the auto-generated comment
|
||||
var requiredImports = @"
|
||||
import AuthorizedApiBase from ""./AuthorizedApiBase"";
|
||||
import IConfig from ""./IConfig"";
|
||||
";
|
||||
|
||||
// Find the end of the auto-generated comment and insert imports
|
||||
var autoGeneratedEndIndex = codeApiClient.IndexOf("//----------------------");
|
||||
if (autoGeneratedEndIndex != -1)
|
||||
{
|
||||
// Find the second occurrence (end of the comment block)
|
||||
autoGeneratedEndIndex = codeApiClient.IndexOf("//----------------------", autoGeneratedEndIndex + 1);
|
||||
if (autoGeneratedEndIndex != -1)
|
||||
{
|
||||
autoGeneratedEndIndex = codeApiClient.IndexOf("\n", autoGeneratedEndIndex) + 1;
|
||||
codeApiClient = codeApiClient.Insert(autoGeneratedEndIndex, requiredImports);
|
||||
}
|
||||
}
|
||||
|
||||
File.WriteAllText(Path.Combine(targetDirectory, "ManagingApi.ts"), codeApiClient);
|
||||
|
||||
var settingsTypes = new TypeScriptClientGeneratorSettings
|
||||
{
|
||||
@@ -53,4 +92,4 @@ var settingsTypes = new TypeScriptClientGeneratorSettings
|
||||
|
||||
var generatorTypes = new TypeScriptClientGenerator(document, settingsTypes);
|
||||
var codeTypes = generatorTypes.GenerateFile();
|
||||
File.WriteAllText("ManagingApiTypes.ts", codeTypes);
|
||||
File.WriteAllText(Path.Combine(targetDirectory, "ManagingApiTypes.ts"), codeTypes);
|
||||
@@ -1,5 +1,5 @@
|
||||
# Use an official Node.js image as the base
|
||||
FROM node:18-alpine
|
||||
FROM node:22.14.0-alpine
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /app
|
||||
@@ -8,37 +8,38 @@ WORKDIR /app
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
||||
|
||||
# Install git and Python
|
||||
#RUN apk update && apk add --no-cache git python3 make g++
|
||||
RUN apk update && apk add --no-cache git python3 make g++
|
||||
|
||||
# Create a symlink for python3 as python
|
||||
#RUN ln -sf /usr/bin/python3 /usr/bin/python
|
||||
# Create a symlink for python3 as python
|
||||
# This might not be strictly necessary for your current issue but good to keep if Python scripts are involved.
|
||||
# RUN ln -sf /usr/bin/python3 /usr/bin/python
|
||||
|
||||
# Copy package.json and package-lock.json to the container
|
||||
# COPY package*.json ./
|
||||
COPY /src/Managing.WebApp/package.json ./
|
||||
# Copy package.json and package-lock.json to the container
|
||||
# COPY package*.json ./
|
||||
COPY /src/Managing.WebApp/package.json ./
|
||||
|
||||
# Install dependencies with the --legacy-peer-deps flag to bypass peer dependency conflicts
|
||||
RUN npm install --legacy-peer-deps
|
||||
RUN npm install -g tailwindcss postcss autoprefixer @tailwindcss/typography
|
||||
# Install dependencies with the --legacy-peer-deps flag to bypass peer dependency conflicts
|
||||
RUN npm install --legacy-peer-deps --loglevel verbose
|
||||
RUN npm install -g tailwindcss postcss autoprefixer @tailwindcss/typography
|
||||
|
||||
# Copy the rest of the app's source code to the container
|
||||
# COPY . .
|
||||
RUN ls -la
|
||||
COPY src/Managing.WebApp/ .
|
||||
RUN node --max-old-space-size=8192 ./node_modules/.bin/vite build
|
||||
# Copy the rest of the app's source code to the container
|
||||
# COPY . .
|
||||
RUN ls -la
|
||||
COPY src/Managing.WebApp/ .
|
||||
RUN node --max-old-space-size=8192 ./node_modules/.bin/vite build
|
||||
|
||||
# Build the app
|
||||
RUN npm run build
|
||||
# Build the app
|
||||
RUN npm run build
|
||||
|
||||
# Use NGINX as the web server
|
||||
FROM nginx:alpine
|
||||
# Use NGINX as the web server
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy the built app to the NGINX web server directory
|
||||
# COPY --from=0 /app/build /usr/share/nginx/html
|
||||
COPY --from=0 /app/dist /usr/share/nginx/html
|
||||
# Copy the built app to the NGINX web server directory
|
||||
# COPY --from=0 /app/build /usr/share/nginx/html
|
||||
COPY --from=0 /app/dist /usr/share/nginx/html
|
||||
|
||||
# Expose port 80 for the NGINX web server
|
||||
EXPOSE 80
|
||||
# Expose port 80 for the NGINX web server
|
||||
EXPOSE 80
|
||||
|
||||
# Start the NGINX web server
|
||||
# Start the NGINX web server
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -32,7 +32,6 @@
|
||||
"canonicalize": "^2.0.0",
|
||||
"classnames": "^2.3.1",
|
||||
"connectkit": "^1.8.2",
|
||||
"crypto": "^1.0.1",
|
||||
"date-fns": "^2.30.0",
|
||||
"elliptic": "^6.6.1",
|
||||
"jotai": "^1.6.7",
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Ticker,
|
||||
Timeframe,
|
||||
TradingBotConfig,
|
||||
TradingBotConfigRequest,
|
||||
UpdateBotConfigRequest
|
||||
} from '../../../generated/ManagingApi'
|
||||
import Toast from '../Toast/Toast'
|
||||
@@ -58,6 +59,11 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
customStopLoss: number
|
||||
customTakeProfit: number
|
||||
customLeverage: number
|
||||
// Synth API fields
|
||||
useSynthApi: boolean
|
||||
useForPositionSizing: boolean
|
||||
useForSignalFiltering: boolean
|
||||
useForDynamicStopLoss: boolean
|
||||
}>({
|
||||
name: '',
|
||||
accountName: '',
|
||||
@@ -77,9 +83,16 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
useCustomMoneyManagement: false,
|
||||
customStopLoss: 0.01,
|
||||
customTakeProfit: 0.02,
|
||||
customLeverage: 1
|
||||
customLeverage: 1,
|
||||
useSynthApi: false,
|
||||
useForPositionSizing: true,
|
||||
useForSignalFiltering: true,
|
||||
useForDynamicStopLoss: true
|
||||
})
|
||||
|
||||
// State for advanced parameters dropdown
|
||||
const [showAdvancedParams, setShowAdvancedParams] = useState(false)
|
||||
|
||||
// Fetch data
|
||||
const { data: accounts } = useQuery({
|
||||
queryFn: async () => {
|
||||
@@ -110,11 +123,11 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
if (mode === 'create' && backtest) {
|
||||
// Initialize from backtest
|
||||
setFormData({
|
||||
name: `Bot-${backtest.config.scenarioName}-${new Date().toISOString().slice(0, 19).replace(/[-:]/g, '')}`,
|
||||
name: `Bot-${backtest.config.scenarioName || 'Custom'}-${new Date().toISOString().slice(0, 19).replace(/[-:]/g, '')}`,
|
||||
accountName: backtest.config.accountName,
|
||||
moneyManagementName: moneyManagements?.[0]?.name || '',
|
||||
ticker: backtest.config.ticker,
|
||||
scenarioName: backtest.config.scenarioName,
|
||||
scenarioName: backtest.config.scenarioName || '',
|
||||
timeframe: backtest.config.timeframe,
|
||||
isForWatchingOnly: false,
|
||||
botTradingBalance: 1000,
|
||||
@@ -128,7 +141,11 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
useCustomMoneyManagement: true, // Default to custom for backtests
|
||||
customStopLoss: backtest.config.moneyManagement?.stopLoss || 0.01,
|
||||
customTakeProfit: backtest.config.moneyManagement?.takeProfit || 0.02,
|
||||
customLeverage: backtest.config.moneyManagement?.leverage || 1
|
||||
customLeverage: backtest.config.moneyManagement?.leverage || 1,
|
||||
useSynthApi: false,
|
||||
useForPositionSizing: true,
|
||||
useForSignalFiltering: true,
|
||||
useForDynamicStopLoss: true
|
||||
})
|
||||
} else if (mode === 'update' && existingBot) {
|
||||
// Initialize from existing bot
|
||||
@@ -137,7 +154,7 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
accountName: existingBot.config.accountName,
|
||||
moneyManagementName: existingBot.config.moneyManagement?.name || '',
|
||||
ticker: existingBot.config.ticker,
|
||||
scenarioName: existingBot.config.scenarioName,
|
||||
scenarioName: existingBot.config.scenarioName || '',
|
||||
timeframe: existingBot.config.timeframe,
|
||||
isForWatchingOnly: existingBot.config.isForWatchingOnly,
|
||||
botTradingBalance: existingBot.config.botTradingBalance,
|
||||
@@ -151,7 +168,11 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
useCustomMoneyManagement: false,
|
||||
customStopLoss: existingBot.config.moneyManagement?.stopLoss || 0.01,
|
||||
customTakeProfit: existingBot.config.moneyManagement?.takeProfit || 0.02,
|
||||
customLeverage: existingBot.config.moneyManagement?.leverage || 1
|
||||
customLeverage: existingBot.config.moneyManagement?.leverage || 1,
|
||||
useSynthApi: existingBot.config.useSynthApi || false,
|
||||
useForPositionSizing: existingBot.config.useForPositionSizing || true,
|
||||
useForSignalFiltering: existingBot.config.useForSignalFiltering || true,
|
||||
useForDynamicStopLoss: existingBot.config.useForDynamicStopLoss || true
|
||||
})
|
||||
} else if (mode === 'create' && !backtest) {
|
||||
// Initialize for new bot creation
|
||||
@@ -174,7 +195,11 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
useCustomMoneyManagement: false,
|
||||
customStopLoss: 0.01,
|
||||
customTakeProfit: 0.02,
|
||||
customLeverage: 1
|
||||
customLeverage: 1,
|
||||
useSynthApi: false,
|
||||
useForPositionSizing: true,
|
||||
useForSignalFiltering: true,
|
||||
useForDynamicStopLoss: true
|
||||
})
|
||||
}
|
||||
}, [mode, backtest, existingBot, accounts, moneyManagements, scenarios])
|
||||
@@ -216,6 +241,17 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
}))
|
||||
}
|
||||
|
||||
const handleSynthApiToggle = (enabled: boolean) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
useSynthApi: enabled,
|
||||
// Reset sub-options when main toggle is turned off
|
||||
useForPositionSizing: enabled ? prev.useForPositionSizing : false,
|
||||
useForSignalFiltering: enabled ? prev.useForSignalFiltering : false,
|
||||
useForDynamicStopLoss: enabled ? prev.useForDynamicStopLoss : false
|
||||
}))
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const t = new Toast(mode === 'create' ? 'Creating bot...' : 'Updating bot...')
|
||||
const client = new BotClient({}, apiUrl)
|
||||
@@ -249,30 +285,31 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
return
|
||||
}
|
||||
|
||||
// Create TradingBotConfig (reused for both create and update)
|
||||
const tradingBotConfig: TradingBotConfig = {
|
||||
// Create TradingBotConfigRequest (instead of TradingBotConfig)
|
||||
const tradingBotConfigRequest: TradingBotConfigRequest = {
|
||||
accountName: formData.accountName,
|
||||
ticker: formData.ticker,
|
||||
scenarioName: formData.scenarioName,
|
||||
scenarioName: formData.scenarioName || undefined,
|
||||
timeframe: formData.timeframe,
|
||||
botType: formData.botType,
|
||||
isForWatchingOnly: formData.isForWatchingOnly,
|
||||
isForBacktest: false,
|
||||
cooldownPeriod: formData.cooldownPeriod,
|
||||
maxLossStreak: formData.maxLossStreak,
|
||||
maxPositionTimeHours: formData.maxPositionTimeHours,
|
||||
flipOnlyWhenInProfit: formData.flipOnlyWhenInProfit,
|
||||
flipPosition: formData.flipPosition,
|
||||
name: formData.name,
|
||||
botTradingBalance: formData.botTradingBalance,
|
||||
moneyManagement: moneyManagement,
|
||||
closeEarlyWhenProfitable: formData.closeEarlyWhenProfitable
|
||||
closeEarlyWhenProfitable: formData.closeEarlyWhenProfitable,
|
||||
useSynthApi: formData.useSynthApi,
|
||||
useForPositionSizing: formData.useForPositionSizing,
|
||||
useForSignalFiltering: formData.useForSignalFiltering,
|
||||
useForDynamicStopLoss: formData.useForDynamicStopLoss
|
||||
}
|
||||
|
||||
if (mode === 'create') {
|
||||
// Create new bot
|
||||
const request: StartBotRequest = {
|
||||
config: tradingBotConfig,
|
||||
config: tradingBotConfigRequest,
|
||||
moneyManagementName: formData.useCustomMoneyManagement ? undefined : formData.moneyManagementName
|
||||
}
|
||||
|
||||
@@ -282,7 +319,7 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
// Update existing bot
|
||||
const request: UpdateBotConfigRequest = {
|
||||
identifier: existingBot!.identifier,
|
||||
config: tradingBotConfig,
|
||||
config: tradingBotConfigRequest,
|
||||
moneyManagementName: formData.useCustomMoneyManagement ? undefined : formData.moneyManagementName
|
||||
}
|
||||
|
||||
@@ -373,7 +410,12 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Timeframe</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Timeframe</span>
|
||||
<div className="tooltip tooltip-top" data-tip="Chart timeframe for analysis. Lower timeframes (1m, 5m) for scalping, higher timeframes (1h, 4h) for swing trading">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<select
|
||||
className="select select-bordered"
|
||||
@@ -390,7 +432,12 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Bot Type</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Bot Type</span>
|
||||
<div className="tooltip tooltip-top" data-tip="ScalpingBot: Quick trades based on short-term signals. FlippingBot: Position reversal strategy that flips between long/short">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<select
|
||||
className="select select-bordered"
|
||||
@@ -408,7 +455,12 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Trading Balance</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Trading Balance</span>
|
||||
<div className="tooltip tooltip-top" data-tip="Amount of capital allocated for this bot's trading activities. This determines maximum position sizes">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
@@ -420,51 +472,14 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Cooldown Period (candles)</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered"
|
||||
value={formData.cooldownPeriod}
|
||||
onChange={(e) => handleInputChange('cooldownPeriod', parseInt(e.target.value))}
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Max Loss Streak</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered"
|
||||
value={formData.maxLossStreak}
|
||||
onChange={(e) => handleInputChange('maxLossStreak', parseInt(e.target.value))}
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Max Position Time (hours)</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered"
|
||||
value={formData.maxPositionTimeHours || ''}
|
||||
onChange={(e) => handleInputChange('maxPositionTimeHours', e.target.value ? parseFloat(e.target.value) : null)}
|
||||
min="0.1"
|
||||
step="0.1"
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Checkboxes */}
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer">
|
||||
<span className="label-text">Watch Only Mode</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Watch Only Mode</span>
|
||||
<div className="tooltip tooltip-top" data-tip="Bot will analyze and generate signals but won't execute actual trades. Good for testing strategies">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
@@ -473,51 +488,161 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer">
|
||||
<span className="label-text">Flip Only When In Profit</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
checked={formData.flipOnlyWhenInProfit}
|
||||
onChange={(e) => handleInputChange('flipOnlyWhenInProfit', e.target.checked)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer">
|
||||
<span className="label-text">Enable Position Flipping</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
checked={formData.flipPosition}
|
||||
onChange={(e) => handleInputChange('flipPosition', e.target.checked)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer">
|
||||
<span className="label-text">Close Early When Profitable</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
checked={formData.closeEarlyWhenProfitable}
|
||||
onChange={(e) => handleInputChange('closeEarlyWhenProfitable', e.target.checked)}
|
||||
disabled={!formData.maxPositionTimeHours}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Advanced Parameters Dropdown */}
|
||||
<div className="divider">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline btn-sm normal-case"
|
||||
onClick={() => setShowAdvancedParams(!showAdvancedParams)}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
Advanced Parameters
|
||||
<svg
|
||||
className={`w-4 h-4 ml-2 transition-transform duration-200 ${showAdvancedParams ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showAdvancedParams && (
|
||||
<div className="space-y-4 border border-primary rounded-lg p-4 bg-base-100">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Cooldown Period (candles)</span>
|
||||
<div className="tooltip tooltip-top" data-tip="Number of candles to wait before allowing another trade after closing a position. Prevents overtrading and allows market to settle">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered"
|
||||
value={formData.cooldownPeriod}
|
||||
onChange={(e) => handleInputChange('cooldownPeriod', parseInt(e.target.value))}
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Max Loss Streak</span>
|
||||
<div className="tooltip tooltip-top" data-tip="Maximum number of consecutive losing trades before stopping the bot. Set to 0 for no limit">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered"
|
||||
value={formData.maxLossStreak}
|
||||
onChange={(e) => handleInputChange('maxLossStreak', parseInt(e.target.value))}
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Max Position Time (hours)</span>
|
||||
<div className="tooltip tooltip-top" data-tip="Maximum time to hold a position before force closing. Leave empty to disable time-based position closure">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered"
|
||||
value={formData.maxPositionTimeHours || ''}
|
||||
onChange={(e) => handleInputChange('maxPositionTimeHours', e.target.value ? parseFloat(e.target.value) : null)}
|
||||
min="0.1"
|
||||
step="0.1"
|
||||
placeholder="Optional"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Flip Only When In Profit</span>
|
||||
<div className="tooltip tooltip-top" data-tip="If enabled, positions will only flip when current position is profitable. Helps avoid flipping during losing streaks">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
checked={formData.flipOnlyWhenInProfit}
|
||||
onChange={(e) => handleInputChange('flipOnlyWhenInProfit', e.target.checked)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Enable Position Flipping</span>
|
||||
<div className="tooltip tooltip-top" data-tip="Allow the bot to flip between long and short positions based on signals. More aggressive trading strategy">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
checked={formData.flipPosition}
|
||||
onChange={(e) => handleInputChange('flipPosition', e.target.checked)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Close Early When Profitable</span>
|
||||
<div className="tooltip tooltip-top" data-tip="If enabled, positions will close early when they become profitable. Conservative approach to lock in gains">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
checked={formData.closeEarlyWhenProfitable}
|
||||
onChange={(e) => handleInputChange('closeEarlyWhenProfitable', e.target.checked)}
|
||||
disabled={!formData.maxPositionTimeHours}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Money Management Section */}
|
||||
<div className="divider">Money Management</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer">
|
||||
<span className="label-text">Use Custom Money Management</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Use Custom Money Management</span>
|
||||
<div className="tooltip tooltip-top" data-tip="Create custom risk management settings instead of using saved presets. Allows fine-tuning stop loss, take profit, and leverage">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
@@ -592,6 +717,84 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Synth API Section */}
|
||||
<div className="divider">Synth API Configuration</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Enable Synth API</span>
|
||||
<div className="tooltip tooltip-top" data-tip="Enable AI-powered probabilistic price forecasts and risk assessment using advanced machine learning models for enhanced trading decisions">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
checked={formData.useSynthApi}
|
||||
onChange={(e) => handleSynthApiToggle(e.target.checked)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Show sub-options only when Synth API is enabled */}
|
||||
{formData.useSynthApi && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Use for Position Sizing</span>
|
||||
<div className="tooltip tooltip-top" data-tip="Use Synth predictions for position sizing adjustments and risk assessment. Optimizes trade size based on confidence levels">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
checked={formData.useForPositionSizing}
|
||||
onChange={(e) => handleInputChange('useForPositionSizing', e.target.checked)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Use for Signal Filtering</span>
|
||||
<div className="tooltip tooltip-top" data-tip="Use Synth predictions to filter trading signals. Only executes trades when AI confidence is high">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
checked={formData.useForSignalFiltering}
|
||||
onChange={(e) => handleInputChange('useForSignalFiltering', e.target.checked)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="label-text">Use for Dynamic Stop Loss</span>
|
||||
<div className="tooltip tooltip-top" data-tip="Use Synth predictions for dynamic stop-loss/take-profit adjustments. Adapts levels based on market conditions">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
checked={formData.useForDynamicStopLoss}
|
||||
onChange={(e) => handleInputChange('useForDynamicStopLoss', e.target.checked)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Validation Messages */}
|
||||
{formData.closeEarlyWhenProfitable && !formData.maxPositionTimeHours && (
|
||||
<div className="alert alert-warning mt-4">
|
||||
|
||||
@@ -4,18 +4,18 @@ import {type SubmitHandler, useForm} from 'react-hook-form'
|
||||
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import {
|
||||
AccountClient,
|
||||
BacktestClient,
|
||||
BotType,
|
||||
DataClient,
|
||||
MoneyManagement,
|
||||
MoneyManagementClient,
|
||||
RunBacktestRequest,
|
||||
Scenario,
|
||||
ScenarioClient,
|
||||
Ticker,
|
||||
Timeframe,
|
||||
TradingBotConfig,
|
||||
AccountClient,
|
||||
BacktestClient,
|
||||
BotType,
|
||||
DataClient,
|
||||
MoneyManagement,
|
||||
MoneyManagementClient,
|
||||
RunBacktestRequest,
|
||||
Scenario,
|
||||
ScenarioClient,
|
||||
Ticker,
|
||||
Timeframe,
|
||||
TradingBotConfigRequest,
|
||||
} from '../../../generated/ManagingApi'
|
||||
import type {BacktestModalProps, IBacktestsFormInput,} from '../../../global/type'
|
||||
import {Loader, Slider} from '../../atoms'
|
||||
@@ -42,7 +42,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
const [startDate, setStartDate] = useState<string>(defaultStartDateString);
|
||||
const [endDate, setEndDate] = useState<string>(defaultEndDateString);
|
||||
|
||||
const { register, handleSubmit, setValue } = useForm<IBacktestsFormInput>({
|
||||
const { register, handleSubmit, setValue, watch } = useForm<IBacktestsFormInput>({
|
||||
defaultValues: {
|
||||
startDate: defaultStartDateString,
|
||||
endDate: defaultEndDateString,
|
||||
@@ -51,9 +51,30 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
maxPositionTimeHours: null, // Default to null (disabled)
|
||||
flipOnlyWhenInProfit: true, // Default to true
|
||||
balance: 10000, // Default balance
|
||||
closeEarlyWhenProfitable: false // Default to false
|
||||
closeEarlyWhenProfitable: false, // Default to false
|
||||
// Synth API defaults
|
||||
useSynthApi: false,
|
||||
useForPositionSizing: true,
|
||||
useForSignalFiltering: true,
|
||||
useForDynamicStopLoss: true
|
||||
}
|
||||
});
|
||||
|
||||
// Watch the useSynthApi value to conditionally show/hide sub-options
|
||||
const useSynthApi = watch('useSynthApi');
|
||||
|
||||
// Reset sub-options when main Synth API toggle is turned off
|
||||
useEffect(() => {
|
||||
if (!useSynthApi) {
|
||||
setValue('useForPositionSizing', false);
|
||||
setValue('useForSignalFiltering', false);
|
||||
setValue('useForDynamicStopLoss', false);
|
||||
}
|
||||
}, [useSynthApi, setValue]);
|
||||
|
||||
// State for advanced parameters dropdown
|
||||
const [showAdvancedParams, setShowAdvancedParams] = useState(false);
|
||||
|
||||
const [selectedAccount, setSelectedAccount] = useState<string>('')
|
||||
const [selectedTimeframe, setSelectedTimeframe] = useState<Timeframe>(Timeframe.OneHour)
|
||||
const [selectedLoopQuantity, setLoopQuantity] = React.useState<number>(
|
||||
@@ -127,36 +148,48 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
console.log(customScenario)
|
||||
|
||||
try {
|
||||
// Create the TradingBotConfig
|
||||
const tradingBotConfig: TradingBotConfig = {
|
||||
// Create the TradingBotConfigRequest (note the Request suffix)
|
||||
const tradingBotConfigRequest: TradingBotConfigRequest = {
|
||||
accountName: form.accountName,
|
||||
ticker: ticker as Ticker,
|
||||
scenarioName: customScenario ? undefined : scenarioName,
|
||||
scenario: customScenario,
|
||||
scenario: customScenario ? {
|
||||
name: customScenario.name || 'Custom Scenario',
|
||||
indicators: customScenario.indicators?.map(indicator => ({
|
||||
name: indicator.name || 'Unnamed Indicator',
|
||||
type: indicator.type!,
|
||||
signalType: indicator.signalType!,
|
||||
minimumHistory: indicator.minimumHistory || 0,
|
||||
period: indicator.period,
|
||||
fastPeriods: indicator.fastPeriods,
|
||||
slowPeriods: indicator.slowPeriods,
|
||||
signalPeriods: indicator.signalPeriods,
|
||||
multiplier: indicator.multiplier,
|
||||
smoothPeriods: indicator.smoothPeriods,
|
||||
stochPeriods: indicator.stochPeriods,
|
||||
cyclePeriods: indicator.cyclePeriods
|
||||
})) || [],
|
||||
loopbackPeriod: customScenario.loopbackPeriod
|
||||
} : undefined,
|
||||
timeframe: form.timeframe,
|
||||
botType: form.botType,
|
||||
isForWatchingOnly: false, // Always false for backtests
|
||||
isForBacktest: true, // Always true for backtests
|
||||
cooldownPeriod: form.cooldownPeriod || 1,
|
||||
maxLossStreak: form.maxLossStreak || 0,
|
||||
maxPositionTimeHours: form.maxPositionTimeHours || null,
|
||||
flipOnlyWhenInProfit: form.flipOnlyWhenInProfit ?? true,
|
||||
flipPosition: form.botType === BotType.FlippingBot, // Set based on bot type
|
||||
name: `Backtest-${customScenario ? customScenario.name : scenarioName}-${ticker}-${new Date().toISOString()}`,
|
||||
botTradingBalance: form.balance,
|
||||
moneyManagement: customMoneyManagement || moneyManagements?.find(m => m.name === selectedMoneyManagement) || moneyManagements?.[0] || {
|
||||
name: 'placeholder',
|
||||
leverage: 1,
|
||||
stopLoss: 0.01,
|
||||
takeProfit: 0.02,
|
||||
timeframe: form.timeframe
|
||||
},
|
||||
closeEarlyWhenProfitable: form.closeEarlyWhenProfitable ?? false
|
||||
closeEarlyWhenProfitable: form.closeEarlyWhenProfitable ?? false,
|
||||
useSynthApi: form.useSynthApi ?? false,
|
||||
useForPositionSizing: form.useForPositionSizing ?? true,
|
||||
useForSignalFiltering: form.useForSignalFiltering ?? true,
|
||||
useForDynamicStopLoss: form.useForDynamicStopLoss ?? true
|
||||
};
|
||||
|
||||
// Create the RunBacktestRequest
|
||||
const request: RunBacktestRequest = {
|
||||
config: tradingBotConfig,
|
||||
config: tradingBotConfigRequest, // Use the request object
|
||||
startDate: new Date(form.startDate),
|
||||
endDate: new Date(form.endDate),
|
||||
balance: form.balance,
|
||||
@@ -199,7 +232,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
function onMoneyManagementChange(e: any) {
|
||||
if (e.target.value === 'custom') {
|
||||
setShowCustomMoneyManagement(true)
|
||||
setCustomMoneyManagement(e.target.value)
|
||||
setCustomMoneyManagement(undefined) // Reset custom money management when switching to custom mode
|
||||
} else {
|
||||
setShowCustomMoneyManagement(false)
|
||||
setCustomMoneyManagement(undefined)
|
||||
@@ -210,7 +243,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
function onScenarioChange(e: any) {
|
||||
if (e.target.value === 'custom') {
|
||||
setShowCustomScenario(true)
|
||||
setCustomScenario(e.target.value)
|
||||
setCustomScenario(undefined) // Reset custom scenario when switching to custom mode
|
||||
} else {
|
||||
setShowCustomScenario(false)
|
||||
setCustomScenario(undefined)
|
||||
@@ -232,6 +265,11 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
useEffect(() => {
|
||||
if (scenarios && scenarios.length > 0 && scenarios[0].name) {
|
||||
setValue('scenarioName', scenarios[0].name);
|
||||
setShowCustomScenario(false); // Hide custom scenario when scenarios are available
|
||||
} else if (scenarios && scenarios.length === 0) {
|
||||
// No scenarios available, automatically show custom scenario creation
|
||||
setShowCustomScenario(true);
|
||||
setValue('scenarioName', ''); // Clear any selected scenario
|
||||
}
|
||||
}, [scenarios, setValue]);
|
||||
|
||||
@@ -263,6 +301,12 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
if (moneyManagements && moneyManagements.length > 0){
|
||||
setSelectedMoneyManagement(moneyManagements[0].name)
|
||||
setCustomMoneyManagement(undefined)
|
||||
setShowCustomMoneyManagement(false) // Hide custom money management when options are available
|
||||
} else if (moneyManagements && moneyManagements.length === 0) {
|
||||
// No money management options available, automatically show custom money management
|
||||
setShowCustomMoneyManagement(true)
|
||||
setSelectedMoneyManagement(undefined)
|
||||
setCustomMoneyManagement(undefined)
|
||||
}
|
||||
}, [moneyManagements])
|
||||
|
||||
@@ -336,13 +380,19 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
},
|
||||
})}
|
||||
>
|
||||
{moneyManagements.map((item) => (
|
||||
<option key={item.name} value={item.name}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
{moneyManagements.length === 0 ? (
|
||||
<option value="" disabled>No money management available - create a custom one below</option>
|
||||
) : (
|
||||
<>
|
||||
{moneyManagements.map((item) => (
|
||||
<option key={item.name} value={item.name}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<option key="custom" value="custom">
|
||||
Custom
|
||||
{moneyManagements.length === 0 ? 'Create Custom Money Management' : 'Custom'}
|
||||
</option>
|
||||
</select>
|
||||
</FormInput>
|
||||
@@ -405,18 +455,31 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
},
|
||||
})}
|
||||
>
|
||||
<option value="" disabled>Select a scenario</option>
|
||||
{scenarios.length === 0 ? (
|
||||
<option value="" disabled>No scenarios available - create a custom one below</option>
|
||||
) : (
|
||||
<option value="" disabled>Select a scenario</option>
|
||||
)}
|
||||
{scenarios.map((item) => (
|
||||
<option key={item.name || 'unnamed'} value={item.name || ''}>
|
||||
{item.name || 'Unnamed Scenario'}
|
||||
</option>
|
||||
))}
|
||||
<option key="custom" value="custom">
|
||||
Custom
|
||||
{scenarios.length === 0 ? 'Create Custom Scenario' : 'Custom'}
|
||||
</option>
|
||||
</select>
|
||||
</FormInput>
|
||||
|
||||
{showCustomScenario && (
|
||||
<div className="mt-6">
|
||||
<CustomScenario
|
||||
onCreateScenario={setCustomScenario}
|
||||
showCustomScenario={showCustomScenario}
|
||||
></CustomScenario>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FormInput label="Tickers" htmlFor="tickers">
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
@@ -433,17 +496,8 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
{showCustomScenario && (
|
||||
<div className="mt-6">
|
||||
<CustomScenario
|
||||
onCreateScenario={setCustomScenario}
|
||||
showCustomScenario={showCustomScenario}
|
||||
></CustomScenario>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Fourth Row: Balance & Cooldown Period */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{/* Fourth Row: Balance */}
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<FormInput label="Balance" htmlFor="balance">
|
||||
<input
|
||||
type="number"
|
||||
@@ -455,125 +509,310 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
}}
|
||||
/>
|
||||
</FormInput>
|
||||
|
||||
<FormInput label="Cooldown Period (candles)" htmlFor="cooldownPeriod">
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered w-full"
|
||||
min="1"
|
||||
step="1"
|
||||
{...register('cooldownPeriod', { valueAsNumber: true })}
|
||||
/>
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
{/* Fifth Row: Max Loss Streak & Max Position Time */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormInput label="Max Loss Streak" htmlFor="maxLossStreak">
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered w-full"
|
||||
min="0"
|
||||
step="1"
|
||||
{...register('maxLossStreak', { valueAsNumber: true })}
|
||||
/>
|
||||
</FormInput>
|
||||
{/* Advanced Parameters Dropdown */}
|
||||
<div className="divider">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline btn-sm normal-case"
|
||||
onClick={() => setShowAdvancedParams(!showAdvancedParams)}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
Advanced Parameters
|
||||
<svg
|
||||
className={`w-4 h-4 ml-2 transition-transform duration-200 ${showAdvancedParams ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<FormInput label="Max Position Time (hours)" htmlFor="maxPositionTimeHours">
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered w-full"
|
||||
min="0"
|
||||
step="0.5"
|
||||
placeholder="Leave empty to disable"
|
||||
{...register('maxPositionTimeHours', { valueAsNumber: true })}
|
||||
/>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
Leave empty to disable time-based position closure
|
||||
{showAdvancedParams && (
|
||||
<div className="space-y-4 border border-primary rounded-lg p-4 bg-base-100">
|
||||
{/* Cooldown Period & Dates */}
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<FormInput
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
Cooldown Period (candles)
|
||||
<div className="tooltip tooltip-top" data-tip="Number of candles to wait before allowing another trade after closing a position. Prevents overtrading and allows market to settle">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
htmlFor="cooldownPeriod"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered w-full"
|
||||
min="1"
|
||||
step="1"
|
||||
{...register('cooldownPeriod', { valueAsNumber: true })}
|
||||
/>
|
||||
</FormInput>
|
||||
</div>
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
{/* Sixth Row: Flip Only When In Profit & Close Early When Profitable */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormInput label="Flip Only When In Profit" htmlFor="flipOnlyWhenInProfit">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
{...register('flipOnlyWhenInProfit')}
|
||||
/>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
If enabled, positions will only flip when current position is profitable
|
||||
{/* Start Date & End Date */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormInput label="Start Date" htmlFor="startDate">
|
||||
<input
|
||||
type="date"
|
||||
className="input input-bordered w-full"
|
||||
value={startDate}
|
||||
onChange={(e) => {
|
||||
setStartDate(e.target.value);
|
||||
setValue('startDate', e.target.value);
|
||||
}}
|
||||
/>
|
||||
</FormInput>
|
||||
|
||||
<FormInput label="End Date" htmlFor="endDate">
|
||||
<input
|
||||
type="date"
|
||||
className="input input-bordered w-full"
|
||||
value={endDate}
|
||||
onChange={(e) => {
|
||||
setEndDate(e.target.value);
|
||||
setValue('endDate', e.target.value);
|
||||
}}
|
||||
/>
|
||||
</FormInput>
|
||||
</div>
|
||||
</FormInput>
|
||||
|
||||
<FormInput label="Close Early When Profitable" htmlFor="closeEarlyWhenProfitable">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
{...register('closeEarlyWhenProfitable')}
|
||||
/>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
If enabled, positions will close early when they become profitable
|
||||
{/* Loop Slider (if enabled) */}
|
||||
{showLoopSlider && (
|
||||
<FormInput
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
Loop
|
||||
<div className="tooltip tooltip-top" data-tip="Number of optimization loops to run for money management. Each loop uses the optimized parameters from the previous iteration">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
htmlFor="loop"
|
||||
>
|
||||
<Slider
|
||||
id="loopSlider"
|
||||
min="1"
|
||||
max="10"
|
||||
value={selectedLoopQuantity.toString()}
|
||||
onChange={(e) => setLoopQuantity(Number(e.target.value))}
|
||||
></Slider>
|
||||
</FormInput>
|
||||
)}
|
||||
|
||||
{/* Max Loss Streak & Max Position Time */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormInput
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
Max Loss Streak
|
||||
<div className="tooltip tooltip-top" data-tip="Maximum number of consecutive losing trades before stopping the bot. Set to 0 for no limit">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
htmlFor="maxLossStreak"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered w-full"
|
||||
min="0"
|
||||
step="1"
|
||||
{...register('maxLossStreak', { valueAsNumber: true })}
|
||||
/>
|
||||
</FormInput>
|
||||
|
||||
<FormInput
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
Max Position Time (hours)
|
||||
<div className="tooltip tooltip-top" data-tip="Maximum time to hold a position before force closing. Leave empty to disable time-based position closure">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
htmlFor="maxPositionTimeHours"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered w-full"
|
||||
min="0"
|
||||
step="0.5"
|
||||
placeholder="Leave empty to disable"
|
||||
{...register('maxPositionTimeHours', { valueAsNumber: true })}
|
||||
/>
|
||||
</FormInput>
|
||||
</div>
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
{/* Seventh Row: Save */}
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<FormInput label="Save" htmlFor="save">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
{...register('save')}
|
||||
/>
|
||||
</FormInput>
|
||||
</div>
|
||||
{/* Flip Only When In Profit & Close Early When Profitable */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormInput
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
Flip Only When In Profit
|
||||
<div className="tooltip tooltip-top" data-tip="If enabled, positions will only flip when current position is profitable. Helps avoid flipping during losing streaks">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
htmlFor="flipOnlyWhenInProfit"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
{...register('flipOnlyWhenInProfit')}
|
||||
/>
|
||||
</FormInput>
|
||||
|
||||
{/* Eighth Row: Start Date & End Date */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormInput label="Start Date" htmlFor="startDate">
|
||||
<input
|
||||
type="date"
|
||||
className="input input-bordered w-full"
|
||||
value={startDate}
|
||||
onChange={(e) => {
|
||||
setStartDate(e.target.value);
|
||||
setValue('startDate', e.target.value);
|
||||
}}
|
||||
/>
|
||||
</FormInput>
|
||||
<FormInput
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
Close Early When Profitable
|
||||
<div className="tooltip tooltip-top" data-tip="If enabled, positions will close early when they become profitable. Conservative approach to lock in gains">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
htmlFor="closeEarlyWhenProfitable"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
{...register('closeEarlyWhenProfitable')}
|
||||
/>
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
<FormInput label="End Date" htmlFor="endDate">
|
||||
<input
|
||||
type="date"
|
||||
className="input input-bordered w-full"
|
||||
value={endDate}
|
||||
onChange={(e) => {
|
||||
setEndDate(e.target.value);
|
||||
setValue('endDate', e.target.value);
|
||||
}}
|
||||
/>
|
||||
</FormInput>
|
||||
</div>
|
||||
{/* Save Option */}
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<FormInput
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
Save Backtest Results
|
||||
<div className="tooltip tooltip-top" data-tip="Save the backtest results to your account for future reference and analysis">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
htmlFor="save"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
{...register('save')}
|
||||
/>
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
{/* Loop Slider (if enabled) */}
|
||||
{showLoopSlider && (
|
||||
<FormInput label="Loop" htmlFor="loop">
|
||||
<Slider
|
||||
id="loopSlider"
|
||||
min="1"
|
||||
max="10"
|
||||
value={selectedLoopQuantity.toString()}
|
||||
onChange={(e) => setLoopQuantity(Number(e.target.value))}
|
||||
></Slider>
|
||||
</FormInput>
|
||||
{/* Synth API Section */}
|
||||
<div className="divider">Synth API Configuration</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<FormInput
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
Enable Synth API
|
||||
<div className="tooltip tooltip-top" data-tip="Enable AI-powered probabilistic price forecasts and risk assessment using advanced machine learning models for enhanced trading decisions">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
htmlFor="useSynthApi"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
{...register('useSynthApi')}
|
||||
/>
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
{/* Show sub-options only when Synth API is enabled */}
|
||||
{useSynthApi && (
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<FormInput
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
Use for Position Sizing
|
||||
<div className="tooltip tooltip-top" data-tip="Use Synth predictions for position sizing adjustments and risk assessment. Optimizes trade size based on confidence levels">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
htmlFor="useForPositionSizing"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
{...register('useForPositionSizing')}
|
||||
/>
|
||||
</FormInput>
|
||||
|
||||
<FormInput
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
Use for Signal Filtering
|
||||
<div className="tooltip tooltip-top" data-tip="Use Synth predictions to filter trading signals. Only executes trades when AI confidence is high">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
htmlFor="useForSignalFiltering"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
{...register('useForSignalFiltering')}
|
||||
/>
|
||||
</FormInput>
|
||||
|
||||
<FormInput
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
Use for Dynamic Stop Loss
|
||||
<div className="tooltip tooltip-top" data-tip="Use Synth predictions for dynamic stop-loss/take-profit adjustments. Adapts levels based on market conditions">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
htmlFor="useForDynamicStopLoss"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
{...register('useForDynamicStopLoss')}
|
||||
/>
|
||||
</FormInput>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="modal-action">
|
||||
<button type="submit" className="btn">
|
||||
Run
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost"
|
||||
onClick={closeModal}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Run Backtest
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -4,21 +4,16 @@ import {CardPosition, CardText} from '../../mollecules'
|
||||
|
||||
interface IBacktestRowDetailsProps {
|
||||
backtest: Backtest;
|
||||
optimizedMoneyManagement: {
|
||||
stopLoss: number;
|
||||
takeProfit: number;
|
||||
};
|
||||
}
|
||||
|
||||
const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
backtest,
|
||||
optimizedMoneyManagement
|
||||
backtest
|
||||
}) => {
|
||||
const {
|
||||
candles,
|
||||
positions,
|
||||
walletBalances,
|
||||
strategiesValues,
|
||||
indicatorsValues,
|
||||
signals,
|
||||
statistics,
|
||||
config
|
||||
@@ -364,7 +359,7 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
candles={candles}
|
||||
positions={positions}
|
||||
walletBalances={walletBalances}
|
||||
strategiesValues={strategiesValues}
|
||||
indicatorsValues={indicatorsValues}
|
||||
signals={signals}
|
||||
></TradeChart>
|
||||
</figure>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ChevronDownIcon, ChevronRightIcon, PlayIcon, TrashIcon} from '@heroicons/react/solid'
|
||||
import {ChevronDownIcon, ChevronRightIcon, CogIcon, PlayIcon, TrashIcon} from '@heroicons/react/solid'
|
||||
import React, {useEffect, useState} from 'react'
|
||||
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
@@ -6,7 +6,7 @@ import type {Backtest} from '../../../generated/ManagingApi'
|
||||
import {BacktestClient} from '../../../generated/ManagingApi'
|
||||
import type {IBacktestCards} from '../../../global/type'
|
||||
import {CardText, SelectColumnFilter, Table} from '../../mollecules'
|
||||
import BotConfigModal from '../../mollecules/BotConfigModal/BotConfigModal'
|
||||
import {UnifiedTradingModal} from '../index'
|
||||
import Toast from '../../mollecules/Toast/Toast'
|
||||
|
||||
import BacktestRowDetails from './backtestRowDetails'
|
||||
@@ -32,6 +32,10 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
|
||||
const [showBotConfigModal, setShowBotConfigModal] = useState(false)
|
||||
const [selectedBacktest, setSelectedBacktest] = useState<Backtest | null>(null)
|
||||
|
||||
// Backtest configuration modal state
|
||||
const [showBacktestConfigModal, setShowBacktestConfigModal] = useState(false)
|
||||
const [selectedBacktestForRerun, setSelectedBacktestForRerun] = useState<Backtest | null>(null)
|
||||
|
||||
const handleOpenBotConfigModal = (backtest: Backtest) => {
|
||||
setSelectedBacktest(backtest)
|
||||
setShowBotConfigModal(true)
|
||||
@@ -42,6 +46,16 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
|
||||
setSelectedBacktest(null)
|
||||
}
|
||||
|
||||
const handleOpenBacktestConfigModal = (backtest: Backtest) => {
|
||||
setSelectedBacktestForRerun(backtest)
|
||||
setShowBacktestConfigModal(true)
|
||||
}
|
||||
|
||||
const handleCloseBacktestConfigModal = () => {
|
||||
setShowBacktestConfigModal(false)
|
||||
setSelectedBacktestForRerun(null)
|
||||
}
|
||||
|
||||
async function deleteBacktest(id: string) {
|
||||
const t = new Toast('Deleting backtest')
|
||||
const client = new BacktestClient({}, apiUrl)
|
||||
@@ -213,6 +227,23 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
|
||||
accessor: 'id',
|
||||
disableFilters: true,
|
||||
},
|
||||
{
|
||||
Cell: ({ cell }: any) => (
|
||||
<>
|
||||
<div className="tooltip" data-tip="Re-run backtest with same config">
|
||||
<button
|
||||
data-value={cell.row.values.name}
|
||||
onClick={() => handleOpenBacktestConfigModal(cell.row.original as Backtest)}
|
||||
>
|
||||
<CogIcon className="text-info w-4"></CogIcon>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
Header: '',
|
||||
accessor: 'rerun',
|
||||
disableFilters: true,
|
||||
},
|
||||
{
|
||||
Cell: ({ cell }: any) => (
|
||||
<>
|
||||
@@ -429,18 +460,29 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
|
||||
renderRowSubCompontent={({ row }: any) => (
|
||||
<BacktestRowDetails
|
||||
backtest={row.original}
|
||||
optimizedMoneyManagement={optimizedMoneyManagement}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Bot Configuration Modal */}
|
||||
{selectedBacktest && (
|
||||
<BotConfigModal
|
||||
<UnifiedTradingModal
|
||||
showModal={showBotConfigModal}
|
||||
mode="create"
|
||||
mode="createBot"
|
||||
backtest={selectedBacktest}
|
||||
onClose={handleCloseBotConfigModal}
|
||||
closeModal={handleCloseBotConfigModal}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Backtest Configuration Modal */}
|
||||
{selectedBacktestForRerun && (
|
||||
<UnifiedTradingModal
|
||||
showModal={showBacktestConfigModal}
|
||||
mode="backtest"
|
||||
backtest={selectedBacktestForRerun}
|
||||
closeModal={handleCloseBacktestConfigModal}
|
||||
setBacktests={setBacktests}
|
||||
showLoopSlider={true}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -16,11 +16,11 @@ import {useEffect, useRef, useState} from 'react'
|
||||
|
||||
import type {
|
||||
Candle,
|
||||
IndicatorsResultBase,
|
||||
IndicatorType,
|
||||
KeyValuePairOfDateTimeAndDecimal,
|
||||
Position,
|
||||
Signal,
|
||||
StrategiesResultBase,
|
||||
StrategyType,
|
||||
} from '../../../../generated/ManagingApi'
|
||||
import {PositionStatus, TradeDirection,} from '../../../../generated/ManagingApi'
|
||||
import useTheme from '../../../../hooks/useTheme'
|
||||
@@ -45,7 +45,7 @@ type ITradeChartProps = {
|
||||
positions: Position[]
|
||||
signals: Signal[]
|
||||
walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null
|
||||
strategiesValues?: { [key in keyof typeof StrategyType]?: StrategiesResultBase; } | null;
|
||||
indicatorsValues?: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; } | null;
|
||||
stream?: Candle | null
|
||||
width: number
|
||||
height: number
|
||||
@@ -56,7 +56,7 @@ const TradeChart = ({
|
||||
positions,
|
||||
signals,
|
||||
walletBalances,
|
||||
strategiesValues,
|
||||
indicatorsValues,
|
||||
stream,
|
||||
width,
|
||||
height,
|
||||
@@ -246,9 +246,6 @@ const TradeChart = ({
|
||||
const data: CandlestickData[] = candles.map((c) => mapCandle(c))
|
||||
let diff = 0; // Default to 0 if there's not enough data to calculate the difference
|
||||
|
||||
console.log(data)
|
||||
console.log(data.length)
|
||||
|
||||
if (data.length > 3) {
|
||||
diff =
|
||||
(data[data.length - 1].time as number) -
|
||||
@@ -308,7 +305,7 @@ const TradeChart = ({
|
||||
}
|
||||
|
||||
// Price panel
|
||||
if (strategiesValues?.EmaTrend != null || strategiesValues?.EmaCross != null)
|
||||
if (indicatorsValues?.EmaTrend != null || indicatorsValues?.EmaCross != null)
|
||||
{
|
||||
const emaSeries = chart.current.addLineSeries({
|
||||
color: theme.secondary,
|
||||
@@ -323,7 +320,7 @@ const TradeChart = ({
|
||||
title: 'EMA',
|
||||
})
|
||||
|
||||
const ema = strategiesValues.EmaTrend?.ema ?? strategiesValues.EmaCross?.ema
|
||||
const ema = indicatorsValues.EmaTrend?.ema ?? indicatorsValues.EmaCross?.ema
|
||||
|
||||
const emaData = ema?.map((w) => {
|
||||
return {
|
||||
@@ -339,7 +336,7 @@ const TradeChart = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (strategiesValues?.SuperTrend != null) {
|
||||
if (indicatorsValues?.SuperTrend != null) {
|
||||
const superTrendSeries = chart.current.addLineSeries({
|
||||
color: theme.info,
|
||||
lineWidth: 1,
|
||||
@@ -351,7 +348,7 @@ const TradeChart = ({
|
||||
|
||||
})
|
||||
|
||||
const superTrend = strategiesValues.SuperTrend.superTrend?.map((w) => {
|
||||
const superTrend = indicatorsValues.SuperTrend.superTrend?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.superTrend,
|
||||
@@ -361,6 +358,46 @@ const TradeChart = ({
|
||||
superTrendSeries.setData(superTrend)
|
||||
}
|
||||
|
||||
// Display chandeliers exits
|
||||
if (indicatorsValues?.ChandelierExit != null) {
|
||||
const chandelierExitsLongsSeries = chart.current.addLineSeries({
|
||||
color: theme.info,
|
||||
lineWidth: 1,
|
||||
priceLineVisible: false,
|
||||
priceLineWidth: 1,
|
||||
title: 'Chandelier Long',
|
||||
pane: 0,
|
||||
})
|
||||
|
||||
const chandelierExitsLongs = indicatorsValues.ChandelierExit.chandelierLong?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.chandelierExit,
|
||||
}
|
||||
})
|
||||
// @ts-ignore
|
||||
chandelierExitsLongsSeries.setData(chandelierExitsLongs)
|
||||
|
||||
const chandelierExitsShortsSeries = chart.current.addLineSeries({
|
||||
color: theme.error,
|
||||
lineWidth: 1,
|
||||
priceLineVisible: false,
|
||||
priceLineWidth: 1,
|
||||
title: 'Chandelier Short',
|
||||
pane: 0,
|
||||
})
|
||||
|
||||
const chandelierExitsShorts = indicatorsValues.ChandelierExit.chandelierShort?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.chandelierExit,
|
||||
}
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
chandelierExitsShortsSeries.setData(chandelierExitsShorts)
|
||||
}
|
||||
|
||||
if (markers.length > 0) {
|
||||
series1.current.setMarkers(markers)
|
||||
}
|
||||
@@ -369,14 +406,14 @@ const TradeChart = ({
|
||||
var paneCount = 1
|
||||
|
||||
|
||||
if (strategiesValues?.RsiDivergence != null || strategiesValues?.RsiDivergenceConfirm != null)
|
||||
if (indicatorsValues?.RsiDivergence != null || indicatorsValues?.RsiDivergenceConfirm != null)
|
||||
{
|
||||
const rsiSeries = chart.current.addLineSeries({
|
||||
pane: paneCount,
|
||||
title: 'RSI',
|
||||
})
|
||||
|
||||
const rsi = strategiesValues.RsiDivergence?.rsi ?? strategiesValues.RsiDivergenceConfirm?.rsi
|
||||
const rsi = indicatorsValues.RsiDivergence?.rsi ?? indicatorsValues.RsiDivergenceConfirm?.rsi
|
||||
|
||||
const rsiData = rsi?.map((w) => {
|
||||
return {
|
||||
@@ -397,7 +434,7 @@ const TradeChart = ({
|
||||
paneCount++
|
||||
}
|
||||
|
||||
if (strategiesValues?.Stc != null) {
|
||||
if (indicatorsValues?.Stc != null) {
|
||||
const stcSeries = chart.current.addBaselineSeries({
|
||||
pane: paneCount,
|
||||
baseValue: {price: 50, type: 'price'},
|
||||
@@ -408,7 +445,7 @@ const TradeChart = ({
|
||||
stcSeries.createPriceLine(buildLine(theme.error, 25, 'low'))
|
||||
stcSeries.createPriceLine(buildLine(theme.info, 75, 'high'))
|
||||
|
||||
const stcData = strategiesValues?.Stc.stc?.map((w) => {
|
||||
const stcData = indicatorsValues?.Stc.stc?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.stc,
|
||||
@@ -430,7 +467,42 @@ const TradeChart = ({
|
||||
paneCount++
|
||||
}
|
||||
|
||||
if (strategiesValues?.MacdCross != null) {
|
||||
if (indicatorsValues?.LaggingStc != null) {
|
||||
const laggingStcSeries = chart.current.addBaselineSeries({
|
||||
pane: paneCount,
|
||||
baseValue: {price: 50, type: 'price'},
|
||||
|
||||
title: 'Lagging STC',
|
||||
})
|
||||
|
||||
laggingStcSeries.createPriceLine(buildLine(theme.error, 25, 'low'))
|
||||
laggingStcSeries.createPriceLine(buildLine(theme.info, 75, 'high'))
|
||||
|
||||
const stcData = indicatorsValues?.LaggingStc.stc?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.stc,
|
||||
}
|
||||
})
|
||||
// @ts-ignore
|
||||
laggingStcSeries.setData(stcData)
|
||||
laggingStcSeries.applyOptions({
|
||||
...baselineOptions,
|
||||
priceLineVisible: true,
|
||||
priceFormat: {
|
||||
minMove: 1,
|
||||
precision: 1,
|
||||
type: 'price',
|
||||
},
|
||||
crosshairMarkerVisible: true,
|
||||
})
|
||||
|
||||
paneCount++
|
||||
}
|
||||
|
||||
console.log(indicatorsValues)
|
||||
if (indicatorsValues?.MacdCross != null) {
|
||||
console.log(indicatorsValues.MacdCross)
|
||||
const histogramSeries = chart.current.addHistogramSeries({
|
||||
color: theme.accent,
|
||||
title: 'MACD',
|
||||
@@ -441,7 +513,7 @@ const TradeChart = ({
|
||||
}
|
||||
})
|
||||
|
||||
const macd = strategiesValues.MacdCross.macd?.map((w) => {
|
||||
const macd = indicatorsValues.MacdCross.macd?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.histogram,
|
||||
@@ -472,7 +544,7 @@ const TradeChart = ({
|
||||
crosshairMarkerVisible: true,
|
||||
})
|
||||
|
||||
const macdData = strategiesValues.MacdCross.macd?.map((w) => {
|
||||
const macdData = indicatorsValues.MacdCross.macd?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.macd,
|
||||
@@ -497,7 +569,7 @@ const TradeChart = ({
|
||||
},
|
||||
})
|
||||
|
||||
const signalData = strategiesValues.MacdCross.macd?.map((w) => {
|
||||
const signalData = indicatorsValues.MacdCross.macd?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.signal,
|
||||
@@ -510,7 +582,7 @@ const TradeChart = ({
|
||||
paneCount++
|
||||
}
|
||||
|
||||
if (strategiesValues?.StochRsiTrend){
|
||||
if (indicatorsValues?.StochRsiTrend){
|
||||
const stochRsiSeries = chart.current.addLineSeries({
|
||||
...baselineOptions,
|
||||
priceLineVisible: false,
|
||||
@@ -518,7 +590,7 @@ const TradeChart = ({
|
||||
pane: paneCount,
|
||||
})
|
||||
|
||||
const stochRsi = strategiesValues.StochRsiTrend.stochRsi?.map((w) => {
|
||||
const stochRsi = indicatorsValues.StochRsiTrend.stochRsi?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.stochRsi,
|
||||
@@ -529,7 +601,7 @@ const TradeChart = ({
|
||||
paneCount++
|
||||
}
|
||||
|
||||
if (strategiesValues?.StDev != null) {
|
||||
if (indicatorsValues?.StDev != null) {
|
||||
const stDevSeries = chart.current.addLineSeries({
|
||||
color: theme.primary,
|
||||
lineWidth: 1,
|
||||
@@ -539,7 +611,7 @@ const TradeChart = ({
|
||||
pane: paneCount,
|
||||
})
|
||||
|
||||
const stDev = strategiesValues.StDev.stdDev?.map((w) => {
|
||||
const stDev = indicatorsValues.StDev.stdDev?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.stdDev,
|
||||
@@ -557,7 +629,7 @@ const TradeChart = ({
|
||||
crosshairMarkerVisible: true,
|
||||
})
|
||||
|
||||
const zScore = strategiesValues.StDev.stdDev?.map((w) => {
|
||||
const zScore = indicatorsValues.StDev.stdDev?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.zScore,
|
||||
@@ -569,6 +641,47 @@ const TradeChart = ({
|
||||
paneCount++
|
||||
}
|
||||
|
||||
|
||||
// Display dual EMA crossover
|
||||
if (indicatorsValues?.DualEmaCross != null) {
|
||||
const fastEmaSeries = chart.current.addLineSeries({
|
||||
color: theme.info,
|
||||
lineWidth: 1,
|
||||
priceLineVisible: false,
|
||||
priceLineWidth: 1,
|
||||
title: 'Fast EMA',
|
||||
pane: paneCount,
|
||||
})
|
||||
|
||||
const fastEma = indicatorsValues.DualEmaCross.fastEma?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.ema,
|
||||
}
|
||||
})
|
||||
// @ts-ignore
|
||||
fastEmaSeries.setData(fastEma)
|
||||
|
||||
const slowEmaSeries = chart.current.addLineSeries({
|
||||
color: theme.primary,
|
||||
lineWidth: 1,
|
||||
priceLineVisible: false,
|
||||
priceLineWidth: 1,
|
||||
title: 'Slow EMA',
|
||||
pane: paneCount,
|
||||
})
|
||||
|
||||
const slowEma = indicatorsValues.DualEmaCross.slowEma?.map((w) => {
|
||||
return {
|
||||
time: moment(w.date).unix(),
|
||||
value: w.ema,
|
||||
}
|
||||
})
|
||||
// @ts-ignore
|
||||
slowEmaSeries.setData(slowEma)
|
||||
paneCount++
|
||||
}
|
||||
|
||||
if (walletBalances != null && walletBalances.length > 0) {
|
||||
const walletSeries = chart.current.addBaselineSeries({
|
||||
baseValue: {price: walletBalances[0].value, type: 'price'},
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# UnifiedTradingModal
|
||||
|
||||
A unified modal component that replaces both `BacktestModal` and `BotConfigModal`. This component handles three different modes:
|
||||
|
||||
- **backtest**: Run backtests with multiple tickers
|
||||
- **createBot**: Create a new trading bot (optionally from a backtest)
|
||||
- **updateBot**: Update an existing bot's configuration
|
||||
|
||||
## Features
|
||||
|
||||
### ✅ **Unified Interface**
|
||||
- Single component for all trading configuration needs
|
||||
- Mode-specific form fields and validation
|
||||
- Consistent UI/UX across all use cases
|
||||
|
||||
### ✅ **Advanced Configuration**
|
||||
- **Advanced Parameters**: Collapsible section with cooldown periods, position limits, trading options
|
||||
- **Risk Management**: Complete `RiskManagement` configuration with preset levels (Conservative, Moderate, Aggressive)
|
||||
- **Synth API Integration**: AI-powered probabilistic forecasts and risk assessment
|
||||
|
||||
### ✅ **Smart Defaults**
|
||||
- Context-aware initialization based on mode
|
||||
- Automatic data loading and form population
|
||||
- Preset risk management configurations
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### 1. Backtest Mode
|
||||
```tsx
|
||||
import { UnifiedTradingModal } from '../../components/organism'
|
||||
|
||||
<UnifiedTradingModal
|
||||
showModal={showModal}
|
||||
closeModal={() => setShowModal(false)}
|
||||
mode="backtest"
|
||||
setBacktests={setBacktests}
|
||||
showLoopSlider={true} // Optional: enable loop optimization
|
||||
/>
|
||||
```
|
||||
|
||||
### 2. Create Bot Mode
|
||||
```tsx
|
||||
<UnifiedTradingModal
|
||||
showModal={showModal}
|
||||
closeModal={() => setShowModal(false)}
|
||||
mode="createBot"
|
||||
/>
|
||||
```
|
||||
|
||||
### 3. Create Bot from Backtest
|
||||
```tsx
|
||||
<UnifiedTradingModal
|
||||
showModal={showModal}
|
||||
closeModal={() => setShowModal(false)}
|
||||
mode="createBot"
|
||||
backtest={selectedBacktest} // Initialize from backtest
|
||||
/>
|
||||
```
|
||||
|
||||
### 4. Update Bot Mode
|
||||
```tsx
|
||||
<UnifiedTradingModal
|
||||
showModal={showModal}
|
||||
closeModal={() => setShowModal(false)}
|
||||
mode="updateBot"
|
||||
existingBot={{
|
||||
identifier: bot.identifier,
|
||||
config: bot.config
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### From BacktestModal
|
||||
```tsx
|
||||
// Old
|
||||
<BacktestModal
|
||||
showModal={showModal}
|
||||
closeModal={closeModal}
|
||||
setBacktests={setBacktests}
|
||||
showLoopSlider={true}
|
||||
/>
|
||||
|
||||
// New
|
||||
<UnifiedTradingModal
|
||||
mode="backtest"
|
||||
showModal={showModal}
|
||||
closeModal={closeModal}
|
||||
setBacktests={setBacktests}
|
||||
showLoopSlider={true}
|
||||
/>
|
||||
```
|
||||
|
||||
### From BotConfigModal
|
||||
```tsx
|
||||
// Old
|
||||
<BotConfigModal
|
||||
showModal={showModal}
|
||||
mode="create"
|
||||
backtest={backtest}
|
||||
onClose={closeModal}
|
||||
/>
|
||||
|
||||
// New
|
||||
<UnifiedTradingModal
|
||||
mode="createBot"
|
||||
showModal={showModal}
|
||||
closeModal={closeModal}
|
||||
backtest={backtest}
|
||||
/>
|
||||
```
|
||||
|
||||
## Risk Management Features
|
||||
|
||||
The component includes a comprehensive Risk Management section with:
|
||||
|
||||
- **Preset Levels**: Conservative, Moderate, Aggressive configurations
|
||||
- **Kelly Criterion**: Position sizing based on edge and odds
|
||||
- **Expected Utility**: Risk-adjusted decision making
|
||||
- **Probability Thresholds**: Adverse and favorable signal validation
|
||||
- **Liquidation Risk**: Maximum acceptable liquidation probability
|
||||
|
||||
All risk management parameters use the official `RiskManagement` type from the API and integrate seamlessly with the backend trading logic.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
export { default } from './UnifiedTradingModal'
|
||||
@@ -2,8 +2,12 @@ export { default as TradeChart } from './Trading/TradeChart/TradeChart'
|
||||
export { default as CardPositionItem } from './Trading/CardPositionItem'
|
||||
export { default as ActiveBots } from './ActiveBots/ActiveBots'
|
||||
export { default as BacktestCards } from './Backtest/backtestCards'
|
||||
export { default as BacktestModal } from './Backtest/backtestModal'
|
||||
export { default as BacktestTable } from './Backtest/backtestTable'
|
||||
// @deprecated - Use UnifiedTradingModal instead
|
||||
export { default as BacktestModal } from './Backtest/backtestModal'
|
||||
export { default as UnifiedTradingModal } from './UnifiedTradingModal'
|
||||
export { default as CustomMoneyManagement } from './CustomMoneyManagement/CustomMoneyManagement'
|
||||
export { default as CustomScenario } from './CustomScenario/CustomScenario'
|
||||
export { default as SpotLightBadge } from './SpotLightBadge/SpotLightBadge'
|
||||
export { default as StatusBadge } from './StatusBadge/StatusBadge'
|
||||
export { default as PositionsList } from './Positions/PositionList'
|
||||
|
||||
@@ -337,6 +337,45 @@ export class BacktestClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<FileResponse>(null as any);
|
||||
}
|
||||
|
||||
backtest_Map(moneyManagementRequest: MoneyManagementRequest): Promise<MoneyManagement> {
|
||||
let url_ = this.baseUrl + "/Backtest";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
const content_ = JSON.stringify(moneyManagementRequest);
|
||||
|
||||
let options_: RequestInit = {
|
||||
body: content_,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processBacktest_Map(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBacktest_Map(response: Response): Promise<MoneyManagement> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as MoneyManagement;
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<MoneyManagement>(null as any);
|
||||
}
|
||||
|
||||
backtest_Backtest(id: string): Promise<Backtest> {
|
||||
let url_ = this.baseUrl + "/Backtest/{id}";
|
||||
if (id === undefined || id === null)
|
||||
@@ -726,6 +765,45 @@ export class BotClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<TradingBotResponse[]>(null as any);
|
||||
}
|
||||
|
||||
bot_Map(moneyManagementRequest: MoneyManagementRequest): Promise<MoneyManagement> {
|
||||
let url_ = this.baseUrl + "/Bot";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
const content_ = JSON.stringify(moneyManagementRequest);
|
||||
|
||||
let options_: RequestInit = {
|
||||
body: content_,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processBot_Map(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBot_Map(response: Response): Promise<MoneyManagement> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as MoneyManagement;
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<MoneyManagement>(null as any);
|
||||
}
|
||||
|
||||
bot_OpenPositionManually(request: OpenPositionManuallyRequest): Promise<Position> {
|
||||
let url_ = this.baseUrl + "/Bot/OpenPosition";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
@@ -1432,7 +1510,7 @@ export class ScenarioClient extends AuthorizedApiBase {
|
||||
this.baseUrl = baseUrl ?? "http://localhost:5000";
|
||||
}
|
||||
|
||||
scenario_GetScenarios(): Promise<Scenario[]> {
|
||||
scenario_GetScenarios(): Promise<ScenarioViewModel[]> {
|
||||
let url_ = this.baseUrl + "/Scenario";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
@@ -1450,13 +1528,13 @@ export class ScenarioClient extends AuthorizedApiBase {
|
||||
});
|
||||
}
|
||||
|
||||
protected processScenario_GetScenarios(response: Response): Promise<Scenario[]> {
|
||||
protected processScenario_GetScenarios(response: Response): Promise<ScenarioViewModel[]> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Scenario[];
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as ScenarioViewModel[];
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
@@ -1464,10 +1542,10 @@ export class ScenarioClient extends AuthorizedApiBase {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<Scenario[]>(null as any);
|
||||
return Promise.resolve<ScenarioViewModel[]>(null as any);
|
||||
}
|
||||
|
||||
scenario_CreateScenario(name: string | null | undefined, loopbackPeriod: number | null | undefined, strategies: string[]): Promise<Scenario> {
|
||||
scenario_CreateScenario(name: string | null | undefined, loopbackPeriod: number | null | undefined, strategies: string[]): Promise<ScenarioViewModel> {
|
||||
let url_ = this.baseUrl + "/Scenario?";
|
||||
if (name !== undefined && name !== null)
|
||||
url_ += "name=" + encodeURIComponent("" + name) + "&";
|
||||
@@ -1493,13 +1571,13 @@ export class ScenarioClient extends AuthorizedApiBase {
|
||||
});
|
||||
}
|
||||
|
||||
protected processScenario_CreateScenario(response: Response): Promise<Scenario> {
|
||||
protected processScenario_CreateScenario(response: Response): Promise<ScenarioViewModel> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Scenario;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as ScenarioViewModel;
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
@@ -1507,7 +1585,7 @@ export class ScenarioClient extends AuthorizedApiBase {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<Scenario>(null as any);
|
||||
return Promise.resolve<ScenarioViewModel>(null as any);
|
||||
}
|
||||
|
||||
scenario_DeleteScenario(name: string | null | undefined): Promise<FileResponse> {
|
||||
@@ -1600,7 +1678,7 @@ export class ScenarioClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<FileResponse>(null as any);
|
||||
}
|
||||
|
||||
scenario_GetIndicators(): Promise<Indicator[]> {
|
||||
scenario_GetIndicators(): Promise<IndicatorViewModel[]> {
|
||||
let url_ = this.baseUrl + "/Scenario/indicator";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
@@ -1618,13 +1696,13 @@ export class ScenarioClient extends AuthorizedApiBase {
|
||||
});
|
||||
}
|
||||
|
||||
protected processScenario_GetIndicators(response: Response): Promise<Indicator[]> {
|
||||
protected processScenario_GetIndicators(response: Response): Promise<IndicatorViewModel[]> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Indicator[];
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as IndicatorViewModel[];
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
@@ -1632,10 +1710,10 @@ export class ScenarioClient extends AuthorizedApiBase {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<Indicator[]>(null as any);
|
||||
return Promise.resolve<IndicatorViewModel[]>(null as any);
|
||||
}
|
||||
|
||||
scenario_CreateIndicator(indicatorType: IndicatorType | undefined, name: string | null | undefined, period: number | null | undefined, fastPeriods: number | null | undefined, slowPeriods: number | null | undefined, signalPeriods: number | null | undefined, multiplier: number | null | undefined, stochPeriods: number | null | undefined, smoothPeriods: number | null | undefined, cyclePeriods: number | null | undefined): Promise<Indicator> {
|
||||
scenario_CreateIndicator(indicatorType: IndicatorType | undefined, name: string | null | undefined, period: number | null | undefined, fastPeriods: number | null | undefined, slowPeriods: number | null | undefined, signalPeriods: number | null | undefined, multiplier: number | null | undefined, stochPeriods: number | null | undefined, smoothPeriods: number | null | undefined, cyclePeriods: number | null | undefined): Promise<IndicatorViewModel> {
|
||||
let url_ = this.baseUrl + "/Scenario/indicator?";
|
||||
if (indicatorType === null)
|
||||
throw new Error("The parameter 'indicatorType' cannot be null.");
|
||||
@@ -1675,13 +1753,13 @@ export class ScenarioClient extends AuthorizedApiBase {
|
||||
});
|
||||
}
|
||||
|
||||
protected processScenario_CreateIndicator(response: Response): Promise<Indicator> {
|
||||
protected processScenario_CreateIndicator(response: Response): Promise<IndicatorViewModel> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Indicator;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as IndicatorViewModel;
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
@@ -1689,7 +1767,7 @@ export class ScenarioClient extends AuthorizedApiBase {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<Indicator>(null as any);
|
||||
return Promise.resolve<IndicatorViewModel>(null as any);
|
||||
}
|
||||
|
||||
scenario_DeleteIndicator(name: string | null | undefined): Promise<FileResponse> {
|
||||
@@ -2779,7 +2857,7 @@ export interface Backtest {
|
||||
walletBalances: KeyValuePairOfDateTimeAndDecimal[];
|
||||
optimizedMoneyManagement: MoneyManagement;
|
||||
user: User;
|
||||
strategiesValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
|
||||
indicatorsValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
|
||||
score: number;
|
||||
}
|
||||
|
||||
@@ -2796,11 +2874,16 @@ export interface TradingBotConfig {
|
||||
maxLossStreak: number;
|
||||
flipPosition: boolean;
|
||||
name: string;
|
||||
riskManagement?: RiskManagement | null;
|
||||
scenario?: Scenario | null;
|
||||
scenarioName?: string | null;
|
||||
maxPositionTimeHours?: number | null;
|
||||
closeEarlyWhenProfitable?: boolean;
|
||||
flipOnlyWhenInProfit: boolean;
|
||||
useSynthApi?: boolean;
|
||||
useForPositionSizing?: boolean;
|
||||
useForSignalFiltering?: boolean;
|
||||
useForDynamicStopLoss?: boolean;
|
||||
}
|
||||
|
||||
export interface MoneyManagement {
|
||||
@@ -2937,6 +3020,29 @@ export enum BotType {
|
||||
FlippingBot = "FlippingBot",
|
||||
}
|
||||
|
||||
export interface RiskManagement {
|
||||
adverseProbabilityThreshold: number;
|
||||
favorableProbabilityThreshold: number;
|
||||
riskAversion: number;
|
||||
kellyMinimumThreshold: number;
|
||||
kellyMaximumCap: number;
|
||||
maxLiquidationProbability: number;
|
||||
signalValidationTimeHorizonHours: number;
|
||||
positionMonitoringTimeHorizonHours: number;
|
||||
positionWarningThreshold: number;
|
||||
positionAutoCloseThreshold: number;
|
||||
kellyFractionalMultiplier: number;
|
||||
riskTolerance: RiskToleranceLevel;
|
||||
useExpectedUtility: boolean;
|
||||
useKellyCriterion: boolean;
|
||||
}
|
||||
|
||||
export enum RiskToleranceLevel {
|
||||
Conservative = "Conservative",
|
||||
Moderate = "Moderate",
|
||||
Aggressive = "Aggressive",
|
||||
}
|
||||
|
||||
export interface Scenario {
|
||||
name?: string | null;
|
||||
indicators?: Indicator[] | null;
|
||||
@@ -3086,6 +3192,7 @@ export interface Signal extends ValueObject {
|
||||
indicatorType: IndicatorType;
|
||||
signalType: SignalType;
|
||||
user?: User | null;
|
||||
indicatorName: string;
|
||||
}
|
||||
|
||||
export enum SignalStatus {
|
||||
@@ -3226,19 +3333,68 @@ export interface SuperTrendResult extends ResultBase {
|
||||
}
|
||||
|
||||
export interface RunBacktestRequest {
|
||||
config?: TradingBotConfig | null;
|
||||
config?: TradingBotConfigRequest | null;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
balance?: number;
|
||||
watchOnly?: boolean;
|
||||
save?: boolean;
|
||||
}
|
||||
|
||||
export interface TradingBotConfigRequest {
|
||||
accountName: string;
|
||||
ticker: Ticker;
|
||||
timeframe: Timeframe;
|
||||
isForWatchingOnly: boolean;
|
||||
botTradingBalance: number;
|
||||
botType: BotType;
|
||||
name: string;
|
||||
cooldownPeriod: number;
|
||||
maxLossStreak: number;
|
||||
scenario?: ScenarioRequest | null;
|
||||
scenarioName?: string | null;
|
||||
moneyManagementName?: string | null;
|
||||
moneyManagement?: MoneyManagement | null;
|
||||
moneyManagement?: MoneyManagementRequest | null;
|
||||
maxPositionTimeHours?: number | null;
|
||||
closeEarlyWhenProfitable?: boolean;
|
||||
flipOnlyWhenInProfit?: boolean;
|
||||
useSynthApi?: boolean;
|
||||
useForPositionSizing?: boolean;
|
||||
useForSignalFiltering?: boolean;
|
||||
useForDynamicStopLoss?: boolean;
|
||||
}
|
||||
|
||||
export interface ScenarioRequest {
|
||||
name: string;
|
||||
indicators: IndicatorRequest[];
|
||||
loopbackPeriod?: number | null;
|
||||
}
|
||||
|
||||
export interface IndicatorRequest {
|
||||
name: string;
|
||||
type: IndicatorType;
|
||||
signalType: SignalType;
|
||||
minimumHistory?: number;
|
||||
period?: number | null;
|
||||
fastPeriods?: number | null;
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
}
|
||||
|
||||
export interface MoneyManagementRequest {
|
||||
name: string;
|
||||
timeframe: Timeframe;
|
||||
stopLoss: number;
|
||||
takeProfit: number;
|
||||
leverage: number;
|
||||
}
|
||||
|
||||
export interface StartBotRequest {
|
||||
config?: TradingBotConfig | null;
|
||||
moneyManagementName?: string | null;
|
||||
config?: TradingBotConfigRequest | null;
|
||||
}
|
||||
|
||||
export interface TradingBotResponse {
|
||||
@@ -3265,8 +3421,9 @@ export interface ClosePositionRequest {
|
||||
|
||||
export interface UpdateBotConfigRequest {
|
||||
identifier: string;
|
||||
config: TradingBotConfig;
|
||||
config: TradingBotConfigRequest;
|
||||
moneyManagementName?: string | null;
|
||||
moneyManagement?: MoneyManagement | null;
|
||||
}
|
||||
|
||||
export interface TickerInfos {
|
||||
@@ -3372,6 +3529,29 @@ export interface BestAgentsResponse {
|
||||
totalPages?: number;
|
||||
}
|
||||
|
||||
export interface ScenarioViewModel {
|
||||
name: string;
|
||||
indicators: IndicatorViewModel[];
|
||||
loopbackPeriod?: number | null;
|
||||
userName: string;
|
||||
}
|
||||
|
||||
export interface IndicatorViewModel {
|
||||
name: string;
|
||||
type: IndicatorType;
|
||||
signalType: SignalType;
|
||||
minimumHistory: number;
|
||||
period?: number | null;
|
||||
fastPeriods?: number | null;
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
userName: string;
|
||||
}
|
||||
|
||||
export enum RiskLevel {
|
||||
Low = "Low",
|
||||
Medium = "Medium",
|
||||
|
||||
887
src/Managing.WebApp/src/generated/ManagingApiTypes.ts
Normal file
887
src/Managing.WebApp/src/generated/ManagingApiTypes.ts
Normal file
@@ -0,0 +1,887 @@
|
||||
//----------------------
|
||||
// <auto-generated>
|
||||
// Generated using the NSwag toolchain v14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org)
|
||||
// </auto-generated>
|
||||
//----------------------
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
|
||||
|
||||
export interface Account {
|
||||
name: string;
|
||||
exchange: TradingExchanges;
|
||||
type: AccountType;
|
||||
key?: string | null;
|
||||
secret?: string | null;
|
||||
user?: User | null;
|
||||
balances?: Balance[] | null;
|
||||
isPrivyWallet?: boolean;
|
||||
}
|
||||
|
||||
export enum TradingExchanges {
|
||||
Binance = "Binance",
|
||||
Kraken = "Kraken",
|
||||
Ftx = "Ftx",
|
||||
Evm = "Evm",
|
||||
GmxV2 = "GmxV2",
|
||||
}
|
||||
|
||||
export enum AccountType {
|
||||
Cex = "Cex",
|
||||
Trader = "Trader",
|
||||
Watch = "Watch",
|
||||
Auth = "Auth",
|
||||
Privy = "Privy",
|
||||
}
|
||||
|
||||
export interface User {
|
||||
name?: string | null;
|
||||
accounts?: Account[] | null;
|
||||
agentName?: string | null;
|
||||
avatarUrl?: string | null;
|
||||
telegramChannel?: string | null;
|
||||
}
|
||||
|
||||
export interface Balance {
|
||||
tokenImage?: string | null;
|
||||
tokenName?: string | null;
|
||||
amount?: number;
|
||||
price?: number;
|
||||
value?: number;
|
||||
tokenAdress?: string | null;
|
||||
chain?: Chain | null;
|
||||
}
|
||||
|
||||
export interface Chain {
|
||||
id?: string | null;
|
||||
rpcUrl?: string | null;
|
||||
name?: string | null;
|
||||
chainId?: number;
|
||||
}
|
||||
|
||||
export interface GmxClaimableSummary {
|
||||
claimableFundingFees?: FundingFeesData | null;
|
||||
claimableUiFees?: UiFeesData | null;
|
||||
rebateStats?: RebateStatsData | null;
|
||||
}
|
||||
|
||||
export interface FundingFeesData {
|
||||
totalUsdc?: number;
|
||||
}
|
||||
|
||||
export interface UiFeesData {
|
||||
totalUsdc?: number;
|
||||
}
|
||||
|
||||
export interface RebateStatsData {
|
||||
totalRebateUsdc?: number;
|
||||
discountUsdc?: number;
|
||||
rebateFactor?: number;
|
||||
discountFactor?: number;
|
||||
}
|
||||
|
||||
export interface Backtest {
|
||||
id: string;
|
||||
finalPnl: number;
|
||||
winRate: number;
|
||||
growthPercentage: number;
|
||||
hodlPercentage: number;
|
||||
config: TradingBotConfig;
|
||||
positions: Position[];
|
||||
signals: Signal[];
|
||||
candles: Candle[];
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
statistics: PerformanceMetrics;
|
||||
fees: number;
|
||||
walletBalances: KeyValuePairOfDateTimeAndDecimal[];
|
||||
optimizedMoneyManagement: MoneyManagement;
|
||||
user: User;
|
||||
indicatorsValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface TradingBotConfig {
|
||||
accountName: string;
|
||||
moneyManagement: MoneyManagement;
|
||||
ticker: Ticker;
|
||||
timeframe: Timeframe;
|
||||
isForWatchingOnly: boolean;
|
||||
botTradingBalance: number;
|
||||
botType: BotType;
|
||||
isForBacktest: boolean;
|
||||
cooldownPeriod: number;
|
||||
maxLossStreak: number;
|
||||
flipPosition: boolean;
|
||||
name: string;
|
||||
riskManagement?: RiskManagement | null;
|
||||
scenario?: Scenario | null;
|
||||
scenarioName?: string | null;
|
||||
maxPositionTimeHours?: number | null;
|
||||
closeEarlyWhenProfitable?: boolean;
|
||||
flipOnlyWhenInProfit: boolean;
|
||||
useSynthApi?: boolean;
|
||||
useForPositionSizing?: boolean;
|
||||
useForSignalFiltering?: boolean;
|
||||
useForDynamicStopLoss?: boolean;
|
||||
}
|
||||
|
||||
export interface MoneyManagement {
|
||||
name: string;
|
||||
timeframe: Timeframe;
|
||||
stopLoss: number;
|
||||
takeProfit: number;
|
||||
leverage: number;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export enum Timeframe {
|
||||
FiveMinutes = "FiveMinutes",
|
||||
FifteenMinutes = "FifteenMinutes",
|
||||
ThirtyMinutes = "ThirtyMinutes",
|
||||
OneHour = "OneHour",
|
||||
FourHour = "FourHour",
|
||||
OneDay = "OneDay",
|
||||
OneMinute = "OneMinute",
|
||||
}
|
||||
|
||||
export enum Ticker {
|
||||
AAVE = "AAVE",
|
||||
ADA = "ADA",
|
||||
APE = "APE",
|
||||
ALGO = "ALGO",
|
||||
ARB = "ARB",
|
||||
ATOM = "ATOM",
|
||||
AVAX = "AVAX",
|
||||
BNB = "BNB",
|
||||
BTC = "BTC",
|
||||
BAL = "BAL",
|
||||
CHZ = "CHZ",
|
||||
COMP = "COMP",
|
||||
CRO = "CRO",
|
||||
CRV = "CRV",
|
||||
DOGE = "DOGE",
|
||||
DOT = "DOT",
|
||||
DYDX = "DYDX",
|
||||
ENS = "ENS",
|
||||
ETC = "ETC",
|
||||
ETH = "ETH",
|
||||
FIL = "FIL",
|
||||
FLM = "FLM",
|
||||
FTM = "FTM",
|
||||
GALA = "GALA",
|
||||
GMX = "GMX",
|
||||
GRT = "GRT",
|
||||
IMX = "IMX",
|
||||
JASMY = "JASMY",
|
||||
KSM = "KSM",
|
||||
LDO = "LDO",
|
||||
LINK = "LINK",
|
||||
LRC = "LRC",
|
||||
LTC = "LTC",
|
||||
MANA = "MANA",
|
||||
MATIC = "MATIC",
|
||||
MKR = "MKR",
|
||||
NEAR = "NEAR",
|
||||
OP = "OP",
|
||||
PEPE = "PEPE",
|
||||
QTUM = "QTUM",
|
||||
REN = "REN",
|
||||
ROSE = "ROSE",
|
||||
RSR = "RSR",
|
||||
RUNE = "RUNE",
|
||||
SAND = "SAND",
|
||||
SOL = "SOL",
|
||||
SRM = "SRM",
|
||||
SUSHI = "SUSHI",
|
||||
THETA = "THETA",
|
||||
UNI = "UNI",
|
||||
USDC = "USDC",
|
||||
USDT = "USDT",
|
||||
WIF = "WIF",
|
||||
XMR = "XMR",
|
||||
XRP = "XRP",
|
||||
XTZ = "XTZ",
|
||||
SHIB = "SHIB",
|
||||
STX = "STX",
|
||||
ORDI = "ORDI",
|
||||
APT = "APT",
|
||||
BOME = "BOME",
|
||||
MEME = "MEME",
|
||||
FLOKI = "FLOKI",
|
||||
MEW = "MEW",
|
||||
TAO = "TAO",
|
||||
BONK = "BONK",
|
||||
WLD = "WLD",
|
||||
TBTC = "tBTC",
|
||||
WBTC_b = "WBTC_b",
|
||||
EIGEN = "EIGEN",
|
||||
SUI = "SUI",
|
||||
SEI = "SEI",
|
||||
USDC_e = "USDC_e",
|
||||
DAI = "DAI",
|
||||
TIA = "TIA",
|
||||
TRX = "TRX",
|
||||
TON = "TON",
|
||||
PENDLE = "PENDLE",
|
||||
WstETH = "wstETH",
|
||||
USDe = "USDe",
|
||||
SATS = "SATS",
|
||||
POL = "POL",
|
||||
XLM = "XLM",
|
||||
BCH = "BCH",
|
||||
ICP = "ICP",
|
||||
RENDER = "RENDER",
|
||||
INJ = "INJ",
|
||||
TRUMP = "TRUMP",
|
||||
MELANIA = "MELANIA",
|
||||
ENA = "ENA",
|
||||
FARTCOIN = "FARTCOIN",
|
||||
AI16Z = "AI16Z",
|
||||
ANIME = "ANIME",
|
||||
BERA = "BERA",
|
||||
VIRTUAL = "VIRTUAL",
|
||||
PENGU = "PENGU",
|
||||
ONDO = "ONDO",
|
||||
FET = "FET",
|
||||
AIXBT = "AIXBT",
|
||||
CAKE = "CAKE",
|
||||
S = "S",
|
||||
JUP = "JUP",
|
||||
HYPE = "HYPE",
|
||||
OM = "OM",
|
||||
DOLO = "DOLO",
|
||||
Unknown = "Unknown",
|
||||
}
|
||||
|
||||
export enum BotType {
|
||||
SimpleBot = "SimpleBot",
|
||||
ScalpingBot = "ScalpingBot",
|
||||
FlippingBot = "FlippingBot",
|
||||
}
|
||||
|
||||
export interface RiskManagement {
|
||||
adverseProbabilityThreshold: number;
|
||||
favorableProbabilityThreshold: number;
|
||||
riskAversion: number;
|
||||
kellyMinimumThreshold: number;
|
||||
kellyMaximumCap: number;
|
||||
maxLiquidationProbability: number;
|
||||
signalValidationTimeHorizonHours: number;
|
||||
positionMonitoringTimeHorizonHours: number;
|
||||
positionWarningThreshold: number;
|
||||
positionAutoCloseThreshold: number;
|
||||
kellyFractionalMultiplier: number;
|
||||
riskTolerance: RiskToleranceLevel;
|
||||
useExpectedUtility: boolean;
|
||||
useKellyCriterion: boolean;
|
||||
}
|
||||
|
||||
export enum RiskToleranceLevel {
|
||||
Conservative = "Conservative",
|
||||
Moderate = "Moderate",
|
||||
Aggressive = "Aggressive",
|
||||
}
|
||||
|
||||
export interface Scenario {
|
||||
name?: string | null;
|
||||
indicators?: Indicator[] | null;
|
||||
loopbackPeriod?: number | null;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface Indicator {
|
||||
name?: string | null;
|
||||
type?: IndicatorType;
|
||||
signalType?: SignalType;
|
||||
minimumHistory?: number;
|
||||
period?: number | null;
|
||||
fastPeriods?: number | null;
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export enum IndicatorType {
|
||||
RsiDivergence = "RsiDivergence",
|
||||
RsiDivergenceConfirm = "RsiDivergenceConfirm",
|
||||
MacdCross = "MacdCross",
|
||||
EmaCross = "EmaCross",
|
||||
ThreeWhiteSoldiers = "ThreeWhiteSoldiers",
|
||||
SuperTrend = "SuperTrend",
|
||||
ChandelierExit = "ChandelierExit",
|
||||
EmaTrend = "EmaTrend",
|
||||
Composite = "Composite",
|
||||
StochRsiTrend = "StochRsiTrend",
|
||||
Stc = "Stc",
|
||||
StDev = "StDev",
|
||||
LaggingStc = "LaggingStc",
|
||||
SuperTrendCrossEma = "SuperTrendCrossEma",
|
||||
DualEmaCross = "DualEmaCross",
|
||||
}
|
||||
|
||||
export enum SignalType {
|
||||
Signal = "Signal",
|
||||
Trend = "Trend",
|
||||
Context = "Context",
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
accountName: string;
|
||||
date: Date;
|
||||
originDirection: TradeDirection;
|
||||
ticker: Ticker;
|
||||
moneyManagement: MoneyManagement;
|
||||
open: Trade;
|
||||
stopLoss: Trade;
|
||||
takeProfit1: Trade;
|
||||
takeProfit2?: Trade | null;
|
||||
profitAndLoss?: ProfitAndLoss | null;
|
||||
status: PositionStatus;
|
||||
signalIdentifier?: string | null;
|
||||
identifier: string;
|
||||
initiator: PositionInitiator;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export enum TradeDirection {
|
||||
None = "None",
|
||||
Short = "Short",
|
||||
Long = "Long",
|
||||
}
|
||||
|
||||
export interface Trade {
|
||||
fee?: number;
|
||||
date: Date;
|
||||
direction: TradeDirection;
|
||||
status: TradeStatus;
|
||||
tradeType: TradeType;
|
||||
ticker: Ticker;
|
||||
quantity: number;
|
||||
price: number;
|
||||
leverage?: number;
|
||||
exchangeOrderId: string;
|
||||
message?: string | null;
|
||||
}
|
||||
|
||||
export enum TradeStatus {
|
||||
PendingOpen = "PendingOpen",
|
||||
Requested = "Requested",
|
||||
Cancelled = "Cancelled",
|
||||
Filled = "Filled",
|
||||
}
|
||||
|
||||
export enum TradeType {
|
||||
Limit = "Limit",
|
||||
Market = "Market",
|
||||
StopMarket = "StopMarket",
|
||||
StopLimit = "StopLimit",
|
||||
StopLoss = "StopLoss",
|
||||
TakeProfit = "TakeProfit",
|
||||
StopLossProfit = "StopLossProfit",
|
||||
StopLossProfitLimit = "StopLossProfitLimit",
|
||||
StopLossLimit = "StopLossLimit",
|
||||
TakeProfitLimit = "TakeProfitLimit",
|
||||
TrailingStop = "TrailingStop",
|
||||
TrailingStopLimit = "TrailingStopLimit",
|
||||
StopLossAndLimit = "StopLossAndLimit",
|
||||
SettlePosition = "SettlePosition",
|
||||
}
|
||||
|
||||
export interface ProfitAndLoss {
|
||||
realized?: number;
|
||||
net?: number;
|
||||
averageOpenPrice?: number;
|
||||
}
|
||||
|
||||
export enum PositionStatus {
|
||||
New = "New",
|
||||
Canceled = "Canceled",
|
||||
Rejected = "Rejected",
|
||||
Updating = "Updating",
|
||||
PartiallyFilled = "PartiallyFilled",
|
||||
Filled = "Filled",
|
||||
Flipped = "Flipped",
|
||||
Finished = "Finished",
|
||||
}
|
||||
|
||||
export enum PositionInitiator {
|
||||
PaperTrading = "PaperTrading",
|
||||
Bot = "Bot",
|
||||
User = "User",
|
||||
CopyTrading = "CopyTrading",
|
||||
}
|
||||
|
||||
export interface ValueObject {
|
||||
}
|
||||
|
||||
export interface Signal extends ValueObject {
|
||||
status: SignalStatus;
|
||||
direction: TradeDirection;
|
||||
confidence: Confidence;
|
||||
timeframe: Timeframe;
|
||||
date: Date;
|
||||
candle: Candle;
|
||||
identifier: string;
|
||||
ticker: Ticker;
|
||||
exchange: TradingExchanges;
|
||||
indicatorType: IndicatorType;
|
||||
signalType: SignalType;
|
||||
user?: User | null;
|
||||
indicatorName: string;
|
||||
}
|
||||
|
||||
export enum SignalStatus {
|
||||
WaitingForPosition = "WaitingForPosition",
|
||||
PositionOpen = "PositionOpen",
|
||||
Expired = "Expired",
|
||||
}
|
||||
|
||||
export enum Confidence {
|
||||
Low = "Low",
|
||||
Medium = "Medium",
|
||||
High = "High",
|
||||
None = "None",
|
||||
}
|
||||
|
||||
export interface Candle {
|
||||
exchange: TradingExchanges;
|
||||
ticker: string;
|
||||
openTime: Date;
|
||||
date: Date;
|
||||
open: number;
|
||||
close: number;
|
||||
volume?: number;
|
||||
high: number;
|
||||
low: number;
|
||||
baseVolume?: number;
|
||||
quoteVolume?: number;
|
||||
tradeCount?: number;
|
||||
takerBuyBaseVolume?: number;
|
||||
takerBuyQuoteVolume?: number;
|
||||
timeframe: Timeframe;
|
||||
}
|
||||
|
||||
export interface PerformanceMetrics {
|
||||
count?: number;
|
||||
sharpeRatio?: number;
|
||||
maxDrawdown?: number;
|
||||
maxDrawdownPc?: number;
|
||||
maxDrawdownRecoveryTime?: string;
|
||||
winningTrades?: number;
|
||||
loosingTrades?: number;
|
||||
totalPnL?: number;
|
||||
}
|
||||
|
||||
export interface KeyValuePairOfDateTimeAndDecimal {
|
||||
key?: Date;
|
||||
value?: number;
|
||||
}
|
||||
|
||||
export interface IndicatorsResultBase {
|
||||
ema?: EmaResult[] | null;
|
||||
fastEma?: EmaResult[] | null;
|
||||
slowEma?: EmaResult[] | null;
|
||||
macd?: MacdResult[] | null;
|
||||
rsi?: RsiResult[] | null;
|
||||
stoch?: StochResult[] | null;
|
||||
stochRsi?: StochRsiResult[] | null;
|
||||
bollingerBands?: BollingerBandsResult[] | null;
|
||||
chandelierShort?: ChandelierResult[] | null;
|
||||
stc?: StcResult[] | null;
|
||||
stdDev?: StdDevResult[] | null;
|
||||
superTrend?: SuperTrendResult[] | null;
|
||||
chandelierLong?: ChandelierResult[] | null;
|
||||
}
|
||||
|
||||
export interface ResultBase {
|
||||
date?: Date;
|
||||
}
|
||||
|
||||
export interface EmaResult extends ResultBase {
|
||||
ema?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface MacdResult extends ResultBase {
|
||||
macd?: number | null;
|
||||
signal?: number | null;
|
||||
histogram?: number | null;
|
||||
fastEma?: number | null;
|
||||
slowEma?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface RsiResult extends ResultBase {
|
||||
rsi?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
/** Stochastic indicator results includes aliases for those who prefer the simpler K,D,J outputs. See documentation for more information. */
|
||||
export interface StochResult extends ResultBase {
|
||||
oscillator?: number | null;
|
||||
signal?: number | null;
|
||||
percentJ?: number | null;
|
||||
k?: number | null;
|
||||
d?: number | null;
|
||||
j?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StochRsiResult extends ResultBase {
|
||||
stochRsi?: number | null;
|
||||
signal?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface BollingerBandsResult extends ResultBase {
|
||||
sma?: number | null;
|
||||
upperBand?: number | null;
|
||||
lowerBand?: number | null;
|
||||
percentB?: number | null;
|
||||
zScore?: number | null;
|
||||
width?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface ChandelierResult extends ResultBase {
|
||||
chandelierExit?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StcResult extends ResultBase {
|
||||
stc?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StdDevResult extends ResultBase {
|
||||
stdDev?: number | null;
|
||||
mean?: number | null;
|
||||
zScore?: number | null;
|
||||
stdDevSma?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface SuperTrendResult extends ResultBase {
|
||||
superTrend?: number | null;
|
||||
upperBand?: number | null;
|
||||
lowerBand?: number | null;
|
||||
}
|
||||
|
||||
export interface RunBacktestRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
balance?: number;
|
||||
watchOnly?: boolean;
|
||||
save?: boolean;
|
||||
}
|
||||
|
||||
export interface TradingBotConfigRequest {
|
||||
accountName: string;
|
||||
ticker: Ticker;
|
||||
timeframe: Timeframe;
|
||||
isForWatchingOnly: boolean;
|
||||
botTradingBalance: number;
|
||||
botType: BotType;
|
||||
name: string;
|
||||
cooldownPeriod: number;
|
||||
maxLossStreak: number;
|
||||
scenario?: ScenarioRequest | null;
|
||||
scenarioName?: string | null;
|
||||
moneyManagementName?: string | null;
|
||||
moneyManagement?: MoneyManagementRequest | null;
|
||||
maxPositionTimeHours?: number | null;
|
||||
closeEarlyWhenProfitable?: boolean;
|
||||
flipOnlyWhenInProfit?: boolean;
|
||||
useSynthApi?: boolean;
|
||||
useForPositionSizing?: boolean;
|
||||
useForSignalFiltering?: boolean;
|
||||
useForDynamicStopLoss?: boolean;
|
||||
}
|
||||
|
||||
export interface ScenarioRequest {
|
||||
name: string;
|
||||
indicators: IndicatorRequest[];
|
||||
loopbackPeriod?: number | null;
|
||||
}
|
||||
|
||||
export interface IndicatorRequest {
|
||||
name: string;
|
||||
type: IndicatorType;
|
||||
signalType: SignalType;
|
||||
minimumHistory?: number;
|
||||
period?: number | null;
|
||||
fastPeriods?: number | null;
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
}
|
||||
|
||||
export interface MoneyManagementRequest {
|
||||
name: string;
|
||||
timeframe: Timeframe;
|
||||
stopLoss: number;
|
||||
takeProfit: number;
|
||||
leverage: number;
|
||||
}
|
||||
|
||||
export interface StartBotRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
}
|
||||
|
||||
export interface TradingBotResponse {
|
||||
status: string;
|
||||
signals: Signal[];
|
||||
positions: Position[];
|
||||
candles: Candle[];
|
||||
winRate: number;
|
||||
profitAndLoss: number;
|
||||
identifier: string;
|
||||
agentName: string;
|
||||
config: TradingBotConfig;
|
||||
}
|
||||
|
||||
export interface OpenPositionManuallyRequest {
|
||||
identifier?: string | null;
|
||||
direction?: TradeDirection;
|
||||
}
|
||||
|
||||
export interface ClosePositionRequest {
|
||||
identifier?: string | null;
|
||||
positionId?: string | null;
|
||||
}
|
||||
|
||||
export interface UpdateBotConfigRequest {
|
||||
identifier: string;
|
||||
config: TradingBotConfigRequest;
|
||||
moneyManagementName?: string | null;
|
||||
moneyManagement?: MoneyManagement | null;
|
||||
}
|
||||
|
||||
export interface TickerInfos {
|
||||
ticker?: Ticker;
|
||||
imageUrl?: string | null;
|
||||
}
|
||||
|
||||
export interface SpotlightOverview {
|
||||
spotlights: Spotlight[];
|
||||
dateTime: Date;
|
||||
identifier?: string;
|
||||
scenarioCount?: number;
|
||||
}
|
||||
|
||||
export interface Spotlight {
|
||||
scenario: Scenario;
|
||||
tickerSignals: TickerSignal[];
|
||||
}
|
||||
|
||||
export interface TickerSignal {
|
||||
ticker: Ticker;
|
||||
fiveMinutes: Signal[];
|
||||
fifteenMinutes: Signal[];
|
||||
oneHour: Signal[];
|
||||
fourHour: Signal[];
|
||||
oneDay: Signal[];
|
||||
}
|
||||
|
||||
export interface StrategiesStatisticsViewModel {
|
||||
totalStrategiesRunning?: number;
|
||||
changeInLast24Hours?: number;
|
||||
}
|
||||
|
||||
export interface TopStrategiesViewModel {
|
||||
topStrategies?: StrategyPerformance[] | null;
|
||||
}
|
||||
|
||||
export interface StrategyPerformance {
|
||||
strategyName?: string | null;
|
||||
pnL?: number;
|
||||
}
|
||||
|
||||
export interface UserStrategyDetailsViewModel {
|
||||
name?: string | null;
|
||||
state?: string | null;
|
||||
pnL?: number;
|
||||
roiPercentage?: number;
|
||||
roiLast24H?: number;
|
||||
runtime?: Date;
|
||||
winRate?: number;
|
||||
totalVolumeTraded?: number;
|
||||
volumeLast24H?: number;
|
||||
wins?: number;
|
||||
losses?: number;
|
||||
positions?: Position[] | null;
|
||||
identifier?: string | null;
|
||||
scenarioName?: string | null;
|
||||
}
|
||||
|
||||
export interface PlatformSummaryViewModel {
|
||||
totalAgents?: number;
|
||||
totalActiveStrategies?: number;
|
||||
totalPlatformPnL?: number;
|
||||
totalPlatformVolume?: number;
|
||||
totalPlatformVolumeLast24h?: number;
|
||||
agentSummaries?: AgentSummaryViewModel[] | null;
|
||||
timeFilter?: string | null;
|
||||
}
|
||||
|
||||
export interface AgentSummaryViewModel {
|
||||
agentName?: string | null;
|
||||
totalPnL?: number;
|
||||
pnLLast24h?: number;
|
||||
totalROI?: number;
|
||||
roiLast24h?: number;
|
||||
wins?: number;
|
||||
losses?: number;
|
||||
averageWinRate?: number;
|
||||
activeStrategiesCount?: number;
|
||||
totalVolume?: number;
|
||||
volumeLast24h?: number;
|
||||
}
|
||||
|
||||
export interface AgentBalanceHistory {
|
||||
agentName?: string | null;
|
||||
agentBalances?: AgentBalance[] | null;
|
||||
}
|
||||
|
||||
export interface AgentBalance {
|
||||
agentName?: string | null;
|
||||
totalValue?: number;
|
||||
totalAccountUsdValue?: number;
|
||||
botsAllocationUsdValue?: number;
|
||||
pnL?: number;
|
||||
time?: Date;
|
||||
}
|
||||
|
||||
export interface BestAgentsResponse {
|
||||
agents?: AgentBalanceHistory[] | null;
|
||||
totalCount?: number;
|
||||
currentPage?: number;
|
||||
pageSize?: number;
|
||||
totalPages?: number;
|
||||
}
|
||||
|
||||
export interface ScenarioViewModel {
|
||||
name: string;
|
||||
indicators: IndicatorViewModel[];
|
||||
loopbackPeriod?: number | null;
|
||||
userName: string;
|
||||
}
|
||||
|
||||
export interface IndicatorViewModel {
|
||||
name: string;
|
||||
type: IndicatorType;
|
||||
signalType: SignalType;
|
||||
minimumHistory: number;
|
||||
period?: number | null;
|
||||
fastPeriods?: number | null;
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: number | null;
|
||||
cyclePeriods?: number | null;
|
||||
userName: string;
|
||||
}
|
||||
|
||||
export enum RiskLevel {
|
||||
Low = "Low",
|
||||
Medium = "Medium",
|
||||
High = "High",
|
||||
Adaptive = "Adaptive",
|
||||
}
|
||||
|
||||
export interface PrivyInitAddressResponse {
|
||||
success?: boolean;
|
||||
usdcHash?: string | null;
|
||||
orderVaultHash?: string | null;
|
||||
exchangeRouterHash?: string | null;
|
||||
error?: string | null;
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
name: string;
|
||||
address: string;
|
||||
signature: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface Workflow {
|
||||
name: string;
|
||||
usage: WorkflowUsage;
|
||||
flows: IFlow[];
|
||||
description: string;
|
||||
}
|
||||
|
||||
export enum WorkflowUsage {
|
||||
Trading = "Trading",
|
||||
Task = "Task",
|
||||
}
|
||||
|
||||
export interface IFlow {
|
||||
id: string;
|
||||
name: string;
|
||||
type: FlowType;
|
||||
description: string;
|
||||
acceptedInputs: FlowOutput[];
|
||||
children?: IFlow[] | null;
|
||||
parameters: FlowParameter[];
|
||||
parentId?: string;
|
||||
output?: string | null;
|
||||
outputTypes: FlowOutput[];
|
||||
}
|
||||
|
||||
export enum FlowType {
|
||||
RsiDivergence = "RsiDivergence",
|
||||
FeedTicker = "FeedTicker",
|
||||
OpenPosition = "OpenPosition",
|
||||
}
|
||||
|
||||
export enum FlowOutput {
|
||||
Signal = "Signal",
|
||||
Candles = "Candles",
|
||||
Position = "Position",
|
||||
MoneyManagement = "MoneyManagement",
|
||||
}
|
||||
|
||||
export interface FlowParameter {
|
||||
value?: any | null;
|
||||
name?: string | null;
|
||||
}
|
||||
|
||||
export interface SyntheticWorkflow {
|
||||
name: string;
|
||||
usage: WorkflowUsage;
|
||||
description: string;
|
||||
flows: SyntheticFlow[];
|
||||
}
|
||||
|
||||
export interface SyntheticFlow {
|
||||
id: string;
|
||||
parentId?: string | null;
|
||||
type: FlowType;
|
||||
parameters: SyntheticFlowParameter[];
|
||||
}
|
||||
|
||||
export interface SyntheticFlowParameter {
|
||||
value: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface FileResponse {
|
||||
data: Blob;
|
||||
status: number;
|
||||
fileName?: string;
|
||||
headers?: { [name: string]: any };
|
||||
}
|
||||
50
src/Managing.WebApp/src/global/type.ts
Normal file
50
src/Managing.WebApp/src/global/type.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// Import types from ManagingApi
|
||||
import type {Backtest, RiskManagement, TradingBotConfig} from '../generated/ManagingApi'
|
||||
|
||||
// Import the existing IBacktestsFormInput from the correct file
|
||||
import type {IBacktestsFormInput} from './type.tsx'
|
||||
|
||||
export interface IRiskManagementInput {
|
||||
adverseProbabilityThreshold: number
|
||||
favorableProbabilityThreshold: number
|
||||
riskAversion: number
|
||||
kellyMinimumThreshold: number
|
||||
kellyMaximumCap: number
|
||||
maxLiquidationProbability: number
|
||||
signalValidationTimeHorizonHours: number
|
||||
positionMonitoringTimeHorizonHours: number
|
||||
positionWarningThreshold: number
|
||||
positionAutoCloseThreshold: number
|
||||
kellyFractionalMultiplier: number
|
||||
riskTolerance: 'Conservative' | 'Moderate' | 'Aggressive'
|
||||
useExpectedUtility: boolean
|
||||
useKellyCriterion: boolean
|
||||
}
|
||||
|
||||
export interface IUnifiedTradingConfigInput extends IBacktestsFormInput {
|
||||
// Bot-specific fields
|
||||
name?: string
|
||||
isForWatchingOnly?: boolean
|
||||
flipPosition?: boolean
|
||||
|
||||
// Risk Management fields
|
||||
riskManagement?: RiskManagement
|
||||
useCustomRiskManagement?: boolean
|
||||
}
|
||||
|
||||
export interface UnifiedTradingModalProps {
|
||||
showModal: boolean
|
||||
closeModal: () => void
|
||||
mode: 'backtest' | 'createBot' | 'updateBot'
|
||||
showLoopSlider?: boolean
|
||||
|
||||
// For backtests
|
||||
setBacktests?: React.Dispatch<React.SetStateAction<Backtest[]>>
|
||||
|
||||
// For bot creation/update
|
||||
backtest?: Backtest // Backtest object when creating from backtest
|
||||
existingBot?: {
|
||||
identifier: string
|
||||
config: TradingBotConfig // TradingBotConfig from API
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,11 @@ import type {
|
||||
FlowOutput,
|
||||
FlowType,
|
||||
IFlow,
|
||||
Indicator,
|
||||
IndicatorViewModel,
|
||||
MoneyManagement,
|
||||
Position,
|
||||
RiskLevel,
|
||||
Scenario,
|
||||
ScenarioViewModel,
|
||||
Signal,
|
||||
Ticker,
|
||||
Timeframe,
|
||||
@@ -112,6 +112,11 @@ export type IBacktestsFormInput = {
|
||||
maxPositionTimeHours?: number | null
|
||||
flipOnlyWhenInProfit?: boolean
|
||||
closeEarlyWhenProfitable?: boolean
|
||||
// Synth API fields
|
||||
useSynthApi?: boolean
|
||||
useForPositionSizing?: boolean
|
||||
useForSignalFiltering?: boolean
|
||||
useForDynamicStopLoss?: boolean
|
||||
}
|
||||
|
||||
export type IBacktestCards = {
|
||||
@@ -123,7 +128,7 @@ export type IBacktestCards = {
|
||||
export type IFormInput = {
|
||||
children: React.ReactNode
|
||||
htmlFor: string
|
||||
label: string
|
||||
label: React.ReactNode
|
||||
inline?: boolean
|
||||
}
|
||||
|
||||
@@ -177,9 +182,9 @@ export type IScenarioFormInput = {
|
||||
loopbackPeriod: number | undefined
|
||||
}
|
||||
export type IScenarioList = {
|
||||
list: Scenario[]
|
||||
indicators?: Indicator[]
|
||||
setScenarios?: React.Dispatch<React.SetStateAction<Scenario[]>>
|
||||
list: ScenarioViewModel[]
|
||||
indicators?: IndicatorViewModel[]
|
||||
setScenarios?: React.Dispatch<React.SetStateAction<ScenarioViewModel[]>>
|
||||
}
|
||||
|
||||
export type IMoneyManagementList = {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useState } from 'react'
|
||||
import React, {useState} from 'react'
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
|
||||
import { BacktestTable } from '../../components/organism'
|
||||
import BacktestModal from '../../components/organism/Backtest/backtestModal'
|
||||
import type { Backtest } from '../../generated/ManagingApi'
|
||||
import UnifiedTradingModal from '../../components/organism/UnifiedTradingModal'
|
||||
import type {Backtest} from '../../generated/ManagingApi'
|
||||
|
||||
const BacktestLoop: React.FC = () => {
|
||||
const [backtestingResult, setBacktestingResult] = useState<Backtest[]>([])
|
||||
const [backtestingResult, setBacktest] = useState<Backtest[]>([])
|
||||
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
|
||||
@@ -20,13 +20,13 @@ const BacktestLoop: React.FC = () => {
|
||||
return (
|
||||
<div className="container mx-auto">
|
||||
<button className="btn" onClick={openModal}>
|
||||
Run optimized loop
|
||||
Run Backtest with Loop
|
||||
</button>
|
||||
<BacktestTable list={backtestingResult} isFetching={false} />
|
||||
<BacktestModal
|
||||
<UnifiedTradingModal
|
||||
mode="backtest"
|
||||
showModal={showModal}
|
||||
closeModal={closeModal}
|
||||
setBacktests={setBacktestingResult}
|
||||
setBacktests={setBacktest}
|
||||
showLoopSlider={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useState } from 'react'
|
||||
import React, {useState} from 'react'
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
|
||||
import { BacktestCards, BacktestModal } from '../../components/organism'
|
||||
import type { Backtest } from '../../generated/ManagingApi'
|
||||
import {BacktestCards, UnifiedTradingModal} from '../../components/organism'
|
||||
import type {Backtest} from '../../generated/ManagingApi'
|
||||
|
||||
const BacktestPlayground: React.FC = () => {
|
||||
const [backtestingResult, setBacktest] = useState<Backtest[]>([])
|
||||
@@ -23,7 +23,8 @@ const BacktestPlayground: React.FC = () => {
|
||||
Run New Backtest
|
||||
</button>
|
||||
<BacktestCards list={backtestingResult} setBacktests={setBacktest} />
|
||||
<BacktestModal
|
||||
<UnifiedTradingModal
|
||||
mode="backtest"
|
||||
showModal={showModal}
|
||||
closeModal={closeModal}
|
||||
setBacktests={setBacktest}
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'react-toastify/dist/ReactToastify.css'
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import {Loader} from '../../components/atoms'
|
||||
import {Modal, Toast} from '../../components/mollecules'
|
||||
import {BacktestModal, BacktestTable} from '../../components/organism'
|
||||
import {BacktestTable, UnifiedTradingModal} from '../../components/organism'
|
||||
import type {Backtest} from '../../generated/ManagingApi'
|
||||
import {BacktestClient} from '../../generated/ManagingApi'
|
||||
|
||||
@@ -101,7 +101,8 @@ const BacktestScanner: React.FC = () => {
|
||||
|
||||
<BacktestTable list={backtestingResult} isFetching={isLoading} setBacktests={setBacktest} />
|
||||
|
||||
<BacktestModal
|
||||
<UnifiedTradingModal
|
||||
mode="backtest"
|
||||
showModal={showModal}
|
||||
closeModal={closeModal}
|
||||
setBacktests={setBacktest}
|
||||
|
||||
@@ -5,8 +5,7 @@ import useApiUrlStore from '../../app/store/apiStore'
|
||||
import {CardPosition, CardSignal, CardText, Toast,} from '../../components/mollecules'
|
||||
import ManualPositionModal from '../../components/mollecules/ManualPositionModal'
|
||||
import TradesModal from '../../components/mollecules/TradesModal/TradesModal'
|
||||
import BotConfigModal from '../../components/mollecules/BotConfigModal/BotConfigModal'
|
||||
import {TradeChart} from '../../components/organism'
|
||||
import {TradeChart, UnifiedTradingModal} from '../../components/organism'
|
||||
import type {BotType, MoneyManagement, Position, TradingBotResponse} from '../../generated/ManagingApi'
|
||||
import {BotClient} from '../../generated/ManagingApi'
|
||||
import type {IBotList} from '../../global/type'
|
||||
@@ -40,7 +39,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
const [showTradesModal, setShowTradesModal] = useState(false)
|
||||
const [selectedBotForTrades, setSelectedBotForTrades] = useState<{ identifier: string; agentName: string } | null>(null)
|
||||
const [showBotConfigModal, setShowBotConfigModal] = useState(false)
|
||||
const [botConfigModalMode, setBotConfigModalMode] = useState<'create' | 'update'>('create')
|
||||
const [botConfigModalMode, setBotConfigModalMode] = useState<'createBot' | 'updateBot'>('createBot')
|
||||
const [selectedBotForUpdate, setSelectedBotForUpdate] = useState<{
|
||||
identifier: string
|
||||
config: any
|
||||
@@ -219,13 +218,13 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
}
|
||||
|
||||
function openCreateBotModal() {
|
||||
setBotConfigModalMode('create')
|
||||
setBotConfigModalMode('createBot')
|
||||
setSelectedBotForUpdate(null)
|
||||
setShowBotConfigModal(true)
|
||||
}
|
||||
|
||||
function openUpdateBotModal(bot: TradingBotResponse) {
|
||||
setBotConfigModalMode('update')
|
||||
setBotConfigModalMode('updateBot')
|
||||
setSelectedBotForUpdate({
|
||||
identifier: bot.identifier,
|
||||
config: bot.config
|
||||
@@ -339,14 +338,17 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
setSelectedBotForTrades(null)
|
||||
}}
|
||||
/>
|
||||
<BotConfigModal
|
||||
showModal={showBotConfigModal}
|
||||
<UnifiedTradingModal
|
||||
mode={botConfigModalMode}
|
||||
existingBot={selectedBotForUpdate || undefined}
|
||||
onClose={() => {
|
||||
showModal={showBotConfigModal}
|
||||
closeModal={() => {
|
||||
setShowBotConfigModal(false)
|
||||
setSelectedBotForUpdate(null)
|
||||
}}
|
||||
existingBot={selectedBotForUpdate ? {
|
||||
identifier: selectedBotForUpdate.identifier,
|
||||
config: selectedBotForUpdate.config
|
||||
} : undefined}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'react-toastify/dist/ReactToastify.css'
|
||||
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import {Toast} from '../../components/mollecules'
|
||||
import type {Indicator} from '../../generated/ManagingApi'
|
||||
import type {IndicatorViewModel} from '../../generated/ManagingApi'
|
||||
import {IndicatorType, ScenarioClient, Timeframe,} from '../../generated/ManagingApi'
|
||||
|
||||
import IndicatorTable from './indicatorTable'
|
||||
@@ -28,7 +28,7 @@ const IndicatorList: React.FC = () => {
|
||||
const [indicatorType, setIndicatorType] = useState<IndicatorType>(
|
||||
IndicatorType.RsiDivergence
|
||||
)
|
||||
const [indicators, setIndicators] = useState<Indicator[]>([])
|
||||
const [indicators, setIndicators] = useState<IndicatorViewModel[]>([])
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const { register, handleSubmit } = useForm<IIndicatorFormInput>()
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
@@ -49,7 +49,7 @@ const IndicatorList: React.FC = () => {
|
||||
form.smoothPeriods,
|
||||
form.cyclePeriods
|
||||
)
|
||||
.then((indicator: Indicator) => {
|
||||
.then((indicator: IndicatorViewModel) => {
|
||||
t.update('success', 'Indicator created')
|
||||
setIndicators((arr) => [...arr, indicator])
|
||||
})
|
||||
@@ -68,7 +68,7 @@ const IndicatorList: React.FC = () => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
scenarioClient.scenario_GetIndicators().then((data: Indicator[]) => {
|
||||
scenarioClient.scenario_GetIndicators().then((data: IndicatorViewModel[]) => {
|
||||
setIndicators(data)
|
||||
})
|
||||
}, [])
|
||||
|
||||
@@ -3,15 +3,15 @@ import React, {useEffect, useState} from 'react'
|
||||
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import {SelectColumnFilter, Table, Toast} from '../../components/mollecules'
|
||||
import type {Indicator} from '../../generated/ManagingApi'
|
||||
import type {IndicatorViewModel} from '../../generated/ManagingApi'
|
||||
import {ScenarioClient} from '../../generated/ManagingApi'
|
||||
|
||||
interface IIndicatorList {
|
||||
list: Indicator[]
|
||||
list: IndicatorViewModel[]
|
||||
}
|
||||
|
||||
const IndicatorTable: React.FC<IIndicatorList> = ({ list }) => {
|
||||
const [rows, setRows] = useState<Indicator[]>([])
|
||||
const [rows, setRows] = useState<IndicatorViewModel[]>([])
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
|
||||
async function deleteIndicator(name: string) {
|
||||
@@ -41,12 +41,6 @@ const IndicatorTable: React.FC<IIndicatorList> = ({ list }) => {
|
||||
accessor: 'type',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Filter: SelectColumnFilter,
|
||||
Header: 'Timeframe',
|
||||
accessor: 'timeframe',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Filter: SelectColumnFilter,
|
||||
Header: 'Signal',
|
||||
|
||||
@@ -17,7 +17,7 @@ const tabs: TabsType = [
|
||||
{
|
||||
Component: IndicatorList,
|
||||
index: 2,
|
||||
label: 'Strategies',
|
||||
label: 'Indicators',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user