├── .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 | 
3 | 
4 | 
5 | 
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 |
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 |
--------------------------------------------------------------------------------