├── .gitignore ├── Readme Images ├── auth.png ├── testing.png ├── trading3.png └── instrument selection.png ├── TradeBot ├── Icons │ ├── Dice.png │ ├── AddIcon.png │ ├── CheckIcon.png │ ├── CloseIcon.png │ └── SearchIcon.png ├── Fonts │ ├── OpenSans-Bold.ttf │ ├── OpenSans-Light.ttf │ ├── OpenSans-Regular.ttf │ ├── OpenSans-SemiBold.ttf │ └── OpenSans-ExtraBold.ttf ├── App.config ├── Properties │ ├── Settings.settings │ ├── Settings.Designer.cs │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── App.xaml.cs ├── Indicators │ ├── MovingAverageCalculation │ │ ├── IMACalculation.cs │ │ ├── ExponentialMACalculation.cs │ │ └── SimpleMACalculation.cs │ ├── Indicator.cs │ ├── MovingAverage.cs │ ├── OscillatorIndicator.cs │ └── Oscillators │ │ ├── RSI.cs │ │ └── MACD.cs ├── TinkoffInterface.cs ├── packages.config ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Candle.cs ├── TradingStrategies │ ├── TradingStrategy.cs │ ├── RandomTradingStrategy.cs │ ├── RsiTradingStrategy.cs │ ├── MacdTradingStrategy.cs │ ├── MaTradingStrategy.cs │ └── MaCrossRsiTradingStrategy.cs ├── IntervalToMaxPeriodConverter.cs ├── Pages │ ├── TabsPage.xaml.cs │ ├── AuthPage.xaml.cs │ ├── AuthPage.xaml │ ├── TradingChart.xaml │ ├── RealTimeTrading.xaml │ ├── TestingTrading.xaml │ ├── InstrumentSelection.xaml.cs │ ├── InstrumentSelection.xaml │ ├── TestingTrading.xaml.cs │ ├── RealTimeTrading.xaml.cs │ ├── TabsPage.xaml │ └── TradingChart.xaml.cs ├── Instrument.cs ├── IndicatorsDialogs │ ├── MovingAverageDialog.xaml.cs │ ├── MovingAverageDialog.xaml │ ├── MacdDialog.xaml.cs │ ├── RsiDialog.xaml.cs │ ├── MacdDialog.xaml │ └── RsiDialog.xaml ├── TradingInterface.cs ├── ShapesPaths.cs ├── BuySellSeries.cs ├── TradeBot.csproj └── App.xaml ├── packages ├── OxyPlot.Wpf.2.0.0 │ ├── .signature.p7s │ ├── OxyPlot_128.png │ ├── OxyPlot.Wpf.2.0.0.nupkg │ └── lib │ │ ├── net45 │ │ └── OxyPlot.Wpf.dll │ │ └── netcoreapp3.0 │ │ └── OxyPlot.Wpf.dll ├── OxyPlot.Core.2.0.0 │ ├── .signature.p7s │ ├── OxyPlot_128.png │ ├── lib │ │ ├── net45 │ │ │ └── OxyPlot.dll │ │ └── netstandard1.0 │ │ │ └── OxyPlot.dll │ └── OxyPlot.Core.2.0.0.nupkg ├── Newtonsoft.Json.12.0.3 │ ├── .signature.p7s │ ├── packageIcon.png │ ├── Newtonsoft.Json.12.0.3.nupkg │ ├── lib │ │ ├── net20 │ │ │ └── Newtonsoft.Json.dll │ │ ├── net35 │ │ │ └── Newtonsoft.Json.dll │ │ ├── net40 │ │ │ └── Newtonsoft.Json.dll │ │ ├── net45 │ │ │ └── Newtonsoft.Json.dll │ │ ├── netstandard1.0 │ │ │ └── Newtonsoft.Json.dll │ │ ├── netstandard1.3 │ │ │ └── Newtonsoft.Json.dll │ │ ├── netstandard2.0 │ │ │ └── Newtonsoft.Json.dll │ │ ├── portable-net45+win8+wp8+wpa81 │ │ │ └── Newtonsoft.Json.dll │ │ └── portable-net40+sl5+win8+wp8+wpa81 │ │ │ └── Newtonsoft.Json.dll │ └── LICENSE.md └── Tinkoff.Trading.OpenApi.1.6.0 │ ├── .signature.p7s │ ├── Tinkoff.Trading.OpenApi.1.6.0.nupkg │ └── lib │ └── netstandard2.0 │ └── Tinkoff.Trading.OpenApi.dll ├── README.md ├── TradeBot.sln.DotSettings.user └── TradeBot.sln /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | TradeBot/bin 3 | TradeBot/obj -------------------------------------------------------------------------------- /Readme Images/auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/Readme Images/auth.png -------------------------------------------------------------------------------- /TradeBot/Icons/Dice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/TradeBot/Icons/Dice.png -------------------------------------------------------------------------------- /Readme Images/testing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/Readme Images/testing.png -------------------------------------------------------------------------------- /Readme Images/trading3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/Readme Images/trading3.png -------------------------------------------------------------------------------- /TradeBot/Icons/AddIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/TradeBot/Icons/AddIcon.png -------------------------------------------------------------------------------- /TradeBot/Icons/CheckIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/TradeBot/Icons/CheckIcon.png -------------------------------------------------------------------------------- /TradeBot/Icons/CloseIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/TradeBot/Icons/CloseIcon.png -------------------------------------------------------------------------------- /TradeBot/Icons/SearchIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/TradeBot/Icons/SearchIcon.png -------------------------------------------------------------------------------- /TradeBot/Fonts/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/TradeBot/Fonts/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /TradeBot/Fonts/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/TradeBot/Fonts/OpenSans-Light.ttf -------------------------------------------------------------------------------- /TradeBot/Fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/TradeBot/Fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /TradeBot/Fonts/OpenSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/TradeBot/Fonts/OpenSans-SemiBold.ttf -------------------------------------------------------------------------------- /Readme Images/instrument selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/Readme Images/instrument selection.png -------------------------------------------------------------------------------- /TradeBot/Fonts/OpenSans-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/TradeBot/Fonts/OpenSans-ExtraBold.ttf -------------------------------------------------------------------------------- /packages/OxyPlot.Wpf.2.0.0/.signature.p7s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/OxyPlot.Wpf.2.0.0/.signature.p7s -------------------------------------------------------------------------------- /packages/OxyPlot.Core.2.0.0/.signature.p7s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/OxyPlot.Core.2.0.0/.signature.p7s -------------------------------------------------------------------------------- /packages/OxyPlot.Core.2.0.0/OxyPlot_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/OxyPlot.Core.2.0.0/OxyPlot_128.png -------------------------------------------------------------------------------- /packages/OxyPlot.Wpf.2.0.0/OxyPlot_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/OxyPlot.Wpf.2.0.0/OxyPlot_128.png -------------------------------------------------------------------------------- /packages/Newtonsoft.Json.12.0.3/.signature.p7s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Newtonsoft.Json.12.0.3/.signature.p7s -------------------------------------------------------------------------------- /packages/Newtonsoft.Json.12.0.3/packageIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Newtonsoft.Json.12.0.3/packageIcon.png -------------------------------------------------------------------------------- /packages/OxyPlot.Core.2.0.0/lib/net45/OxyPlot.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/OxyPlot.Core.2.0.0/lib/net45/OxyPlot.dll -------------------------------------------------------------------------------- /packages/OxyPlot.Wpf.2.0.0/OxyPlot.Wpf.2.0.0.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/OxyPlot.Wpf.2.0.0/OxyPlot.Wpf.2.0.0.nupkg -------------------------------------------------------------------------------- /packages/OxyPlot.Core.2.0.0/OxyPlot.Core.2.0.0.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/OxyPlot.Core.2.0.0/OxyPlot.Core.2.0.0.nupkg -------------------------------------------------------------------------------- /packages/OxyPlot.Wpf.2.0.0/lib/net45/OxyPlot.Wpf.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/OxyPlot.Wpf.2.0.0/lib/net45/OxyPlot.Wpf.dll -------------------------------------------------------------------------------- /packages/Tinkoff.Trading.OpenApi.1.6.0/.signature.p7s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Tinkoff.Trading.OpenApi.1.6.0/.signature.p7s -------------------------------------------------------------------------------- /packages/OxyPlot.Core.2.0.0/lib/netstandard1.0/OxyPlot.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/OxyPlot.Core.2.0.0/lib/netstandard1.0/OxyPlot.dll -------------------------------------------------------------------------------- /packages/Newtonsoft.Json.12.0.3/Newtonsoft.Json.12.0.3.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Newtonsoft.Json.12.0.3/Newtonsoft.Json.12.0.3.nupkg -------------------------------------------------------------------------------- /packages/Newtonsoft.Json.12.0.3/lib/net20/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Newtonsoft.Json.12.0.3/lib/net20/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /packages/Newtonsoft.Json.12.0.3/lib/net35/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Newtonsoft.Json.12.0.3/lib/net35/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /packages/Newtonsoft.Json.12.0.3/lib/net40/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Newtonsoft.Json.12.0.3/lib/net40/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /packages/Newtonsoft.Json.12.0.3/lib/net45/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Newtonsoft.Json.12.0.3/lib/net45/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /packages/OxyPlot.Wpf.2.0.0/lib/netcoreapp3.0/OxyPlot.Wpf.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/OxyPlot.Wpf.2.0.0/lib/netcoreapp3.0/OxyPlot.Wpf.dll -------------------------------------------------------------------------------- /packages/Newtonsoft.Json.12.0.3/lib/netstandard1.0/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Newtonsoft.Json.12.0.3/lib/netstandard1.0/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /packages/Newtonsoft.Json.12.0.3/lib/netstandard1.3/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Newtonsoft.Json.12.0.3/lib/netstandard1.3/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /packages/Newtonsoft.Json.12.0.3/lib/netstandard2.0/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Newtonsoft.Json.12.0.3/lib/netstandard2.0/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TradeBot 2 | ![image](Readme%20Images/auth.png) 3 | ![image](Readme%20Images/instrument%20selection.png) 4 | ![image](Readme%20Images/testing.png) 5 | ![image](Readme%20Images/trading3.png) 6 | -------------------------------------------------------------------------------- /packages/Tinkoff.Trading.OpenApi.1.6.0/Tinkoff.Trading.OpenApi.1.6.0.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Tinkoff.Trading.OpenApi.1.6.0/Tinkoff.Trading.OpenApi.1.6.0.nupkg -------------------------------------------------------------------------------- /TradeBot/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/Newtonsoft.Json.12.0.3/lib/portable-net45+win8+wp8+wpa81/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Newtonsoft.Json.12.0.3/lib/portable-net45+win8+wp8+wpa81/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /packages/Tinkoff.Trading.OpenApi.1.6.0/lib/netstandard2.0/Tinkoff.Trading.OpenApi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Tinkoff.Trading.OpenApi.1.6.0/lib/netstandard2.0/Tinkoff.Trading.OpenApi.dll -------------------------------------------------------------------------------- /packages/Newtonsoft.Json.12.0.3/lib/portable-net40+sl5+win8+wp8+wpa81/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smooth-vlad/TradeBot/HEAD/packages/Newtonsoft.Json.12.0.3/lib/portable-net40+sl5+win8+wp8+wpa81/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /TradeBot/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /TradeBot/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace TradeBot 4 | { 5 | /// 6 | /// Логика взаимодействия для App.xaml 7 | /// 8 | public partial class App : Application 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /TradeBot/Indicators/MovingAverageCalculation/IMACalculation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace TradeBot 5 | { 6 | public interface IMaCalculation 7 | { 8 | string Title { get; } 9 | 10 | List Calculate(Func valueByIndex, int fromIndex, int toIndex, int period); 11 | } 12 | } -------------------------------------------------------------------------------- /TradeBot/TinkoffInterface.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Tinkoff.Trading.OpenApi.Network; 7 | 8 | namespace TradeBot 9 | { 10 | public static class TinkoffInterface 11 | { 12 | public static Context Context { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /TradeBot/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /TradeBot/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /TradeBot/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using Tinkoff.Trading.OpenApi.Network; 3 | 4 | namespace TradeBot 5 | { 6 | /// 7 | /// Логика взаимодействия для MainWindow.xaml 8 | /// 9 | public partial class MainWindow : Window 10 | { 11 | public MainWindow() 12 | { 13 | InitializeComponent(); 14 | 15 | AuthPage.Connect += AuthPage_Connect; 16 | } 17 | 18 | private void AuthPage_Connect(Context context) 19 | { 20 | TinkoffInterface.Context = context; 21 | Content = new MainPage(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /TradeBot/Candle.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot.Series; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Tinkoff.Trading.OpenApi.Models; 8 | 9 | namespace TradeBot 10 | { 11 | public class Candle : HighLowItem 12 | { 13 | public DateTime DateTime { get; } 14 | 15 | public Candle(int x, CandlePayload candle) 16 | { 17 | Close = (double)candle.Close; 18 | Open = (double)candle.Open; 19 | High = (double)candle.High; 20 | Low = (double)candle.Low; 21 | DateTime = candle.Time; 22 | X = x; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TradeBot/TradingStrategies/TradingStrategy.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot.Series; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TradeBot 9 | { 10 | public abstract class TradingStrategy 11 | { 12 | protected TradingStrategy(List candles) 13 | { 14 | this.candles = candles; 15 | } 16 | 17 | public List candles { get; private set; } 18 | 19 | public abstract Signal? GetSignal(int candleIndex); 20 | 21 | public enum Signal 22 | { 23 | Buy, 24 | Sell, 25 | Close, 26 | } 27 | 28 | public abstract void Reset(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /TradeBot.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 | 2 | ExplicitlyExcluded 3 | ExplicitlyExcluded -------------------------------------------------------------------------------- /TradeBot/Indicators/MovingAverageCalculation/ExponentialMACalculation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace TradeBot 5 | { 6 | public class ExponentialMaCalculation : IMaCalculation 7 | { 8 | public string Title => "Exponential Moving Average"; 9 | 10 | public List Calculate(Func valueByIndex, int fromIndex, int toIndex, int period) 11 | { 12 | var multiplier = 2.0 / (period + 1.0); 13 | var ema = new List { SimpleMaCalculation.CalculateAverage(valueByIndex, toIndex, period) }; 14 | 15 | for (var i = 1; i < toIndex - fromIndex; ++i) 16 | ema.Add(valueByIndex(toIndex - i) * multiplier + ema[i - 1] * (1 - multiplier)); 17 | 18 | ema.Reverse(); 19 | return ema; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /TradeBot/Indicators/Indicator.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot; 2 | using OxyPlot.Series; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace TradeBot 7 | { 8 | public abstract class Indicator 9 | { 10 | protected List candles; 11 | public bool AreSeriesAttached { get; protected set; } 12 | 13 | public abstract void UpdateSeries(); 14 | 15 | public abstract void ResetSeries(); 16 | 17 | public abstract void DetachFromChart(); 18 | 19 | public abstract void AttachToChart(ElementCollection chart); 20 | 21 | public delegate void SeriesUpdatedDelegate(); 22 | public SeriesUpdatedDelegate SeriesUpdated; 23 | 24 | protected Indicator(List candles) 25 | { 26 | this.candles = candles ?? throw new ArgumentNullException("candles should be set"); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /TradeBot/TradingStrategies/RandomTradingStrategy.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot.Series; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Windows; 5 | 6 | namespace TradeBot 7 | { 8 | public class RandomTradingStrategy : TradingStrategy 9 | { 10 | static Random random = new Random(); 11 | 12 | public RandomTradingStrategy(List candles) 13 | : base(candles) 14 | { 15 | } 16 | 17 | public override Signal? GetSignal(int candleIndex) 18 | { 19 | var n = random.Next(100); 20 | 21 | return n switch 22 | { 23 | > 90 => Signal.Buy, 24 | > 80 => Signal.Sell, 25 | _ => null, 26 | }; 27 | } 28 | 29 | public override void Reset() 30 | { 31 | //throw new NotImplementedException(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /TradeBot/Indicators/MovingAverageCalculation/SimpleMACalculation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace TradeBot 5 | { 6 | public class SimpleMaCalculation : IMaCalculation 7 | { 8 | public string Title => "Simple Moving Average"; 9 | 10 | public List Calculate(Func valueByIndex, int fromIndex, int toIndex, int period) 11 | { 12 | var result = new List(toIndex - fromIndex); 13 | for (var i = fromIndex; i < toIndex; ++i) 14 | result.Add(CalculateAverage(valueByIndex, i, period)); 15 | return result; 16 | } 17 | 18 | public static double CalculateAverage(Func valueByIndex, int fromIndex, int period) 19 | { 20 | double sum = 0; 21 | for (var j = 0; j < period; ++j) 22 | sum += valueByIndex(fromIndex + j); 23 | return sum / period; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /TradeBot/IntervalToMaxPeriodConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Tinkoff.Trading.OpenApi.Models; 4 | 5 | namespace TradeBot 6 | { 7 | public static class IntervalToMaxPeriodConverter 8 | { 9 | public static TimeSpan GetMaxPeriod(CandleInterval interval) 10 | { 11 | return interval switch 12 | { 13 | CandleInterval.Minute => TimeSpan.FromDays(1), 14 | CandleInterval.FiveMinutes => TimeSpan.FromDays(1), 15 | CandleInterval.QuarterHour => TimeSpan.FromDays(1), 16 | CandleInterval.HalfHour => TimeSpan.FromDays(1), 17 | CandleInterval.Hour => TimeSpan.FromDays(7).Add(TimeSpan.FromHours(-1)), 18 | CandleInterval.Day => TimeSpan.FromDays(364), 19 | CandleInterval.Week => TimeSpan.FromDays(364 * 2), 20 | CandleInterval.Month => TimeSpan.FromDays(364 * 10), 21 | _ => throw new KeyNotFoundException(), 22 | }; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TradeBot/TradingStrategies/RsiTradingStrategy.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot.Series; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace TradeBot 10 | { 11 | class RsiTradingStrategy : TradingStrategy 12 | { 13 | Rsi rsi; 14 | 15 | public RsiTradingStrategy(List candles, Rsi rsi) 16 | : base(candles) 17 | { 18 | this.rsi = rsi; 19 | } 20 | 21 | public override Signal? GetSignal(int candleIndex) 22 | { 23 | if (candleIndex > rsi.Values.Count - 2) 24 | return null; 25 | 26 | if (rsi.Values[candleIndex].Y > rsi.OverboughtLine) 27 | { 28 | return Signal.Sell; 29 | } 30 | if (rsi.Values[candleIndex].Y < rsi.OversoldLine) 31 | { 32 | return Signal.Buy; 33 | } 34 | return null; 35 | } 36 | 37 | public override void Reset() 38 | { 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/Newtonsoft.Json.12.0.3/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2007 James Newton-King 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /TradeBot.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29806.167 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TradeBot", "TradeBot\TradeBot.csproj", "{C84AC606-AB1B-4A5E-9834-F5F4F8767E6A}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {C84AC606-AB1B-4A5E-9834-F5F4F8767E6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {C84AC606-AB1B-4A5E-9834-F5F4F8767E6A}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {C84AC606-AB1B-4A5E-9834-F5F4F8767E6A}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {C84AC606-AB1B-4A5E-9834-F5F4F8767E6A}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {7C6426DC-37CD-4882-9D25-12C95D79065F} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /TradeBot/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace TradeBot.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /TradeBot/TradingStrategies/MacdTradingStrategy.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot.Series; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TradeBot 9 | { 10 | class MacdTradingStrategy : TradingStrategy 11 | { 12 | Macd macd; 13 | 14 | public MacdTradingStrategy(List candles, Macd macd) 15 | : base(candles) 16 | { 17 | this.macd = macd; 18 | } 19 | 20 | public override Signal? GetSignal(int candleIndex) 21 | { 22 | if (candleIndex > macd.MacdValues.Count - 2 23 | || candleIndex > macd.SignalValues.Count - 2 24 | || candleIndex > macd.histogramValues.Count - 2) 25 | return null; 26 | 27 | if (macd.histogramValues[candleIndex + 1].Value * macd.histogramValues[candleIndex].Value < 0) 28 | return macd.histogramValues[candleIndex].Value > 0 29 | ? Signal.Buy 30 | : Signal.Sell; 31 | 32 | return null; 33 | 34 | } 35 | 36 | public override void Reset() 37 | { 38 | 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /TradeBot/TradingStrategies/MaTradingStrategy.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot.Series; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Windows; 5 | 6 | namespace TradeBot 7 | { 8 | public class MaTradingStrategy : TradingStrategy 9 | { 10 | private MovingAverage ma; 11 | 12 | public MaTradingStrategy(List candles, MovingAverage movingAverage) 13 | : base(candles) 14 | { 15 | ma = movingAverage ?? throw new ArgumentNullException("moving average is null"); 16 | } 17 | 18 | public override Signal? GetSignal(int candleIndex) 19 | { 20 | if (candleIndex > candles.Count - ma.Period - 2) 21 | return null; 22 | 23 | if ((candles[candleIndex + 1].Close - ma.Values[candleIndex + 1].Y) * 24 | (candles[candleIndex].Close - ma.Values[candleIndex].Y) < 0) 25 | { 26 | return candles[candleIndex].Close > ma.Values[candleIndex].Y 27 | ? Signal.Buy 28 | : Signal.Sell; 29 | } 30 | 31 | return null; 32 | } 33 | 34 | public override void Reset() 35 | { 36 | //throw new NotImplementedException(); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /TradeBot/Pages/TabsPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace TradeBot 5 | { 6 | /// 7 | /// Логика взаимодействия для MainPage.xaml 8 | /// 9 | public partial class MainPage : Page 10 | { 11 | public MainPage() 12 | { 13 | InitializeComponent(); 14 | 15 | AddInstrumentSelectionTab(); 16 | } 17 | 18 | private void AddInstrumentSelectionTab() 19 | { 20 | var newItem = new TabItem(); 21 | newItem.Content = new InstrumentSelection(newItem); 22 | newItem.Header = "Instrument Selection"; 23 | newItem.IsSelected = true; 24 | 25 | TabControl.Items.Insert(TabControl.Items.Count, newItem); 26 | } 27 | 28 | private void AddTabButton_Selected(object sender, RoutedEventArgs e) 29 | { 30 | AddInstrumentSelectionTab(); 31 | } 32 | 33 | private void CloseTabButton_Selected(object sender, RoutedEventArgs e) 34 | { 35 | var btn = sender as Button; 36 | if (TabControl.Items.Count <= 1) 37 | AddInstrumentSelectionTab(); 38 | 39 | TabControl.Items.Remove(btn.TemplatedParent); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /TradeBot/Pages/AuthPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using Tinkoff.Trading.OpenApi.Network; 5 | 6 | namespace TradeBot 7 | { 8 | /// 9 | /// Логика взаимодействия для AuthPage.xaml 10 | /// 11 | public partial class AuthPage : Page 12 | { 13 | public delegate void ConnectionHandler(Context context); 14 | public event ConnectionHandler Connect; 15 | 16 | public AuthPage() 17 | { 18 | InitializeComponent(); 19 | 20 | TokenTextBox.Focus(); 21 | } 22 | 23 | private async void authButton_Click(object sender, RoutedEventArgs e) 24 | { 25 | TokenErrorTextBlock.Text = string.Empty; 26 | try 27 | { 28 | var connection = ConnectionFactory.GetSandboxConnection(TokenTextBox.Text); 29 | var context = connection.Context; 30 | var allegedStocks = await context.MarketStocksAsync(); 31 | Connect?.Invoke(context); 32 | } 33 | catch (Exception) 34 | { 35 | TokenErrorTextBlock.Text = "* Token is invalid."; 36 | TokenTextBox.Focus(); 37 | } 38 | } 39 | 40 | private void TokenTextBox_OnTextChanged(object sender, TextChangedEventArgs e) 41 | { 42 | var tb = sender as TextBox; 43 | if (AuthButton != null) 44 | AuthButton.IsEnabled = !string.IsNullOrEmpty(tb.Text); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /TradeBot/Instrument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Tinkoff.Trading.OpenApi.Models; 6 | 7 | namespace TradeBot 8 | { 9 | public class Instrument 10 | { 11 | public MarketInstrument ActiveInstrument { get; private set; } 12 | public enum States 13 | { 14 | Bought, // long trade 15 | Sold, // short trade 16 | Empty, 17 | } 18 | 19 | public States State { get; set; } 20 | public double DealPrice { get; set; } 21 | public int DealLots { get; set; } 22 | public double TotalPrice 23 | { 24 | get 25 | { 26 | double dealTotalPrice = DealPrice * DealLots; 27 | return State switch 28 | { 29 | States.Bought => dealTotalPrice, 30 | States.Sold => -dealTotalPrice, 31 | _ => 0, 32 | }; 33 | } 34 | } 35 | 36 | public Instrument(MarketInstrument instrument) 37 | { 38 | ActiveInstrument = instrument; 39 | } 40 | 41 | public void ResetState() 42 | { 43 | State = States.Empty; 44 | DealPrice = 0; 45 | DealLots = 0; 46 | } 47 | 48 | public async Task> GetCandles(DateTime from, DateTime to, CandleInterval interval) 49 | { 50 | var candles = await TinkoffInterface.Context.MarketCandlesAsync(ActiveInstrument.Figi, from, to, interval); 51 | if (candles == null) 52 | return null; 53 | var result = candles.Candles.ToList(); 54 | 55 | result.Reverse(); 56 | return result; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /TradeBot/IndicatorsDialogs/MovingAverageDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace TradeBot 4 | { 5 | /// 6 | /// Логика взаимодействия для MovingAverageDialog.xaml 7 | /// 8 | public partial class MovingAverageDialog : Window 9 | { 10 | public enum CalculationMethod 11 | { 12 | Simple, 13 | Exponential 14 | } 15 | 16 | public MovingAverageDialog() 17 | { 18 | InitializeComponent(); 19 | 20 | TypeComboBox.Items.Add(CalculationMethod.Simple.ToString()); 21 | TypeComboBox.Items.Add(CalculationMethod.Exponential.ToString()); 22 | TypeComboBox.SelectedIndex = 0; 23 | } 24 | 25 | public int Period { get; private set; } 26 | public CalculationMethod Type { get; private set; } 27 | public float Weight { get; private set; } 28 | 29 | private void addButton_Click(object sender, RoutedEventArgs e) 30 | { 31 | PeriodErrorTextBlock.Text = string.Empty; 32 | if (!int.TryParse(PeriodTextBox.Text.Trim(), out var period)) 33 | { 34 | PeriodErrorTextBlock.Text = "* Not a number"; 35 | PeriodTextBox.Focus(); 36 | return; 37 | } 38 | 39 | if (period < 1) 40 | { 41 | PeriodErrorTextBlock.Text = "* Value should be >= 1"; 42 | PeriodTextBox.Focus(); 43 | return; 44 | } 45 | 46 | Period = period; 47 | Type = (CalculationMethod)TypeComboBox.SelectedIndex; 48 | DialogResult = true; 49 | } 50 | 51 | private void cancelButton_Click(object sender, RoutedEventArgs e) 52 | { 53 | DialogResult = false; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /TradeBot/Pages/AuthPage.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | Authorization 26 | 27 | 28 | 30 | 32 | TinkoffAPI Token 33 | 34 | 36 | 37 | 38 | 44 | 45 | 46 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /TradeBot/TradingInterface.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot; 2 | using OxyPlot.Series; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using static TradeBot.Instrument; 7 | 8 | namespace TradeBot 9 | { 10 | public class TradingInterface 11 | { 12 | public double Balance { get; set; } 13 | 14 | public TradingInterface(double initialBalance) 15 | { 16 | Balance = initialBalance; 17 | } 18 | 19 | public void ClosePosition(Instrument instrument, double price) 20 | { 21 | if (price < 0) throw new ArgumentOutOfRangeException("price should be positive"); 22 | if (instrument.State == States.Empty) throw new InvalidOperationException("can't sell because state is not bought"); 23 | 24 | var diff = price * instrument.DealLots; 25 | if (instrument.State == States.Bought) 26 | Balance += diff; 27 | else 28 | Balance -= diff; 29 | 30 | instrument.DealLots = 0; 31 | instrument.State = States.Empty; 32 | } 33 | 34 | public void OpenPosition(Instrument instrument, double price, int lots, bool isShort) 35 | { 36 | if (instrument.State != States.Empty) throw new InvalidOperationException("can't buy because state is bought already (sell first)"); 37 | int maxLots = (int)(Balance / price); 38 | if (lots < 0 || lots > maxLots) throw new ArgumentOutOfRangeException("lots should be > 0 and <= maxLots"); 39 | if (price < 0) throw new ArgumentOutOfRangeException("price should be positive"); 40 | 41 | instrument.DealPrice = price; 42 | instrument.DealLots = lots; 43 | instrument.State = isShort ? States.Sold : States.Bought; 44 | 45 | var diff = instrument.DealPrice * instrument.DealLots; 46 | if (instrument.State == States.Bought) 47 | Balance -= diff; 48 | else 49 | Balance += diff; 50 | } 51 | 52 | public void OpenPosition(Instrument instrument, double price, bool isShort) 53 | { 54 | if (price < 0) throw new ArgumentOutOfRangeException("price should be positive"); 55 | 56 | int maxLots = (int)(Balance / price); 57 | OpenPosition(instrument, price, maxLots, isShort); 58 | } 59 | 60 | public void Reset(double newBalance) 61 | { 62 | Balance = newBalance; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /TradeBot/TradingStrategies/MaCrossRsiTradingStrategy.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot.Series; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace TradeBot 10 | { 11 | class MaCrossRsiTradingStrategy : TradingStrategy 12 | { 13 | Rsi rsi; 14 | MovingAverage shortMa; 15 | MovingAverage longMa; 16 | 17 | Queue lastRsiSignals = new Queue(); 18 | 19 | public MaCrossRsiTradingStrategy(List candles, Rsi rsi, MovingAverage shortMa, MovingAverage longMa) 20 | : base(candles) 21 | { 22 | this.rsi = rsi; 23 | this.shortMa = shortMa; 24 | this.longMa = longMa; 25 | } 26 | 27 | public override Signal? GetSignal(int candleIndex) 28 | { 29 | if (candleIndex > rsi.Values.Count - 2 30 | || candleIndex > candles.Count - shortMa.Period - 2 31 | || candleIndex > candles.Count - longMa.Period - 2) 32 | return null; 33 | 34 | if (rsi.Values[candleIndex].Y > rsi.OverboughtLine) 35 | { 36 | lastRsiSignals.Enqueue(Signal.Sell); 37 | } 38 | else if (rsi.Values[candleIndex].Y < rsi.OversoldLine) 39 | { 40 | lastRsiSignals.Enqueue(Signal.Buy); 41 | } 42 | else 43 | lastRsiSignals.Enqueue(null); 44 | if (lastRsiSignals.Count > 10) 45 | lastRsiSignals.Dequeue(); 46 | 47 | if ((shortMa.Values[candleIndex + 1].Y - longMa.Values[candleIndex + 1].Y) * 48 | (shortMa.Values[candleIndex].Y - longMa.Values[candleIndex].Y) < 0) 49 | { 50 | if (shortMa.Values[candleIndex].Y > longMa.Values[candleIndex].Y) 51 | { 52 | if (lastRsiSignals.Contains(Signal.Buy)) 53 | return Signal.Buy; 54 | else 55 | return Signal.Close; 56 | } 57 | else 58 | { 59 | if (lastRsiSignals.Contains(Signal.Sell)) 60 | return Signal.Sell; 61 | else 62 | return Signal.Close; 63 | } 64 | } 65 | 66 | return null; 67 | } 68 | 69 | public override void Reset() 70 | { 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /TradeBot/Indicators/MovingAverage.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot; 2 | using OxyPlot.Series; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace TradeBot 7 | { 8 | public class MovingAverage : Indicator 9 | { 10 | public IMaCalculation MovingAverageCalculation { get; } 11 | 12 | public int Period { get; } 13 | 14 | private LineSeries series; 15 | public IReadOnlyList Values => series.Points; 16 | 17 | private ElementCollection chart; 18 | 19 | public MovingAverage(int period, IMaCalculation calculationMethod, 20 | List candles) 21 | : base(candles) 22 | { 23 | if (period < 1) 24 | throw new ArgumentOutOfRangeException(); 25 | 26 | this.Period = period; 27 | MovingAverageCalculation = calculationMethod ?? throw new ArgumentNullException(); 28 | 29 | series = new LineSeries 30 | { 31 | Title = MovingAverageCalculation.Title 32 | }; 33 | } 34 | 35 | public override void UpdateSeries() 36 | { 37 | if (candles.Count < Period) 38 | return; 39 | 40 | if (series.Points.Count > Period * 2) 41 | { 42 | series.Points.RemoveRange(series.Points.Count - Period * 2, Period * 2); 43 | } 44 | var movingAverage = MovingAverageCalculation.Calculate( 45 | index => candles[index].Close, 46 | series.Points.Count, 47 | candles.Count - Period, 48 | Period); 49 | series.Points.Capacity += movingAverage.Count; 50 | foreach (var t in movingAverage) 51 | { 52 | series.Points.Add(new DataPoint(series.Points.Count, t)); 53 | } 54 | 55 | SeriesUpdated?.Invoke(); 56 | } 57 | 58 | public override void AttachToChart(ElementCollection chart) 59 | { 60 | if (AreSeriesAttached || chart == null) 61 | return; 62 | 63 | this.chart = chart; 64 | 65 | this.chart.Add(series); 66 | AreSeriesAttached = true; 67 | } 68 | 69 | public override void DetachFromChart() 70 | { 71 | chart?.Remove(series); 72 | 73 | chart = null; 74 | AreSeriesAttached = false; 75 | } 76 | 77 | public override void ResetSeries() 78 | { 79 | series.Points.Clear(); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /TradeBot/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using System.Windows; 4 | 5 | // Общие сведения об этой сборке предоставляются следующим набором 6 | // набор атрибутов. Измените значения этих атрибутов, чтобы изменить сведения, 7 | // связанные со сборкой. 8 | [assembly: AssemblyTitle("TradeBot")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TradeBot")] 13 | [assembly: AssemblyCopyright("Copyright © 2020")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми 18 | // для компонентов COM. Если необходимо обратиться к типу в этой сборке через 19 | // из модели COM, установите атрибут ComVisible для этого типа в значение true. 20 | [assembly: ComVisible(false)] 21 | 22 | //Чтобы начать создание локализуемых приложений, задайте 23 | //CultureYouAreCodingWith в файле .csproj 24 | //в . Например, при использовании английского (США) 25 | //в своих исходных файлах установите в en-US. Затем отмените преобразование в комментарий 26 | //атрибута NeutralResourceLanguage ниже. Обновите "en-US" в 27 | //строка внизу для обеспечения соответствия настройки UICulture в файле проекта. 28 | 29 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 30 | 31 | [assembly: ThemeInfo( 32 | ResourceDictionaryLocation.None, //где расположены словари ресурсов по конкретным тематикам 33 | //(используется, если ресурс не найден на странице, 34 | // или в словарях ресурсов приложения) 35 | ResourceDictionaryLocation.SourceAssembly //где расположен словарь универсальных ресурсов 36 | //(используется, если ресурс не найден на странице, 37 | // в приложении или в каких-либо словарях ресурсов для конкретной темы) 38 | )] 39 | 40 | // Сведения о версии для сборки включают четыре следующих значения: 41 | // 42 | // Основной номер версии 43 | // Дополнительный номер версии 44 | // Номер сборки 45 | // Номер редакции 46 | // 47 | // Можно задать все значения или принять номера сборки и редакции по умолчанию 48 | // используя "*", как показано ниже: 49 | // [assembly: AssemblyVersion("1.0.*")] 50 | [assembly: AssemblyVersion("1.0.0.0")] 51 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /TradeBot/Pages/TradingChart.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /TradeBot/IndicatorsDialogs/MovingAverageDialog.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 27 | Period 28 | 29 | 31 | 32 | 33 | 37 | 12 38 | 39 | 40 | 43 | Type 44 | 45 | 46 | 50 | 51 | 56 | 63 | 64 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /TradeBot/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Этот код создан программой. 4 | // Исполняемая версия:4.0.30319.42000 5 | // 6 | // Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае 7 | // повторной генерации кода. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace TradeBot.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// Класс ресурса со строгой типизацией для поиска локализованных строк и т.д. 17 | /// 18 | // Этот класс создан автоматически классом StronglyTypedResourceBuilder 19 | // с помощью такого средства, как ResGen или Visual Studio. 20 | // Чтобы добавить или удалить член, измените файл .ResX и снова запустите ResGen 21 | // с параметром /str или перестройте свой проект VS. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | public class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Возвращает кэшированный экземпляр ResourceManager, использованный этим классом. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | public static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TradeBot.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Перезаписывает свойство CurrentUICulture текущего потока для всех 51 | /// обращений к ресурсу с помощью этого класса ресурса со строгой типизацией. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | public static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /TradeBot/Indicators/OscillatorIndicator.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot; 2 | using OxyPlot.Axes; 3 | using OxyPlot.Series; 4 | using OxyPlot.Wpf; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using LinearAxis = OxyPlot.Axes.LinearAxis; 8 | 9 | namespace TradeBot 10 | { 11 | public abstract class OscillatorIndicator : Indicator 12 | { 13 | public abstract (double min, double max)? YAxisRange { get; } 14 | 15 | public (PlotView view, LinearAxis x, LinearAxis y) Plot { get; protected set; } 16 | 17 | public OscillatorIndicator(List candles) 18 | : base(candles) 19 | { 20 | CreatePlot(); 21 | } 22 | 23 | public void CreatePlot() 24 | { 25 | var plot = new PlotView 26 | { 27 | Model = new PlotModel 28 | { 29 | TextColor = OxyColor.FromArgb(140, 0, 0, 0), 30 | PlotAreaBorderThickness = new OxyThickness(0, 1, 0, 1), 31 | PlotAreaBorderColor = OxyColor.FromArgb(10, 0, 0, 0), 32 | LegendPosition = LegendPosition.LeftTop, 33 | LegendBackground = OxyColor.FromRgb(245, 245, 245), 34 | } 35 | }; 36 | 37 | var y = new LinearAxis // y axis (left) 38 | { 39 | Position = AxisPosition.Left, 40 | IsPanEnabled = false, 41 | IsZoomEnabled = false, 42 | IntervalLength = 20, 43 | MajorGridlineThickness = 0, 44 | MinorGridlineThickness = 0, 45 | MajorGridlineColor = OxyColor.FromArgb(10, 0, 0, 0), 46 | MajorGridlineStyle = LineStyle.Solid, 47 | TicklineColor = OxyColor.FromArgb(10, 0, 0, 0), 48 | TickStyle = TickStyle.Outside 49 | }; 50 | 51 | var x = new LinearAxis // x axis (bottom) 52 | { 53 | Position = AxisPosition.Bottom, 54 | MajorGridlineStyle = LineStyle.Solid, 55 | MajorGridlineThickness = 2, 56 | MinorGridlineStyle = LineStyle.None, 57 | TicklineColor = OxyColor.FromArgb(10, 0, 0, 0), 58 | TickStyle = TickStyle.None, 59 | LabelFormatter = v => string.Empty, 60 | EndPosition = 0, 61 | StartPosition = 1, 62 | MajorGridlineColor = OxyColor.FromArgb(10, 0, 0, 0) 63 | }; 64 | if (!YAxisRange.HasValue) 65 | { 66 | x.AxisChanged += (object sender, AxisChangedEventArgs e) => 67 | { 68 | TradingChart.AdjustYExtent(x, y, plot.Model); 69 | plot.InvalidatePlot(); 70 | }; 71 | SeriesUpdated += () => 72 | { 73 | TradingChart.AdjustYExtent(x, y, plot.Model); 74 | plot.InvalidatePlot(); 75 | }; 76 | } 77 | else 78 | { 79 | y.Zoom(YAxisRange.Value.min, YAxisRange.Value.max); 80 | x.AxisChanged += (object sender, AxisChangedEventArgs e) => 81 | { 82 | plot.InvalidatePlot(); 83 | }; 84 | SeriesUpdated += () => 85 | { 86 | plot.InvalidatePlot(); 87 | }; 88 | } 89 | 90 | plot.ActualController.UnbindAll(); 91 | 92 | plot.Model.Axes.Add(x); 93 | plot.Model.Axes.Add(y); 94 | 95 | this.Plot = (plot, x, y); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /TradeBot/IndicatorsDialogs/MacdDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace TradeBot 4 | { 5 | /// 6 | /// Логика взаимодействия для MovingAverageDialog.xaml 7 | /// 8 | public partial class MacdDialog : Window 9 | { 10 | public enum CalculationMethod 11 | { 12 | Simple, 13 | Exponential 14 | } 15 | 16 | public MacdDialog() 17 | { 18 | InitializeComponent(); 19 | 20 | TypeComboBox.Items.Add(CalculationMethod.Simple.ToString()); 21 | TypeComboBox.Items.Add(CalculationMethod.Exponential.ToString()); 22 | TypeComboBox.SelectedIndex = 1; 23 | } 24 | 25 | public int ShortPeriod { get; private set; } 26 | public int LongPeriod { get; private set; } 27 | public int HistogramPeriod { get; private set; } 28 | public float Weight { get; private set; } 29 | public CalculationMethod Type { get; private set; } 30 | 31 | private void addButton_Click(object sender, RoutedEventArgs e) 32 | { 33 | ShortPeriodErrorTextBlock.Text = string.Empty; 34 | LongPeriodErrorTextBlock.Text = string.Empty; 35 | HistogramPeriodErrorTextBlock.Text = string.Empty; 36 | 37 | { 38 | if (!int.TryParse(ShortPeriodTextBox.Text.Trim(), out var shortPeriod)) 39 | { 40 | ShortPeriodErrorTextBlock.Text = "* Not a number"; 41 | ShortPeriodTextBox.Focus(); 42 | return; 43 | } 44 | 45 | if (shortPeriod < 1) 46 | { 47 | ShortPeriodErrorTextBlock.Text = "* Value should be >= 1"; 48 | ShortPeriodTextBox.Focus(); 49 | return; 50 | } 51 | 52 | ShortPeriod = shortPeriod; 53 | } 54 | 55 | { 56 | if (!int.TryParse(LongPeriodTextBox.Text.Trim(), out var longPeriod)) 57 | { 58 | LongPeriodErrorTextBlock.Text = "* Not a number"; 59 | LongPeriodTextBox.Focus(); 60 | return; 61 | } 62 | 63 | if (longPeriod < 1) 64 | { 65 | LongPeriodErrorTextBlock.Text = "* Value should be >= 1"; 66 | LongPeriodTextBox.Focus(); 67 | return; 68 | } 69 | 70 | if (longPeriod <= ShortPeriod) 71 | { 72 | LongPeriodErrorTextBlock.Text = "* Value should be > 'Short Period'"; 73 | LongPeriodTextBox.Focus(); 74 | return; 75 | } 76 | 77 | LongPeriod = longPeriod; 78 | } 79 | 80 | { 81 | if (!int.TryParse(HistogramPeriodTextBox.Text.Trim(), out var histogramPeriod)) 82 | { 83 | HistogramPeriodErrorTextBlock.Text = "* Not a number"; 84 | HistogramPeriodTextBox.Focus(); 85 | return; 86 | } 87 | 88 | if (histogramPeriod < 1) 89 | { 90 | HistogramPeriodErrorTextBlock.Text = "* Value should be >= 1"; 91 | HistogramPeriodTextBox.Focus(); 92 | return; 93 | } 94 | HistogramPeriod = histogramPeriod; 95 | } 96 | 97 | Type = (CalculationMethod)TypeComboBox.SelectedIndex; 98 | DialogResult = true; 99 | } 100 | 101 | private void cancelButton_Click(object sender, RoutedEventArgs e) 102 | { 103 | DialogResult = false; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /TradeBot/IndicatorsDialogs/RsiDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | 4 | namespace TradeBot 5 | { 6 | /// 7 | /// Логика взаимодействия для MovingAverageDialog.xaml 8 | /// 9 | public partial class RsiDialog : Window 10 | { 11 | public RsiDialog() 12 | { 13 | InitializeComponent(); 14 | } 15 | 16 | public int Period { get; private set; } 17 | public double OverboughtLine { get; private set; } 18 | public double OversoldLine { get; private set; } 19 | 20 | private void addButton_Click(object sender, RoutedEventArgs e) 21 | { 22 | PeriodErrorTextBlock.Text = string.Empty; 23 | OverboughtLineErrorTextBlock.Text = string.Empty; 24 | OversoldLineErrorTextBlock.Text = string.Empty; 25 | 26 | { 27 | if (!int.TryParse(PeriodTextBox.Text.Trim(), out var period)) 28 | { 29 | PeriodErrorTextBlock.Text = "* Not a number"; 30 | PeriodTextBox.Focus(); 31 | return; 32 | } 33 | 34 | if (period < 1) 35 | { 36 | PeriodErrorTextBlock.Text = "* Value should be >= 1"; 37 | PeriodTextBox.Focus(); 38 | return; 39 | } 40 | 41 | Period = period; 42 | } 43 | 44 | { 45 | if (OverboughtLineSlider.Value <= OversoldLineSlider.Value) 46 | { 47 | OverboughtLineErrorTextBlock.Text = "* should be higher than Oversold Line"; 48 | OversoldLineErrorTextBlock.Text = "* should be lower than Overbought Line"; 49 | OverboughtLineSlider.Focus(); 50 | return; 51 | } 52 | 53 | OverboughtLine = OverboughtLineSlider.Value; 54 | OversoldLine = OversoldLineSlider.Value; 55 | } 56 | 57 | DialogResult = true; 58 | } 59 | 60 | private void cancelButton_Click(object sender, RoutedEventArgs e) 61 | { 62 | DialogResult = false; 63 | } 64 | 65 | private void OverboughtLineSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) 66 | { 67 | OverboughtLineSlider.Value = Math.Round(e.NewValue); 68 | 69 | if (OverboughtLineSlider == null || OversoldLineSlider == null) 70 | return; 71 | if (OverboughtLineSlider.Value <= OversoldLineSlider.Value) 72 | { 73 | OverboughtLineErrorTextBlock.Text = "* should be higher than Oversold Line"; 74 | OversoldLineErrorTextBlock.Text = "* should be lower than Overbought Line"; 75 | } 76 | else 77 | { 78 | OverboughtLineErrorTextBlock.Text = string.Empty; 79 | OversoldLineErrorTextBlock.Text = string.Empty; 80 | } 81 | } 82 | 83 | private void OversoldLineSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) 84 | { 85 | OversoldLineSlider.Value = Math.Round(e.NewValue); 86 | 87 | if (OverboughtLineSlider == null || OversoldLineSlider == null) 88 | return; 89 | if (OverboughtLineSlider.Value <= OversoldLineSlider.Value) 90 | { 91 | OverboughtLineErrorTextBlock.Text = "* should be higher than Oversold Line"; 92 | OversoldLineErrorTextBlock.Text = "* should be lower than Overbought Line"; 93 | } 94 | else 95 | { 96 | OverboughtLineErrorTextBlock.Text = string.Empty; 97 | OversoldLineErrorTextBlock.Text = string.Empty; 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /TradeBot/Pages/RealTimeTrading.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 27 | Balance: 10000 28 | 29 | 30 | 38 | 39 | Moving Average 40 | MACD 41 | Random 42 | RSI 43 | MA Cross + RSI 44 | 45 | 46 | 49 | 50 | 51 | 52 | 55 | 56 | 64 | 65 | 1 MINUTE 66 | 5 MINUTES 67 | 15 MINUTES 68 | 30 MINUTES 69 | 1 HOUR 70 | 1 DAY 71 | 1 WEEK 72 | 1 MONTH 73 | 74 | 75 | 78 | 79 | 80 | 81 | 82 | 83 | 85 | 86 | -------------------------------------------------------------------------------- /TradeBot/Indicators/Oscillators/RSI.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot; 2 | using OxyPlot.Series; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace TradeBot 10 | { 11 | public class Rsi : OscillatorIndicator 12 | { 13 | public int Period { get; private set; } 14 | public double OverboughtLine{ get; private set; } 15 | public double OversoldLine { get; private set; } 16 | 17 | private LineSeries series; 18 | public IReadOnlyList Values => series.Points; 19 | 20 | public override (double min, double max)? YAxisRange => (0, 100); 21 | 22 | private ElementCollection chart; 23 | 24 | public Rsi(List candles, int period, double overboughtLine, double oversoldLine) 25 | : base(candles) 26 | { 27 | if (period < 1 || overboughtLine <= oversoldLine || 28 | oversoldLine >= 100 || oversoldLine <= 0) 29 | throw new ArgumentOutOfRangeException(); 30 | 31 | Period = period; 32 | OverboughtLine = overboughtLine; 33 | OversoldLine = oversoldLine; 34 | 35 | Plot.y.ExtraGridlineStyle = LineStyle.LongDash; 36 | Plot.y.ExtraGridlineColor = OxyColor.FromArgb(30, 0, 0, 0); 37 | Plot.y.ExtraGridlines = new double[] 38 | { 39 | OverboughtLine, 40 | OversoldLine, 41 | }; 42 | 43 | series = new LineSeries 44 | { 45 | Title = "RSI", 46 | }; 47 | } 48 | 49 | public override void AttachToChart(ElementCollection chart) 50 | { 51 | if (AreSeriesAttached || chart == null) 52 | return; 53 | 54 | this.chart = chart; 55 | 56 | chart.Add(series); 57 | 58 | AreSeriesAttached = true; 59 | } 60 | 61 | public override void DetachFromChart() 62 | { 63 | chart?.Remove(series); 64 | 65 | AreSeriesAttached = false; 66 | } 67 | 68 | public override void ResetSeries() 69 | { 70 | series.Points.Clear(); 71 | } 72 | 73 | public override void UpdateSeries() 74 | { 75 | if (candles.Count < Period) 76 | return; 77 | 78 | if (series.Points.Count > Period * 2) 79 | { 80 | series.Points.RemoveRange(series.Points.Count - Period * 2, Period * 2); 81 | } 82 | for (int i = series.Points.Count; i < candles.Count - Period; ++i) 83 | { 84 | double u = 0; 85 | { 86 | int count = 0; 87 | for (int j = 0; j < Period; ++j) 88 | { 89 | if (candles[i + j].Close > candles[i + j + 1].Close) 90 | { 91 | u += candles[i + j].Close - candles[i + j + 1].Close; 92 | count++; 93 | } 94 | } 95 | u /= count; 96 | } 97 | double d = 0; 98 | { 99 | int count = 0; 100 | for (int j = 0; j < Period; ++j) 101 | { 102 | if (candles[i + j].Close < candles[i + j + 1].Close) 103 | { 104 | d += candles[i + j + 1].Close - candles[i + j].Close; 105 | count++; 106 | } 107 | } 108 | d /= count; 109 | } 110 | 111 | double rs; 112 | if (u == 0 || d == 0) 113 | rs = 100; 114 | else 115 | rs = 100 - (100 / (1 + u / d)); 116 | series.Points.Add(new DataPoint(i, rs)); 117 | } 118 | 119 | SeriesUpdated?.Invoke(); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /TradeBot/Pages/TestingTrading.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 29 | 30 | 37 | Balance: 10000 38 | 39 | 40 | 48 | 49 | Moving Average 50 | MACD 51 | Random 52 | RSI 53 | MA Cross + RSI 54 | 55 | 56 | 59 | 60 | 61 | 62 | 65 | 66 | 74 | 75 | 1 MINUTE 76 | 5 MINUTES 77 | 15 MINUTES 78 | 30 MINUTES 79 | 1 HOUR 80 | 1 DAY 81 | 1 WEEK 82 | 1 MONTH 83 | 84 | 85 | 86 | 89 | 90 | -------------------------------------------------------------------------------- /TradeBot/IndicatorsDialogs/MacdDialog.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 31 | Short Period 32 | 33 | 35 | 36 | 37 | 41 | 12 42 | 43 | 44 | 46 | 48 | Long Period 49 | 50 | 52 | 53 | 54 | 58 | 26 59 | 60 | 61 | 63 | 65 | Histogram Period 66 | 67 | 69 | 70 | 71 | 75 | 9 76 | 77 | 78 | 81 | Moving Average Calculation Method 82 | 83 | 84 | 88 | 89 | 94 | 101 | 102 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /TradeBot/Pages/InstrumentSelection.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Data; 6 | using Tinkoff.Trading.OpenApi.Models; 7 | 8 | namespace TradeBot 9 | { 10 | /// 11 | /// Логика взаимодействия для StockSelection.xaml 12 | /// 13 | public partial class InstrumentSelection : UserControl 14 | { 15 | private readonly TabItem parent; 16 | private List instrumentsLabels; 17 | private MarketInstrumentList instruments; 18 | 19 | public InstrumentSelection(TabItem parent) 20 | { 21 | InitializeComponent(); 22 | 23 | this.parent = parent; 24 | 25 | Dispatcher.Invoke(() => StockRadioButton.IsChecked = true); 26 | } 27 | 28 | private void Button_Click(object sender, RoutedEventArgs e) 29 | { 30 | try 31 | { 32 | var activeInstrument = 33 | instruments.Instruments[instrumentsLabels.FindIndex(v => v == TickerComboBox.Text)]; 34 | 35 | parent.Header = activeInstrument.Name; 36 | 37 | if (RealTimeRadioButton.IsChecked == true) 38 | { 39 | parent.Header += " (Real-Time)"; 40 | parent.Content = new RealTimeTrading(activeInstrument); 41 | } 42 | else if (TestingRadioButton.IsChecked == true) 43 | { 44 | parent.Header += " (Testing)"; 45 | parent.Content = new TestingTrading(activeInstrument); 46 | } 47 | } 48 | catch (Exception ex) 49 | { 50 | MessageBox.Show(ex.Message); 51 | } 52 | } 53 | 54 | private void TickerComboBox_OnTextChanged(object sender, TextChangedEventArgs e) 55 | { 56 | if (TickerComboBox.ItemsSource == null) 57 | return; 58 | 59 | var tb = (TextBox)e.OriginalSource; 60 | if (tb.SelectionStart != 0) 61 | TickerComboBox.SelectedItem = null; 62 | 63 | SelectButton.IsEnabled = TickerComboBox.SelectedItem != null; 64 | 65 | if (TickerComboBox.SelectedItem != null) return; 66 | var cv = (CollectionView)CollectionViewSource.GetDefaultView(TickerComboBox.ItemsSource); 67 | cv.Filter = s => 68 | ((string)s).IndexOf(TickerComboBox.Text, StringComparison.OrdinalIgnoreCase) >= 0; 69 | 70 | TickerComboBox.IsDropDownOpen = cv.Count > 0; 71 | tb.SelectionLength = 0; 72 | tb.SelectionStart = tb.Text.Length; 73 | } 74 | 75 | private async void EtfRadioButton_OnChecked(object sender, RoutedEventArgs e) 76 | { 77 | instruments = await TinkoffInterface.Context.MarketEtfsAsync(); 78 | if (instruments == null) 79 | { 80 | MessageBox.Show("Failed to get list of ETFs"); 81 | return; 82 | } 83 | instrumentsLabels = instruments.Instruments.ConvertAll(v => $"{v.Ticker} - {v.Name}"); 84 | TickerComboBox.ItemsSource = instrumentsLabels; 85 | 86 | } 87 | 88 | private async void StockRadioButton_OnChecked(object sender, RoutedEventArgs e) 89 | { 90 | instruments = await TinkoffInterface.Context.MarketStocksAsync(); 91 | if (instruments == null) 92 | { 93 | MessageBox.Show("Failed to get list of stocks"); 94 | return; 95 | } 96 | instrumentsLabels = instruments.Instruments.ConvertAll(v => $"{v.Ticker} - {v.Name}"); 97 | TickerComboBox.ItemsSource = instrumentsLabels; 98 | } 99 | 100 | private async void CurrencyRadioButton_OnChecked(object sender, RoutedEventArgs e) 101 | { 102 | instruments = await TinkoffInterface.Context.MarketCurrenciesAsync(); 103 | if (instruments == null) 104 | { 105 | MessageBox.Show("Failed to get list of currencies"); 106 | return; 107 | } 108 | instrumentsLabels = instruments.Instruments.ConvertAll(v => $"{v.Ticker} - {v.Name}"); 109 | TickerComboBox.ItemsSource = instrumentsLabels; 110 | } 111 | 112 | private void TickerComboBox_GotFocus(object sender, RoutedEventArgs e) 113 | { 114 | if (TickerComboBox.ItemsSource == null) 115 | return; 116 | var cv = (CollectionView)CollectionViewSource.GetDefaultView(TickerComboBox.ItemsSource); 117 | TickerComboBox.IsDropDownOpen = cv.Count > 0; 118 | } 119 | 120 | private void RandomizeImage_Click(object sender, RoutedEventArgs e) 121 | { 122 | if (TickerComboBox.ItemsSource == null) 123 | return; 124 | var rnd = new Random(); 125 | TickerComboBox.SelectedIndex = rnd.Next(TickerComboBox.Items.Count); 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /TradeBot/Pages/InstrumentSelection.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | Trading Mode 30 | 31 | 32 | 36 | 41 | You can use your trading strategies in real-time 42 | 43 | 44 | 48 | You can test your trading strategies over a certain period of time 49 | 50 | 51 | 52 | 56 | Instrument 57 | 58 | 59 | 60 | 63 | ETF 64 | 65 | 68 | Stock 69 | 70 | 73 | Currency 74 | 75 | 76 | 77 | 87 | 88 | 89 | 100 | 101 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /TradeBot/IndicatorsDialogs/RsiDialog.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 31 | Period 32 | 33 | 35 | 36 | 37 | 41 | 12 42 | 43 | 44 | 46 | 48 | Overbought Line 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 65 | 71 | 72 | 73 | 74 | 75 | 77 | 79 | Oversold Line 80 | 81 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 97 | 103 | 104 | 105 | 106 | 107 | 112 | 119 | 120 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /TradeBot/Indicators/Oscillators/MACD.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot; 2 | using OxyPlot.Series; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace TradeBot 7 | { 8 | public class Macd : OscillatorIndicator 9 | { 10 | public int DifferencePeriod { get; } 11 | public int LongPeriod { get; } 12 | public int ShortPeriod { get; } 13 | public IMaCalculation MovingAverageCalculation { get; } 14 | 15 | public IReadOnlyList MacdValues => macdSeries.Points; 16 | public IReadOnlyList SignalValues => signalSeries.Points; 17 | public IReadOnlyList histogramValues => histogramSeries.Items; 18 | 19 | public override (double min, double max)? YAxisRange => null; 20 | 21 | private MovingAverage longMovingAverage; 22 | private MovingAverage shortMovingAverage; 23 | private LineSeries macdSeries; 24 | private LineSeries signalSeries; 25 | private HistogramSeries histogramSeries; 26 | 27 | private ElementCollection chart; 28 | 29 | public Macd(IMaCalculation calculationMethod, 30 | int shortPeriod, int longPeriod, int differencePeriod, 31 | List candles) 32 | : base(candles) 33 | { 34 | if (shortPeriod < 1 || longPeriod < 1 || differencePeriod < 1 || 35 | shortPeriod >= longPeriod) 36 | throw new ArgumentOutOfRangeException(); 37 | 38 | MovingAverageCalculation = calculationMethod ?? throw new ArgumentNullException(); 39 | this.ShortPeriod = shortPeriod; 40 | this.LongPeriod = longPeriod; 41 | this.DifferencePeriod = differencePeriod; 42 | 43 | shortMovingAverage = new MovingAverage(shortPeriod, MovingAverageCalculation, candles); 44 | longMovingAverage = new MovingAverage(longPeriod, MovingAverageCalculation, candles); 45 | 46 | histogramSeries = new HistogramSeries 47 | { 48 | Title = "MACD Histogram", 49 | ColorMapping = (hli) => 50 | { 51 | if (hli.Value < 0) 52 | return OxyColor.FromRgb(214, 107, 107); 53 | return OxyColor.FromRgb(121, 229, 112); 54 | }, 55 | }; 56 | macdSeries = new LineSeries 57 | { 58 | Title = "MACD" 59 | }; 60 | signalSeries = new LineSeries 61 | { 62 | Title = "MACD Signal Line" 63 | }; 64 | } 65 | 66 | public override void UpdateSeries() 67 | { 68 | if (candles.Count < Math.Max(LongPeriod, DifferencePeriod)) 69 | return; 70 | 71 | shortMovingAverage.UpdateSeries(); 72 | 73 | longMovingAverage.UpdateSeries(); 74 | 75 | macdSeries.Points.Clear(); 76 | for (var i = 0; i < candles.Count - LongPeriod; ++i) 77 | macdSeries.Points.Add(new DataPoint(i, shortMovingAverage.Values[i].Y - longMovingAverage.Values[i].Y)); 78 | 79 | // signalSeries 80 | { 81 | if (signalSeries.Points.Count > DifferencePeriod * 2) 82 | signalSeries.Points.RemoveRange(signalSeries.Points.Count - DifferencePeriod * 2, DifferencePeriod * 2); 83 | 84 | var movingAverage = MovingAverageCalculation.Calculate(index => macdSeries.Points[index].Y, signalSeries.Points.Count, macdSeries.Points.Count - DifferencePeriod, DifferencePeriod); 85 | signalSeries.Points.Capacity += movingAverage.Count; 86 | 87 | foreach (var t in movingAverage) 88 | { 89 | var x = signalSeries.Points.Count; 90 | signalSeries.Points.Add(new DataPoint(x, t)); 91 | } 92 | } 93 | 94 | // macdHistogramSeries 95 | { 96 | histogramSeries.Items.Clear(); 97 | for (var i = 0; i < signalSeries.Points.Count && i < macdSeries.Points.Count; ++i) 98 | { 99 | var val = macdSeries.Points[i].Y - signalSeries.Points[i].Y; 100 | histogramSeries.Items.Add(new HistogramItem(i - 0.2, i + 0.2, val, 1)); 101 | } 102 | } 103 | 104 | SeriesUpdated?.Invoke(); 105 | } 106 | 107 | public override void AttachToChart(ElementCollection chart) 108 | { 109 | if (AreSeriesAttached || chart == null) 110 | return; 111 | 112 | this.chart = chart; 113 | 114 | this.chart.Add(histogramSeries); 115 | this.chart.Add(macdSeries); 116 | this.chart.Add(signalSeries); 117 | 118 | AreSeriesAttached = true; 119 | } 120 | 121 | public override void DetachFromChart() 122 | { 123 | if (chart == null) 124 | return; 125 | 126 | chart.Remove(histogramSeries); 127 | chart.Remove(macdSeries); 128 | chart.Remove(signalSeries); 129 | 130 | chart = null; 131 | AreSeriesAttached = false; 132 | } 133 | 134 | public override void ResetSeries() 135 | { 136 | shortMovingAverage.ResetSeries(); 137 | longMovingAverage.ResetSeries(); 138 | macdSeries.Points.Clear(); 139 | signalSeries.Points.Clear(); 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /TradeBot/Pages/TestingTrading.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | using Tinkoff.Trading.OpenApi.Models; 5 | 6 | namespace TradeBot 7 | { 8 | /// 9 | /// Логика взаимодействия для TestingTrading.xaml 10 | /// 11 | public partial class TestingTrading : UserControl 12 | { 13 | public TestingTrading(MarketInstrument activeInstrument) 14 | { 15 | InitializeComponent(); 16 | 17 | if (activeInstrument == null) 18 | throw new ArgumentNullException(); 19 | 20 | TradingChart.instrument = new Instrument(activeInstrument); 21 | 22 | DataContext = this; 23 | 24 | IntervalComboBox.SelectedIndex = 5; 25 | StrategyComboBox.SelectedIndex = 0; 26 | } 27 | 28 | private void SetEverythingEnabled(bool value) 29 | { 30 | SimulateButton.IsEnabled = value; 31 | } 32 | 33 | private void ShowBalance() 34 | { 35 | var balance = TradingChart.TradingInterface.Balance + TradingChart.instrument.TotalPrice; 36 | BalanceTextBlock.Text = $"Balance: {Math.Round(balance)}"; 37 | BalanceTextBlock.Visibility = Visibility.Visible; 38 | } 39 | 40 | private void ResetState() 41 | { 42 | TradingChart.RemoveIndicators(); 43 | TradingChart.RemoveMarkers(); 44 | BalanceTextBlock.Visibility = Visibility.Collapsed; 45 | } 46 | 47 | // ================================================== 48 | // events 49 | // ================================================== 50 | 51 | private void MA_OnSelected(object sender, RoutedEventArgs e) 52 | { 53 | ResetState(); 54 | 55 | var ma = new MovingAverage(50, new ExponentialMaCalculation(), TradingChart.Candles); 56 | TradingChart.AddIndicator(ma); 57 | var strategy = new MaTradingStrategy(TradingChart.Candles, ma); 58 | TradingChart.SetStrategy(strategy); 59 | } 60 | 61 | private void MACD_OnSelected(object sender, RoutedEventArgs e) 62 | { 63 | ResetState(); 64 | 65 | var macd = new Macd(new ExponentialMaCalculation(), 12, 26, 9, TradingChart.Candles); 66 | TradingChart.AddIndicator(macd); 67 | var strategy = new MacdTradingStrategy(TradingChart.Candles, macd); 68 | TradingChart.SetStrategy(strategy); 69 | } 70 | 71 | private void Random_OnSelected(object sender, RoutedEventArgs e) 72 | { 73 | TradingChart.RemoveIndicators(); 74 | 75 | var strategy = new RandomTradingStrategy(TradingChart.Candles); 76 | TradingChart.SetStrategy(strategy); 77 | } 78 | 79 | private void RSI_OnSelected(object sender, RoutedEventArgs e) 80 | { 81 | ResetState(); 82 | 83 | var rsi = new Rsi(TradingChart.Candles, 12, 70, 30); 84 | TradingChart.AddIndicator(rsi); 85 | var strategy = new RsiTradingStrategy(TradingChart.Candles, rsi); 86 | TradingChart.SetStrategy(strategy); 87 | } 88 | 89 | private void MACrossRSI_OnSelected(object sender, RoutedEventArgs e) 90 | { 91 | ResetState(); 92 | 93 | var rsi = new Rsi(TradingChart.Candles, 14, 66, 33); 94 | TradingChart.AddIndicator(rsi); 95 | var ma = new MovingAverage(13, new ExponentialMaCalculation(), TradingChart.Candles); 96 | TradingChart.AddIndicator(ma); 97 | var ma2 = new MovingAverage(4, new ExponentialMaCalculation(), TradingChart.Candles); 98 | TradingChart.AddIndicator(ma2); 99 | var strategy = new MaCrossRsiTradingStrategy(TradingChart.Candles, rsi, ma2, ma); 100 | TradingChart.SetStrategy(strategy); 101 | } 102 | 103 | private async void simulateButton_Click(object sender, RoutedEventArgs e) 104 | { 105 | SetEverythingEnabled(false); 106 | await TradingChart.BeginTesting(); 107 | SetEverythingEnabled(true); 108 | ShowBalance(); 109 | } 110 | 111 | private void ListBoxItem1m_OnSelected(object sender, RoutedEventArgs e) 112 | { 113 | TradingChart.candleInterval = CandleInterval.Minute; 114 | TradingChart.RestartSeries(); 115 | } 116 | 117 | private void ListBoxItem5m_OnSelected(object sender, RoutedEventArgs e) 118 | { 119 | TradingChart.candleInterval = CandleInterval.FiveMinutes; 120 | TradingChart.RestartSeries(); 121 | } 122 | 123 | private void ListBoxItem15m_OnSelected(object sender, RoutedEventArgs e) 124 | { 125 | TradingChart.candleInterval = CandleInterval.QuarterHour; 126 | TradingChart.RestartSeries(); 127 | } 128 | 129 | private void ListBoxItem30m_OnSelected(object sender, RoutedEventArgs e) 130 | { 131 | TradingChart.candleInterval = CandleInterval.HalfHour; 132 | TradingChart.RestartSeries(); 133 | } 134 | 135 | private void ListBoxItem1h_OnSelected(object sender, RoutedEventArgs e) 136 | { 137 | TradingChart.candleInterval = CandleInterval.Hour; 138 | TradingChart.RestartSeries(); 139 | } 140 | 141 | private void ListBoxItem1d_OnSelected(object sender, RoutedEventArgs e) 142 | { 143 | TradingChart.candleInterval = CandleInterval.Day; 144 | TradingChart.RestartSeries(); 145 | } 146 | 147 | private void ListBoxItem1w_OnSelected(object sender, RoutedEventArgs e) 148 | { 149 | TradingChart.candleInterval = CandleInterval.Week; 150 | TradingChart.RestartSeries(); 151 | } 152 | 153 | private void ListBoxItem1mn_OnSelected(object sender, RoutedEventArgs e) 154 | { 155 | TradingChart.candleInterval = CandleInterval.Month; 156 | TradingChart.RestartSeries(); 157 | } 158 | } 159 | } -------------------------------------------------------------------------------- /TradeBot/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /TradeBot/Pages/RealTimeTrading.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using Tinkoff.Trading.OpenApi.Models; 6 | using static TradeBot.Instrument; 7 | 8 | namespace TradeBot 9 | { 10 | /// 11 | /// Логика взаимодействия для RealTimeTrading.xaml 12 | /// 13 | public partial class RealTimeTrading : UserControl 14 | { 15 | private Timer candlesTimer; 16 | 17 | public RealTimeTrading(MarketInstrument activeInstrument) 18 | { 19 | InitializeComponent(); 20 | 21 | if (activeInstrument == null) 22 | throw new ArgumentNullException(); 23 | 24 | TradingChart.instrument = new Instrument(activeInstrument); 25 | 26 | candlesTimer = new Timer(e => CandlesTimerElapsed(), 27 | null, 28 | TimeSpan.FromSeconds(5), 29 | TimeSpan.FromSeconds(0.3) 30 | ); 31 | 32 | DataContext = this; 33 | 34 | ShowBalance(); 35 | 36 | IntervalComboBox.SelectedIndex = 5; 37 | StrategyComboBox.SelectedIndex = 0; 38 | } 39 | 40 | private void ShowBalance() 41 | { 42 | var balance = TradingChart.TradingInterface.Balance + TradingChart.instrument.TotalPrice; 43 | BalanceTextBlock.Text = $"Balance: {Math.Round(balance)}"; 44 | } 45 | 46 | // ================================================== 47 | // events 48 | // ================================================== 49 | 50 | private void MA_OnSelected(object sender, RoutedEventArgs e) 51 | { 52 | TradingChart.RemoveIndicators(); 53 | 54 | var ma = new MovingAverage(50, new ExponentialMaCalculation(), TradingChart.Candles); 55 | TradingChart.AddIndicator(ma); 56 | var strategy = new MaTradingStrategy(TradingChart.Candles, ma); 57 | TradingChart.SetStrategy(strategy); 58 | } 59 | 60 | private void MACD_OnSelected(object sender, RoutedEventArgs e) 61 | { 62 | TradingChart.RemoveIndicators(); 63 | 64 | var macd = new Macd(new ExponentialMaCalculation(), 12, 26, 9, TradingChart.Candles); 65 | TradingChart.AddIndicator(macd); 66 | var strategy = new MacdTradingStrategy(TradingChart.Candles, macd); 67 | TradingChart.SetStrategy(strategy); 68 | } 69 | 70 | private void Random_OnSelected(object sender, RoutedEventArgs e) 71 | { 72 | TradingChart.RemoveIndicators(); 73 | 74 | var strategy = new RandomTradingStrategy(TradingChart.Candles); 75 | TradingChart.SetStrategy(strategy); 76 | } 77 | private void RSI_OnSelected(object sender, RoutedEventArgs e) 78 | { 79 | TradingChart.RemoveIndicators(); 80 | 81 | var rsi = new Rsi(TradingChart.Candles, 14, 70, 30); 82 | TradingChart.AddIndicator(rsi); 83 | var strategy = new RsiTradingStrategy(TradingChart.Candles, rsi); 84 | TradingChart.SetStrategy(strategy); 85 | } 86 | 87 | private void MACrossRSI_OnSelected(object sender, RoutedEventArgs e) 88 | { 89 | TradingChart.RemoveIndicators(); 90 | 91 | var rsi = new Rsi(TradingChart.Candles, 14, 66, 33); 92 | TradingChart.AddIndicator(rsi); 93 | var ma = new MovingAverage(13, new ExponentialMaCalculation(), TradingChart.Candles); 94 | TradingChart.AddIndicator(ma); 95 | var ma2 = new MovingAverage(4, new ExponentialMaCalculation(), TradingChart.Candles); 96 | TradingChart.AddIndicator(ma2); 97 | var strategy = new MaCrossRsiTradingStrategy(TradingChart.Candles, rsi, ma2, ma); 98 | TradingChart.SetStrategy(strategy); 99 | } 100 | 101 | private async void CandlesTimerElapsed() 102 | { 103 | if (TradingChart.LoadingCandlesTask == null) 104 | return; 105 | await TradingChart.LoadingCandlesTask; 106 | 107 | // TO TEST 'REAL TIME TRADING' 108 | TradingChart.rightCandleDateAhead = TradingChart.rightCandleDateAhead.AddDays(1); 109 | 110 | await Dispatcher.InvokeAsync(async () => 111 | { 112 | await TradingChart.LoadNewCandles(); 113 | ShowBalance(); 114 | }); 115 | } 116 | 117 | private void ListBoxItem1m_OnSelected(object sender, RoutedEventArgs e) 118 | { 119 | TradingChart.candleInterval = CandleInterval.Minute; 120 | TradingChart.RestartSeries(); 121 | } 122 | 123 | private void ListBoxItem5m_OnSelected(object sender, RoutedEventArgs e) 124 | { 125 | TradingChart.candleInterval = CandleInterval.FiveMinutes; 126 | TradingChart.RestartSeries(); 127 | } 128 | 129 | private void ListBoxItem15m_OnSelected(object sender, RoutedEventArgs e) 130 | { 131 | TradingChart.candleInterval = CandleInterval.QuarterHour; 132 | TradingChart.RestartSeries(); 133 | } 134 | 135 | private void ListBoxItem30m_OnSelected(object sender, RoutedEventArgs e) 136 | { 137 | TradingChart.candleInterval = CandleInterval.HalfHour; 138 | TradingChart.RestartSeries(); 139 | } 140 | 141 | private void ListBoxItem1h_OnSelected(object sender, RoutedEventArgs e) 142 | { 143 | TradingChart.candleInterval = CandleInterval.Hour; 144 | TradingChart.RestartSeries(); 145 | } 146 | 147 | private void ListBoxItem1d_OnSelected(object sender, RoutedEventArgs e) 148 | { 149 | TradingChart.candleInterval = CandleInterval.Day; 150 | TradingChart.RestartSeries(); 151 | } 152 | 153 | private void ListBoxItem1w_OnSelected(object sender, RoutedEventArgs e) 154 | { 155 | TradingChart.candleInterval = CandleInterval.Week; 156 | TradingChart.RestartSeries(); 157 | } 158 | 159 | private void ListBoxItem1mn_OnSelected(object sender, RoutedEventArgs e) 160 | { 161 | TradingChart.candleInterval = CandleInterval.Month; 162 | TradingChart.RestartSeries(); 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /TradeBot/ShapesPaths.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TradeBot 9 | { 10 | public static class ShapesPaths 11 | { 12 | public static readonly ScreenPoint[] arrowUp = new ScreenPoint[] 13 | { 14 | new ScreenPoint( 0.00029779876708979636 , -0.24970157623291012 ), 15 | new ScreenPoint( 0.04427529823780063 , -0.20572470040321356 ), 16 | new ScreenPoint( 0.08817485317476104 , -0.16182470247641206 ), 17 | new ScreenPoint( 0.13269182288646708 , -0.11730728354454045 ), 18 | new ScreenPoint( 0.1767044906068127 , -0.0732941716929339 ), 19 | new ScreenPoint( 0.22098802762925618 , -0.02901018780589104 ), 20 | new ScreenPoint( 0.19512860770225515 , 0.014773759751319893 ), 21 | new ScreenPoint( 0.1506962484959512 , -0.010914035148397061 ), 22 | new ScreenPoint( 0.10669233240783216 , -0.05491830723941321 ), 23 | new ScreenPoint( 0.06251614664137362 , -0.0990948504024744 ), 24 | new ScreenPoint( 0.031249375000000024 , -0.11160618734739725 ), 25 | new ScreenPoint( 0.031249375000000024 , -0.049040613944157985 ), 26 | new ScreenPoint( 0.031249375000000024 , 0.013599984368681928 ), 27 | new ScreenPoint( 0.031249375000000024 , 0.07544406250000002 ), 28 | new ScreenPoint( 0.031249375000000024 , 0.13844331692704004 ), 29 | new ScreenPoint( 0.031249375000000024 , 0.20090995705756365 ), 30 | new ScreenPoint( 0.031249375000000024 , 0.26356355224609374 ), 31 | new ScreenPoint( -0.012836604461669898 , 0.28125 ), 32 | new ScreenPoint( -0.03125062499999992 , 0.23718888163566598 ), 33 | new ScreenPoint( -0.031250624999999976 , 0.17415053282165893 ), 34 | new ScreenPoint( -0.031250624999999976 , 0.11141119590029114 ), 35 | new ScreenPoint( -0.031250624999999976 , 0.048919391352683306 ), 36 | new ScreenPoint( -0.031250624999999976 , -0.013587971776723884 ), 37 | new ScreenPoint( -0.031250624999999976 , -0.0757956788293086 ), 38 | new ScreenPoint( -0.036395150690078704 , -0.1253242035150528 ), 39 | new ScreenPoint( -0.08102486584410068 , -0.08069430796168747 ), 40 | new ScreenPoint( -0.12564851677492256 , -0.03607047665603458 ), 41 | new ScreenPoint( -0.16932469273567202 , 0.007605875849723831 ), 42 | new ScreenPoint( -0.2136098959416151 , -0.003584503668546679 ), 43 | new ScreenPoint( -0.20232136824768038 , -0.04778550675231963 ), 44 | new ScreenPoint( -0.15854969411700964 , -0.09155718088299031 ), 45 | new ScreenPoint( -0.1137122979274392 , -0.1363945770725608 ), 46 | new ScreenPoint( -0.06968928178850564 , -0.18041759321149442 ), 47 | new ScreenPoint( -0.02554794310539965 , -0.2245589318946004 ), 48 | }; 49 | 50 | public static readonly ScreenPoint[] plus = new ScreenPoint[] 51 | { 52 | new ScreenPoint( -0.1875 , 0.0625 ), 53 | new ScreenPoint( -0.1875 , -0.0625 ), 54 | new ScreenPoint( -0.0625 , -0.0625 ), 55 | new ScreenPoint( -0.0625 , -0.1875 ), 56 | new ScreenPoint( 0.0625 , -0.1875 ), 57 | new ScreenPoint( 0.0625 , -0.0625 ), 58 | new ScreenPoint( 0.1875 , -0.0625 ), 59 | new ScreenPoint( 0.1875 , 0.0625 ), 60 | new ScreenPoint( 0.0625 , 0.0625 ), 61 | new ScreenPoint( 0.0625 , 0.1875 ), 62 | new ScreenPoint( -0.0625 , 0.1875 ), 63 | new ScreenPoint( -0.0625 , 0.0625 ), 64 | new ScreenPoint( -0.1875 , 0.0625 ), 65 | }; 66 | 67 | public static readonly ScreenPoint[] minus = new ScreenPoint[] 68 | { 69 | new ScreenPoint( -0.1875 , 0.0625 ), 70 | new ScreenPoint( -0.1875 , -0.0625 ), 71 | new ScreenPoint( 0.1875 , -0.0625 ), 72 | new ScreenPoint( 0.1875 , 0.0625 ), 73 | new ScreenPoint( -0.1875 , 0.0625 ), 74 | }; 75 | 76 | public static readonly ScreenPoint[] arrowDown = new ScreenPoint[] 77 | { 78 | new ScreenPoint( -0.00041218078613286524 , 0.2808984161376953 ), 79 | new ScreenPoint( -0.044389674596786555 , 0.23692157540321346 ), 80 | new ScreenPoint( -0.08828967252358794 , 0.19302157747641202 ), 81 | new ScreenPoint( -0.1328070914554596 , 0.14850415854454035 ), 82 | new ScreenPoint( -0.17682020330706616 , 0.10449104669293385 ), 83 | new ScreenPoint( -0.22110418719410896 , 0.060207062805891054 ), 84 | new ScreenPoint( -0.19524436524868016 , 0.01642311524868012 ), 85 | new ScreenPoint( -0.1508117316737771 , 0.04211034285970028 ), 86 | new ScreenPoint( -0.10680763758420947 , 0.0861139029449225 ), 87 | new ScreenPoint( -0.0626312731194496 , 0.1302897313147784 ), 88 | new ScreenPoint( -0.031364374999999944 , 0.14280067626349635 ), 89 | new ScreenPoint( -0.031364374999999944 , 0.0802354828637093 ), 90 | new ScreenPoint( -0.031364375 , 0.017595265009999195 ), 91 | new ScreenPoint( -0.031364375 , -0.044248437500000015 ), 92 | new ScreenPoint( -0.031364375 , -0.10724730928954673 ), 93 | new ScreenPoint( -0.031364375 , -0.1697135700175073 ), 94 | new ScreenPoint( -0.031364375 , -0.23236678466796873 ), 95 | new ScreenPoint( 0.012721604461669922 , -0.250053125 ), 96 | new ScreenPoint( 0.03113562499999989 , -0.20599234106540681 ), 97 | new ScreenPoint( 0.031135625 , -0.1429544707208406 ), 98 | new ScreenPoint( 0.031135625 , -0.0802156099993735 ), 99 | new ScreenPoint( 0.03113562500000011 , -0.01772427977286284 ), 100 | new ScreenPoint( 0.031135625 , 0.044782608917355526 ), 101 | new ScreenPoint( 0.031135625 , 0.10698984380518084 ), 102 | new ScreenPoint( 0.037008128653168604 , 0.15579004382193085 ), 103 | new ScreenPoint( 0.08090966464988891 , 0.11188886273853493 ), 104 | new ScreenPoint( 0.12553313520587972 , 0.06726575293220582 ), 105 | new ScreenPoint( 0.16920913462162013 , 0.02359010660648353 ), 106 | new ScreenPoint( 0.21349387866854663 , 0.03478114594161519 ), 107 | new ScreenPoint( 0.20220534441648974 , 0.07898238175231964 ), 108 | new ScreenPoint( 0.15843402710072696 , 0.12275405588299038 ), 109 | new ScreenPoint( 0.11359699641354393 , 0.16759145207256076 ), 110 | new ScreenPoint( 0.0695743391383905 , 0.21161446821149443 ), 111 | new ScreenPoint( 0.025433360283598327 , 0.2557558068946004 ), 112 | }; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /TradeBot/BuySellSeries.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot; 2 | using OxyPlot.Series; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace TradeBot 10 | { 11 | public class BuySellSeries 12 | { 13 | private LineSeries mainSeries; 14 | private ScatterSeries openPositionsCirclesSeries; 15 | private ScatterSeries closePositionsGreenCirclesSeries; 16 | private ScatterSeries closePositionsRedCirclesSeries; 17 | private ScatterSeries openPositionsUpShapesSeries; 18 | private ScatterSeries openPositionsDownShapesSeries; 19 | private ScatterSeries closePositionsPlusShapesSeries; 20 | private ScatterSeries closePositionsMinusShapesSeries; 21 | 22 | private ScatterSeries[] scatterSeries; 23 | 24 | public bool IsShort { get; private set; } = false; 25 | public double? OpenPrice { get; private set; } 26 | public bool isPositionOpened => OpenPrice.HasValue; 27 | 28 | public bool AreSeriesAttached { get; private set; } 29 | 30 | public BuySellSeries() 31 | { 32 | mainSeries = new LineSeries 33 | { 34 | Title = "Operations", 35 | Color = OxyColor.FromRgb(55, 55, 55), 36 | }; 37 | openPositionsCirclesSeries = new ScatterSeries 38 | { 39 | Title = "Open position (arrow down - short postion)", 40 | MarkerType = MarkerType.Circle, 41 | MarkerSize = 8, 42 | MarkerFill = OxyColor.FromRgb(40, 40, 40), 43 | }; 44 | closePositionsGreenCirclesSeries = new ScatterSeries 45 | { 46 | Title = "Profitable trade", 47 | MarkerType = MarkerType.Circle, 48 | MarkerSize = 8, 49 | MarkerFill = OxyColor.FromRgb(121, 229, 112), 50 | MarkerStroke = OxyColor.FromRgb(40, 40, 40), 51 | MarkerStrokeThickness = 2, 52 | }; 53 | closePositionsRedCirclesSeries = new ScatterSeries 54 | { 55 | Title = "Unprofitable trade", 56 | MarkerType = MarkerType.Circle, 57 | MarkerSize = 8, 58 | MarkerFill = OxyColor.FromRgb(214, 107, 107), 59 | MarkerStroke = OxyColor.FromRgb(40, 40, 40), 60 | MarkerStrokeThickness = 2, 61 | }; 62 | openPositionsUpShapesSeries = new ScatterSeries 63 | { 64 | MarkerType = MarkerType.Custom, 65 | MarkerOutline = ShapesPaths.arrowUp, 66 | MarkerSize = 16, 67 | MarkerFill = OxyColor.FromRgb(255, 255, 255), 68 | }; 69 | openPositionsDownShapesSeries = new ScatterSeries 70 | { 71 | MarkerType = MarkerType.Custom, 72 | MarkerOutline = ShapesPaths.arrowDown, 73 | MarkerSize = 16, 74 | MarkerFill = OxyColor.FromRgb(255, 255, 255), 75 | }; 76 | closePositionsPlusShapesSeries = new ScatterSeries 77 | { 78 | MarkerType = MarkerType.Custom, 79 | MarkerOutline = ShapesPaths.plus, 80 | MarkerSize = 16, 81 | MarkerFill = OxyColor.FromRgb(55, 55, 55), 82 | }; 83 | closePositionsMinusShapesSeries = new ScatterSeries 84 | { 85 | MarkerType = MarkerType.Custom, 86 | MarkerOutline = ShapesPaths.minus, 87 | MarkerSize = 16, 88 | MarkerFill = OxyColor.FromRgb(55, 55, 55), 89 | }; 90 | 91 | scatterSeries = new ScatterSeries[] 92 | { 93 | closePositionsGreenCirclesSeries, 94 | closePositionsRedCirclesSeries, 95 | closePositionsPlusShapesSeries, 96 | closePositionsMinusShapesSeries, 97 | openPositionsCirclesSeries, 98 | openPositionsUpShapesSeries, 99 | openPositionsDownShapesSeries, 100 | }; 101 | } 102 | 103 | public void AttachToChart(ElementCollection chart) 104 | { 105 | if (AreSeriesAttached || chart == null) 106 | return; 107 | 108 | chart.Add(mainSeries); 109 | foreach (var s in scatterSeries) 110 | chart.Add(s); 111 | AreSeriesAttached = true; 112 | } 113 | 114 | public void ClearSeries() 115 | { 116 | mainSeries.Points.Clear(); 117 | foreach (var s in scatterSeries) 118 | s.Points.Clear(); 119 | OpenPrice = null; 120 | IsShort = false; 121 | } 122 | 123 | public void OffsetSeries(int offset) 124 | { 125 | var mainSeriesBuff = new List(mainSeries.Points.Count); 126 | mainSeriesBuff.AddRange(mainSeries.Points.Select( 127 | point => new DataPoint(point.X + offset, point.Y))); 128 | mainSeries.Points.Clear(); 129 | mainSeries.Points.AddRange(mainSeriesBuff); 130 | 131 | foreach (var s in scatterSeries) 132 | { 133 | var buff = new List(s.Points.Count); 134 | buff.AddRange(s.Points.Select( 135 | point => new ScatterPoint(point.X + offset, point.Y))); 136 | s.Points.Clear(); 137 | s.Points.AddRange(buff); 138 | } 139 | } 140 | 141 | public void OpenPosition(int candleIndex, double openPrice, bool isShort) 142 | { 143 | if (this.OpenPrice.HasValue) 144 | throw new InvalidOperationException("can't open position if its already opened"); 145 | this.IsShort = isShort; 146 | this.OpenPrice = openPrice; 147 | 148 | var newPoint = new DataPoint(candleIndex - 0.25, openPrice); 149 | var newPointScatter = new ScatterPoint(candleIndex - 0.25, openPrice); 150 | 151 | mainSeries.Points.Add(newPoint); 152 | openPositionsCirclesSeries.Points.Add(newPointScatter); 153 | if (isShort) 154 | openPositionsDownShapesSeries.Points.Add(newPointScatter); 155 | else 156 | openPositionsUpShapesSeries.Points.Add(newPointScatter); 157 | } 158 | 159 | public void ClosePosition(int candleIndex, double closePrice) 160 | { 161 | if (!this.OpenPrice.HasValue) 162 | throw new InvalidOperationException("can't close position because there is no opened position"); 163 | 164 | var newPoint = new DataPoint(candleIndex + 0.25, closePrice); 165 | var newPointScatter = new ScatterPoint(candleIndex + 0.25, closePrice); 166 | 167 | mainSeries.Points.Add(newPoint); 168 | mainSeries.Points.Add(new DataPoint(double.NaN, double.NaN)); 169 | var isGrowth = OpenPrice - closePrice < 0; 170 | if (isGrowth && IsShort 171 | || !isGrowth && !IsShort) 172 | { 173 | closePositionsRedCirclesSeries.Points.Add(newPointScatter); 174 | closePositionsMinusShapesSeries.Points.Add(newPointScatter); 175 | } 176 | else 177 | { 178 | closePositionsGreenCirclesSeries.Points.Add(newPointScatter); 179 | closePositionsPlusShapesSeries.Points.Add(newPointScatter); 180 | } 181 | 182 | this.OpenPrice = null; 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /TradeBot/TradeBot.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C84AC606-AB1B-4A5E-9834-F5F4F8767E6A} 8 | WinExe 9 | TradeBot 10 | TradeBot 11 | v4.8 12 | 512 13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 4 15 | true 16 | true 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | preview 28 | 29 | 30 | AnyCPU 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | preview 38 | 39 | 40 | 41 | 42 | 43 | 44 | ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll 45 | 46 | 47 | ..\packages\OxyPlot.Core.2.0.0\lib\net45\OxyPlot.dll 48 | 49 | 50 | ..\packages\OxyPlot.Wpf.2.0.0\lib\net45\OxyPlot.Wpf.dll 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 4.0 64 | 65 | 66 | ..\packages\Tinkoff.Trading.OpenApi.1.6.0\lib\netstandard2.0\Tinkoff.Trading.OpenApi.dll 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | MSBuild:Compile 75 | Designer 76 | 77 | 78 | 79 | 80 | RsiDialog.xaml 81 | 82 | 83 | MacdDialog.xaml 84 | 85 | 86 | MovingAverageDialog.xaml 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | AuthPage.xaml 98 | 99 | 100 | 101 | 102 | 103 | 104 | TabsPage.xaml 105 | 106 | 107 | TestingTrading.xaml 108 | 109 | 110 | RealTimeTrading.xaml 111 | 112 | 113 | InstrumentSelection.xaml 114 | 115 | 116 | TradingChart.xaml 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | MSBuild:Compile 125 | Designer 126 | 127 | 128 | Designer 129 | MSBuild:Compile 130 | 131 | 132 | Designer 133 | MSBuild:Compile 134 | 135 | 136 | MSBuild:Compile 137 | Designer 138 | 139 | 140 | App.xaml 141 | Code 142 | 143 | 144 | 145 | MainWindow.xaml 146 | Code 147 | 148 | 149 | Designer 150 | MSBuild:Compile 151 | 152 | 153 | Designer 154 | MSBuild:Compile 155 | 156 | 157 | MSBuild:Compile 158 | Designer 159 | 160 | 161 | Designer 162 | MSBuild:Compile 163 | 164 | 165 | Designer 166 | MSBuild:Compile 167 | 168 | 169 | Designer 170 | MSBuild:Compile 171 | 172 | 173 | 174 | 175 | 176 | Code 177 | 178 | 179 | True 180 | True 181 | Resources.resx 182 | 183 | 184 | True 185 | Settings.settings 186 | True 187 | 188 | 189 | PublicResXFileCodeGenerator 190 | Resources.Designer.cs 191 | Designer 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | SettingsSingleFileGenerator 201 | Settings.Designer.cs 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /TradeBot/Pages/TabsPage.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 86 | 87 | 88 | 127 | 128 | 129 | 130 | 131 | 136 | 140 | 141 | 142 | 143 | 144 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /TradeBot/Pages/TradingChart.xaml.cs: -------------------------------------------------------------------------------- 1 | using OxyPlot; 2 | using OxyPlot.Axes; 3 | using OxyPlot.Series; 4 | using OxyPlot.Wpf; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | using System.Windows.Controls; 11 | using Tinkoff.Trading.OpenApi.Models; 12 | using static TradeBot.Instrument; 13 | using Axis = OxyPlot.Axes.Axis; 14 | using LinearAxis = OxyPlot.Axes.LinearAxis; 15 | using LineSeries = OxyPlot.Series.LineSeries; 16 | using PlotCommands = OxyPlot.PlotCommands; 17 | 18 | namespace TradeBot 19 | { 20 | /// 21 | /// Логика взаимодействия для TradingChart.xaml 22 | /// 23 | public partial class TradingChart : UserControl 24 | { 25 | private readonly PlotModel model; 26 | private readonly LinearAxis xAxis; 27 | private readonly LinearAxis yAxis; 28 | 29 | private CandleStickSeries candlesSeries; 30 | public List Candles => candlesSeries.Items; 31 | private BuySellSeries buySellSeries; 32 | 33 | private List indicators = new List(); 34 | 35 | private List<(PlotView plot, LinearAxis x, LinearAxis y)> oscillatorsPlots 36 | = new List<(PlotView plot, LinearAxis x, LinearAxis y)>(); 37 | 38 | private Instrument _instrument; 39 | public Instrument instrument 40 | { 41 | get => _instrument; 42 | set 43 | { 44 | _instrument = value; 45 | candlesSeries.Title = value.ActiveInstrument.Name; 46 | } 47 | } 48 | 49 | public TradingInterface TradingInterface { get; private set; } 50 | 51 | public CandleInterval candleInterval = CandleInterval.Hour; 52 | 53 | private int candlesLoadsFailed; 54 | private int loadedCandles; 55 | private int LoadedCandles 56 | { 57 | get => loadedCandles; 58 | set 59 | { 60 | loadedCandles = value; 61 | CandlesAdded?.Invoke(); 62 | } 63 | } 64 | 65 | private DateTime rightCandleDate; // newest 66 | private DateTime leftCandleDate; // oldest 67 | public DateTime rightCandleDateAhead; // TO TEST 'REAL TIME TRADING' 68 | 69 | public Task LoadingCandlesTask { get; private set; } 70 | 71 | public delegate void NewCandlesLoadedDelegate(int count); 72 | public event NewCandlesLoadedDelegate NewCandlesLoaded; 73 | 74 | public delegate void CandlesAddedDelegate(); 75 | public event CandlesAddedDelegate CandlesAdded; 76 | 77 | TradingStrategy tradingStrategy; 78 | public double? StopLoss { get; protected set; } 79 | 80 | public TradingChart() 81 | { 82 | InitializeComponent(); 83 | 84 | rightCandleDate = leftCandleDate = DateTime.Now; 85 | 86 | TradingInterface = new TradingInterface(10_000); 87 | 88 | model = new PlotModel 89 | { 90 | TextColor = OxyColor.FromArgb(140, 0, 0, 0), 91 | PlotAreaBorderThickness = new OxyThickness(0, 1, 0, 1), 92 | PlotAreaBorderColor = OxyColor.FromArgb(10, 0, 0, 0), 93 | LegendPosition = LegendPosition.LeftTop, 94 | LegendBackground = OxyColor.FromRgb(245, 245, 245), 95 | }; 96 | 97 | yAxis = new LinearAxis // y axis (left) 98 | { 99 | Position = AxisPosition.Left, 100 | IsPanEnabled = false, 101 | IsZoomEnabled = false, 102 | MajorGridlineThickness = 0, 103 | MinorGridlineThickness = 0, 104 | MajorGridlineColor = OxyColor.FromArgb(10, 0, 0, 0), 105 | MajorGridlineStyle = LineStyle.Solid, 106 | TicklineColor = OxyColor.FromArgb(10, 0, 0, 0), 107 | TickStyle = TickStyle.Outside 108 | }; 109 | 110 | xAxis = new LinearAxis // x axis (bottom) 111 | { 112 | Position = AxisPosition.Bottom, 113 | MajorGridlineStyle = LineStyle.Solid, 114 | TicklineColor = OxyColor.FromArgb(10, 0, 0, 0), 115 | TickStyle = TickStyle.Outside, 116 | MaximumRange = 200, 117 | MinimumRange = 15, 118 | AbsoluteMinimum = -10, 119 | EndPosition = 0, 120 | StartPosition = 1, 121 | MajorGridlineThickness = 2, 122 | MinorGridlineThickness = 0, 123 | MajorGridlineColor = OxyColor.FromArgb(10, 0, 0, 0) 124 | }; 125 | 126 | model.Axes.Add(yAxis); 127 | model.Axes.Add(xAxis); 128 | 129 | candlesSeries = new CandleStickSeries 130 | { 131 | Title = "Instrument", 132 | DecreasingColor = OxyColor.FromRgb(214, 107, 107), 133 | IncreasingColor = OxyColor.FromRgb(121, 229, 112), 134 | StrokeThickness = 1, 135 | TrackerFormatString = "Time: {DateTime:dd.MM.yyyy HH:mm}" + Environment.NewLine 136 | + "Price: {4}", 137 | }; 138 | 139 | model.Series.Add(candlesSeries); 140 | 141 | buySellSeries = new BuySellSeries(); 142 | buySellSeries.AttachToChart(model.Series); 143 | 144 | xAxis.LabelFormatter = d => 145 | { 146 | if (Candles.Count <= (int)d || !(d >= 0)) return ""; 147 | var date = ((Candle)Candles[(int)d]).DateTime; 148 | switch (candleInterval) 149 | { 150 | case CandleInterval.Minute: 151 | case CandleInterval.TwoMinutes: 152 | case CandleInterval.ThreeMinutes: 153 | case CandleInterval.FiveMinutes: 154 | case CandleInterval.TenMinutes: 155 | case CandleInterval.QuarterHour: 156 | case CandleInterval.HalfHour: 157 | return date.ToString("HH:mm"); 158 | 159 | case CandleInterval.Hour: 160 | case CandleInterval.Day: 161 | case CandleInterval.Week: 162 | return date.ToString("dd MMMM"); 163 | 164 | case CandleInterval.Month: 165 | return date.ToString("yyyy"); 166 | 167 | default: 168 | return ""; 169 | } 170 | }; 171 | xAxis.AxisChanged += async (object sender, AxisChangedEventArgs e) => 172 | { 173 | if (LoadingCandlesTask != null && LoadingCandlesTask.IsCompleted) 174 | { 175 | LoadingCandlesTask = LoadMoreCandlesAndUpdateSeries(); 176 | await LoadingCandlesTask; 177 | } 178 | }; 179 | 180 | xAxis.AxisChanged += (object sender, AxisChangedEventArgs e) => 181 | { 182 | AdjustYExtent(xAxis, yAxis, model); 183 | }; 184 | 185 | yAxis.LabelFormatter = d => $"{d} {instrument.ActiveInstrument.Currency}"; 186 | 187 | PlotView.Model = model; 188 | 189 | PlotView.ActualController.BindMouseDown(OxyMouseButton.Left, PlotCommands.PanAt); 190 | PlotView.ActualController.BindMouseDown(OxyMouseButton.Right, PlotCommands.SnapTrack); 191 | 192 | NewCandlesLoaded += (int count) => 193 | { 194 | buySellSeries.OffsetSeries(count); 195 | }; 196 | 197 | CandlesAdded += () => 198 | { 199 | AdjustYExtent(xAxis, yAxis, model); 200 | PlotView.InvalidatePlot(); 201 | }; 202 | 203 | DataContext = this; 204 | } 205 | 206 | public void SetStrategy(TradingStrategy ts) 207 | { 208 | tradingStrategy = ts; 209 | } 210 | 211 | public void AddIndicator(Indicator indicator) 212 | { 213 | indicators.Add(indicator); 214 | 215 | NewCandlesLoaded += (int count) => 216 | { 217 | indicator.ResetSeries(); 218 | indicator.UpdateSeries(); 219 | }; 220 | 221 | if (indicator is OscillatorIndicator oscillator) 222 | { 223 | var (plot, x, y) = oscillator.Plot; 224 | 225 | Grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(150) }); 226 | Grid.Children.Add(plot); 227 | plot.SetValue(Grid.RowProperty, Grid.RowDefinitions.Count - 1); 228 | 229 | x.Zoom(xAxis.ActualMinimum, xAxis.ActualMaximum); 230 | 231 | xAxis.AxisChanged += (object sender, AxisChangedEventArgs e) => 232 | { 233 | x.Zoom(xAxis.ActualMinimum, xAxis.ActualMaximum); 234 | }; 235 | 236 | oscillatorsPlots.Add((plot, x, y)); 237 | 238 | oscillator.AttachToChart(plot.Model.Series); 239 | } 240 | else 241 | { 242 | indicator.AttachToChart(model.Series); 243 | indicator.SeriesUpdated += () => 244 | { 245 | AdjustYExtent(xAxis, yAxis, model); 246 | PlotView.InvalidatePlot(); 247 | }; 248 | } 249 | 250 | indicator.UpdateSeries(); 251 | } 252 | 253 | public void RemoveIndicators() 254 | { 255 | foreach (var indicator in indicators) 256 | indicator.DetachFromChart(); 257 | indicators = new List(); 258 | 259 | if (oscillatorsPlots.Count > 0) 260 | { 261 | Grid.Children.RemoveRange(1, Grid.RowDefinitions.Count - 1); 262 | Grid.RowDefinitions.RemoveRange(1, Grid.RowDefinitions.Count - 1); 263 | oscillatorsPlots.Clear(); 264 | } 265 | 266 | AdjustYExtent(xAxis, yAxis, model); 267 | PlotView.InvalidatePlot(); 268 | } 269 | 270 | public void RemoveMarkers() 271 | { 272 | buySellSeries.ClearSeries(); 273 | AdjustYExtent(xAxis, yAxis, model); 274 | PlotView.InvalidatePlot(); 275 | } 276 | 277 | private int CalculateMinSeriesLength() 278 | { 279 | var result = (int)xAxis.ActualMaximum; 280 | IEnumerable enumerable() 281 | { 282 | foreach (var series in model.Series) 283 | { 284 | // TODO: check by title is not good 285 | if (series.GetType() == typeof(LineSeries) && ((LineSeries)series).Title != "Operations") 286 | { 287 | yield return ((LineSeries)series).Points.Count; 288 | } 289 | } 290 | } 291 | 292 | // check series on the main plot 293 | 294 | result = enumerable().Concat(new[] { result }).Min(); 295 | 296 | // check other series outside of the main plot 297 | foreach (var series in oscillatorsPlots.SelectMany(plot => plot.plot.Model.Series)) 298 | { 299 | int count = series switch 300 | { 301 | LineSeries ls => ls.Points.Count, 302 | HistogramSeries hs => hs.Items.Count, 303 | _ => int.MaxValue, 304 | }; 305 | if (count < result) result = count; 306 | } 307 | return result; 308 | } 309 | 310 | public static void AdjustYExtent(Axis x, Axis y, PlotModel m) 311 | { 312 | var points = new List(); 313 | 314 | foreach (var series in m.Series) 315 | if (series.GetType() == typeof(CandleStickSeries)) 316 | { 317 | points.AddRange(((CandleStickSeries)series).Items 318 | .FindAll(p => p.X >= x.ActualMinimum && p.X <= x.ActualMaximum) 319 | .ConvertAll(v => (float)v.High)); 320 | points.AddRange(((CandleStickSeries)series).Items 321 | .FindAll(p => p.X >= x.ActualMinimum && p.X <= x.ActualMaximum) 322 | .ConvertAll(v => (float)v.Low)); 323 | } 324 | else if (series.GetType() == typeof(LineSeries)) 325 | { 326 | points.AddRange(((LineSeries)series).Points 327 | .FindAll(p => p.X >= x.ActualMinimum && p.X <= x.ActualMaximum) 328 | .ConvertAll(v => (float)v.Y)); 329 | } 330 | else if (series.GetType() == typeof(HistogramSeries)) 331 | { 332 | points.AddRange(((HistogramSeries)series).Items 333 | .FindAll(p => p.RangeStart >= x.ActualMinimum && p.RangeStart <= x.ActualMaximum) 334 | .ConvertAll(v => (float)v.Value)); 335 | } 336 | 337 | var min = double.MaxValue; 338 | var max = double.MinValue; 339 | 340 | foreach (var point in points) 341 | { 342 | if (point > max) 343 | max = point; 344 | if (point < min) 345 | min = point; 346 | } 347 | 348 | if (min == double.MaxValue || max == double.MinValue) 349 | return; 350 | 351 | var extent = max - min; 352 | var margin = 0; 353 | 354 | y.IsZoomEnabled = true; 355 | y.Zoom(min - margin, max + margin); 356 | y.IsZoomEnabled = false; 357 | } 358 | 359 | private async Task LoadMoreCandlesAndUpdateSeries() 360 | { 361 | try 362 | { 363 | var minSeriesLength = CalculateMinSeriesLength(); 364 | 365 | while ((xAxis.ActualMaximum - 3 >= LoadedCandles || xAxis.ActualMaximum - 3 >= minSeriesLength) 366 | && candlesLoadsFailed < 10) 367 | { 368 | await LoadMoreCandles(); 369 | foreach (var indicator in indicators) 370 | indicator.UpdateSeries(); 371 | 372 | minSeriesLength = CalculateMinSeriesLength(); 373 | } 374 | } 375 | catch (Exception ex) 376 | { 377 | //MessageBox.Show(ex.Message); 378 | candlesLoadsFailed++; 379 | } 380 | } 381 | 382 | private async Task LoadMoreCandles() 383 | { 384 | if (candlesLoadsFailed >= 10 || 385 | LoadedCandles > xAxis.ActualMaximum + 100) 386 | return; 387 | 388 | var period = IntervalToMaxPeriodConverter.GetMaxPeriod(candleInterval); 389 | var candles = await instrument.GetCandles(leftCandleDate - period, 390 | leftCandleDate, candleInterval); 391 | if (candles == null) 392 | return; 393 | leftCandleDate -= period; 394 | if (candles.Count == 0) 395 | { 396 | candlesLoadsFailed += 1; 397 | return; 398 | } 399 | 400 | for (var i = 0; i < candles.Count; ++i) 401 | { 402 | var candle = candles[i]; 403 | Candles.Add(new Candle(LoadedCandles + i, candle)); 404 | } 405 | 406 | LoadedCandles += candles.Count; 407 | 408 | candlesLoadsFailed = 0; 409 | } 410 | 411 | public async void RestartSeries() 412 | { 413 | buySellSeries.ClearSeries(); 414 | Candles.Clear(); 415 | 416 | LoadedCandles = 0; 417 | candlesLoadsFailed = 0; 418 | // TO TEST 'REAL TIME TRADING' 419 | leftCandleDate = rightCandleDate = rightCandleDateAhead = DateTime.Now.AddDays(-120); 420 | 421 | tradingStrategy?.Reset(); 422 | StopLoss = null; 423 | instrument.ResetState(); 424 | 425 | foreach (var indicator in indicators) 426 | indicator.ResetSeries(); 427 | 428 | LoadingCandlesTask = LoadMoreCandlesAndUpdateSeries(); 429 | await LoadingCandlesTask; 430 | 431 | xAxis.Zoom(0, 75); 432 | } 433 | 434 | public async Task BeginTesting() 435 | { 436 | TradingInterface.Reset(10_000); 437 | buySellSeries.ClearSeries(); 438 | tradingStrategy.Reset(); 439 | 440 | await Task.Factory.StartNew(() => 441 | { 442 | for (var i = Candles.Count - 1; i >= 0; --i) 443 | { 444 | UpdateSignals(i, tradingStrategy); 445 | } 446 | }); 447 | PlotView.InvalidatePlot(); 448 | 449 | if (instrument.State != States.Empty) 450 | { 451 | buySellSeries.ClosePosition(0, instrument.DealPrice); 452 | TradingInterface.ClosePosition(instrument, instrument.DealPrice); 453 | } 454 | } 455 | 456 | public void PlaceStopLoss(int openCandleIndex, double openPrice, bool isShort, double percentage) 457 | { 458 | int div = 1000; 459 | var resLong = CalculateMaxMinPrice(openCandleIndex, 200); 460 | var resShort = CalculateMaxMinPrice(openCandleIndex, 50); 461 | (double max, double min) res = ((resLong.max + resShort.max) / 2, (resLong.min + resShort.min) / 2); 462 | double step = (res.max - res.min) / div; 463 | if (isShort) 464 | step = -step; 465 | 466 | StopLoss = openPrice - step * (percentage * div); 467 | } 468 | 469 | public bool HasCrossedStopLoss(double price) 470 | { 471 | if (StopLoss == null) 472 | return false; 473 | return price < StopLoss && instrument.State == States.Bought 474 | || price > StopLoss && instrument.State == States.Sold; 475 | } 476 | 477 | private (double max, double min) CalculateMaxMinPrice(int startIndex, int period) 478 | { 479 | double maxPrice = double.MinValue; 480 | double minPrice = double.MaxValue; 481 | for (int j = startIndex; j < period + startIndex && j < Candles.Count; ++j) 482 | { 483 | var h = Candles[j].High; 484 | var l = Candles[j].Low; 485 | if (h > maxPrice) 486 | maxPrice = h; 487 | if (l < minPrice) 488 | minPrice = l; 489 | } 490 | return (maxPrice, minPrice); 491 | } 492 | 493 | private void UpdateSignals(int i, TradingStrategy tradingStrategy) 494 | { 495 | var candle = Candles[i]; 496 | 497 | if (HasCrossedStopLoss(Candles[i].Close)) 498 | { 499 | buySellSeries.ClosePosition(i, candle.Close); 500 | TradingInterface.ClosePosition(instrument, candle.Close); 501 | StopLoss = null; 502 | } 503 | 504 | var signal = tradingStrategy.GetSignal(i); 505 | 506 | if (signal == TradingStrategy.Signal.Buy 507 | && instrument.State != States.Bought) 508 | { // buy signal 509 | if (instrument.State != States.Empty) 510 | { 511 | buySellSeries.ClosePosition(i, candle.Close); 512 | TradingInterface.ClosePosition(instrument, candle.Close); 513 | StopLoss = null; 514 | } 515 | //else 516 | { 517 | buySellSeries.OpenPosition(i, candle.Close, false); 518 | TradingInterface.OpenPosition(instrument, candle.Close, false); 519 | PlaceStopLoss(i, candle.Close, false, 0.15); 520 | } 521 | } 522 | else if (signal == TradingStrategy.Signal.Sell 523 | && instrument.State != States.Sold) 524 | { // sell signal 525 | if (instrument.State != States.Empty) 526 | { 527 | buySellSeries.ClosePosition(i, candle.Close); 528 | TradingInterface.ClosePosition(instrument, candle.Close); 529 | StopLoss = null; 530 | } 531 | //else 532 | { 533 | buySellSeries.OpenPosition(i, candle.Close, true); 534 | TradingInterface.OpenPosition(instrument, candle.Close, true); 535 | PlaceStopLoss(i, candle.Close, true, 0.15); 536 | } 537 | } 538 | else if (signal == TradingStrategy.Signal.Close) 539 | { 540 | if (instrument.State != States.Empty) 541 | { 542 | buySellSeries.ClosePosition(i, candle.Close); 543 | TradingInterface.ClosePosition(instrument, candle.Close); 544 | StopLoss = null; 545 | } 546 | } 547 | } 548 | 549 | public async Task LoadNewCandles() 550 | { 551 | List candles = await instrument.GetCandles(rightCandleDate, 552 | rightCandleDateAhead, candleInterval); 553 | 554 | if (candles == null || candles.Count == 0) 555 | return; 556 | 557 | rightCandleDate = rightCandleDateAhead; 558 | 559 | var c = candles.Select((candle, i) => new Candle(i, candle)).Cast().ToList(); 560 | Candles.ForEach(v => v.X += candles.Count); 561 | Candles.InsertRange(0, c); 562 | 563 | LoadedCandles += candles.Count; 564 | NewCandlesLoaded?.Invoke(candles.Count); 565 | UpdateSignals(0, tradingStrategy); 566 | } 567 | 568 | // ================================ 569 | // ========= Events ============== 570 | // ================================ 571 | 572 | private void MovingAverage_Click(object sender, RoutedEventArgs e) 573 | { 574 | var dialog = new MovingAverageDialog(); 575 | if (dialog.ShowDialog() != true) return; 576 | IMaCalculation calculationMethod = dialog.Type switch 577 | { 578 | MovingAverageDialog.CalculationMethod.Simple => new SimpleMaCalculation(), 579 | MovingAverageDialog.CalculationMethod.Exponential => new ExponentialMaCalculation(), 580 | _ => new SimpleMaCalculation(), 581 | }; 582 | AddIndicator(new MovingAverage(dialog.Period, calculationMethod, Candles)); 583 | } 584 | 585 | private void MACD_Click(object sender, RoutedEventArgs e) 586 | { 587 | var dialog = new MacdDialog(); 588 | if (dialog.ShowDialog() != true) return; 589 | IMaCalculation calculationMethod = dialog.Type switch 590 | { 591 | MacdDialog.CalculationMethod.Simple => new SimpleMaCalculation(), 592 | MacdDialog.CalculationMethod.Exponential => new ExponentialMaCalculation(), 593 | _ => new SimpleMaCalculation(), 594 | }; 595 | AddIndicator(new Macd( 596 | calculationMethod, dialog.ShortPeriod, dialog.LongPeriod, dialog.HistogramPeriod, 597 | Candles)); 598 | } 599 | 600 | private void Rsi_Click(object sender, RoutedEventArgs e) 601 | { 602 | var dialog = new RsiDialog(); 603 | if (dialog.ShowDialog() != true) return; 604 | AddIndicator(new Rsi(Candles, dialog.Period, dialog.OverboughtLine, dialog.OversoldLine)); 605 | } 606 | 607 | private void RemoveMarkersButton_Click(object sender, RoutedEventArgs e) 608 | { 609 | RemoveMarkers(); 610 | } 611 | } 612 | } -------------------------------------------------------------------------------- /TradeBot/App.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | #FFFFFF 18 | #2F2F2F 19 | #424242 20 | #1A1A1A 21 | #7300F1 22 | #5C00BF 23 | #9C49F4 24 | #E5E5E5 25 | #D5D5D5 26 | #F5F5F5 27 | 28 | 29 | pack://application:,,,/Fonts/#Open Sans 30 | 31 | 32 | 39 | 40 | 41 | 49 | 50 | 51 | 57 | 58 | 59 | 106 | 107 | 108 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 157 | 158 | 159 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 239 | 240 | 241 | 243 | 246 | 247 | 248 | 322 | 323 | 324 | 325 | 329 | 332 | 336 | 337 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 351 | 352 | 353 | 354 | 356 | 357 | 358 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 435 | 438 | 439 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 457 | 459 | 461 | 463 | 464 | 465 | 466 | 467 | 468 | 553 | 554 | 555 | 561 | 562 | 563 | 599 | 600 | 601 | 636 | 637 | 638 | 647 | 648 | 649 | --------------------------------------------------------------------------------