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
6 changes: 6 additions & 0 deletions csharp/TraderBot/FinancialStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public class FinancialStorage
public readonly TLinkAddress TypeAsStringOperationFieldType;
public readonly TLinkAddress TypeAsEnumOperationFieldType;
public readonly TLinkAddress TradesOperationFieldType;
public readonly TLinkAddress PortfolioType;
public readonly TLinkAddress AllocationPercentType;
public readonly TLinkAddress RebalanceActionType;

public FinancialStorage()
{
Expand Down Expand Up @@ -113,6 +116,9 @@ public FinancialStorage()
OperationCurrencyFieldType = GetOrCreateType(AssetType, nameof(OperationCurrencyFieldType));
RubType = GetOrCreateType(OperationCurrencyFieldType, nameof(RubType));
AmountType = GetOrCreateType(Type, nameof(AmountType));
PortfolioType = GetOrCreateType(Type, nameof(PortfolioType));
AllocationPercentType = GetOrCreateType(PortfolioType, nameof(AllocationPercentType));
RebalanceActionType = GetOrCreateType(PortfolioType, nameof(RebalanceActionType));

// var amountAddress = Storage.GetOrCreate(AmountType, DecimalToRationalConverter.Convert(RubBalance));
// var rubAmountAddress = Storage.GetOrCreate(RubType, amountAddress);
Expand Down
192 changes: 192 additions & 0 deletions csharp/TraderBot/PORTFOLIO_BALANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Portfolio Balance Algorithm

This document describes the portfolio auto-balance algorithm implementation for the TraderBot that uses Deep (associative data storage).

## Overview

The portfolio balance algorithm automatically rebalances a trading portfolio according to predefined asset allocation percentages. This feature allows traders to maintain their desired risk/return profile by automatically adjusting positions when allocations drift beyond specified thresholds.

## Features

### Core Functionality
- **Automatic Portfolio Analysis**: Continuously monitors current asset allocations vs. target percentages
- **Deep Storage Integration**: Uses associative data storage (Platform.Data.Doublets) to store portfolio state and decisions
- **Configurable Thresholds**: Customizable rebalance triggers and check intervals
- **Multi-Asset Support**: Handles various asset types (ETFs, Shares, Cash, etc.)
- **Risk Management**: Only rebalances when deviations exceed specified thresholds

### Deep Storage Benefits
- **Associative Data Model**: Portfolio relationships stored as links between concepts
- **Rational Number Precision**: Exact decimal calculations without floating-point errors
- **Query Capabilities**: Efficient queries for portfolio analysis and historical tracking
- **Data Persistence**: Portfolio state and rebalance history maintained across restarts

## Configuration

### Basic Setup
Add portfolio balance configuration to your `appsettings.json`:

```json
{
"TradingSettings": {
"PortfolioBalance": {
"Enabled": true,
"RebalanceThresholdPercent": 5.0,
"RebalanceCheckInterval": "00:10:00",
"AssetAllocations": [
{
"AssetType": "Gold",
"Ticker": "TGLD",
"TargetPercent": 25.0,
"Instrument": "Etf"
},
{
"AssetType": "USD",
"Ticker": "FXMM",
"TargetPercent": 25.0,
"Instrument": "Etf"
},
{
"AssetType": "TCS Group stocks",
"Ticker": "TCSG",
"TargetPercent": 50.0,
"Instrument": "Shares"
}
]
}
}
}
```

### Configuration Parameters
- **Enabled**: Whether portfolio balancing is active
- **RebalanceThresholdPercent**: Minimum deviation (%) required to trigger rebalancing
- **RebalanceCheckInterval**: How often to analyze portfolio (format: HH:MM:SS)
- **AssetAllocations**: Array of target allocations for each asset

## Algorithm Details

### Portfolio Analysis Process
1. **Portfolio Snapshot**: Retrieve current positions and values
2. **Allocation Calculation**: Calculate current percentage allocations
3. **Deviation Analysis**: Compare current vs. target allocations
4. **Threshold Check**: Identify assets exceeding rebalance thresholds
5. **Action Generation**: Create buy/sell actions to restore target allocations
6. **Deep Storage**: Store analysis results in associative format

### Rebalance Logic
```
For each asset:
current_percent = (current_value / total_portfolio_value) * 100
deviation = |current_percent - target_percent|

if deviation > threshold_percent:
target_value = total_portfolio_value * (target_percent / 100)
amount_to_rebalance = target_value - current_value

action = amount_to_rebalance > 0 ? BUY : SELL
store_in_deep_storage(asset, deviation, action, amount)
```

### Deep Storage Schema
The algorithm stores portfolio data using these associative relationships:
- **Asset β†’ TargetAllocation**: Links assets to their target percentages
- **Asset β†’ CurrentAllocation**: Links assets to their current percentages
- **Asset β†’ RebalanceAction**: Links assets to required rebalance amounts
- **Portfolio β†’ Type**: Categorizes different portfolio data types

## Example Usage

### Running the Algorithm
The algorithm runs automatically as part of the TradingService when enabled. It:
1. Checks portfolio balance at configured intervals
2. Logs analysis results and required actions
3. Executes rebalancing for instruments managed by current trading instance
4. Stores all decisions and state in Deep storage

### Sample Output
```
[10:00:00] Starting portfolio balance analysis
[10:00:01] Asset TGLD: Current 15.2%, Target 25.0%, Deviation 9.8%
[10:00:01] Asset FXMM: Current 28.5%, Target 25.0%, Deviation 3.5%
[10:00:01] Asset TCSG: Current 56.3%, Target 50.0%, Deviation 6.3%
[10:00:02] Rebalance needed for TGLD: Buy 9800.00 RUB
[10:00:02] Rebalance needed for TCSG: Sell 6300.00 RUB
[10:00:02] Portfolio analysis complete. 2 rebalance actions identified
```

## Testing

### Unit Tests
Run the included tests to verify algorithm functionality:

```bash
cd TraderBot
dotnet run --test
```

### Test Coverage
- **Portfolio Balance Calculations**: Validates percentage calculations and thresholds
- **Rebalance Action Generation**: Tests buy/sell decision logic
- **Deep Storage Integration**: Verifies associative data storage and retrieval

## Integration Notes

### Multi-Instance Coordination
- Each TradingService instance handles its configured instrument
- Portfolio-wide rebalancing requires coordination between instances
- Deep storage provides shared state for cross-instance communication

### Risk Considerations
- Algorithm respects existing trading rules (time windows, minimum amounts)
- Cash balance validation before executing buy orders
- Position validation before executing sell orders
- Gradual rebalancing to minimize market impact

## Architecture

### Key Components
- **PortfolioBalanceAlgorithm**: Core analysis and decision engine
- **PortfolioBalanceSettings**: Configuration model
- **RebalanceAction**: Action representation with metadata
- **FinancialStorage**: Deep storage interface for portfolio data

### Deep Storage Benefits
- **Exact Arithmetic**: Rational numbers prevent rounding errors
- **Associative Queries**: Efficient relationship-based data access
- **State Persistence**: Portfolio history maintained across restarts
- **Concurrent Access**: Thread-safe storage for multi-instance scenarios

## Future Enhancements

### Planned Features
- **Historical Analysis**: Track rebalancing performance over time
- **Advanced Strategies**: Support for momentum-based and volatility-adjusted allocations
- **Risk Metrics**: Value-at-Risk and correlation analysis
- **Automated Reporting**: Portfolio performance dashboards

### Deep Storage Extensions
- **Graph Queries**: Complex portfolio relationship analysis
- **Machine Learning**: Pattern recognition for optimal rebalancing timing
- **Distributed Storage**: Multi-node portfolio state synchronization

## Troubleshooting

### Common Issues
1. **Configuration Errors**: Ensure asset allocations sum to 100%
2. **API Access**: Verify Tinkoff InvestAPI credentials and permissions
3. **Storage Issues**: Check Deep storage initialization and memory limits
4. **Timing Conflicts**: Avoid overlapping rebalance intervals

### Debug Information
Enable detailed logging by setting log level to "Debug" in configuration:

```json
{
"Logging": {
"LogLevel": {
"TraderBot.PortfolioBalanceAlgorithm": "Debug"
}
}
}
```
182 changes: 182 additions & 0 deletions csharp/TraderBot/PortfolioBalanceAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
using Tinkoff.InvestApi;
using Tinkoff.InvestApi.V1;
using Microsoft.Extensions.Logging;

namespace TraderBot;

public class PortfolioBalanceAlgorithm
{
private readonly FinancialStorage _storage;
private readonly InvestApiClient _investApi;
private readonly ILogger<PortfolioBalanceAlgorithm> _logger;
private readonly PortfolioBalanceSettings _settings;
private readonly Account _account;

public PortfolioBalanceAlgorithm(
FinancialStorage storage,
InvestApiClient investApi,
ILogger<PortfolioBalanceAlgorithm> logger,
PortfolioBalanceSettings settings,
Account account)
{
_storage = storage;
_investApi = investApi;
_logger = logger;
_settings = settings;
_account = account;
}

public async Task<List<RebalanceAction>> AnalyzePortfolioBalance()
{
_logger.LogInformation("Starting portfolio balance analysis");

var currentPortfolio = await GetCurrentPortfolio();
var totalPortfolioValue = currentPortfolio.Values.Sum();

if (totalPortfolioValue <= 0)
{
_logger.LogWarning("Portfolio has no value, skipping rebalance");
return new List<RebalanceAction>();
}

var rebalanceActions = new List<RebalanceAction>();

foreach (var allocation in _settings.AssetAllocations)
{
// Store allocation data in Deep storage using FinancialStorage
var assetTickerLink = _storage.StringToUnicodeSequenceConverter.Convert(allocation.Ticker);
var targetPercentRational = _storage.DecimalToRationalConverter.Convert(allocation.TargetPercent);

var currentValue = currentPortfolio.GetValueOrDefault(allocation.Ticker, 0);
var currentPercent = totalPortfolioValue > 0 ? (currentValue / totalPortfolioValue) * 100 : 0;
var targetPercent = allocation.TargetPercent;
var deviation = Math.Abs(currentPercent - targetPercent);

// Store current allocation in Deep storage
var currentPercentRational = _storage.DecimalToRationalConverter.Convert(currentPercent);

_logger.LogInformation($"Asset {allocation.Ticker}: Current {currentPercent:F2}%, Target {targetPercent:F2}%, Deviation {deviation:F2}%");

if (deviation > _settings.RebalanceThresholdPercent)
{
var targetValue = totalPortfolioValue * (targetPercent / 100);
var amountToRebalance = targetValue - currentValue;

var rebalanceAction = new RebalanceAction
{
Ticker = allocation.Ticker,
AssetType = allocation.AssetType,
Instrument = allocation.Instrument,
CurrentValue = currentValue,
TargetValue = targetValue,
AmountToRebalance = amountToRebalance,
CurrentPercent = currentPercent,
TargetPercent = targetPercent,
Action = amountToRebalance > 0 ? RebalanceActionType.Buy : RebalanceActionType.Sell
};

rebalanceActions.Add(rebalanceAction);

// Store rebalance information in Deep storage
var rebalanceAmountRational = _storage.DecimalToRationalConverter.Convert(Math.Abs(amountToRebalance));

_logger.LogInformation($"Rebalance needed for {allocation.Ticker}: {rebalanceAction.Action} {Math.Abs(amountToRebalance):F2} RUB");
}
}

_logger.LogInformation($"Portfolio analysis complete. {rebalanceActions.Count} rebalance actions identified");
return rebalanceActions;
}

private async Task<Dictionary<string, decimal>> GetCurrentPortfolio()
{
var portfolio = new Dictionary<string, decimal>();

try
{
var portfolioResponse = await _investApi.Operations.GetPortfolioAsync(new PortfolioRequest
{
AccountId = _account.Id
});

foreach (var position in portfolioResponse.Positions)
{
try
{
var currentValue = TradingService.MoneyValueToDecimal(position.CurrentPrice) * position.Quantity;
// Use position.Figi as identifier since we don't need to resolve to ticker for this demo
var ticker = await GetTickerByFigi(position.Figi) ?? position.Figi;
portfolio[ticker] = currentValue;

_logger.LogInformation($"Position {ticker}: {position.Quantity} units, value {currentValue:F2} RUB");
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"Error processing position {position.Figi}");
}
}

var cashPositions = await _investApi.Operations.GetPositionsAsync(new PositionsRequest
{
AccountId = _account.Id
});

foreach (var money in cashPositions.Money)
{
if (money.Currency.ToLower() == "rub")
{
var cashValue = TradingService.MoneyValueToDecimal(money);
portfolio["CASH_RUB"] = cashValue;
_logger.LogInformation($"Cash position RUB: {cashValue:F2}");
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting current portfolio");
}

return portfolio;
}

private async Task<string?> GetTickerByFigi(string figi)
{
try
{
var etfs = await _investApi.Instruments.EtfsAsync();
var etf = etfs.Instruments.FirstOrDefault(e => e.Figi == figi);
if (etf != null) return etf.Ticker;

var shares = await _investApi.Instruments.SharesAsync();
var share = shares.Instruments.FirstOrDefault(s => s.Figi == figi);
if (share != null) return share.Ticker;

return null;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error getting ticker by FIGI {figi}");
return null;
}
}
}

public class RebalanceAction
{
public string Ticker { get; set; } = string.Empty;
public string AssetType { get; set; } = string.Empty;
public Instrument Instrument { get; set; }
public decimal CurrentValue { get; set; }
public decimal TargetValue { get; set; }
public decimal AmountToRebalance { get; set; }
public decimal CurrentPercent { get; set; }
public decimal TargetPercent { get; set; }
public RebalanceActionType Action { get; set; }
}

public enum RebalanceActionType
{
Buy,
Sell,
Hold
}
Loading
Loading