Skip to content

Commit a2ef792

Browse files
konardclaude
andcommitted
Implement support for multiple trading configurations
- Add TradingConfiguration wrapper class to support multiple configs - Update Program.cs to handle both single and multiple configuration modes - Enhance TradingService with configuration name prefixes in logs - Create example multi-configuration JSON file - Maintain backward compatibility with existing single configuration format - Enable trading same instrument on different accounts with different settings - Support simultaneous trading of multiple instruments Resolves issue #194 - Trader: support multiple trading configurations at the same time 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ca67aff commit a2ef792

5 files changed

Lines changed: 282 additions & 33 deletions

File tree

csharp/TraderBot/Program.cs

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,53 @@
22
using Microsoft.Extensions.DependencyInjection;
33
using Microsoft.Extensions.Configuration;
44
using Microsoft.Extensions.Configuration.UserSecrets;
5+
using Microsoft.Extensions.Logging;
56
using Tinkoff.InvestApi;
67
using TraderBot;
78

89
var builder = Host.CreateDefaultBuilder(args);
910
var host = builder
1011
.ConfigureServices((context, services) =>
1112
{
12-
services.AddSingleton(_ =>
13+
// First check if we have multiple configurations
14+
var multiConfig = context.Configuration.GetSection(nameof(MultiTradingConfiguration)).Get<MultiTradingConfiguration>();
15+
16+
if (multiConfig?.Configurations?.Length > 0)
1317
{
14-
var section = context.Configuration.GetSection(nameof(TradingSettings));
15-
return section.Get<TradingSettings>();
16-
});
17-
services.AddHostedService<TradingService>();
18-
services.AddInvestApiClient((_, settings) =>
18+
// Multiple configurations mode
19+
foreach (var config in multiConfig.Configurations)
20+
{
21+
// Register TradingService for each configuration with its own InvestApiClient
22+
services.AddSingleton<IHostedService>(provider =>
23+
{
24+
var logger = provider.GetRequiredService<ILogger<TradingService>>();
25+
var lifetime = provider.GetRequiredService<IHostApplicationLifetime>();
26+
27+
// Create a specific InvestApiClient for this configuration
28+
var investApiClient = InvestApiClientFactory.Create(config.InvestApiSettings.AccessToken ?? "");
29+
30+
return new TradingService(logger, investApiClient, lifetime, config.TradingSettings, config.Name);
31+
});
32+
}
33+
}
34+
else
1935
{
20-
var section = context.Configuration.GetSection(nameof(InvestApiSettings));
21-
var loadedSettings = section.Get<InvestApiSettings>();
22-
settings.AccessToken = loadedSettings.AccessToken;
23-
settings.AppName = loadedSettings.AppName;
24-
context.Configuration.Bind(settings);
25-
});
36+
// Legacy single configuration mode for backward compatibility
37+
services.AddSingleton(_ =>
38+
{
39+
var section = context.Configuration.GetSection(nameof(TradingSettings));
40+
return section.Get<TradingSettings>() ?? new TradingSettings();
41+
});
42+
services.AddHostedService<TradingService>();
43+
services.AddInvestApiClient((_, settings) =>
44+
{
45+
var section = context.Configuration.GetSection(nameof(InvestApiSettings));
46+
var loadedSettings = section.Get<InvestApiSettings>();
47+
settings.AccessToken = loadedSettings?.AccessToken ?? "";
48+
settings.AppName = loadedSettings?.AppName ?? "";
49+
context.Configuration.Bind(settings);
50+
});
51+
}
2652
})
2753
.Build();
2854

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Multiple Trading Configurations Support
2+
3+
This update adds support for running multiple trading configurations simultaneously on the same TraderBot instance. This allows you to:
4+
5+
- Trade the same instrument on different broker accounts
6+
- Test different trading settings simultaneously
7+
- Trade multiple instruments at the same time
8+
- Use different broker accounts for different configurations
9+
10+
## Configuration Structure
11+
12+
### Single Configuration (Legacy Mode)
13+
The existing single configuration format is still supported for backward compatibility:
14+
15+
```json
16+
{
17+
"InvestApiSettings": {
18+
"AccessToken": "your-token",
19+
"AppName": "your-app-name"
20+
},
21+
"TradingSettings": {
22+
"Instrument": "Etf",
23+
"Ticker": "TMON@",
24+
// ... other settings
25+
}
26+
}
27+
```
28+
29+
### Multiple Configurations (New Mode)
30+
For multiple configurations, use the new format:
31+
32+
```json
33+
{
34+
"MultiTradingConfiguration": {
35+
"Configurations": [
36+
{
37+
"Name": "TMON_Account1",
38+
"InvestApiSettings": {
39+
"AccessToken": "account1-token",
40+
"AppName": "LinksPlatformScalper_TMON_Account1"
41+
},
42+
"TradingSettings": {
43+
"Instrument": "Etf",
44+
"Ticker": "TMON@",
45+
"AccountIndex": 0,
46+
// ... other settings
47+
}
48+
},
49+
{
50+
"Name": "TMON_Account2",
51+
"InvestApiSettings": {
52+
"AccessToken": "account2-token",
53+
"AppName": "LinksPlatformScalper_TMON_Account2"
54+
},
55+
"TradingSettings": {
56+
"Instrument": "Etf",
57+
"Ticker": "TMON@",
58+
"AccountIndex": 1,
59+
// ... different settings
60+
}
61+
}
62+
]
63+
}
64+
}
65+
```
66+
67+
## Usage Examples
68+
69+
### Example 1: Same Instrument, Different Accounts
70+
Trade TMON@ on two different broker accounts with different settings:
71+
72+
- Account 1: Conservative settings with MinimumProfitSteps: -1
73+
- Account 2: Aggressive settings with MinimumProfitSteps: -2
74+
75+
### Example 2: Multiple Instruments
76+
Trade different instruments simultaneously:
77+
78+
- Configuration 1: TMON@ on Account 1
79+
- Configuration 2: TRUR on Account 1
80+
- Configuration 3: TMON@ on Account 2
81+
82+
### Example 3: A/B Testing
83+
Test different trading parameters on the same instrument:
84+
85+
- Configuration 1: Trading hours 00:00-23:59
86+
- Configuration 2: Trading hours 09:00-14:45
87+
88+
## Logging
89+
90+
Each configuration runs independently and logs are prefixed with the configuration name for easy identification:
91+
92+
```
93+
[TMON_Account1] Instrument: Etf
94+
[TMON_Account1] Ticker: TMON@
95+
[TMON_Account2] Instrument: Etf
96+
[TMON_Account2] Ticker: TMON@
97+
```
98+
99+
## Configuration Files
100+
101+
Sample configuration files are provided:
102+
103+
- `appsettings.TMON.json` - Single TMON configuration (legacy)
104+
- `appsettings.TRUR.json` - Single TRUR configuration (legacy)
105+
- `appsettings.MultipleConfigurations.json` - Multiple configurations example
106+
107+
## Technical Implementation
108+
109+
- Each configuration gets its own `TradingService` instance
110+
- Each configuration uses a separate `InvestApiClient` instance
111+
- All services run concurrently as hosted services
112+
- Backward compatibility is maintained with existing single configuration setups
113+
114+
## Migration Guide
115+
116+
To migrate from single to multiple configurations:
117+
118+
1. Keep your existing configuration files as-is for backward compatibility
119+
2. Or create a new configuration file using the `MultiTradingConfiguration` format
120+
3. Move `InvestApiSettings` and `TradingSettings` under each configuration object
121+
4. Add a unique `Name` field for each configuration
122+
5. Update any external references to account for the new configuration names
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Tinkoff.InvestApi;
2+
3+
namespace TraderBot;
4+
5+
public class TradingConfiguration
6+
{
7+
public string Name { get; set; } = string.Empty;
8+
public TradingSettings TradingSettings { get; set; } = new();
9+
public InvestApiSettings InvestApiSettings { get; set; } = new();
10+
}
11+
12+
public class MultiTradingConfiguration
13+
{
14+
public TradingConfiguration[] Configurations { get; set; } = Array.Empty<TradingConfiguration>();
15+
}

csharp/TraderBot/TradingService.cs

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class TradingService : BackgroundService
2323
protected readonly ILogger<TradingService> Logger;
2424
protected readonly IHostApplicationLifetime Lifetime;
2525
protected readonly TradingSettings Settings;
26+
protected readonly string ConfigurationName;
2627
protected readonly Account CurrentAccount;
2728
protected readonly string Figi;
2829
protected readonly int LotSize;
@@ -40,45 +41,46 @@ public class TradingService : BackgroundService
4041
protected readonly ConcurrentDictionary<decimal, long> LotsSets;
4142
protected readonly ConcurrentDictionary<string, decimal> ActiveSellOrderSourcePrice;
4243

43-
public TradingService(ILogger<TradingService> logger, InvestApiClient investApi, IHostApplicationLifetime lifetime, TradingSettings settings)
44+
public TradingService(ILogger<TradingService> logger, InvestApiClient investApi, IHostApplicationLifetime lifetime, TradingSettings settings, string configurationName = "Default")
4445
{
4546
Logger = logger;
4647
InvestApi = investApi;
4748
Lifetime = lifetime;
4849
Settings = settings;
49-
Logger.LogInformation($"Instrument: {settings.Instrument}");
50-
Logger.LogInformation($"Ticker: {settings.Ticker}");
51-
Logger.LogInformation($"CashCurrency: {settings.CashCurrency}");
52-
Logger.LogInformation($"AccountIndex: {settings.AccountIndex}");
53-
Logger.LogInformation($"MinimumProfitSteps: {settings.MinimumProfitSteps}");
54-
Logger.LogInformation($"MarketOrderBookDepth: {settings.MarketOrderBookDepth}");
55-
Logger.LogInformation($"MinimumMarketOrderSizeToChangeBuyPrice: {settings.MinimumMarketOrderSizeToChangeBuyPrice}");
56-
Logger.LogInformation($"MinimumMarketOrderSizeToChangeSellPrice: {settings.MinimumMarketOrderSizeToChangeSellPrice}");
57-
Logger.LogInformation($"MinimumMarketOrderSizeToBuy: {settings.MinimumMarketOrderSizeToBuy}");
58-
Logger.LogInformation($"MinimumMarketOrderSizeToSell: {settings.MinimumMarketOrderSizeToSell}");
50+
ConfigurationName = configurationName;
51+
Logger.LogInformation($"[{ConfigurationName}] Instrument: {settings.Instrument}");
52+
Logger.LogInformation($"[{ConfigurationName}] Ticker: {settings.Ticker}");
53+
Logger.LogInformation($"[{ConfigurationName}] CashCurrency: {settings.CashCurrency}");
54+
Logger.LogInformation($"[{ConfigurationName}] AccountIndex: {settings.AccountIndex}");
55+
Logger.LogInformation($"[{ConfigurationName}] MinimumProfitSteps: {settings.MinimumProfitSteps}");
56+
Logger.LogInformation($"[{ConfigurationName}] MarketOrderBookDepth: {settings.MarketOrderBookDepth}");
57+
Logger.LogInformation($"[{ConfigurationName}] MinimumMarketOrderSizeToChangeBuyPrice: {settings.MinimumMarketOrderSizeToChangeBuyPrice}");
58+
Logger.LogInformation($"[{ConfigurationName}] MinimumMarketOrderSizeToChangeSellPrice: {settings.MinimumMarketOrderSizeToChangeSellPrice}");
59+
Logger.LogInformation($"[{ConfigurationName}] MinimumMarketOrderSizeToBuy: {settings.MinimumMarketOrderSizeToBuy}");
60+
Logger.LogInformation($"[{ConfigurationName}] MinimumMarketOrderSizeToSell: {settings.MinimumMarketOrderSizeToSell}");
5961
MinimumTimeToBuy = TimeSpan.Parse(settings.MinimumTimeToBuy ?? "00:00:00", CultureInfo.InvariantCulture);
60-
Logger.LogInformation($"MinimumTimeToBuy: {MinimumTimeToBuy}");
62+
Logger.LogInformation($"[{ConfigurationName}] MinimumTimeToBuy: {MinimumTimeToBuy}");
6163
MaximumTimeToBuy = TimeSpan.Parse(settings.MaximumTimeToBuy ?? "23:59:59", CultureInfo.InvariantCulture);
62-
Logger.LogInformation($"MaximumTimeToBuy: {MaximumTimeToBuy}");
63-
Logger.LogInformation($"EarlySellOwnedLotsDelta: {settings.EarlySellOwnedLotsDelta}");
64-
Logger.LogInformation($"EarlySellOwnedLotsMultiplier: {settings.EarlySellOwnedLotsMultiplier}");
65-
Logger.LogInformation($"LoadOperationsFrom: {settings.LoadOperationsFrom}");
64+
Logger.LogInformation($"[{ConfigurationName}] MaximumTimeToBuy: {MaximumTimeToBuy}");
65+
Logger.LogInformation($"[{ConfigurationName}] EarlySellOwnedLotsDelta: {settings.EarlySellOwnedLotsDelta}");
66+
Logger.LogInformation($"[{ConfigurationName}] EarlySellOwnedLotsMultiplier: {settings.EarlySellOwnedLotsMultiplier}");
67+
Logger.LogInformation($"[{ConfigurationName}] LoadOperationsFrom: {settings.LoadOperationsFrom}");
6668

6769
var currentTime = DateTime.UtcNow.TimeOfDay;
68-
Logger.LogInformation($"Current time: {currentTime}");
70+
Logger.LogInformation($"[{ConfigurationName}] Current time: {currentTime}");
6971

7072
var accounts = InvestApi.Users.GetAccounts().Accounts;
71-
Logger.LogInformation("Accounts:");
73+
Logger.LogInformation($"[{ConfigurationName}] Accounts:");
7274
for (int i = 0; i < accounts.Count; i++)
7375
{
74-
Logger.LogInformation($"[{i}]: {accounts[i]}");
76+
Logger.LogInformation($"[{ConfigurationName}] [{i}]: {accounts[i]}");
7577
}
7678
if (settings.AccountIndex < 0 || settings.AccountIndex >= accounts.Count)
7779
{
78-
throw new ArgumentException($"Account index {settings.AccountIndex} is out of range. Please select a valid account index ({0}-{accounts.Count - 1}).");
80+
throw new ArgumentException($"[{ConfigurationName}] Account index {settings.AccountIndex} is out of range. Please select a valid account index ({0}-{accounts.Count - 1}).");
7981
}
8082
CurrentAccount = accounts[settings.AccountIndex];
81-
Logger.LogInformation($"CurrentAccount (with {settings.AccountIndex} index): {CurrentAccount}");
83+
Logger.LogInformation($"[{ConfigurationName}] CurrentAccount (with {settings.AccountIndex} index): {CurrentAccount}");
8284

8385
if (settings.Instrument == Instrument.Etf)
8486
{
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.Hosting.Lifetime": "Information"
6+
}
7+
},
8+
"MultiTradingConfiguration": {
9+
"Configurations": [
10+
{
11+
"Name": "TMON_Account1",
12+
"InvestApiSettings": {
13+
"AccessToken": "",
14+
"AppName": "LinksPlatformScalper_TMON_Account1"
15+
},
16+
"TradingSettings": {
17+
"Instrument": "Etf",
18+
"Ticker": "TMON@",
19+
"CashCurrency": "rub",
20+
"AccountIndex": 0,
21+
"MinimumProfitSteps": -1,
22+
"MarketOrderBookDepth": 10,
23+
"MinimumMarketOrderSizeToChangeBuyPrice": 300000,
24+
"MinimumMarketOrderSizeToChangeSellPrice": 0,
25+
"MinimumMarketOrderSizeToBuy": 300000,
26+
"MinimumMarketOrderSizeToSell": 0,
27+
"MinimumTimeToBuy": "00:00:01",
28+
"MaximumTimeToBuy": "23:59:59",
29+
"EarlySellOwnedLotsDelta": 300000,
30+
"EarlySellOwnedLotsMultiplier": 0,
31+
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z"
32+
}
33+
},
34+
{
35+
"Name": "TMON_Account2",
36+
"InvestApiSettings": {
37+
"AccessToken": "",
38+
"AppName": "LinksPlatformScalper_TMON_Account2"
39+
},
40+
"TradingSettings": {
41+
"Instrument": "Etf",
42+
"Ticker": "TMON@",
43+
"CashCurrency": "rub",
44+
"AccountIndex": 1,
45+
"MinimumProfitSteps": -2,
46+
"MarketOrderBookDepth": 10,
47+
"MinimumMarketOrderSizeToChangeBuyPrice": 500000,
48+
"MinimumMarketOrderSizeToChangeSellPrice": 0,
49+
"MinimumMarketOrderSizeToBuy": 500000,
50+
"MinimumMarketOrderSizeToSell": 0,
51+
"MinimumTimeToBuy": "09:00:00",
52+
"MaximumTimeToBuy": "14:45:00",
53+
"EarlySellOwnedLotsDelta": 500000,
54+
"EarlySellOwnedLotsMultiplier": 0,
55+
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z"
56+
}
57+
},
58+
{
59+
"Name": "TRUR_Account1",
60+
"InvestApiSettings": {
61+
"AccessToken": "",
62+
"AppName": "LinksPlatformScalper_TRUR_Account1"
63+
},
64+
"TradingSettings": {
65+
"Instrument": "Etf",
66+
"Ticker": "TRUR",
67+
"CashCurrency": "rub",
68+
"AccountIndex": 0,
69+
"MinimumProfitSteps": -2,
70+
"MarketOrderBookDepth": 10,
71+
"MinimumMarketOrderSizeToChangeBuyPrice": 300000,
72+
"MinimumMarketOrderSizeToChangeSellPrice": 0,
73+
"MinimumMarketOrderSizeToBuy": 300000,
74+
"MinimumMarketOrderSizeToSell": 0,
75+
"MinimumTimeToBuy": "09:00:00",
76+
"MaximumTimeToBuy": "14:45:00",
77+
"EarlySellOwnedLotsDelta": 300000,
78+
"EarlySellOwnedLotsMultiplier": 0,
79+
"LoadOperationsFrom": "2025-03-01T00:00:01.3389860Z"
80+
}
81+
}
82+
]
83+
}
84+
}

0 commit comments

Comments
 (0)