Блог им. KonstantinBesedin









using Cryptian.Core.Configuration;
using Cryptian.Core.Constants;
using Cryptian.Core.Enums;
using Cryptian.Core.Events;
using Cryptian.Core.Stratigies;
using Cryptian.Indicators.Candlesticks;
using Cryptian.Indicators.Ema;
using Cryptian.Indicators.Macd;
using Cryptian.Indicators.Volume;
using Microsoft.Extensions.Logging;
namespace Cryptian.Strategies.Examples.UITests
{
/// <summary>
/// UI-тест: Short с 2 TP (50%+50%) и SL на двух символах.
/// Pull-стиль: вся логика в OnKline, GetActiveTrade как единственный источник правды.
/// </summary>
public sealed class MultiTpStrategy : Strategy
{
private static readonly Dictionary<string, decimal> Quantities = new()
{
["BTCUSDT"] = 0.002m,
["1000PEPEUSDT"] = 1000m,
};
protected override StrategyConfig Configure()
{
return new StrategyConfig()
.Chart(c => c
.Pane(p => p
.Indicator(new CandlesticksIndicator())
.Indicator(new EmaIndicator(20)))
.Pane(p => p.Height(20.0).Indicator(new VolumeIndicator()))
.Pane(p => p.Height(20.0).Indicator(new MacdIndicator())))
.Exchange(Exchange.Bybit, e => e
.Symbols(Quantities.Keys.ToArray())
.Klines(k => k.Timeframe(Timeframe.M1, 500).Timeframe(Timeframe.M5, 500))
.Tickers()
.Trading(t => t.ClosedHistory(500)));
}
public override void OnKline(KlineEvent e)
{
if (!e.IsClosed || e.Timeframe != Timeframe.M1)
{
return;
}
if (!Quantities.TryGetValue(e.Symbol, out var qty))
{
return;
}
// Source of truth — активная сделка (Pending или Open).
// Есть сделка → ждём SL/TP.
if (Trading.GetActiveTrade(e.Symbol) is not null)
{
return;
}
var price = (decimal)e.Kline.Close;
var dec = GetPriceDecimals(e.Symbol);
var sl = Math.Round(price * 1.010m, dec);
var tp1 = Math.Round(price * 0.990m, dec);
var tp2 = Math.Round(price * 0.980m, dec);
Logger.LogInformation("[MultiTp] {Symbol} Sell {Qty} @ Market | SL={Sl} TP1={Tp1}(50%) TP2={Tp2}(50%)",
e.Symbol, qty, sl, tp1, tp2);
Trading.Sell(e.Symbol, qty)
.Market()
.StopLoss(sl)
.TakeProfit(tp1, 0.5m)
.TakeProfit(tp2, 0.5m)
.Place();
}
/// <summary>
/// Количество десятичных знаков цены из TickSize символа.
/// </summary>
private int GetPriceDecimals(string symbol)
{
var info = Symbols.Get(symbol);
if (info is null)
{
return 2;
}
var tick = info.TickSize;
var decimals = 0;
while (tick < 1m && decimals < 10)
{
tick *= 10;
decimals++;
}
return decimals;
}
}
}
Да и С# в России сейчас никому не нужон, на Хабре не так давно это обсуждали, так что.
Да и в backtrader все уже есть.