Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 38 additions & 12 deletions csharp/TraderBot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,53 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.UserSecrets;
using Microsoft.Extensions.Logging;
using Tinkoff.InvestApi;
using TraderBot;

var builder = Host.CreateDefaultBuilder(args);
var host = builder
.ConfigureServices((context, services) =>
{
services.AddSingleton(_ =>
// First check if we have multiple configurations
var multiConfig = context.Configuration.GetSection(nameof(MultiTradingConfiguration)).Get<MultiTradingConfiguration>();

if (multiConfig?.Configurations?.Length > 0)
{
var section = context.Configuration.GetSection(nameof(TradingSettings));
return section.Get<TradingSettings>();
});
services.AddHostedService<TradingService>();
services.AddInvestApiClient((_, settings) =>
// Multiple configurations mode
foreach (var config in multiConfig.Configurations)
{
// Register TradingService for each configuration with its own InvestApiClient
services.AddSingleton<IHostedService>(provider =>
{
var logger = provider.GetRequiredService<ILogger<TradingService>>();
var lifetime = provider.GetRequiredService<IHostApplicationLifetime>();

// Create a specific InvestApiClient for this configuration
var investApiClient = InvestApiClientFactory.Create(config.InvestApiSettings.AccessToken ?? "");

return new TradingService(logger, investApiClient, lifetime, config.TradingSettings, config.Name);
});
}
}
else
{
var section = context.Configuration.GetSection(nameof(InvestApiSettings));
var loadedSettings = section.Get<InvestApiSettings>();
settings.AccessToken = loadedSettings.AccessToken;
settings.AppName = loadedSettings.AppName;
context.Configuration.Bind(settings);
});
// Legacy single configuration mode for backward compatibility
services.AddSingleton(_ =>
{
var section = context.Configuration.GetSection(nameof(TradingSettings));
return section.Get<TradingSettings>() ?? new TradingSettings();
});
services.AddHostedService<TradingService>();
services.AddInvestApiClient((_, settings) =>
{
var section = context.Configuration.GetSection(nameof(InvestApiSettings));
var loadedSettings = section.Get<InvestApiSettings>();
settings.AccessToken = loadedSettings?.AccessToken ?? "";
settings.AppName = loadedSettings?.AppName ?? "";
context.Configuration.Bind(settings);
});
}
})
.Build();

Expand Down
122 changes: 122 additions & 0 deletions csharp/TraderBot/README.MultipleConfigurations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Multiple Trading Configurations Support

This update adds support for running multiple trading configurations simultaneously on the same TraderBot instance. This allows you to:

- Trade the same instrument on different broker accounts
- Test different trading settings simultaneously
- Trade multiple instruments at the same time
- Use different broker accounts for different configurations

## Configuration Structure

### Single Configuration (Legacy Mode)
The existing single configuration format is still supported for backward compatibility:

```json
{
"InvestApiSettings": {
"AccessToken": "your-token",
"AppName": "your-app-name"
},
"TradingSettings": {
"Instrument": "Etf",
"Ticker": "TMON@",
// ... other settings
}
}
```

### Multiple Configurations (New Mode)
For multiple configurations, use the new format:

```json
{
"MultiTradingConfiguration": {
"Configurations": [
{
"Name": "TMON_Account1",
"InvestApiSettings": {
"AccessToken": "account1-token",
"AppName": "LinksPlatformScalper_TMON_Account1"
},
"TradingSettings": {
"Instrument": "Etf",
"Ticker": "TMON@",
"AccountIndex": 0,
// ... other settings
}
},
{
"Name": "TMON_Account2",
"InvestApiSettings": {
"AccessToken": "account2-token",
"AppName": "LinksPlatformScalper_TMON_Account2"
},
"TradingSettings": {
"Instrument": "Etf",
"Ticker": "TMON@",
"AccountIndex": 1,
// ... different settings
}
}
]
}
}
```

## Usage Examples

### Example 1: Same Instrument, Different Accounts
Trade TMON@ on two different broker accounts with different settings:

- Account 1: Conservative settings with MinimumProfitSteps: -1
- Account 2: Aggressive settings with MinimumProfitSteps: -2

### Example 2: Multiple Instruments
Trade different instruments simultaneously:

- Configuration 1: TMON@ on Account 1
- Configuration 2: TRUR on Account 1
- Configuration 3: TMON@ on Account 2

### Example 3: A/B Testing
Test different trading parameters on the same instrument:

- Configuration 1: Trading hours 00:00-23:59
- Configuration 2: Trading hours 09:00-14:45

## Logging

Each configuration runs independently and logs are prefixed with the configuration name for easy identification:

```
[TMON_Account1] Instrument: Etf
[TMON_Account1] Ticker: TMON@
[TMON_Account2] Instrument: Etf
[TMON_Account2] Ticker: TMON@
```

## Configuration Files

Sample configuration files are provided:

- `appsettings.TMON.json` - Single TMON configuration (legacy)
- `appsettings.TRUR.json` - Single TRUR configuration (legacy)
- `appsettings.MultipleConfigurations.json` - Multiple configurations example

## Technical Implementation

- Each configuration gets its own `TradingService` instance
- Each configuration uses a separate `InvestApiClient` instance
- All services run concurrently as hosted services
- Backward compatibility is maintained with existing single configuration setups

## Migration Guide

To migrate from single to multiple configurations:

1. Keep your existing configuration files as-is for backward compatibility
2. Or create a new configuration file using the `MultiTradingConfiguration` format
3. Move `InvestApiSettings` and `TradingSettings` under each configuration object
4. Add a unique `Name` field for each configuration
5. Update any external references to account for the new configuration names
15 changes: 15 additions & 0 deletions csharp/TraderBot/TradingConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Tinkoff.InvestApi;

namespace TraderBot;

public class TradingConfiguration
{
public string Name { get; set; } = string.Empty;
public TradingSettings TradingSettings { get; set; } = new();
public InvestApiSettings InvestApiSettings { get; set; } = new();
}

public class MultiTradingConfiguration
{
public TradingConfiguration[] Configurations { get; set; } = Array.Empty<TradingConfiguration>();
}
44 changes: 23 additions & 21 deletions csharp/TraderBot/TradingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class TradingService : BackgroundService
protected readonly ILogger<TradingService> Logger;
protected readonly IHostApplicationLifetime Lifetime;
protected readonly TradingSettings Settings;
protected readonly string ConfigurationName;
protected readonly Account CurrentAccount;
protected readonly string Figi;
protected readonly int LotSize;
Expand All @@ -40,45 +41,46 @@ public class TradingService : BackgroundService
protected readonly ConcurrentDictionary<decimal, long> LotsSets;
protected readonly ConcurrentDictionary<string, decimal> ActiveSellOrderSourcePrice;

public TradingService(ILogger<TradingService> logger, InvestApiClient investApi, IHostApplicationLifetime lifetime, TradingSettings settings)
public TradingService(ILogger<TradingService> logger, InvestApiClient investApi, IHostApplicationLifetime lifetime, TradingSettings settings, string configurationName = "Default")
{
Logger = logger;
InvestApi = investApi;
Lifetime = lifetime;
Settings = settings;
Logger.LogInformation($"Instrument: {settings.Instrument}");
Logger.LogInformation($"Ticker: {settings.Ticker}");
Logger.LogInformation($"CashCurrency: {settings.CashCurrency}");
Logger.LogInformation($"AccountIndex: {settings.AccountIndex}");
Logger.LogInformation($"MinimumProfitSteps: {settings.MinimumProfitSteps}");
Logger.LogInformation($"MarketOrderBookDepth: {settings.MarketOrderBookDepth}");
Logger.LogInformation($"MinimumMarketOrderSizeToChangeBuyPrice: {settings.MinimumMarketOrderSizeToChangeBuyPrice}");
Logger.LogInformation($"MinimumMarketOrderSizeToChangeSellPrice: {settings.MinimumMarketOrderSizeToChangeSellPrice}");
Logger.LogInformation($"MinimumMarketOrderSizeToBuy: {settings.MinimumMarketOrderSizeToBuy}");
Logger.LogInformation($"MinimumMarketOrderSizeToSell: {settings.MinimumMarketOrderSizeToSell}");
ConfigurationName = configurationName;
Logger.LogInformation($"[{ConfigurationName}] Instrument: {settings.Instrument}");
Logger.LogInformation($"[{ConfigurationName}] Ticker: {settings.Ticker}");
Logger.LogInformation($"[{ConfigurationName}] CashCurrency: {settings.CashCurrency}");
Logger.LogInformation($"[{ConfigurationName}] AccountIndex: {settings.AccountIndex}");
Logger.LogInformation($"[{ConfigurationName}] MinimumProfitSteps: {settings.MinimumProfitSteps}");
Logger.LogInformation($"[{ConfigurationName}] MarketOrderBookDepth: {settings.MarketOrderBookDepth}");
Logger.LogInformation($"[{ConfigurationName}] MinimumMarketOrderSizeToChangeBuyPrice: {settings.MinimumMarketOrderSizeToChangeBuyPrice}");
Logger.LogInformation($"[{ConfigurationName}] MinimumMarketOrderSizeToChangeSellPrice: {settings.MinimumMarketOrderSizeToChangeSellPrice}");
Logger.LogInformation($"[{ConfigurationName}] MinimumMarketOrderSizeToBuy: {settings.MinimumMarketOrderSizeToBuy}");
Logger.LogInformation($"[{ConfigurationName}] MinimumMarketOrderSizeToSell: {settings.MinimumMarketOrderSizeToSell}");
MinimumTimeToBuy = TimeSpan.Parse(settings.MinimumTimeToBuy ?? "00:00:00", CultureInfo.InvariantCulture);
Logger.LogInformation($"MinimumTimeToBuy: {MinimumTimeToBuy}");
Logger.LogInformation($"[{ConfigurationName}] MinimumTimeToBuy: {MinimumTimeToBuy}");
MaximumTimeToBuy = TimeSpan.Parse(settings.MaximumTimeToBuy ?? "23:59:59", CultureInfo.InvariantCulture);
Logger.LogInformation($"MaximumTimeToBuy: {MaximumTimeToBuy}");
Logger.LogInformation($"EarlySellOwnedLotsDelta: {settings.EarlySellOwnedLotsDelta}");
Logger.LogInformation($"EarlySellOwnedLotsMultiplier: {settings.EarlySellOwnedLotsMultiplier}");
Logger.LogInformation($"LoadOperationsFrom: {settings.LoadOperationsFrom}");
Logger.LogInformation($"[{ConfigurationName}] MaximumTimeToBuy: {MaximumTimeToBuy}");
Logger.LogInformation($"[{ConfigurationName}] EarlySellOwnedLotsDelta: {settings.EarlySellOwnedLotsDelta}");
Logger.LogInformation($"[{ConfigurationName}] EarlySellOwnedLotsMultiplier: {settings.EarlySellOwnedLotsMultiplier}");
Logger.LogInformation($"[{ConfigurationName}] LoadOperationsFrom: {settings.LoadOperationsFrom}");

var currentTime = DateTime.UtcNow.TimeOfDay;
Logger.LogInformation($"Current time: {currentTime}");
Logger.LogInformation($"[{ConfigurationName}] Current time: {currentTime}");

var accounts = InvestApi.Users.GetAccounts().Accounts;
Logger.LogInformation("Accounts:");
Logger.LogInformation($"[{ConfigurationName}] Accounts:");
for (int i = 0; i < accounts.Count; i++)
{
Logger.LogInformation($"[{i}]: {accounts[i]}");
Logger.LogInformation($"[{ConfigurationName}] [{i}]: {accounts[i]}");
}
if (settings.AccountIndex < 0 || settings.AccountIndex >= accounts.Count)
{
throw new ArgumentException($"Account index {settings.AccountIndex} is out of range. Please select a valid account index ({0}-{accounts.Count - 1}).");
throw new ArgumentException($"[{ConfigurationName}] Account index {settings.AccountIndex} is out of range. Please select a valid account index ({0}-{accounts.Count - 1}).");
}
CurrentAccount = accounts[settings.AccountIndex];
Logger.LogInformation($"CurrentAccount (with {settings.AccountIndex} index): {CurrentAccount}");
Logger.LogInformation($"[{ConfigurationName}] CurrentAccount (with {settings.AccountIndex} index): {CurrentAccount}");

if (settings.Instrument == Instrument.Etf)
{
Expand Down
84 changes: 84 additions & 0 deletions csharp/TraderBot/appsettings.MultipleConfigurations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"MultiTradingConfiguration": {
"Configurations": [
{
"Name": "TMON_Account1",
"InvestApiSettings": {
"AccessToken": "",
"AppName": "LinksPlatformScalper_TMON_Account1"
},
"TradingSettings": {
"Instrument": "Etf",
"Ticker": "TMON@",
"CashCurrency": "rub",
"AccountIndex": 0,
"MinimumProfitSteps": -1,
"MarketOrderBookDepth": 10,
"MinimumMarketOrderSizeToChangeBuyPrice": 300000,
"MinimumMarketOrderSizeToChangeSellPrice": 0,
"MinimumMarketOrderSizeToBuy": 300000,
"MinimumMarketOrderSizeToSell": 0,
"MinimumTimeToBuy": "00:00:01",
"MaximumTimeToBuy": "23:59:59",
"EarlySellOwnedLotsDelta": 300000,
"EarlySellOwnedLotsMultiplier": 0,
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z"
}
},
{
"Name": "TMON_Account2",
"InvestApiSettings": {
"AccessToken": "",
"AppName": "LinksPlatformScalper_TMON_Account2"
},
"TradingSettings": {
"Instrument": "Etf",
"Ticker": "TMON@",
"CashCurrency": "rub",
"AccountIndex": 1,
"MinimumProfitSteps": -2,
"MarketOrderBookDepth": 10,
"MinimumMarketOrderSizeToChangeBuyPrice": 500000,
"MinimumMarketOrderSizeToChangeSellPrice": 0,
"MinimumMarketOrderSizeToBuy": 500000,
"MinimumMarketOrderSizeToSell": 0,
"MinimumTimeToBuy": "09:00:00",
"MaximumTimeToBuy": "14:45:00",
"EarlySellOwnedLotsDelta": 500000,
"EarlySellOwnedLotsMultiplier": 0,
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z"
}
},
{
"Name": "TRUR_Account1",
"InvestApiSettings": {
"AccessToken": "",
"AppName": "LinksPlatformScalper_TRUR_Account1"
},
"TradingSettings": {
"Instrument": "Etf",
"Ticker": "TRUR",
"CashCurrency": "rub",
"AccountIndex": 0,
"MinimumProfitSteps": -2,
"MarketOrderBookDepth": 10,
"MinimumMarketOrderSizeToChangeBuyPrice": 300000,
"MinimumMarketOrderSizeToChangeSellPrice": 0,
"MinimumMarketOrderSizeToBuy": 300000,
"MinimumMarketOrderSizeToSell": 0,
"MinimumTimeToBuy": "09:00:00",
"MaximumTimeToBuy": "14:45:00",
"EarlySellOwnedLotsDelta": 300000,
"EarlySellOwnedLotsMultiplier": 0,
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z"
}
}
]
}
}
Loading