Initial Commit
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
PortfolioManager/bin/**
|
||||
PortfolioManager/obj/**
|
||||
|
||||
9
PortfolioManager/.vscode/launch.json
vendored
Normal file
9
PortfolioManager/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
]
|
||||
}
|
||||
27
PortfolioManager/App.axaml
Normal file
27
PortfolioManager/App.axaml
Normal file
@@ -0,0 +1,27 @@
|
||||
<Application
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="PortfolioManager.App"
|
||||
xmlns:theme="clr-namespace:Eremex.AvaloniaUI.Themes.DeltaDesign;assembly=Eremex.Avalonia.Themes.DeltaDesign"
|
||||
xmlns:local="using:PortfolioManager"
|
||||
RequestedThemeVariant="Light">
|
||||
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceInclude Source="avares://LoadingIndicators.Avalonia/LoadingIndicators.axaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator/>
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
<theme:DeltaDesignTheme/>
|
||||
<FluentTheme />
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||
</Application.Styles>
|
||||
|
||||
</Application>
|
||||
81
PortfolioManager/App.axaml.cs
Normal file
81
PortfolioManager/App.axaml.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Data.Core;
|
||||
using Avalonia.Data.Core.Plugins;
|
||||
using System.Linq;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using PortfolioManager.ViewModels;
|
||||
using PortfolioManager.Views;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Avalonia.Controls;
|
||||
using Axiom.Utils;
|
||||
|
||||
namespace PortfolioManager;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
// Avoid duplicate validations from both Avalonia and the CommunityToolkit.
|
||||
// More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
|
||||
DisableAvaloniaDataAnnotationValidation();
|
||||
MainWindowViewModel mainWindowViewModel = new MainWindowViewModel();
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = mainWindowViewModel,
|
||||
};
|
||||
|
||||
EventHandler requestCloseHandler = null;
|
||||
requestCloseHandler = delegate
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, $"RequestCloseHandler");
|
||||
if (null != mainWindowViewModel)
|
||||
{
|
||||
mainWindowViewModel.RequestClose -= requestCloseHandler;
|
||||
mainWindowViewModel.Dispose();
|
||||
mainWindowViewModel = null;
|
||||
}
|
||||
if (null != desktop.MainWindow)
|
||||
{
|
||||
desktop.MainWindow.Close();
|
||||
desktop.MainWindow = null;
|
||||
}
|
||||
};
|
||||
mainWindowViewModel.RequestClose += requestCloseHandler;
|
||||
|
||||
EventHandler<WindowClosingEventArgs> closingHandler = null;
|
||||
closingHandler = delegate
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, $"ClosingHandler");
|
||||
if (null != mainWindowViewModel)
|
||||
{
|
||||
mainWindowViewModel.Dispose();
|
||||
}
|
||||
};
|
||||
|
||||
desktop.MainWindow.Closing += closingHandler;
|
||||
}
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
|
||||
private void DisableAvaloniaDataAnnotationValidation()
|
||||
{
|
||||
// Get an array of plugins to remove
|
||||
var dataValidationPluginsToRemove =
|
||||
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();
|
||||
|
||||
// remove each entry found
|
||||
foreach (var plugin in dataValidationPluginsToRemove)
|
||||
{
|
||||
BindingPlugins.DataValidators.Remove(plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
PortfolioManager/Assets/HighSeas.jpg
Normal file
BIN
PortfolioManager/Assets/HighSeas.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
BIN
PortfolioManager/Assets/avalonia-logo.ico
Normal file
BIN
PortfolioManager/Assets/avalonia-logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
54
PortfolioManager/Command/RelayCommand.cs
Normal file
54
PortfolioManager/Command/RelayCommand.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
//using System.Windows.Input;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Avalonia.Labs.Input;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace PortfolioManager.Command
|
||||
{
|
||||
public class MyRelayCommand : IRelayCommand
|
||||
{
|
||||
readonly Action<object> execute;
|
||||
readonly Predicate<object> canExecute;
|
||||
public MyRelayCommand(Action<object> execute)
|
||||
: this(execute, null)
|
||||
{
|
||||
}
|
||||
|
||||
public MyRelayCommand(Action<object> execute, Predicate<object> canExecute)
|
||||
{
|
||||
if (execute == null) throw new ArgumentNullException("execute");
|
||||
this.execute = execute;
|
||||
this.canExecute = canExecute;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public bool CanExecute(object parameter)
|
||||
{
|
||||
return canExecute == null ? true : canExecute(parameter);
|
||||
}
|
||||
|
||||
public event EventHandler CanExecuteChanged
|
||||
{
|
||||
add
|
||||
{
|
||||
// CommandManager.RequerySuggested += value;
|
||||
}
|
||||
remove
|
||||
{
|
||||
// CommandManager.RequerySuggested -= value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute(object parameter)
|
||||
{
|
||||
execute(parameter);
|
||||
}
|
||||
|
||||
public void NotifyCanExecuteChanged()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
11
PortfolioManager/DataSeriesViewModels/CompositeDataSource.cs
Normal file
11
PortfolioManager/DataSeriesViewModels/CompositeDataSource.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Avalonia.Media;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Eremex.AvaloniaUI.Charts;
|
||||
|
||||
namespace PortfolioManager.DataSeriesViewModels
|
||||
{
|
||||
public partial class CompositeDataSource : ObservableObject
|
||||
{
|
||||
[ObservableProperty] ISeriesDataAdapter dataAdapter;
|
||||
}
|
||||
}
|
||||
61
PortfolioManager/Dialogs/ClosePositionDialog.axaml
Normal file
61
PortfolioManager/Dialogs/ClosePositionDialog.axaml
Normal file
@@ -0,0 +1,61 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:vm="using:PortfolioManager.Dialogs"
|
||||
x:Class="PortfolioManager.Dialogs.ClosePositionDialog"
|
||||
x:DataType="vm:ClosePositionDialogViewModel"
|
||||
Title="Close Position Dialog"
|
||||
Width="640" Height="350"
|
||||
CanResize="false"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<StackPanel Margin="5" Orientation="Vertical">
|
||||
<Grid DockPanel.Dock="Left" Margin="0,2,4,2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="6" />
|
||||
<ColumnDefinition Width="75*" />
|
||||
<ColumnDefinition Width="6" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="25*" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label FontWeight="Bold" FontSize="16" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Content="{Binding Path=CompanyName, Mode=OneWay}"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0">Symbol</Label>
|
||||
<TextBox Margin="0,4,0,4" Grid.Row="2" Grid.Column="2" IsReadOnly="true" MinWidth="35" Text="{Binding Path=Symbol, Mode=OneWay}"/>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="0" MinWidth="35">Purchase Date</Label>
|
||||
<TextBox Margin="0,4,0,4" IsReadOnly="true" Grid.Row="3" Grid.Column="2" Text="{Binding Path=PurchaseDate, Mode=OneWay}"/>
|
||||
|
||||
<Label Grid.Row="4" Grid.Column="0" MinWidth="35">Sell Date</Label>
|
||||
<TextBox Margin="0,4,0,4" IsReadOnly="false" Grid.Row="4" Grid.Column="2" Text="{Binding Path=SellDate, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
|
||||
|
||||
<Label Grid.Row="5" Grid.Column="0" MinWidth="35">Sell Price</Label>
|
||||
<TextBox Margin="0,4,0,4" Grid.Row="5" Grid.Column="2" MinWidth="75" Text="{Binding Path=SellPrice, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
|
||||
|
||||
<TextBox Margin="0,4,0,4" Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="5" Text="{Binding Path=Message, Mode=TwoWay}"/>
|
||||
|
||||
<Button Command="{Binding Path=OkButtonClickCommand}" Content="_Ok" MinWidth="70" IsDefault="False" Margin="2" Grid.Row="9" Grid.RowSpan="2" Grid.Column="0" IsEnabled="{Binding Path=OkEnabled, , Mode=TwoWay}"/>
|
||||
<Button Command="{Binding Path=CancelButtonClick}" Content="_Cancel" MinWidth="70" IsCancel="True" Margin="2" Grid.Row="11" Grid.RowSpan="2" Grid.Column="0"/>
|
||||
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
13
PortfolioManager/Dialogs/ClosePositionDialog.axaml.cs
Normal file
13
PortfolioManager/Dialogs/ClosePositionDialog.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace PortfolioManager.Dialogs;
|
||||
|
||||
public partial class ClosePositionDialog : Window
|
||||
{
|
||||
public ClosePositionDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
185
PortfolioManager/Dialogs/ClosePositionDialogViewModel.cs
Normal file
185
PortfolioManager/Dialogs/ClosePositionDialogViewModel.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Rendering.Composition;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using MarketData;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Generator.Interface;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.ViewModels;
|
||||
|
||||
namespace PortfolioManager.Dialogs
|
||||
{
|
||||
public partial class ClosePositionDialogViewModel : DialogViewModelBase
|
||||
{
|
||||
// private IPosition sourcePosition;
|
||||
private IPurePosition sourcePosition;
|
||||
private String message = String.Empty;
|
||||
private bool okEnabled = false;
|
||||
|
||||
public ClosePositionDialogViewModel(Window dialogWindow, IPurePosition clonedPosition)
|
||||
: base(dialogWindow)
|
||||
{
|
||||
sourcePosition = clonedPosition;
|
||||
if (Utility.IsEpoch(sourcePosition.SellDate))
|
||||
{
|
||||
sourcePosition.SellDate = DateTime.Now;
|
||||
}
|
||||
OkEnabled = Validate();
|
||||
DisplayName = "Close Position";
|
||||
}
|
||||
|
||||
public String Symbol
|
||||
{
|
||||
get { return sourcePosition.Symbol; }
|
||||
}
|
||||
|
||||
public String CompanyName
|
||||
{
|
||||
get
|
||||
{
|
||||
CompanyProfile companyProfile = CompanyProfileDA.GetCompanyProfile(sourcePosition.Symbol);
|
||||
return companyProfile.CompanyName;
|
||||
}
|
||||
}
|
||||
|
||||
public String PurchaseDate
|
||||
{
|
||||
get
|
||||
{
|
||||
return sourcePosition.PurchaseDate.ToShortDateString();
|
||||
}
|
||||
}
|
||||
|
||||
public String SellDate
|
||||
{
|
||||
get
|
||||
{
|
||||
return sourcePosition.SellDate.ToShortDateString();
|
||||
}
|
||||
set
|
||||
{
|
||||
try
|
||||
{
|
||||
sourcePosition.SellDate = Utility.ParseDate(value);
|
||||
base.OnPropertyChanged("SellDate");
|
||||
OkEnabled = Validate();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Message = "SellDate must be a valid date.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String SellPrice
|
||||
{
|
||||
get
|
||||
{
|
||||
return Utility.FormatCurrency(sourcePosition.CurrentPrice, 3);
|
||||
}
|
||||
set
|
||||
{
|
||||
try
|
||||
{
|
||||
sourcePosition.CurrentPrice = Utility.ParseCurrency(value);
|
||||
base.OnPropertyChanged("SellPrice");
|
||||
OkEnabled = Validate();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Message = "Sell price must be a valid number.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute =nameof(Validate))]
|
||||
public async Task OkButtonClick()
|
||||
{
|
||||
IsSuccess = true;
|
||||
await Close();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task CancelButtonClick()
|
||||
{
|
||||
await Close();
|
||||
}
|
||||
|
||||
public IPurePosition GetResult()
|
||||
{
|
||||
return sourcePosition;
|
||||
}
|
||||
|
||||
public String Message
|
||||
{
|
||||
get
|
||||
{
|
||||
return message;
|
||||
}
|
||||
set
|
||||
{
|
||||
message = value;
|
||||
base.OnPropertyChanged("Message");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMessage(String message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public bool OkEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return okEnabled;
|
||||
}
|
||||
set
|
||||
{
|
||||
okEnabled = value;
|
||||
base.OnPropertyChanged("OkEnabled");
|
||||
}
|
||||
}
|
||||
|
||||
private bool Validate()
|
||||
{
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
if (null == Symbol)
|
||||
{
|
||||
SetMessage("Invalid Symbol.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Utility.IsEpoch(sourcePosition.PurchaseDate))
|
||||
{
|
||||
SetMessage("Invalid Purchase Date.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Utility.IsEpoch(sourcePosition.SellDate))
|
||||
{
|
||||
SetMessage("Invalid Sell Date.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!dateGenerator.IsMarketOpen(sourcePosition.SellDate))
|
||||
{
|
||||
SetMessage("Market closed on Sell Date.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (double.IsNaN(sourcePosition.CurrentPrice))
|
||||
{
|
||||
SetMessage("Invalid Sell Price.");
|
||||
return false;
|
||||
}
|
||||
|
||||
SetMessage("");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
64
PortfolioManager/Dialogs/EditPositionDialog.axaml
Normal file
64
PortfolioManager/Dialogs/EditPositionDialog.axaml
Normal file
@@ -0,0 +1,64 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:vm="using:PortfolioManager.Dialogs"
|
||||
x:Class="PortfolioManager.Dialogs.EditPositionDialog"
|
||||
x:DataType="vm:EditPositionDialogViewModel"
|
||||
Title="Edit Position Dialog"
|
||||
Width="640" Height="350"
|
||||
CanResize="false"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<StackPanel Margin="5" Orientation="Vertical">
|
||||
<Grid DockPanel.Dock="Left" Margin="0,2,4,2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="6" />
|
||||
<ColumnDefinition Width="75*" />
|
||||
<ColumnDefinition Width="6" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="25*" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label FontWeight="Bold" FontSize="16" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Content="{Binding Path=CompanyName, Mode=OneWay}"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0">Symbol</Label>
|
||||
<TextBox Margin="0,4,0,4" Grid.Row="2" Grid.Column="2" IsReadOnly="true" MinWidth="35" Text="{Binding Path=Symbol, Mode=OneWay}"/>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="0" MinWidth="35">Purchase Date</Label>
|
||||
<TextBox Margin="0,4,0,4" IsReadOnly="true" Grid.Row="3" Grid.Column="2" Text="{Binding Path=PurchaseDate, Mode=OneWay}"/>
|
||||
|
||||
<Label Grid.Row="4" Grid.Column="0" MinWidth="75">Purchase Price</Label>
|
||||
<TextBox Margin="0,4,0,4" Grid.Row="4" Grid.Column="2" IsReadOnly="false" Text="{Binding Path=PurchasePrice, UpdateSourceTrigger=LostFocus, Mode=TwoWay}" />
|
||||
|
||||
<Label Grid.Row="5" Grid.Column="0">Initial Stop Limit</Label>
|
||||
<TextBox Margin="0,4,0,4" Grid.Row="5" Grid.Column="2" MinWidth="35" Text="{Binding Path=InitialStopLimit, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
|
||||
|
||||
<CheckBox Grid.Row="6" Grid.Column="0" IsChecked="{Binding Path=SyncTrailingStop, UpdateSourceTrigger=LostFocus, Mode=TwoWay}">Sync Trailing Stop</CheckBox>
|
||||
<Label Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="3" FontWeight="Bold" Content="{Binding Path=InitialStopRecommendation, Mode=OneWay}" />
|
||||
|
||||
<Label Grid.Row="8" Grid.Column="0">Trailing Stop</Label>
|
||||
<TextBox Grid.Row="8" Grid.Column="2" MinWidth="35" Text="{Binding Path=TrailingStopLimit, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
|
||||
|
||||
<Button Command="{Binding Path=OkButtonClickCommand}" Content="_Ok" MinWidth="70" IsDefault="False" Margin="2" Grid.Row="9" Grid.RowSpan="2" Grid.Column="0"/>
|
||||
<Button Command="{Binding Path=CancelButtonClick}" Content="_Cancel" MinWidth="70" IsCancel="True" Margin="2" Grid.Row="11" Grid.RowSpan="2" Grid.Column="0"/>
|
||||
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
13
PortfolioManager/Dialogs/EditPositionDialog.axaml.cs
Normal file
13
PortfolioManager/Dialogs/EditPositionDialog.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace PortfolioManager.Dialogs;
|
||||
|
||||
public partial class EditPositionDialog : Window
|
||||
{
|
||||
public EditPositionDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
56
PortfolioManager/Dialogs/EditPositionDialogNoStop.axaml
Normal file
56
PortfolioManager/Dialogs/EditPositionDialogNoStop.axaml
Normal file
@@ -0,0 +1,56 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:vm="using:PortfolioManager.Dialogs"
|
||||
x:Class="PortfolioManager.Dialogs.EditPositionDialogNoStop"
|
||||
Title="EditPositionDialogNoStop"
|
||||
x:DataType="vm:EditPositionDialogNoStopViewModel"
|
||||
Width="640" Height="350"
|
||||
CanResize="false"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<StackPanel Margin="5" Orientation="Vertical">
|
||||
<Grid DockPanel.Dock="Left" Margin="0,2,4,2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="6" />
|
||||
<ColumnDefinition Width="75*" />
|
||||
<ColumnDefinition Width="6" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="25*" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
<RowDefinition Height="25" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Label FontWeight="Bold" FontSize="16" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Content="{Binding Path=CompanyName, Mode=OneWay}"/>
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0">Symbol</Label>
|
||||
<TextBox Margin="0,4,0,4" Grid.Row="2" Grid.Column="2" IsReadOnly="true" MinWidth="35" Text="{Binding Path=Symbol, Mode=OneWay}"/>
|
||||
|
||||
<Label Grid.Row="3" Grid.Column="0" MinWidth="35">Purchase Date</Label>
|
||||
<TextBox Margin="0,4,0,4" IsReadOnly="true" Grid.Row="3" Grid.Column="2" Text="{Binding Path=PurchaseDate, Mode=OneWay}"/>
|
||||
|
||||
<Label Grid.Row="4" Grid.Column="0" MinWidth="75">Purchase Price</Label>
|
||||
<TextBox Margin="0,4,0,4" Grid.Row="4" Grid.Column="2" IsReadOnly="false" Text="{Binding Path=PurchasePrice, UpdateSourceTrigger=LostFocus, Mode=TwoWay}" />
|
||||
|
||||
<Button Command="{Binding Path=OkButtonClickCommand}" Content="_Ok" MinWidth="70" IsDefault="False" Margin="2" Grid.Row="9" Grid.RowSpan="2" Grid.Column="0"/>
|
||||
<Button Command="{Binding Path=CancelButtonClick}" Content="_Cancel" MinWidth="70" IsCancel="True" Margin="2" Grid.Row="11" Grid.RowSpan="2" Grid.Column="0"/>
|
||||
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
</Window>
|
||||
13
PortfolioManager/Dialogs/EditPositionDialogNoStop.axaml.cs
Normal file
13
PortfolioManager/Dialogs/EditPositionDialogNoStop.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace PortfolioManager.Dialogs;
|
||||
|
||||
public partial class EditPositionDialogNoStop : Window
|
||||
{
|
||||
public EditPositionDialogNoStop()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Generator.Interface;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.ViewModels;
|
||||
|
||||
namespace PortfolioManager.Dialogs
|
||||
{
|
||||
public partial class EditPositionDialogNoStopViewModel : DialogViewModelBase
|
||||
{
|
||||
private IPurePosition sourcePosition;
|
||||
|
||||
public EditPositionDialogNoStopViewModel(Window dialogWindow, IPurePosition clonedPosition)
|
||||
: base(dialogWindow)
|
||||
{
|
||||
sourcePosition = clonedPosition;
|
||||
DisplayName = "Edit Position";
|
||||
}
|
||||
|
||||
public String Symbol
|
||||
{
|
||||
get { return sourcePosition.Symbol; }
|
||||
}
|
||||
|
||||
public String CompanyName
|
||||
{
|
||||
get
|
||||
{
|
||||
CompanyProfile companyProfile = CompanyProfileDA.GetCompanyProfile(sourcePosition.Symbol);
|
||||
return companyProfile.CompanyName;
|
||||
}
|
||||
}
|
||||
|
||||
public String PurchaseDate
|
||||
{
|
||||
get
|
||||
{
|
||||
return sourcePosition.PurchaseDate.ToShortDateString();
|
||||
}
|
||||
}
|
||||
|
||||
public String PurchasePrice
|
||||
{
|
||||
get
|
||||
{
|
||||
return Utility.FormatCurrency(sourcePosition.PurchasePrice);
|
||||
}
|
||||
set
|
||||
{
|
||||
sourcePosition.PurchasePrice = Utility.ParseCurrency(value);
|
||||
base.OnPropertyChanged("PurchasePrice");
|
||||
}
|
||||
}
|
||||
|
||||
public IPurePosition GetResult()
|
||||
{
|
||||
return sourcePosition;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OkButtonClick()
|
||||
{
|
||||
IsSuccess = true;
|
||||
await Close();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task CancelButtonClick()
|
||||
{
|
||||
await Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
131
PortfolioManager/Dialogs/EditPositionDialogViewModel.cs
Normal file
131
PortfolioManager/Dialogs/EditPositionDialogViewModel.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Generator.Interface;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.ViewModels;
|
||||
|
||||
namespace PortfolioManager.Dialogs
|
||||
{
|
||||
public partial class EditPositionDialogViewModel : DialogViewModelBase
|
||||
{
|
||||
private IPosition sourcePosition;
|
||||
private bool syncTrailingStop = true;
|
||||
|
||||
public EditPositionDialogViewModel(Window dialogWindow, IPosition clonedPosition)
|
||||
: base(dialogWindow)
|
||||
{
|
||||
sourcePosition = clonedPosition;
|
||||
DisplayName = "Edit Position";
|
||||
}
|
||||
|
||||
public String Symbol
|
||||
{
|
||||
get { return sourcePosition.Symbol; }
|
||||
}
|
||||
|
||||
public String CompanyName
|
||||
{
|
||||
get
|
||||
{
|
||||
CompanyProfile companyProfile = CompanyProfileDA.GetCompanyProfile(sourcePosition.Symbol);
|
||||
return companyProfile.CompanyName;
|
||||
}
|
||||
}
|
||||
|
||||
public String PurchaseDate
|
||||
{
|
||||
get
|
||||
{
|
||||
return sourcePosition.PurchaseDate.ToShortDateString();
|
||||
}
|
||||
}
|
||||
|
||||
public String PurchasePrice
|
||||
{
|
||||
get
|
||||
{
|
||||
return Utility.FormatCurrency(sourcePosition.PurchasePrice);
|
||||
}
|
||||
set
|
||||
{
|
||||
sourcePosition.PurchasePrice = Utility.ParseCurrency(value);
|
||||
base.OnPropertyChanged("PurchasePrice");
|
||||
base.OnPropertyChanged("InitialStopRecommendation");
|
||||
}
|
||||
}
|
||||
|
||||
public String InitialStopLimit
|
||||
{
|
||||
get
|
||||
{
|
||||
return Utility.FormatCurrency(sourcePosition.InitialStopLimit);
|
||||
}
|
||||
set
|
||||
{
|
||||
sourcePosition.InitialStopLimit = Utility.ParseCurrency(value);
|
||||
base.OnPropertyChanged("InitialStopLimit");
|
||||
if (syncTrailingStop)
|
||||
{
|
||||
TrailingStopLimit = Utility.FormatCurrency(sourcePosition.InitialStopLimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SyncTrailingStop
|
||||
{
|
||||
get
|
||||
{
|
||||
return syncTrailingStop;
|
||||
}
|
||||
set
|
||||
{
|
||||
syncTrailingStop = value;
|
||||
base.OnPropertyChanged("SyncTrailingStop");
|
||||
}
|
||||
}
|
||||
|
||||
public String InitialStopRecommendation
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Recommended Initial Stop: " + Utility.FormatCurrency(sourcePosition.PurchasePrice * (1.00 - sourcePosition.PositionRiskPercentDecimal), 2);
|
||||
}
|
||||
}
|
||||
|
||||
public String TrailingStopLimit
|
||||
{
|
||||
get
|
||||
{
|
||||
return Utility.FormatCurrency(sourcePosition.TrailingStopLimit);
|
||||
}
|
||||
set
|
||||
{
|
||||
if (Utility.ParseCurrency(value).Equals(sourcePosition.TrailingStopLimit)) return;
|
||||
sourcePosition.TrailingStopLimit = Utility.ParseCurrency(value);
|
||||
base.OnPropertyChanged("TrailingStopLimit");
|
||||
}
|
||||
}
|
||||
|
||||
public IPosition GetResult()
|
||||
{
|
||||
return sourcePosition;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task OkButtonClick()
|
||||
{
|
||||
IsSuccess = true;
|
||||
await Close();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task CancelButtonClick()
|
||||
{
|
||||
await Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
24
PortfolioManager/Interface/IPositionModel.cs
Normal file
24
PortfolioManager/Interface/IPositionModel.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PortfolioManager.Interface
|
||||
{
|
||||
public interface IPositionModel
|
||||
{
|
||||
String Symbol {get;set;}
|
||||
|
||||
DateTime PurchaseDate {get;set;}
|
||||
|
||||
DateTime SellDate {get;set;}
|
||||
|
||||
double Shares {get;set;}
|
||||
|
||||
double PurchasePrice {get;set;}
|
||||
|
||||
double CurrentPrice {get;set;} // if sell date is not epoch then this is the sell price
|
||||
|
||||
}
|
||||
}
|
||||
333
PortfolioManager/Models/CMPositionModelCollection.cs
Normal file
333
PortfolioManager/Models/CMPositionModelCollection.cs
Normal file
@@ -0,0 +1,333 @@
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using MarketData.Generator.CMMomentum;
|
||||
using Position=MarketData.Generator.CMMomentum.Position;
|
||||
using PortfolioManager.ViewModels;
|
||||
using PortfolioManager.UIUtils;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace PortfolioManager.Models
|
||||
{
|
||||
public class CMPositionModelCollection : ObservableCollection<CMPositionModel>
|
||||
{
|
||||
public void Add(ActivePositions activePositions)
|
||||
{
|
||||
List<int> slotKeys = new List<int>(activePositions.Keys);
|
||||
for (int keyIndex = 0; keyIndex < slotKeys.Count; keyIndex++)
|
||||
{
|
||||
Positions slotPositions = activePositions[slotKeys[keyIndex]];
|
||||
foreach (Position position in slotPositions) Add(new CMPositionModel(position, keyIndex));
|
||||
}
|
||||
}
|
||||
public void Add(Positions allPositions)
|
||||
{
|
||||
foreach (Position position in allPositions) Add(new CMPositionModel(position));
|
||||
}
|
||||
public void OnCollectionChanged()
|
||||
{
|
||||
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, null));
|
||||
}
|
||||
}
|
||||
// ************************************************************************************************************************************************************************************
|
||||
public class CMPositionModel : ModelBase
|
||||
{
|
||||
private Position position = new Position();
|
||||
private int slot;
|
||||
private int priceChangeDirection = 0;
|
||||
private double prevPrice = double.NaN;
|
||||
private DateTime lastUpdated = DateTime.Now;
|
||||
private double rsi3 = double.NaN;
|
||||
|
||||
public CMPositionModel()
|
||||
{
|
||||
}
|
||||
|
||||
public CMPositionModel(Position position, int slot)
|
||||
{
|
||||
Slot = slot;
|
||||
Symbol = position.Symbol;
|
||||
PurchaseDate = position.PurchaseDate;
|
||||
SellDate = position.SellDate;
|
||||
Shares = position.Shares;
|
||||
PurchasePrice = position.PurchasePrice;
|
||||
CurrentPrice = position.CurrentPrice;
|
||||
Beta = position.Beta;
|
||||
BetaMonths = position.BetaMonths;
|
||||
SharpeRatio = position.SharpeRatio;
|
||||
CNNPrediction = position.CNNPrediction;
|
||||
}
|
||||
|
||||
public CMPositionModel(Position position)
|
||||
{
|
||||
slot = -1;
|
||||
Symbol = position.Symbol;
|
||||
PurchaseDate = position.PurchaseDate;
|
||||
SellDate = position.SellDate;
|
||||
Shares = position.Shares;
|
||||
PurchasePrice = position.PurchasePrice;
|
||||
CurrentPrice = position.CurrentPrice;
|
||||
Beta = position.Beta;
|
||||
BetaMonths = position.BetaMonths;
|
||||
SharpeRatio = position.SharpeRatio;
|
||||
CNNPrediction = position.CNNPrediction;
|
||||
}
|
||||
|
||||
public Position Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
public String Symbol
|
||||
{
|
||||
get { return position.Symbol; }
|
||||
set { position.Symbol = value; base.OnPropertyChanged("Symbol"); }
|
||||
}
|
||||
|
||||
public DateTime PurchaseDate
|
||||
{
|
||||
get
|
||||
{
|
||||
return position.PurchaseDate;
|
||||
}
|
||||
set
|
||||
{
|
||||
position.PurchaseDate = value;
|
||||
base.OnPropertyChanged("PurchaseDate");
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush PurchaseDateColor
|
||||
{
|
||||
get
|
||||
{
|
||||
DateGenerator dateGenerator=new DateGenerator();
|
||||
if(!dateGenerator.IsMarketOpen(PurchaseDate)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime SellDate
|
||||
{
|
||||
get { return position.SellDate; }
|
||||
set { position.SellDate = value; base.OnPropertyChanged("SellDate"); }
|
||||
}
|
||||
|
||||
public IBrush SellDateColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(Utility.IsEpoch(SellDate)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
DateGenerator dateGenerator=new DateGenerator();
|
||||
if(!dateGenerator.IsMarketOpen(SellDate)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public int DaysHeld
|
||||
{
|
||||
get
|
||||
{
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
if (Utility.IsEpoch(SellDate)) return dateGenerator.DaysBetween(PurchaseDate, DateTime.Now.Date);
|
||||
return dateGenerator.DaysBetween(PurchaseDate, SellDate);
|
||||
}
|
||||
}
|
||||
|
||||
public int Slot
|
||||
{
|
||||
get { return slot; }
|
||||
set { slot = value; base.OnPropertyChanged("Slot"); }
|
||||
}
|
||||
|
||||
public String SlotAsString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (-1 == Slot) return Constants.CONST_DASHES;
|
||||
return Slot.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public double Shares
|
||||
{
|
||||
get { return position.Shares; }
|
||||
set { position.Shares = value; base.OnPropertyChanged("Shares"); base.OnPropertyChanged("Exposure"); base.OnPropertyChanged("ActiveExposure"); base.OnPropertyChanged("MarketValue"); base.OnPropertyChanged("ActiveMarketValue"); base.OnPropertyChanged("GainLoss"); base.OnPropertyChanged("GainLossPcnt"); }
|
||||
}
|
||||
|
||||
public double PurchasePrice
|
||||
{
|
||||
get { return position.PurchasePrice; }
|
||||
set { position.PurchasePrice = value; base.OnPropertyChanged("PurchasePrice"); base.OnPropertyChanged("Exposure"); base.OnPropertyChanged("ActiveExposure"); base.OnPropertyChanged("GainLoss"); base.OnPropertyChanged("GainLossPcnt"); }
|
||||
}
|
||||
|
||||
public double CurrentPrice
|
||||
{
|
||||
get { return position.CurrentPrice; }
|
||||
set
|
||||
{
|
||||
if (position.CurrentPrice == value) return;
|
||||
position.CurrentPrice = value;
|
||||
if (double.IsNaN(prevPrice)) { prevPrice = position.CurrentPrice; priceChangeDirection = 0; }
|
||||
else if (prevPrice > position.CurrentPrice) priceChangeDirection = -1;
|
||||
else if (prevPrice == position.CurrentPrice) priceChangeDirection = 0;
|
||||
else priceChangeDirection = 1;
|
||||
prevPrice = position.CurrentPrice;
|
||||
base.OnPropertyChanged("CurrentPrice");
|
||||
base.OnPropertyChanged("MarketValue");
|
||||
base.OnPropertyChanged("ActiveMarketValue");
|
||||
base.OnPropertyChanged("GainLoss");
|
||||
base.OnPropertyChanged("GainLossPcnt");
|
||||
base.OnPropertyChanged("CurrentPriceColor");
|
||||
}
|
||||
}
|
||||
|
||||
public double RSI3
|
||||
{
|
||||
get { return rsi3; }
|
||||
set
|
||||
{
|
||||
if (rsi3 == value) return;
|
||||
rsi3 = value;
|
||||
base.OnPropertyChanged("RSI3");
|
||||
base.OnPropertyChanged("RSI3Color");
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime LastUpdated
|
||||
{
|
||||
get { return lastUpdated; }
|
||||
set { lastUpdated = value; base.OnPropertyChanged("LastUpdated"); }
|
||||
}
|
||||
|
||||
public IBrush CurrentPriceColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (priceChangeDirection > 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if (priceChangeDirection < 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush RSI3Color
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (RSI3 < 10 || RSI3 > 80) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public double Exposure
|
||||
{
|
||||
get { return Shares * PurchasePrice; }
|
||||
}
|
||||
|
||||
public double ActiveExposure
|
||||
{
|
||||
get { return IsActivePosition ? Exposure : 0.00; }
|
||||
}
|
||||
|
||||
public IBrush ActiveExposureColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CNNPrediction
|
||||
{
|
||||
get { return position.CNNPrediction; }
|
||||
set { position.CNNPrediction = value; base.OnPropertyChanged("CNNPrediction"); }
|
||||
}
|
||||
|
||||
public double MarketValue
|
||||
{
|
||||
get { return Shares * CurrentPrice; }
|
||||
}
|
||||
|
||||
public double ActiveMarketValue
|
||||
{
|
||||
get { return IsActivePosition ? MarketValue : 0.00; }
|
||||
}
|
||||
|
||||
public IBrush ActiveMarketValueColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (ActiveMarketValue > Exposure) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if (ActiveMarketValue < Exposure) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public double GainLoss
|
||||
{
|
||||
get { return MarketValue - Exposure; }
|
||||
}
|
||||
|
||||
public IBrush GainLossColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (GainLoss > 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if (GainLoss < 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public double GainLossPcnt
|
||||
{
|
||||
get { return (MarketValue - Exposure) / Exposure; }
|
||||
}
|
||||
|
||||
public IBrush GainLossPcntColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (GainLoss > 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if (GainLoss < 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public double Beta
|
||||
{
|
||||
get { return position.Beta; }
|
||||
set { position.Beta = value; base.OnPropertyChanged("Beta"); }
|
||||
}
|
||||
|
||||
public int BetaMonths
|
||||
{
|
||||
get { return position.BetaMonths; }
|
||||
set { position.BetaMonths = value; base.OnPropertyChanged("BetaMonths"); }
|
||||
}
|
||||
|
||||
public double SharpeRatio
|
||||
{
|
||||
get { return position.SharpeRatio; }
|
||||
set { position.SharpeRatio = value; base.OnPropertyChanged("SharpeRatio"); }
|
||||
}
|
||||
|
||||
public bool IsActivePosition
|
||||
{
|
||||
get { return Utility.IsEpoch(SellDate) ? true : false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
515
PortfolioManager/Models/CMTPositionModelCollection.cs
Normal file
515
PortfolioManager/Models/CMTPositionModelCollection.cs
Normal file
@@ -0,0 +1,515 @@
|
||||
using MarketData.Generator;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using PortfolioManager.ViewModels;
|
||||
using PortfolioManager.UIUtils;
|
||||
using System.Collections.Specialized;
|
||||
using MarketData.Generator.CMTrend;
|
||||
using MarketData;
|
||||
|
||||
using Position=MarketData.Generator.CMTrend.Position;
|
||||
using StopLimit=MarketData.MarketDataModel.StopLimit;
|
||||
|
||||
using MarketData.DataAccess;
|
||||
using PortfolioManager.Interface;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace PortfolioManager.Models
|
||||
{
|
||||
public class CMTPositionModelCollection:ObservableCollection<CMTPositionModel>
|
||||
{
|
||||
public void Add(ActivePositions activePositions)
|
||||
{
|
||||
foreach(Position position in activePositions)Add(new CMTPositionModel(position));
|
||||
}
|
||||
public void Add(Positions allPositions)
|
||||
{
|
||||
foreach(Position position in allPositions) Add(new CMTPositionModel(position));
|
||||
}
|
||||
public void OnCollectionChanged()
|
||||
{
|
||||
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset,null));
|
||||
}
|
||||
}
|
||||
|
||||
public class CMTPositionModel : ModelBase, IPositionModel
|
||||
{
|
||||
private Position position=new Position();
|
||||
private int priceChangeDirection=0;
|
||||
private double prevPrice=double.NaN;
|
||||
private double currentPriceLow=double.NaN;
|
||||
private DateTime lastUpdated=DateTime.Now;
|
||||
private double edgeRatio=double.NaN;
|
||||
|
||||
public CMTPositionModel()
|
||||
{
|
||||
}
|
||||
public CMTPositionModel(Position position)
|
||||
{
|
||||
Symbol=position.Symbol;
|
||||
PurchaseDate=position.PurchaseDate;
|
||||
SellDate=position.SellDate;
|
||||
Shares=position.Shares;
|
||||
PurchasePrice=position.PurchasePrice;
|
||||
CurrentPrice=position.CurrentPrice;
|
||||
PositionRiskPercentDecimal=position.PositionRiskPercentDecimal;
|
||||
InitialStopLimit=position.InitialStopLimit;
|
||||
TrailingStopLimit=position.TrailingStopLimit;
|
||||
LastStopAdjustment=position.LastStopAdjustment;
|
||||
this.position.R=position.R;
|
||||
this.position.C=position.C;
|
||||
}
|
||||
public void UpdateProperties()
|
||||
{
|
||||
base.OnPropertyChanged("CurrentPrice");
|
||||
base.OnPropertyChanged("MarketValue");
|
||||
base.OnPropertyChanged("ActiveMarketValue");
|
||||
base.OnPropertyChanged("GainLoss");
|
||||
base.OnPropertyChanged("GainLossPcnt");
|
||||
base.OnPropertyChanged("RMultiple");
|
||||
base.OnPropertyChanged("EdgeRatioAsString");
|
||||
|
||||
base.OnPropertyChanged("CurrentPriceColor");
|
||||
base.OnPropertyChanged("TrailingStopLimitColor");
|
||||
base.OnPropertyChanged("InitialStopLimitColor");
|
||||
base.OnPropertyChanged("TotalRiskExposureColor");
|
||||
base.OnPropertyChanged("ActiveMarketValueColor");
|
||||
base.OnPropertyChanged("GainLossColor");
|
||||
base.OnPropertyChanged("GainLossPcntColor");
|
||||
base.OnPropertyChanged("EdgeRatioAsStringColor");
|
||||
base.OnPropertyChanged("RMultipleColor");
|
||||
}
|
||||
public Position Position
|
||||
{
|
||||
get{return position;}
|
||||
set
|
||||
{
|
||||
Symbol=position.Symbol;
|
||||
PurchaseDate=position.PurchaseDate;
|
||||
SellDate=position.SellDate;
|
||||
Shares=position.Shares;
|
||||
PurchasePrice=position.PurchasePrice;
|
||||
CurrentPrice=position.CurrentPrice;
|
||||
PositionRiskPercentDecimal=position.PositionRiskPercentDecimal;
|
||||
InitialStopLimit=position.InitialStopLimit;
|
||||
TrailingStopLimit=position.TrailingStopLimit;
|
||||
LastStopAdjustment=position.LastStopAdjustment;
|
||||
this.position.R=position.R;
|
||||
this.position.C=position.C;
|
||||
}
|
||||
}
|
||||
|
||||
public String Symbol
|
||||
{
|
||||
get { return position.Symbol; }
|
||||
set { position.Symbol = value; base.OnPropertyChanged("Symbol"); }
|
||||
}
|
||||
|
||||
public IBrush SymbolColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
public DateTime PurchaseDate
|
||||
{
|
||||
get { return position.PurchaseDate; }
|
||||
set { position.PurchaseDate=value; base.OnPropertyChanged("PurchaseDate"); }
|
||||
}
|
||||
|
||||
public IBrush PurchaseDateColor
|
||||
{
|
||||
get
|
||||
{
|
||||
DateGenerator dateGenerator=new DateGenerator();
|
||||
if(!dateGenerator.IsMarketOpen(PurchaseDate)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime SellDate
|
||||
{
|
||||
get { return position.SellDate; }
|
||||
set { position.SellDate=value; base.OnPropertyChanged("SellDate"); }
|
||||
}
|
||||
|
||||
public IBrush SellDateColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(Utility.IsEpoch(SellDate)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
DateGenerator dateGenerator=new DateGenerator();
|
||||
if(!dateGenerator.IsMarketOpen(SellDate)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
public int DaysHeld
|
||||
{
|
||||
get
|
||||
{
|
||||
DateGenerator dateGenerator=new DateGenerator();
|
||||
if(Utility.IsEpoch(SellDate)) return dateGenerator.DaysBetween(PurchaseDate,DateTime.Now.Date);
|
||||
return dateGenerator.DaysBetween(PurchaseDate,SellDate);
|
||||
}
|
||||
}
|
||||
public IBrush DaysHeldColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
// Here we take R as based upon our StopLimit.
|
||||
public double R
|
||||
{
|
||||
get
|
||||
{
|
||||
if(position.TrailingStopLimit>position.PurchasePrice)return 0.00; // here we are considering the current risk/share which is based on our stop limit
|
||||
return position.TrailingStopLimit>position.PurchasePrice?0.00:position.PurchasePrice-position.TrailingStopLimit;
|
||||
}
|
||||
}
|
||||
public IBrush RColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
public double TotalRiskExposure
|
||||
{
|
||||
get{return R*position.Shares;}
|
||||
}
|
||||
public IBrush TotalRiskExposureColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
public double EdgeRatio
|
||||
{
|
||||
get{return edgeRatio;}
|
||||
set
|
||||
{
|
||||
edgeRatio=value;
|
||||
base.OnPropertyChanged("EdgeRatio");
|
||||
base.OnPropertyChanged("EdgeRatioAsStringColor");
|
||||
}
|
||||
}
|
||||
public String EdgeRatioAsString
|
||||
{
|
||||
get{return Utility.FormatNumber(edgeRatio,2,false);}
|
||||
}
|
||||
public IBrush EdgeRatioAsStringColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if(double.IsNaN(edgeRatio))return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
if(edgeRatio>=1.00)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
|
||||
public String RMultipleAsString
|
||||
{
|
||||
get{return Utility.FormatNumber((position.CurrentPrice-position.PurchasePrice)/(position.PurchasePrice-position.InitialStopLimit),2,false)+"R";} // always based on original position risk
|
||||
}
|
||||
|
||||
public double RMultiple
|
||||
{
|
||||
get{return (position.CurrentPrice-position.PurchasePrice)/(position.PurchasePrice-position.InitialStopLimit);} // always based on original position risk
|
||||
}
|
||||
|
||||
public IBrush RMultipleColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
public double PositionRiskPercentDecimal
|
||||
{
|
||||
get{return position.PositionRiskPercentDecimal;}
|
||||
set { position.PositionRiskPercentDecimal=value; base.OnPropertyChanged("PositionRiskPercentDecimal"); }
|
||||
}
|
||||
public double InitialStopLimit
|
||||
{
|
||||
get { return position.InitialStopLimit; }
|
||||
set
|
||||
{
|
||||
position.InitialStopLimit=value;
|
||||
base.OnPropertyChanged("InitialStopLimit");
|
||||
base.OnPropertyChanged("InitialStopLimitColor");
|
||||
}
|
||||
}
|
||||
public IBrush InitialStopLimitColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if(!Utility.IsEpoch(position.LastStopAdjustment)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black); // if we have a trailing stop then we are no longer using the initial stop
|
||||
StopLimit stopLimit=PortfolioDA.GetStopLimit(position.Symbol);
|
||||
if(null==stopLimit||!stopLimit.StopPrice.Equals(Math.Round(position.InitialStopLimit,2))) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Purple);
|
||||
if(currentPriceLow<=position.InitialStopLimit) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
public double TrailingStopLimit
|
||||
{
|
||||
get { return position.TrailingStopLimit; }
|
||||
set
|
||||
{
|
||||
position.TrailingStopLimit=value;
|
||||
base.OnPropertyChanged("TrailingStopLimit");
|
||||
base.OnPropertyChanged("TrailingStopLimitColor");
|
||||
}
|
||||
}
|
||||
public IBrush TrailingStopLimitColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if(currentPriceLow<=position.TrailingStopLimit)
|
||||
{
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
}
|
||||
StopLimit stopLimit=PortfolioDA.GetStopLimit(position.Symbol);
|
||||
if(null==stopLimit || !stopLimit.StopPrice.Equals(Math.Round(position.TrailingStopLimit,2)))
|
||||
{
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Purple);
|
||||
}
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
public DateTime LastStopAdjustment
|
||||
{
|
||||
get { return position.LastStopAdjustment; }
|
||||
set { position.LastStopAdjustment=value; base.OnPropertyChanged("LastStopAdjustment"); }
|
||||
}
|
||||
public IBrush LastStopAdjustmentColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
public int DaysSinceLastStopAdjustment
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition)return int.MinValue;
|
||||
DateGenerator dateGenerator=new DateGenerator();
|
||||
DateTime today=DateTime.Now;
|
||||
int daysSinceLastStopAdjustment=int.MinValue;
|
||||
if(Utility.IsEpoch(position.LastStopAdjustment)) daysSinceLastStopAdjustment=dateGenerator.DaysBetweenActual(today,position.PurchaseDate);
|
||||
else daysSinceLastStopAdjustment=dateGenerator.DaysBetweenActual(today,position.LastStopAdjustment);
|
||||
return daysSinceLastStopAdjustment;
|
||||
}
|
||||
}
|
||||
public IBrush DaysSinceLastStopAdjustmentColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
int daysSinceLastStopAdjustment=DaysSinceLastStopAdjustment;
|
||||
if(int.MinValue.Equals(daysSinceLastStopAdjustment))return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if(daysSinceLastStopAdjustment>=90) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else if(daysSinceLastStopAdjustment>=60) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red); // I made them both red because yellow and purple looked horrible
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
}
|
||||
}
|
||||
|
||||
// ***
|
||||
public double Shares
|
||||
{
|
||||
get { return position.Shares; }
|
||||
set { position.Shares=value; base.OnPropertyChanged("Shares"); base.OnPropertyChanged("Exposure"); base.OnPropertyChanged("ActiveExposure"); base.OnPropertyChanged("MarketValue"); base.OnPropertyChanged("ActiveMarketValue"); base.OnPropertyChanged("GainLoss"); base.OnPropertyChanged("GainLossPcnt"); }
|
||||
}
|
||||
public IBrush SharesColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
public double PurchasePrice
|
||||
{
|
||||
get { return position.PurchasePrice; }
|
||||
set { position.PurchasePrice=value; base.OnPropertyChanged("PurchasePrice"); base.OnPropertyChanged("Exposure"); base.OnPropertyChanged("ActiveExposure"); base.OnPropertyChanged("GainLoss"); base.OnPropertyChanged("GainLossPcnt"); }
|
||||
}
|
||||
public IBrush PurchasePriceColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
public double CurrentPrice
|
||||
{
|
||||
get { return position.CurrentPrice; }
|
||||
set
|
||||
{
|
||||
if(position.CurrentPrice==value) return;
|
||||
position.CurrentPrice=value;
|
||||
if(double.IsNaN(prevPrice)) { prevPrice=position.CurrentPrice; priceChangeDirection=0; }
|
||||
else if(prevPrice>position.CurrentPrice) priceChangeDirection=-1;
|
||||
else if(prevPrice==position.CurrentPrice) priceChangeDirection=0;
|
||||
else priceChangeDirection=1;
|
||||
prevPrice=position.CurrentPrice;
|
||||
UpdateProperties();
|
||||
}
|
||||
}
|
||||
public IBrush CurrentPriceColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if(priceChangeDirection>0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if(priceChangeDirection<0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
|
||||
public double CurrentPriceLow
|
||||
{
|
||||
get{return currentPriceLow;}
|
||||
set
|
||||
{
|
||||
if(currentPriceLow==value)return;
|
||||
currentPriceLow=value;
|
||||
base.OnPropertyChanged("CurrentPriceLow");
|
||||
base.OnPropertyChanged("CurrentPriceLowAsString");
|
||||
base.OnPropertyChanged("TrailingStopLimitColor");
|
||||
base.OnPropertyChanged("InitialStopLimitColor");
|
||||
}
|
||||
}
|
||||
// ***
|
||||
public String CurrentPriceLowAsString
|
||||
{
|
||||
get { return Utility.FormatCurrency(currentPriceLow); }
|
||||
}
|
||||
public IBrush CurrentPriceLowAsStringColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
public DateTime LastUpdated
|
||||
{
|
||||
get { return lastUpdated; }
|
||||
set { lastUpdated=value; base.OnPropertyChanged("LastUpdated"); }
|
||||
}
|
||||
public IBrush LastUpdatedColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
// ***
|
||||
|
||||
public double Exposure
|
||||
{
|
||||
get { return Shares*PurchasePrice; }
|
||||
}
|
||||
public double ActiveExposure
|
||||
{
|
||||
get { return IsActivePosition?Exposure:0.00; }
|
||||
}
|
||||
public IBrush ActiveExposureColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
public double MarketValue
|
||||
{
|
||||
get { return Shares*CurrentPrice; }
|
||||
}
|
||||
public double ActiveMarketValue
|
||||
{
|
||||
get { return IsActivePosition?MarketValue:0.00; }
|
||||
}
|
||||
public IBrush ActiveMarketValueColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if(ActiveMarketValue>Exposure) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if(ActiveMarketValue<Exposure) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public double GainLoss
|
||||
{
|
||||
get { return MarketValue - Exposure; }
|
||||
}
|
||||
|
||||
public IBrush GainLossColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (GainLoss > 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if (GainLoss < 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
public double GainLossPcnt
|
||||
{
|
||||
get { return (MarketValue-Exposure)/Exposure; }
|
||||
}
|
||||
public IBrush GainLossPcntColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if(GainLoss>0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if(GainLoss<0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
public bool IsActivePosition
|
||||
{
|
||||
get { return Utility.IsEpoch(SellDate)?true:false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
140
PortfolioManager/Models/GainLossModel.cs
Normal file
140
PortfolioManager/Models/GainLossModel.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Eremex.AvaloniaUI.Charts;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.MarketDataModel.GainLoss;
|
||||
using MarketData.Numerical;
|
||||
using PortfolioManager.DataSeriesViewModels;
|
||||
|
||||
namespace PortfolioManager.Models
|
||||
{
|
||||
public class GainLossModel
|
||||
{
|
||||
private GainLossModel()
|
||||
{
|
||||
}
|
||||
|
||||
public static CompositeDataSource Empty()
|
||||
{
|
||||
CompositeDataSource compositeDataSource = new CompositeDataSource()
|
||||
{
|
||||
DataAdapter = new SortedDateTimeDataAdapter()
|
||||
};
|
||||
return compositeDataSource;
|
||||
}
|
||||
|
||||
// This is the active gain/loss as number or percent.
|
||||
public static CompositeDataSource GainLoss(ModelPerformanceSeries gainLossList, bool useGainLoss)
|
||||
{
|
||||
if (null == gainLossList) return Empty();
|
||||
SortedDateTimeDataAdapter sortedDateTimeDataAdapter = new SortedDateTimeDataAdapter();
|
||||
foreach (ModelPerformanceItem modelPerformanceItem in gainLossList)
|
||||
{
|
||||
sortedDateTimeDataAdapter.Add(modelPerformanceItem.Date, useGainLoss ? modelPerformanceItem.CumulativeGainLoss : modelPerformanceItem.CumProdMinusOne * 100.00);
|
||||
}
|
||||
CompositeDataSource compositeDataSource = new CompositeDataSource()
|
||||
{
|
||||
DataAdapter = sortedDateTimeDataAdapter
|
||||
};
|
||||
return compositeDataSource;
|
||||
}
|
||||
|
||||
// This is the active gain/loss as number or percent.
|
||||
public static CompositeDataSource GainLoss(GainLossCompoundModelCollection gainLossList, bool useGainLoss)
|
||||
{
|
||||
if (null == gainLossList) return Empty();
|
||||
GainLossCompoundModelCollection sortedCollection = new GainLossCompoundModelCollection(gainLossList.OrderBy(x => x.Date).ToList());
|
||||
SortedDateTimeDataAdapter sortedDateTimeDataAdapter = new SortedDateTimeDataAdapter();
|
||||
foreach (GainLossCompoundModel gainLossCompoundModel in sortedCollection)
|
||||
{
|
||||
sortedDateTimeDataAdapter.Add(gainLossCompoundModel.Date, useGainLoss ? gainLossCompoundModel.ActiveGainLoss : gainLossCompoundModel.ActiveGainLossPercent);
|
||||
}
|
||||
CompositeDataSource compositeDataSource = new CompositeDataSource()
|
||||
{
|
||||
DataAdapter = sortedDateTimeDataAdapter
|
||||
};
|
||||
return compositeDataSource;
|
||||
}
|
||||
|
||||
// This is the total gain loss as number or percent
|
||||
public static CompositeDataSource TotalGainLoss(GainLossCompoundModelCollection gainLossList, bool useGainLoss)
|
||||
{
|
||||
if (null == gainLossList) return Empty();
|
||||
GainLossCompoundModelCollection sortedCollection = new GainLossCompoundModelCollection(gainLossList.OrderBy(x => x.Date).ToList());
|
||||
SortedDateTimeDataAdapter sortedDateTimeDataAdapter = new SortedDateTimeDataAdapter();
|
||||
foreach (GainLossCompoundModel gainLossCompoundModel in sortedCollection)
|
||||
{
|
||||
sortedDateTimeDataAdapter.Add(gainLossCompoundModel.Date, useGainLoss ? gainLossCompoundModel.TotalGainLoss : gainLossCompoundModel.TotalGainLossPercent);
|
||||
}
|
||||
CompositeDataSource compositeDataSource = new CompositeDataSource()
|
||||
{
|
||||
DataAdapter = sortedDateTimeDataAdapter
|
||||
};
|
||||
return compositeDataSource;
|
||||
}
|
||||
|
||||
// This is the least squares composite data source based on the active gain/loss
|
||||
public static CompositeDataSource LeastSquares(GainLossCompoundModelCollection gainLossList, bool useGainLoss)
|
||||
{
|
||||
if (null == gainLossList) return Empty();
|
||||
LeastSquaresResult leastSquaresResult = LeastSquaresFit(gainLossList, useGainLoss);
|
||||
GainLossCompoundModelCollection sortedCollection = new GainLossCompoundModelCollection(gainLossList.OrderBy(x => x.Date).ToList());
|
||||
SortedDateTimeDataAdapter sortedDateTimeDataAdapter = new SortedDateTimeDataAdapter();
|
||||
|
||||
for (int index = 0; index < sortedCollection.Count; index++)
|
||||
{
|
||||
GainLossCompoundModel gainLossCompoundModel = sortedCollection[index];
|
||||
int leastSquaresIndex = (leastSquaresResult.LeastSquares.Length - 1) - index;
|
||||
sortedDateTimeDataAdapter.Add(gainLossCompoundModel.Date, leastSquaresResult.LeastSquares[leastSquaresIndex]);
|
||||
}
|
||||
CompositeDataSource compositeDataSource = new CompositeDataSource()
|
||||
{
|
||||
DataAdapter = sortedDateTimeDataAdapter
|
||||
};
|
||||
return compositeDataSource;
|
||||
}
|
||||
|
||||
// This is the least squares composite data source based on the active gain/loss
|
||||
public static CompositeDataSource TotalLeastSquares(GainLossCompoundModelCollection gainLossList, bool useGainLoss)
|
||||
{
|
||||
if (null == gainLossList) return Empty();
|
||||
LeastSquaresResult leastSquaresResult = TotalLeastSquaresFit(gainLossList, useGainLoss);
|
||||
GainLossCompoundModelCollection sortedCollection = new GainLossCompoundModelCollection(gainLossList.OrderBy(x => x.Date).ToList());
|
||||
SortedDateTimeDataAdapter sortedDateTimeDataAdapter = new SortedDateTimeDataAdapter();
|
||||
|
||||
for (int index = 0; index < sortedCollection.Count; index++)
|
||||
{
|
||||
GainLossCompoundModel gainLossCompoundModel = sortedCollection[index];
|
||||
int leastSquaresIndex = (leastSquaresResult.LeastSquares.Length - 1) - index;
|
||||
sortedDateTimeDataAdapter.Add(gainLossCompoundModel.Date, leastSquaresResult.LeastSquares[leastSquaresIndex]);
|
||||
}
|
||||
CompositeDataSource compositeDataSource = new CompositeDataSource()
|
||||
{
|
||||
DataAdapter = sortedDateTimeDataAdapter
|
||||
};
|
||||
return compositeDataSource;
|
||||
}
|
||||
|
||||
// ***************************************** C A L C U L A T O R S **********************************
|
||||
|
||||
// This is the LeastSquares fit based on the active gain/loss
|
||||
public static LeastSquaresResult LeastSquaresFit(GainLossCompoundModelCollection gainLossList, bool useGainLoss)
|
||||
{
|
||||
double[] values = null;
|
||||
if (useGainLoss) values = (from GainLossCompoundModel gainLoss in gainLossList select gainLoss.ActiveGainLoss).ToList().ToArray();
|
||||
else values = (from GainLossCompoundModel gainLoss in gainLossList select gainLoss.ActiveGainLossPercent).ToList().ToArray();
|
||||
LeastSquaresResult leastSquaresResult = Numerics.LeastSquares(values);
|
||||
return leastSquaresResult;
|
||||
}
|
||||
|
||||
// This is the LeastSquares fit based on the total gain/loss
|
||||
public static LeastSquaresResult TotalLeastSquaresFit(GainLossCompoundModelCollection gainLossList,bool useGainLoss)
|
||||
{
|
||||
double[] values=null;
|
||||
if(useGainLoss)values=(from GainLossCompoundModel gainLoss in gainLossList select gainLoss.TotalGainLoss).ToList().ToArray();
|
||||
else values=(from GainLossCompoundModel gainLoss in gainLossList select gainLoss.TotalGainLossPercent).ToList().ToArray();
|
||||
LeastSquaresResult leastSquaresResult=Numerics.LeastSquares(values);
|
||||
return leastSquaresResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
351
PortfolioManager/Models/MGPositionModelCollection.cs
Normal file
351
PortfolioManager/Models/MGPositionModelCollection.cs
Normal file
@@ -0,0 +1,351 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using Avalonia.Media;
|
||||
using MarketData.Generator.Momentum;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.UIUtils;
|
||||
using PortfolioManager.ViewModels;
|
||||
|
||||
namespace PortfolioManager.Models
|
||||
{
|
||||
public class MGPositionModelCollection : ObservableCollection<MGPositionModel>
|
||||
{
|
||||
public void Add(ActivePositions activePositions)
|
||||
{
|
||||
List<int> slotKeys=new List<int>(activePositions.Keys);
|
||||
for(int keyIndex=0;keyIndex<slotKeys.Count;keyIndex++)
|
||||
{
|
||||
Positions slotPositions=activePositions[slotKeys[keyIndex]];
|
||||
foreach (MarketData.Generator.Momentum.Position position in slotPositions) Add(new MGPositionModel(position, keyIndex));
|
||||
}
|
||||
}
|
||||
public void Add(MarketData.Generator.Momentum.Positions allPositions)
|
||||
{
|
||||
foreach (MarketData.Generator.Momentum.Position position in allPositions) Add(new MGPositionModel(position));
|
||||
}
|
||||
public void OnCollectionChanged()
|
||||
{
|
||||
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset,null));
|
||||
}
|
||||
}
|
||||
public class MGPositionModel : ModelBase
|
||||
{
|
||||
private MarketData.Generator.Momentum.Position position = new MarketData.Generator.Momentum.Position();
|
||||
private int slot;
|
||||
private int priceChangeDirection=0;
|
||||
private double prevPrice=double.NaN;
|
||||
private DateTime lastUpdated=DateTime.Now;
|
||||
private double rsi3=double.NaN;
|
||||
|
||||
public MGPositionModel()
|
||||
{
|
||||
}
|
||||
public MGPositionModel(MarketData.Generator.Momentum.Position position, int slot)
|
||||
{
|
||||
Slot=slot;
|
||||
Symbol=position.Symbol;
|
||||
PurchaseDate=position.PurchaseDate;
|
||||
SellDate=position.SellDate;
|
||||
Shares=position.Shares;
|
||||
PurchasePrice=position.PurchasePrice;
|
||||
CurrentPrice=position.CurrentPrice;
|
||||
Volume=position.Volume;
|
||||
Return1D=position.Return1D;
|
||||
CumReturn252=position.CumReturn252;
|
||||
IDIndicator=position.IDIndicator;
|
||||
Score=position.Score;
|
||||
MaxDrawdown=position.MaxDrawdown;
|
||||
MaxUpside=position.MaxUpside;
|
||||
Velocity=position.Velocity;
|
||||
PE=position.PE;
|
||||
Beta=position.Beta;
|
||||
ZacksRank=position.ZacksRank;
|
||||
SharpeRatio = position.SharpeRatio;
|
||||
}
|
||||
public MGPositionModel(MarketData.Generator.Momentum.Position position)
|
||||
{
|
||||
slot=-1;
|
||||
Symbol=position.Symbol;
|
||||
PurchaseDate=position.PurchaseDate;
|
||||
SellDate=position.SellDate;
|
||||
Shares=position.Shares;
|
||||
PurchasePrice=position.PurchasePrice;
|
||||
CurrentPrice=position.CurrentPrice;
|
||||
Volume=position.Volume;
|
||||
Return1D=position.Return1D;
|
||||
CumReturn252=position.CumReturn252;
|
||||
IDIndicator=position.IDIndicator;
|
||||
Score=position.Score;
|
||||
MaxDrawdown=position.MaxDrawdown;
|
||||
MaxUpside=position.MaxUpside;
|
||||
Velocity=position.Velocity;
|
||||
PE=position.PE;
|
||||
Beta=position.Beta;
|
||||
ZacksRank=position.ZacksRank;
|
||||
SharpeRatio = position.SharpeRatio;
|
||||
}
|
||||
|
||||
public MarketData.Generator.Momentum.Position Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
public String Symbol
|
||||
{
|
||||
get{return position.Symbol;}
|
||||
set{position.Symbol=value;base.OnPropertyChanged("Symbol");}
|
||||
}
|
||||
public DateTime PurchaseDate
|
||||
{
|
||||
get{return position.PurchaseDate;}
|
||||
set{position.PurchaseDate=value;base.OnPropertyChanged("PurchaseDate");}
|
||||
}
|
||||
|
||||
public IBrush PurchaseDateColor
|
||||
{
|
||||
get
|
||||
{
|
||||
DateGenerator dateGenerator=new DateGenerator();
|
||||
if(!dateGenerator.IsMarketOpen(PurchaseDate)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush SellDateColor
|
||||
{
|
||||
get
|
||||
{
|
||||
DateGenerator dateGenerator=new DateGenerator();
|
||||
if(!dateGenerator.IsMarketOpen(SellDate)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
if(!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime SellDate
|
||||
{
|
||||
get{return position.SellDate;}
|
||||
set{position.SellDate=value;base.OnPropertyChanged("SellDate");}
|
||||
}
|
||||
public int DaysHeld
|
||||
{
|
||||
get
|
||||
{
|
||||
DateGenerator dateGenerator=new DateGenerator();
|
||||
if(Utility.IsEpoch(SellDate))return dateGenerator.DaysBetween(PurchaseDate,DateTime.Now.Date);
|
||||
return dateGenerator.DaysBetween(PurchaseDate,SellDate);
|
||||
}
|
||||
}
|
||||
public int Slot
|
||||
{
|
||||
get{return slot;}
|
||||
set{slot=value;base.OnPropertyChanged("Slot");}
|
||||
}
|
||||
public String SlotAsString
|
||||
{
|
||||
get
|
||||
{
|
||||
if(-1==Slot)return Constants.CONST_DASHES;
|
||||
return Slot.ToString();
|
||||
}
|
||||
}
|
||||
public double Shares
|
||||
{
|
||||
get{return position.Shares;}
|
||||
set{position.Shares=value;base.OnPropertyChanged("Shares");base.OnPropertyChanged("Exposure");base.OnPropertyChanged("ActiveExposure");base.OnPropertyChanged("MarketValue");base.OnPropertyChanged("ActiveMarketValue");base.OnPropertyChanged("GainLoss");base.OnPropertyChanged("GainLossPcnt");}
|
||||
}
|
||||
public double PurchasePrice
|
||||
{
|
||||
get{return position.PurchasePrice;}
|
||||
set{position.PurchasePrice=value;base.OnPropertyChanged("PurchasePrice");base.OnPropertyChanged("Exposure");base.OnPropertyChanged("ActiveExposure");base.OnPropertyChanged("GainLoss");base.OnPropertyChanged("GainLossPcnt");}
|
||||
}
|
||||
public double CurrentPrice
|
||||
{
|
||||
get{return position.CurrentPrice;}
|
||||
set
|
||||
{
|
||||
if(position.CurrentPrice==value)return;
|
||||
position.CurrentPrice=value;
|
||||
if(double.IsNaN(prevPrice)){prevPrice=position.CurrentPrice;priceChangeDirection=0;}
|
||||
else if(prevPrice>position.CurrentPrice)priceChangeDirection=-1;
|
||||
else if(prevPrice==position.CurrentPrice)priceChangeDirection=0;
|
||||
else priceChangeDirection=1;
|
||||
prevPrice=position.CurrentPrice;
|
||||
base.OnPropertyChanged("CurrentPrice");
|
||||
base.OnPropertyChanged("MarketValue");
|
||||
base.OnPropertyChanged("ActiveMarketValue");
|
||||
base.OnPropertyChanged("GainLoss");
|
||||
base.OnPropertyChanged("GainLossPcnt");
|
||||
base.OnPropertyChanged("CurrentPriceColor");
|
||||
}
|
||||
}
|
||||
public double RSI3
|
||||
{
|
||||
get{return rsi3;}
|
||||
set
|
||||
{
|
||||
if(rsi3==value)return;
|
||||
rsi3=value;
|
||||
base.OnPropertyChanged("RSI3");
|
||||
base.OnPropertyChanged("RSI3Color");
|
||||
}
|
||||
}
|
||||
public DateTime LastUpdated
|
||||
{
|
||||
get{return lastUpdated;}
|
||||
set{lastUpdated=value;base.OnPropertyChanged("LastUpdated");}
|
||||
}
|
||||
public IBrush CurrentPriceColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if(priceChangeDirection>0)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if(priceChangeDirection<0)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
public IBrush RSI3Color
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if(RSI3<10||RSI3>80)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
public double Exposure
|
||||
{
|
||||
get{return Shares*PurchasePrice;}
|
||||
}
|
||||
public double ActiveExposure
|
||||
{
|
||||
get{return IsActivePosition?Exposure:0.00;}
|
||||
}
|
||||
public IBrush ActiveExposureColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
public double MarketValue
|
||||
{
|
||||
get{return Shares*CurrentPrice;}
|
||||
}
|
||||
public double ActiveMarketValue
|
||||
{
|
||||
get{return IsActivePosition?MarketValue:0.00;}
|
||||
}
|
||||
public IBrush ActiveMarketValueColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (ActiveMarketValue > Exposure) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if (ActiveMarketValue < Exposure) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
public long Volume
|
||||
{
|
||||
get{return position.Volume;}
|
||||
set{position.Volume=value;base.OnPropertyChanged("Volume");}
|
||||
}
|
||||
public double Return1D
|
||||
{
|
||||
get{return position.Return1D;}
|
||||
set{position.Return1D=value;base.OnPropertyChanged("Return1D");}
|
||||
}
|
||||
public double GainLoss
|
||||
{
|
||||
get{return MarketValue-Exposure;}
|
||||
}
|
||||
public IBrush GainLossColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if(GainLoss>0)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if(GainLoss<0)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
public double GainLossPcnt
|
||||
{
|
||||
get{return (MarketValue-Exposure)/Exposure;}
|
||||
}
|
||||
public IBrush GainLossPcntColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!IsActivePosition)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if(GainLoss>0)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if(GainLoss<0)return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
public double CumReturn252
|
||||
{
|
||||
get{return position.CumReturn252;}
|
||||
set{position.CumReturn252=value;base.OnPropertyChanged("CumReturn252");}
|
||||
}
|
||||
public double IDIndicator
|
||||
{
|
||||
get{return position.IDIndicator;}
|
||||
set{position.IDIndicator=value;base.OnPropertyChanged("IDIndicator");}
|
||||
}
|
||||
public double Score
|
||||
{
|
||||
get { return position.Score; }
|
||||
set { position.Score=value; base.OnPropertyChanged("Score"); }
|
||||
}
|
||||
public double MaxDrawdown
|
||||
{
|
||||
get{return position.MaxDrawdown;}
|
||||
set{position.MaxDrawdown=value;base.OnPropertyChanged("MaxDrawdown");}
|
||||
}
|
||||
public double MaxUpside
|
||||
{
|
||||
get{return position.MaxUpside;}
|
||||
set{position.MaxUpside=value;base.OnPropertyChanged("MaxUpside");}
|
||||
}
|
||||
public double Velocity
|
||||
{
|
||||
get{return position.Velocity;}
|
||||
set{position.Velocity=value;base.OnPropertyChanged("Velocity");}
|
||||
}
|
||||
public double PE
|
||||
{
|
||||
get{return position.PE;}
|
||||
set{position.PE=value;base.OnPropertyChanged("PE");}
|
||||
}
|
||||
public String ZacksRank
|
||||
{
|
||||
get {return position.ZacksRank;}
|
||||
set{position.ZacksRank=value;base.OnPropertyChanged("ZacksRank");}
|
||||
}
|
||||
public double Beta
|
||||
{
|
||||
get{return position.Beta;}
|
||||
set{position.Beta=value;base.OnPropertyChanged("Beta");}
|
||||
}
|
||||
public double SharpeRatio
|
||||
{
|
||||
get { return position.SharpeRatio; }
|
||||
set { position.SharpeRatio = value; base.OnPropertyChanged("SharpeRatio"); }
|
||||
}
|
||||
public bool IsActivePosition
|
||||
{
|
||||
get{return Utility.IsEpoch(SellDate)?true:false;}
|
||||
}
|
||||
}
|
||||
}
|
||||
593
PortfolioManager/Models/MGSHPositionModelCollection.cs
Normal file
593
PortfolioManager/Models/MGSHPositionModelCollection.cs
Normal file
@@ -0,0 +1,593 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using Avalonia.Media;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Generator.MGSHMomentum;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.Interface;
|
||||
using PortfolioManager.UIUtils;
|
||||
using PortfolioManager.ViewModels;
|
||||
|
||||
namespace PortfolioManager.Models
|
||||
{
|
||||
public class MGSHPositionModelCollection : ObservableCollection<MGSHPositionModel>
|
||||
{
|
||||
public void Add(MGSHActivePositions activePositions)
|
||||
{
|
||||
List<int> slotKeys = new List<int>(activePositions.Keys);
|
||||
for (int keyIndex = 0; keyIndex < slotKeys.Count; keyIndex++)
|
||||
{
|
||||
MGSHPositions slotPositions = activePositions[slotKeys[keyIndex]];
|
||||
foreach (MGSHPosition position in slotPositions) Add(new MGSHPositionModel(position, keyIndex));
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(MGSHPositions allPositions)
|
||||
{
|
||||
foreach (MGSHPosition position in allPositions) Add(new MGSHPositionModel(position));
|
||||
}
|
||||
|
||||
public void Add(MGSHPositions hedgePositions, int slotIndex = -1)
|
||||
{
|
||||
if (null == hedgePositions) return;
|
||||
foreach (MGSHPosition position in hedgePositions) Add(new MGSHPositionModel(position, slotIndex));
|
||||
}
|
||||
|
||||
public void OnCollectionChanged()
|
||||
{
|
||||
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, null));
|
||||
}
|
||||
}
|
||||
|
||||
public class MGSHPositionModel : ModelBase, IPositionModel
|
||||
{
|
||||
private MGSHPosition position = new MGSHPosition();
|
||||
private int slot;
|
||||
private int priceChangeDirection = 0;
|
||||
private double prevPrice = double.NaN;
|
||||
private DateTime lastUpdated = DateTime.Now;
|
||||
private double currentPriceLow = double.NaN;
|
||||
private double rsi3 = double.NaN;
|
||||
|
||||
public MGSHPositionModel()
|
||||
{
|
||||
}
|
||||
|
||||
public MGSHPositionModel(MGSHPosition position, int slot)
|
||||
{
|
||||
Slot = slot;
|
||||
Symbol = position.Symbol;
|
||||
PurchaseDate = position.PurchaseDate;
|
||||
SellDate = position.SellDate;
|
||||
Shares = position.Shares;
|
||||
PurchasePrice = position.PurchasePrice;
|
||||
CurrentPrice = position.CurrentPrice;
|
||||
Volume = position.Volume;
|
||||
Return1D = position.Return1D;
|
||||
CumReturn252 = position.CumReturn252;
|
||||
IDIndicator = position.IDIndicator;
|
||||
Score = position.Score;
|
||||
Velocity = position.Velocity;
|
||||
PE = position.PE;
|
||||
Beta = position.Beta;
|
||||
InitialStopLimit = position.InitialStopLimit;
|
||||
TrailingStopLimit = position.TrailingStopLimit;
|
||||
LastStopAdjustment = position.LastStopAdjustment;
|
||||
PositionRiskPercentDecimal = position.PositionRiskPercentDecimal;
|
||||
Comment = position.Comment;
|
||||
}
|
||||
public MGSHPositionModel(MGSHPosition position)
|
||||
{
|
||||
slot = -1;
|
||||
Symbol = position.Symbol;
|
||||
PurchaseDate = position.PurchaseDate;
|
||||
SellDate = position.SellDate;
|
||||
Shares = position.Shares;
|
||||
PurchasePrice = position.PurchasePrice;
|
||||
CurrentPrice = position.CurrentPrice;
|
||||
Volume = position.Volume;
|
||||
Return1D = position.Return1D;
|
||||
CumReturn252 = position.CumReturn252;
|
||||
IDIndicator = position.IDIndicator;
|
||||
Score = position.Score;
|
||||
Velocity = position.Velocity;
|
||||
PE = position.PE;
|
||||
Beta = position.Beta;
|
||||
InitialStopLimit = position.InitialStopLimit;
|
||||
TrailingStopLimit = position.TrailingStopLimit;
|
||||
LastStopAdjustment = position.LastStopAdjustment;
|
||||
PositionRiskPercentDecimal = position.PositionRiskPercentDecimal;
|
||||
Comment = position.Comment;
|
||||
}
|
||||
|
||||
public MGSHPosition Position
|
||||
{
|
||||
get { return position; }
|
||||
set
|
||||
{
|
||||
Slot = -1;
|
||||
Symbol = position.Symbol;
|
||||
PurchaseDate = position.PurchaseDate;
|
||||
SellDate = position.SellDate;
|
||||
Shares = position.Shares;
|
||||
PurchasePrice = position.PurchasePrice;
|
||||
CurrentPrice = position.CurrentPrice;
|
||||
Volume = position.Volume;
|
||||
Return1D = position.Return1D;
|
||||
CumReturn252 = position.CumReturn252;
|
||||
IDIndicator = position.IDIndicator;
|
||||
Score = position.Score;
|
||||
Velocity = position.Velocity;
|
||||
PE = position.PE;
|
||||
Beta = position.Beta;
|
||||
InitialStopLimit = position.InitialStopLimit;
|
||||
TrailingStopLimit = position.TrailingStopLimit;
|
||||
LastStopAdjustment = position.LastStopAdjustment;
|
||||
PositionRiskPercentDecimal = position.PositionRiskPercentDecimal;
|
||||
Comment = position.Comment;
|
||||
this.position.R = position.R;
|
||||
}
|
||||
}
|
||||
|
||||
public double CurrentPriceLow
|
||||
{
|
||||
get { return currentPriceLow; }
|
||||
set
|
||||
{
|
||||
if (currentPriceLow == value) return;
|
||||
currentPriceLow = value;
|
||||
base.OnPropertyChanged("CurrentPriceLow");
|
||||
base.OnPropertyChanged("TrailingStopLimitColor");
|
||||
base.OnPropertyChanged("InitialStopLimitColor");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateProperties()
|
||||
{
|
||||
base.OnPropertyChanged("CurrentPrice");
|
||||
base.OnPropertyChanged("MarketValue");
|
||||
base.OnPropertyChanged("ActiveMarketValue");
|
||||
base.OnPropertyChanged("GainLoss");
|
||||
base.OnPropertyChanged("GainLossPcnt");
|
||||
base.OnPropertyChanged("Comment");
|
||||
base.OnPropertyChanged("PositionRiskPercentDecimal");
|
||||
base.OnPropertyChanged("R");
|
||||
base.OnPropertyChanged("TotalRiskExposure");
|
||||
base.OnPropertyChanged("RMultiple");
|
||||
|
||||
base.OnPropertyChanged("CurrentPriceColor");
|
||||
base.OnPropertyChanged("TrailingStopLimitColor");
|
||||
base.OnPropertyChanged("InitialStopLimitColor");
|
||||
base.OnPropertyChanged("ActiveMarketValueColor");
|
||||
base.OnPropertyChanged("GainLossColor");
|
||||
base.OnPropertyChanged("GainLossPcntColor");
|
||||
base.OnPropertyChanged("RColor");
|
||||
base.OnPropertyChanged("TotalRiskExposureColor");
|
||||
base.OnPropertyChanged("RMultipleColor");
|
||||
}
|
||||
|
||||
public String Symbol
|
||||
{
|
||||
get { return position.Symbol; }
|
||||
set { position.Symbol = value; base.OnPropertyChanged("Symbol"); }
|
||||
}
|
||||
|
||||
public String Comment
|
||||
{
|
||||
get { return position.Comment; }
|
||||
set { position.Comment = value; base.OnPropertyChanged("Comment"); }
|
||||
}
|
||||
|
||||
public DateTime PurchaseDate
|
||||
{
|
||||
get { return position.PurchaseDate; }
|
||||
set { position.PurchaseDate = value; base.OnPropertyChanged("PurchaseDate"); }
|
||||
}
|
||||
|
||||
public IBrush PurchaseDateColor
|
||||
{
|
||||
get
|
||||
{
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
if (!dateGenerator.IsMarketOpen(PurchaseDate)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush SellDateColor
|
||||
{
|
||||
get
|
||||
{
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
if (!dateGenerator.IsMarketOpen(SellDate)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime SellDate
|
||||
{
|
||||
get { return position.SellDate; }
|
||||
set { position.SellDate = value; base.OnPropertyChanged("SellDate"); }
|
||||
}
|
||||
public int DaysHeld
|
||||
{
|
||||
get
|
||||
{
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
if (Utility.IsEpoch(SellDate)) return dateGenerator.DaysBetween(PurchaseDate, DateTime.Now.Date);
|
||||
return dateGenerator.DaysBetween(PurchaseDate, SellDate);
|
||||
}
|
||||
}
|
||||
public int Slot
|
||||
{
|
||||
get { return slot; }
|
||||
set { slot = value; base.OnPropertyChanged("Slot"); }
|
||||
}
|
||||
public String SlotAsString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (-1 == Slot) return Constants.CONST_DASHES;
|
||||
return Slot.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public double PositionRiskPercentDecimal
|
||||
{
|
||||
get
|
||||
{
|
||||
return position.PositionRiskPercentDecimal;
|
||||
}
|
||||
set
|
||||
{
|
||||
position.PositionRiskPercentDecimal = value;
|
||||
base.OnPropertyChanged("PositionRiskPercentDecimal");
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************************************************************************************************************
|
||||
// ******************************************************************** R I S K *******************************************************
|
||||
// ************************************************************************************************************************************
|
||||
|
||||
public double R
|
||||
{
|
||||
get
|
||||
{
|
||||
if (position.TrailingStopLimit > position.PurchasePrice) return 0.00; // here we are considering the current risk/share which is based on our stop limit
|
||||
return position.TrailingStopLimit > position.PurchasePrice ? 0.00 : position.PurchasePrice - position.TrailingStopLimit;
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush RColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public String RMultipleAsString
|
||||
{
|
||||
get { return Utility.FormatNumber((position.CurrentPrice - position.PurchasePrice) / (position.PurchasePrice - position.InitialStopLimit), 2, false) + "R"; } // always based on original position risk
|
||||
}
|
||||
|
||||
public double RMultiple
|
||||
{
|
||||
get { return (position.CurrentPrice - position.PurchasePrice) / (position.PurchasePrice - position.InitialStopLimit); } // always based on original position risk
|
||||
}
|
||||
|
||||
public IBrush RMultipleColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public double TotalRiskExposure
|
||||
{
|
||||
get { return R * position.Shares; }
|
||||
}
|
||||
|
||||
public IBrush TotalRiskExposureColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************************************************************************************************************
|
||||
public int DaysSinceLastStopAdjustment
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return int.MinValue;
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
DateTime today = DateTime.Now;
|
||||
int daysSinceLastStopAdjustment = int.MinValue;
|
||||
if (Utility.IsEpoch(position.LastStopAdjustment)) daysSinceLastStopAdjustment = dateGenerator.DaysBetweenActual(today, position.PurchaseDate);
|
||||
else daysSinceLastStopAdjustment = dateGenerator.DaysBetweenActual(today, position.LastStopAdjustment);
|
||||
return daysSinceLastStopAdjustment;
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush DaysSinceLastStopAdjustmentColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
int daysSinceLastStopAdjustment = DaysSinceLastStopAdjustment;
|
||||
if (int.MinValue.Equals(daysSinceLastStopAdjustment)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (daysSinceLastStopAdjustment >= 90) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else if (daysSinceLastStopAdjustment >= 60) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red); // I made them both red because yellow and purple looked horrible
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
}
|
||||
}
|
||||
|
||||
public double Shares
|
||||
{
|
||||
get { return position.Shares; }
|
||||
set { position.Shares = value; base.OnPropertyChanged("Shares"); base.OnPropertyChanged("Exposure"); base.OnPropertyChanged("ActiveExposure"); base.OnPropertyChanged("MarketValue"); base.OnPropertyChanged("ActiveMarketValue"); base.OnPropertyChanged("GainLoss"); base.OnPropertyChanged("GainLossPcnt"); }
|
||||
}
|
||||
|
||||
public double PurchasePrice
|
||||
{
|
||||
get { return position.PurchasePrice; }
|
||||
set { position.PurchasePrice = value; base.OnPropertyChanged("PurchasePrice"); base.OnPropertyChanged("Exposure"); base.OnPropertyChanged("ActiveExposure"); base.OnPropertyChanged("GainLoss"); base.OnPropertyChanged("GainLossPcnt"); }
|
||||
}
|
||||
|
||||
public double CurrentPrice
|
||||
{
|
||||
get { return position.CurrentPrice; }
|
||||
set
|
||||
{
|
||||
if (position.CurrentPrice == value) return;
|
||||
position.CurrentPrice = value;
|
||||
if (double.IsNaN(prevPrice)) { prevPrice = position.CurrentPrice; priceChangeDirection = 0; }
|
||||
else if (prevPrice > position.CurrentPrice) priceChangeDirection = -1;
|
||||
else if (prevPrice == position.CurrentPrice) priceChangeDirection = 0;
|
||||
else priceChangeDirection = 1;
|
||||
prevPrice = position.CurrentPrice;
|
||||
UpdateProperties();
|
||||
}
|
||||
}
|
||||
|
||||
public double RSI3
|
||||
{
|
||||
get { return rsi3; }
|
||||
set
|
||||
{
|
||||
if (rsi3 == value) return;
|
||||
rsi3 = value;
|
||||
base.OnPropertyChanged("RSI3");
|
||||
base.OnPropertyChanged("RSI3Color");
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime LastUpdated
|
||||
{
|
||||
get { return lastUpdated; }
|
||||
set { lastUpdated = value; base.OnPropertyChanged("LastUpdated"); }
|
||||
}
|
||||
|
||||
public IBrush CurrentPriceColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (priceChangeDirection > 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if (priceChangeDirection < 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush RSI3Color
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (RSI3 < 10 || RSI3 > 80) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public double Exposure
|
||||
{
|
||||
get { return Shares * PurchasePrice; }
|
||||
}
|
||||
|
||||
public double ActiveExposure
|
||||
{
|
||||
get { return IsActivePosition ? Exposure : 0.00; }
|
||||
}
|
||||
|
||||
public IBrush ActiveExposureColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public double MarketValue
|
||||
{
|
||||
get { return Shares * CurrentPrice; }
|
||||
}
|
||||
|
||||
public double ActiveMarketValue
|
||||
{
|
||||
get { return IsActivePosition ? MarketValue : 0.00; }
|
||||
}
|
||||
|
||||
public IBrush ActiveMarketValueColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (ActiveMarketValue > Exposure) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if (ActiveMarketValue < Exposure) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public long Volume
|
||||
{
|
||||
get { return position.Volume; }
|
||||
set { position.Volume = value; base.OnPropertyChanged("Volume"); }
|
||||
}
|
||||
|
||||
public double Return1D
|
||||
{
|
||||
get { return position.Return1D; }
|
||||
set { position.Return1D = value; base.OnPropertyChanged("Return1D"); }
|
||||
}
|
||||
|
||||
public double GainLoss
|
||||
{
|
||||
get { return MarketValue - Exposure; }
|
||||
}
|
||||
|
||||
public IBrush GainLossColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (GainLoss > 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if (GainLoss < 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public double GainLossPcnt
|
||||
{
|
||||
get { return (MarketValue - Exposure) / Exposure; }
|
||||
}
|
||||
|
||||
public IBrush GainLossPcntColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (GainLoss > 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Green);
|
||||
else if (GainLoss < 0) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
else return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public double CumReturn252
|
||||
{
|
||||
get { return position.CumReturn252; }
|
||||
set { position.CumReturn252 = value; base.OnPropertyChanged("CumReturn252"); }
|
||||
}
|
||||
|
||||
public double IDIndicator
|
||||
{
|
||||
get { return position.IDIndicator; }
|
||||
set { position.IDIndicator = value; base.OnPropertyChanged("IDIndicator"); }
|
||||
}
|
||||
|
||||
public double Score
|
||||
{
|
||||
get { return position.Score; }
|
||||
set { position.Score = value; base.OnPropertyChanged("Score"); }
|
||||
}
|
||||
|
||||
public double Velocity
|
||||
{
|
||||
get { return position.Velocity; }
|
||||
set { position.Velocity = value; base.OnPropertyChanged("Velocity"); }
|
||||
}
|
||||
|
||||
public double PE
|
||||
{
|
||||
get { return position.PE; }
|
||||
set { position.PE = value; base.OnPropertyChanged("PE"); }
|
||||
}
|
||||
|
||||
public double Beta
|
||||
{
|
||||
get { return position.Beta; }
|
||||
set { position.Beta = value; base.OnPropertyChanged("Beta"); }
|
||||
}
|
||||
|
||||
public bool IsActivePosition
|
||||
{
|
||||
get { return Utility.IsEpoch(SellDate) ? true : false; }
|
||||
}
|
||||
|
||||
public double InitialStopLimit
|
||||
{
|
||||
get { return position.InitialStopLimit; }
|
||||
set
|
||||
{
|
||||
position.InitialStopLimit = value;
|
||||
base.OnPropertyChanged("InitialStopLimit");
|
||||
base.OnPropertyChanged("InitialStopLimitColor");
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush InitialStopLimitColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (!Utility.IsEpoch(position.LastStopAdjustment)) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black); // if we have a trailing stop then we are no longer using the initial stop
|
||||
StopLimit stopLimit = PortfolioDA.GetStopLimit(position.Symbol);
|
||||
if (null == stopLimit || !stopLimit.StopPrice.Equals(Math.Round(position.InitialStopLimit, 2))) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Purple);
|
||||
if (currentPriceLow <= position.InitialStopLimit) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public double TrailingStopLimit
|
||||
{
|
||||
get { return position.TrailingStopLimit; }
|
||||
set
|
||||
{
|
||||
position.TrailingStopLimit = value;
|
||||
base.OnPropertyChanged("TrailingStopLimit");
|
||||
base.OnPropertyChanged("TrailingStopLimitColor");
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush TrailingStopLimitColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
if (currentPriceLow <= position.TrailingStopLimit)
|
||||
{
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
}
|
||||
StopLimit stopLimit = PortfolioDA.GetStopLimit(position.Symbol);
|
||||
if (null == stopLimit || !stopLimit.StopPrice.Equals(Math.Round(position.TrailingStopLimit, 2))) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Purple);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime LastStopAdjustment
|
||||
{
|
||||
get { return position.LastStopAdjustment; }
|
||||
set { position.LastStopAdjustment = value; base.OnPropertyChanged("LastStopAdjustment"); }
|
||||
}
|
||||
|
||||
public IBrush LastStopAdjustmentColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsActivePosition) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Blue);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
PortfolioManager/Models/MovingAverageModel.cs
Normal file
49
PortfolioManager/Models/MovingAverageModel.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Eremex.AvaloniaUI.Charts;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.MarketDataModel.GainLoss;
|
||||
using PortfolioManager.DataSeriesViewModels;
|
||||
|
||||
namespace PortfolioManager.Models
|
||||
{
|
||||
public class MovingAverageModel
|
||||
{
|
||||
private MovingAverageModel()
|
||||
{
|
||||
}
|
||||
|
||||
public static CompositeDataSource Empty()
|
||||
{
|
||||
CompositeDataSource compositeDataSource = new CompositeDataSource()
|
||||
{
|
||||
DataAdapter = new SortedDateTimeDataAdapter()
|
||||
};
|
||||
return compositeDataSource;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the moving average composite data source
|
||||
/// </summary>
|
||||
/// <param name="gainLossList"></param>
|
||||
/// <param name="useGainLoss"></param>
|
||||
/// <returns></returns>
|
||||
public static CompositeDataSource CreateCompositeDataSource(DMAValues dmaValues)
|
||||
{
|
||||
if (null == dmaValues) return Empty();
|
||||
List<DMAValue> sortedValues = new List<DMAValue>(dmaValues.OrderBy(x => x.Date));
|
||||
|
||||
SortedDateTimeDataAdapter sortedDateTimeDataAdapter = new SortedDateTimeDataAdapter();
|
||||
foreach (DMAValue dmaValue in sortedValues)
|
||||
{
|
||||
sortedDateTimeDataAdapter.Add(dmaValue.Date,dmaValue.MAValue);
|
||||
}
|
||||
CompositeDataSource compositeDataSource = new CompositeDataSource()
|
||||
{
|
||||
DataAdapter = sortedDateTimeDataAdapter
|
||||
};
|
||||
return compositeDataSource;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
59
PortfolioManager/PortfolioManager.csproj
Normal file
59
PortfolioManager/PortfolioManager.csproj
Normal file
@@ -0,0 +1,59 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
|
||||
<Nullable>disable</Nullable>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
<NoWarn>CA1416;CS8622,CS8769;CS0108;CS8602;CS8601;CS8620;CS8618;CS8603;CS8767;CS8625;CS8604;CS8600;CS8604</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Models\" />
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Boneyard\ARM64\MarketData\MarketDataLib\MarketDataLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Have to copy this shared library manually because publish was deploying an incorrect version of it
|
||||
that was not running on the pi -->
|
||||
<ItemGroup>
|
||||
<None Update="libSkiaSharp.so">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia.Labs.CommandManager" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.1" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.1" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.1">
|
||||
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
|
||||
<PackageReference Include="Eremex.Avalonia.Controls" Version="1.1.142" />
|
||||
<PackageReference Include="Eremex.Avalonia.Themes.DeltaDesign" Version="1.1.142" />
|
||||
<PackageReference Include="LoadingIndicators.Avalonia" Version="11.0.11.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostClean" AfterTargets="Clean">
|
||||
<RemoveDir Directories="$(BaseIntermediateOutputPath)" /><!-- obj -->
|
||||
<RemoveDir Directories="$(BaseOutputPath)" /><!-- bin -->
|
||||
</Target>
|
||||
</Project>
|
||||
24
PortfolioManager/PortfolioManager.sln
Normal file
24
PortfolioManager/PortfolioManager.sln
Normal file
@@ -0,0 +1,24 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.2.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PortfolioManager", "PortfolioManager.csproj", "{5E9565B6-29DD-1DBB-4B36-668515E703C9}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{5E9565B6-29DD-1DBB-4B36-668515E703C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5E9565B6-29DD-1DBB-4B36-668515E703C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5E9565B6-29DD-1DBB-4B36-668515E703C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5E9565B6-29DD-1DBB-4B36-668515E703C9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5F666C3E-8CC7-42E9-9502-9AF65AA1C805}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
43
PortfolioManager/Program.cs
Normal file
43
PortfolioManager/Program.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Avalonia;
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using MarketData.Configuration;
|
||||
using MarketData;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace PortfolioManager;
|
||||
|
||||
sealed class Program
|
||||
{
|
||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
MDTrace.LogLevel = LogLevel.VERBOSE;
|
||||
String strLogFile = "portfolio_manager.log";
|
||||
Trace.Listeners.Add(new TextWriterTraceListener(strLogFile));
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, "[STARTING]");
|
||||
|
||||
IConfigurationBuilder builder = new ConfigurationBuilder()
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
|
||||
IConfigurationRoot configurationRoot = builder.Build();
|
||||
GlobalConfig.Instance.Configuration = configurationRoot; // This call sets up configuration stuff so it needs to be first.
|
||||
try { BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); }
|
||||
catch (Exception exception) { MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString()); }
|
||||
}
|
||||
// public static void Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
193
PortfolioManager/Readme.txt
Normal file
193
PortfolioManager/Readme.txt
Normal file
@@ -0,0 +1,193 @@
|
||||
dotnet new avalonia.window -na EditPositionDialog -n PortfolioManager.Dialog
|
||||
dotnet new avalonia.usercontrol -na [namespace] -n [name]
|
||||
|
||||
From Views Folder....
|
||||
dotnet new avalonia.usercontrol -na MomentumView -n PortfolioManager.Views
|
||||
|
||||
dotnet new avalonia.usercontrol -na GainLossView -n PortfolioManager.Views
|
||||
|
||||
|
||||
editor.inlineSuggest.enabled
|
||||
|
||||
|
||||
dotnet new avalonia.window -na EditPositionDialog -n PortfolioManager.Dialogs
|
||||
dotnet new avalonia.window -na EditPositionDialogNoStop -n PortfolioManager.Dialogs
|
||||
|
||||
1) Install dotnet 8.0 sdk (C:\download\dotnetcore\dotnet-sdk-8.0.409-win-x64.exe)
|
||||
https://dotnet.microsoft.com/en-us/download/dotnet/8.0
|
||||
|
||||
2) Install Avalonia for VSCode extension
|
||||
|
||||
3) dotnet new install Avalonia.Templates
|
||||
|
||||
4) dotnet new avalonia.mvvm -o MyApp
|
||||
|
||||
5) in order to target Linux you need to publish to that target (i.e.) dotnet publish -r linux-arm64
|
||||
|
||||
Skip
|
||||
6) dotnet add package HyperText.Avalonia
|
||||
|
||||
Skip
|
||||
7) dotnet add package Avalonia.Labs.CommandManager --version 11.2.0
|
||||
|
||||
8) dotnet add package Avalonia.Controls.DataGrid
|
||||
|
||||
skip
|
||||
9) dotnet add package Avalonia.Skia.Linux.Natives --version <version_number>
|
||||
For Avalonia applications targeting .NET 8 on Linux, you'll need Avalonia.Skia.Linux.Natives version 11.3.0. This version is designed to work with the latest Avalonia framework, which is 11.3.0. The other components, like SkiaSharp and its native assets, should also be at version 2.88.9 or higher for optimal compatibility, as per the documentation.
|
||||
|
||||
Skip
|
||||
10) dotnet add package SkiaSharp.NativeAssets.Linux.NoDependencies
|
||||
|
||||
11) Add this to the project file in order to get the correct version of libSkiaSharp.so
|
||||
<!-- Have to copy this shared library manually because publish was deploying an incorrect version of it
|
||||
that was not running on the pi -->
|
||||
<ItemGroup>
|
||||
<None Update="libSkiaSharp.so">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
Skip
|
||||
12) SkiaSharp.Views
|
||||
|
||||
13) add package Avalonia.Attached
|
||||
|
||||
|
||||
14) vscode preferences -> settings -> terminal.integrated.copyOnSelection -> check the box
|
||||
|
||||
15) Avalonia Cartesian Chart
|
||||
https://eremexcontrols.net/articles/controls/charts/get-started-with-charts-mvvm.html
|
||||
https://eremexcontrols.net/articles/get-started/create-new-avalonia-project-using-avalonia-templates.html
|
||||
https://www.eremexcontrols.net/articles/controls/charts/cartesian-series-views/line-series-vew.html
|
||||
https://eremexcontrols.net/articles/controls/charts/get-started-with-charts.html
|
||||
https://eremexcontrols.net/articles/controls/charts/cartesian-series-views/line-series-vew.html
|
||||
|
||||
|
||||
|
||||
|
||||
dotnet add package Eremex.Avalonia.Controls
|
||||
dotnet add package Eremex.Avalonia.Themes.DeltaDesign
|
||||
|
||||
In App.axaml Replace
|
||||
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="PortfolioManager.App"
|
||||
xmlns:local="using:PortfolioManager"
|
||||
RequestedThemeVariant="Default"
|
||||
>
|
||||
|
||||
|
||||
with
|
||||
<Application
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="PortfolioManager.App"
|
||||
xmlns:local="using:PortfolioManager"
|
||||
xmlns:theme="clr-namespace:Eremex.AvaloniaUI.Themes.DeltaDesign;assembly=Eremex.Avalonia.Themes.DeltaDesign"
|
||||
RequestedThemeVariant="Light">
|
||||
|
||||
16) dotnet add package LoadingIndicators.Avalonia --version 11.0.11.1
|
||||
https://www.nuget.org/packages/LoadingIndicators.Avalonia
|
||||
|
||||
|
||||
17) Creating Dialogs in Avalonia
|
||||
https://www.google.com/search?client=firefox-b-1-d&q=how+to+create+a+simple+dialog+in+avalonia
|
||||
|
||||
|
||||
18) dotnet add package Avalonia.ReactiveUI
|
||||
|
||||
Also Replace
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||
</Application.Styles>
|
||||
|
||||
|
||||
with
|
||||
|
||||
<Application.Styles>
|
||||
<theme:DeltaDesignTheme/>
|
||||
<!-- .... -->
|
||||
</Application.Styles>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
dotnet new avalonia.usercontrol -na [namespace] -n [name]
|
||||
dotnet new avalonia.window -na [namespace] -n [name]
|
||||
|
||||
dotnet new avalonia.window -na EditPositionDialog -n PortfolioManager.Dialog
|
||||
|
||||
|
||||
https://github.com/AvaloniaUI/avalonia-dotnet-templates
|
||||
|
||||
RelayCommands
|
||||
https://stackoverflow.com/questions/77978828/fire-relaycommand-from-datagrid-with-avaloniaui-and-community-toolkit-mvvm
|
||||
|
||||
|
||||
|
||||
|
||||
To install SkiaSharp native assets for ARM64, you'll need to ensure your project is set up to target the correct runtime identifier (RID) and then add the necessary NuGet packages. Specifically, you'll want to use the linux-arm64 RID and install the SkiaSharp native assets packages for Linux and specifically those without dependencies.
|
||||
Here's a step-by-step guide:
|
||||
|
||||
Set the target framework and RID:
|
||||
In your project file (e.g., MyProject.csproj), add or modify the <TargetFramework> and <RuntimeIdentifier> elements:
|
||||
|
||||
Code
|
||||
|
||||
<TargetFramework>net7.0</TargetFramework> <!-- Or your target framework -->
|
||||
<RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
|
||||
|
||||
1. Install the SkiaSharp native assets packages:
|
||||
Use the NuGet Package Manager to install the following packages:
|
||||
SkiaSharp.NativeAssets.Linux (or SkiaSharp.NativeAssets.Linux.NoDependencies if you prefer to not depend on external font config packages).
|
||||
|
||||
If you're using a custom runtime, you might also need SkiaSharp.NativeAssets.Linux.NoDependencies or SkiaSharp.NativeAssets.Linux.
|
||||
|
||||
2. Build and test:
|
||||
|
||||
Build your project and ensure that it compiles and runs correctly on your ARM64 target.
|
||||
You may need to adjust your Dockerfile or other deployment configuration to include the necessary Linux packages if using SkiaSharp.NativeAssets.Linux.
|
||||
|
||||
Important considerations:
|
||||
|
||||
Dependencies:
|
||||
If you choose SkiaSharp.NativeAssets.Linux, ensure your container or deployment environment has the necessary libraries (e.g., libfontconfig1, libGLX, etc.).
|
||||
|
||||
Linux Distributions:
|
||||
If you're targeting a specific Linux distribution, you might need to adjust the RID accordingly (e.g., linux-musl-arm64 for Alpine Linux).
|
||||
Containers:
|
||||
If you're running your application in a container, you'll need to include the SkiaSharp native assets within the container image.
|
||||
VS Code:
|
||||
If you're using VS Code and are deploying a container, make sure the workspace is in the container and that you can see the full file system structure of the container.
|
||||
|
||||
By following these steps, you can successfully install and use SkiaSharp's native assets on your ARM64 target
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/7929646/how-to-programmatically-select-a-tabitem-in-wpf-tabcontrol
|
||||
|
||||
|
||||
|
||||
<DataGrid>
|
||||
|
||||
<Interaction.Behaviors>
|
||||
<EventTriggerBehavior EventName="SelectionChanged">
|
||||
<InvokeCommandAction Command="{Binding SelectionChangedCommand}"></InvokeCommandAction>
|
||||
</EventTriggerBehavior>
|
||||
</Interaction.Behaviors>
|
||||
|
||||
...
|
||||
</DataGrid>
|
||||
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow:selected">
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Foreground" Value="Black" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="Opacity" Value=".50" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
33
PortfolioManager/UIUtils/BrushCollection.cs
Normal file
33
PortfolioManager/UIUtils/BrushCollection.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace PortfolioManager.UIUtils
|
||||
{
|
||||
public class BrushCollection
|
||||
{
|
||||
public enum BrushColor{Black=0,Red=1,Green=2,Blue=3,Yellow=4,LightGreen=5,Purple=6,Indigo=7,White=8,Cyan=9};
|
||||
public static Brush[] ContextBrushes={
|
||||
new SolidColorBrush(Color.FromRgb(0, 0, 0)),
|
||||
new SolidColorBrush(Color.FromRgb(255, 0, 0)),
|
||||
new SolidColorBrush(Color.FromRgb(0, 128, 0)),
|
||||
new SolidColorBrush(Color.FromRgb(0, 0, 255)),
|
||||
new SolidColorBrush(Color.FromRgb(255, 255, 0)),
|
||||
new SolidColorBrush(Color.FromRgb(144, 238, 144)),
|
||||
new SolidColorBrush(Color.FromRgb(128, 0, 128)),
|
||||
new SolidColorBrush(Color.FromRgb(75, 0, 130)),
|
||||
new SolidColorBrush(Color.FromRgb(255, 255, 255)),
|
||||
new SolidColorBrush(Color.FromRgb(0, 255, 255)),
|
||||
};
|
||||
private BrushCollection()
|
||||
{
|
||||
}
|
||||
public static IBrush GetContextBrush(BrushColor brushColor)
|
||||
{
|
||||
return ContextBrushes[(int)brushColor];
|
||||
}
|
||||
}
|
||||
}
|
||||
223
PortfolioManager/UIUtils/UIUtils.cs
Normal file
223
PortfolioManager/UIUtils/UIUtils.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia.Data.Converters;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
|
||||
namespace PortfolioManager.UIUtils
|
||||
{
|
||||
public class RMultipleValueConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
double doubleValue = (double)value;
|
||||
if (double.IsNaN(doubleValue) || double.IsInfinity(doubleValue)) return Constants.CONST_DASHES;
|
||||
return Utility.FormatNumber(doubleValue,2)+"R";
|
||||
}
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class DoubleValueConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
double doubleValue = (double)value;
|
||||
if (double.IsNaN(doubleValue) || double.IsInfinity(doubleValue)) return Constants.CONST_DASHES;
|
||||
if (null != parameter) return Utility.FormatNumber(doubleValue, int.Parse(parameter.ToString()));
|
||||
return Utility.FormatNumber(doubleValue, 4);
|
||||
}
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class CurrencyValueConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
double doubleValue = (double)value;
|
||||
if (double.IsNaN(doubleValue) || double.IsInfinity(doubleValue)) return Constants.CONST_DASHES;
|
||||
if (null != parameter) return Utility.FormatCurrency(doubleValue, int.Parse(parameter.ToString()));
|
||||
return Utility.FormatCurrency(doubleValue, 4);
|
||||
}
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class IntValueConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
int intValue = (int)value;
|
||||
if (int.MinValue.Equals(intValue)) return Constants.CONST_DASHES;
|
||||
if (null != parameter) return Utility.FormatNumber(intValue, int.Parse(parameter.ToString()), true);
|
||||
return Utility.FormatNumber(intValue, 2, true);
|
||||
}
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class BoolValueConverter:IValueConverter
|
||||
{
|
||||
public object Convert(object value,Type targetType,object parameter,System.Globalization.CultureInfo culture)
|
||||
{
|
||||
bool boolValue=(bool)value;
|
||||
if(null!=parameter)
|
||||
{
|
||||
if("0".Equals(parameter.ToString())) return boolValue?"T":"F";
|
||||
if("1".Equals(parameter.ToString())) return boolValue?"Y":"N";
|
||||
if("2".Equals(parameter.ToString())) return boolValue?"Yes":"No";
|
||||
if("3".Equals(parameter.ToString())) return boolValue?"True":"False";
|
||||
}
|
||||
return boolValue.ToString();
|
||||
}
|
||||
public object ConvertBack(object value,Type targetType,object parameter,System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class DateValueConverter:IValueConverter
|
||||
{
|
||||
public object Convert(object value,Type targetType,object parameter,System.Globalization.CultureInfo culture)
|
||||
{
|
||||
DateTime dateValue=(DateTime)value;
|
||||
if(Utility.IsEpoch(dateValue))return Constants.CONST_DASHES;
|
||||
return Utility.DateTimeToStringMMSDDSYYYY(dateValue);
|
||||
}
|
||||
public object ConvertBack(object value,Type targetType,object parameter,System.Globalization.CultureInfo culture)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// ********************************************************************
|
||||
public class SubStringConverter : IValueConverter
|
||||
{
|
||||
/// <summary> the zero-based starting character position </summary>
|
||||
public int StartIndex { get; set; }
|
||||
|
||||
/// <summary> The number of characters in the substring </summary>
|
||||
public int Length { get; set; }
|
||||
|
||||
/// <summary> shows "..." if value was truncated after StartIndex</summary>
|
||||
public bool ShowEllipse { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
string valueString = value as string;
|
||||
if (string.IsNullOrWhiteSpace(valueString) == false)
|
||||
{
|
||||
if (Length > 0 && Length < (valueString.Length + StartIndex))
|
||||
{
|
||||
if (ShowEllipse)
|
||||
return valueString.Substring(StartIndex, Length - 3) + "...";
|
||||
else
|
||||
return valueString.Substring(StartIndex, Length);
|
||||
}
|
||||
else if (StartIndex < valueString.Length)
|
||||
return valueString.Substring(StartIndex);
|
||||
else
|
||||
return ""; //because startIndex must be past the length of the string
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
public class TextMarkerOffsets
|
||||
{
|
||||
private static int MAX_HITS=12;
|
||||
private Dictionary<String,double> tradesBySymbolAndDate=new Dictionary<String,double>();
|
||||
private double defaultOffset=0.00;
|
||||
private double increment=-18;
|
||||
private int hits;
|
||||
|
||||
public TextMarkerOffsets()
|
||||
{
|
||||
hits=0;
|
||||
}
|
||||
public TextMarkerOffsets(double defaultOffset)
|
||||
{
|
||||
this.defaultOffset=defaultOffset;
|
||||
hits=0;
|
||||
}
|
||||
public TextMarkerOffsets(double defaultOffset,double increment)
|
||||
{
|
||||
this.defaultOffset=defaultOffset;
|
||||
this.increment=increment;
|
||||
this.hits=0;
|
||||
}
|
||||
public double GetOffset(PortfolioTrade portfolioTrade)
|
||||
{
|
||||
String key;
|
||||
double offset=defaultOffset;
|
||||
|
||||
if (portfolioTrade.IsOpen)
|
||||
{
|
||||
key = portfolioTrade.Symbol + portfolioTrade.TradeDate.ToShortDateString();
|
||||
}
|
||||
else
|
||||
{
|
||||
key = portfolioTrade.Symbol + portfolioTrade.SellDate.ToShortDateString();
|
||||
}
|
||||
if (tradesBySymbolAndDate.ContainsKey(key))
|
||||
{
|
||||
if(hits>MAX_HITS)increment=5;
|
||||
offset=tradesBySymbolAndDate[key]+increment;
|
||||
tradesBySymbolAndDate[key]=offset;
|
||||
hits++;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset=defaultOffset;
|
||||
tradesBySymbolAndDate.Add(key,defaultOffset);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
// public class MenuItemSorter : IComparer<System.Windows.Controls.MenuItem>
|
||||
// {
|
||||
// public int Compare(System.Windows.Controls.MenuItem v1,System.Windows.Controls.MenuItem v2)
|
||||
// {
|
||||
// return v1.Header.ToString().CompareTo(v2.Header.ToString());
|
||||
// }
|
||||
// }
|
||||
// public static class UIServices
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// A value indicating whether the UI is currently busy
|
||||
// /// </summary>
|
||||
// //private static bool isBusy;
|
||||
|
||||
// /// <summary>
|
||||
// /// Sets the busystate as busy.
|
||||
// /// </summary>
|
||||
// public static void ClearProperty(int fromSeconds, EventHandler dispatchEventHandler)
|
||||
// {
|
||||
// new DispatcherTimer(TimeSpan.FromSeconds(fromSeconds), DispatcherPriority.ApplicationIdle, dispatchEventHandler, Application.Current.Dispatcher);
|
||||
// }
|
||||
// public static void SortMenuItems(ObservableCollection<System.Windows.Controls.MenuItem> menuCollection)
|
||||
// {
|
||||
// List<System.Windows.Controls.MenuItem> items=new List<System.Windows.Controls.MenuItem>();
|
||||
// foreach(System.Windows.Controls.MenuItem item in menuCollection)items.Add(item);
|
||||
// items.Sort(new MenuItemSorter());
|
||||
// menuCollection.Clear();
|
||||
// foreach(System.Windows.Controls.MenuItem item in items)menuCollection.Add(item);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
32
PortfolioManager/ViewLocator.cs
Normal file
32
PortfolioManager/ViewLocator.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using PortfolioManager.ViewModels;
|
||||
|
||||
namespace PortfolioManager;
|
||||
|
||||
public class ViewLocator : IDataTemplate
|
||||
{
|
||||
|
||||
public Control Build(object param)
|
||||
{
|
||||
if (param is null)
|
||||
return null;
|
||||
|
||||
var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
|
||||
var type = Type.GetType(name);
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
return (Control)Activator.CreateInstance(type)!;
|
||||
}
|
||||
|
||||
return new TextBlock { Text = "Not Found: " + name };
|
||||
}
|
||||
|
||||
public bool Match(object data)
|
||||
{
|
||||
if (null == data) return false;
|
||||
return data is ViewModelBase;
|
||||
}
|
||||
}
|
||||
609
PortfolioManager/ViewModels/CMMomentumViewModel.cs
Normal file
609
PortfolioManager/ViewModels/CMMomentumViewModel.cs
Normal file
@@ -0,0 +1,609 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Eremex.AvaloniaUI.Controls;
|
||||
using MarketData;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Generator;
|
||||
using MarketData.Generator.CMMomentum;
|
||||
using MarketData.Generator.Interface;
|
||||
using MarketData.Generator.Model;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.DataSeriesViewModels;
|
||||
using PortfolioManager.Dialogs;
|
||||
using PortfolioManager.Models;
|
||||
using PortfolioManager.UIUtils;
|
||||
using Position = MarketData.Generator.CMMomentum.Position;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public partial class CMMomentumViewModel : WorkspaceViewModel
|
||||
{
|
||||
private bool isBusy = false;
|
||||
private CMSessionParams sessionParams;
|
||||
private CMParams cmParams;
|
||||
private ObservableCollection<String> nvpDictionaryKeys = default;
|
||||
private NVPDictionary nvpDictionary = null;
|
||||
private String selectedParameter = null;
|
||||
private String pathFileName;
|
||||
private String initialPath;
|
||||
private ModelPerformanceSeries modelPerformanceSeries = null;
|
||||
private ModelStatistics modelStatistics = null;
|
||||
private bool showAsGainLoss = true;
|
||||
private CMPositionModelCollection positions = null;
|
||||
private CMPositionModel selectedPosition = null;
|
||||
private bool showMarkers = false;
|
||||
|
||||
public CMMomentumViewModel()
|
||||
{
|
||||
DisplayName = "CMMomentum Model";
|
||||
PropertyChanged += OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public override String Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
public override String DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == pathFileName) return "CMMomentum Model";
|
||||
String pureFileName = Utility.GetFileNameNoExtension(pathFileName);
|
||||
return "CMMomentum Model (" + pureFileName + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get
|
||||
{
|
||||
return isBusy;
|
||||
}
|
||||
set
|
||||
{
|
||||
isBusy = value;
|
||||
base.OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowMarkers
|
||||
{
|
||||
get
|
||||
{
|
||||
return showMarkers;
|
||||
}
|
||||
set
|
||||
{
|
||||
showMarkers = value;
|
||||
base.OnPropertyChanged("ShowMarkers");
|
||||
}
|
||||
}
|
||||
|
||||
public String TradeDate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
return Utility.DateTimeToStringMMSDDSYYYY(dateGenerator.FindPrevBusinessDay(sessionParams.TradeDate));
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<String> Parameters
|
||||
{
|
||||
get { return nvpDictionaryKeys; }
|
||||
}
|
||||
|
||||
public String SelectedParameter
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedParameter;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedParameter = value;
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
}
|
||||
}
|
||||
|
||||
public String ParameterValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == nvpDictionary || null == nvpDictionaryKeys || null == selectedParameter) return null;
|
||||
return nvpDictionary[selectedParameter].Value;
|
||||
}
|
||||
}
|
||||
|
||||
public String CashBalance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.CashBalance);
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<MenuItem> PositionsMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
ObservableCollection<MenuItem> collection = new ObservableCollection<MenuItem>();
|
||||
collection.Add(new MenuItem() { Header = "Close Position...", Command = CloseCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Edit Position...", Command = EditCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Add To WatchList", Command = AddToWatchListCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Remove From WatchList", Command = RemoveFromWatchListCommand, StaysOpenOnClick = false });
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReloadEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return !String.IsNullOrEmpty(pathFileName);
|
||||
}
|
||||
}
|
||||
|
||||
public CMPositionModelCollection AllPositions
|
||||
{
|
||||
get { return positions; }
|
||||
}
|
||||
|
||||
public String NonTradeableCash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.NonTradeableCash);
|
||||
}
|
||||
}
|
||||
|
||||
public String PercentButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!showAsGainLoss) return "Show $";
|
||||
else return "Show %";
|
||||
}
|
||||
}
|
||||
|
||||
public String ModelExpectation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return "";
|
||||
return Utility.FormatNumber(modelStatistics.Expectancy, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush ExpectationColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
if (modelStatistics.Expectancy > 0.00) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public CMPositionModel SelectedPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedPosition;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedPosition = value;
|
||||
base.OnPropertyChanged("SelectedPosition");
|
||||
}
|
||||
}
|
||||
|
||||
public CompositeDataSource Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelPerformanceSeries) return GainLossModel.Empty();
|
||||
CompositeDataSource compositeDataSource = null;
|
||||
compositeDataSource = GainLossModel.GainLoss(modelPerformanceSeries, showAsGainLoss);
|
||||
return compositeDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
public String GraphTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams || null == modelPerformanceSeries) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Positions allPositions = sessionParams.GetCombinedPositions();
|
||||
DateTime minDate = allPositions.Min(x => x.PurchaseDate);
|
||||
DateTime maxDate = PricingDA.GetLatestDate();
|
||||
|
||||
if (modelPerformanceSeries.Count < 2)
|
||||
{
|
||||
sb.Append(showAsGainLoss ? "$ GainLoss" : "% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(showAsGainLoss ? Utility.FormatCurrency(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss) : Utility.FormatPercent(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne));
|
||||
return sb.ToString();
|
||||
}
|
||||
if (showAsGainLoss)
|
||||
{
|
||||
double latestGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss;
|
||||
double change = modelPerformanceSeries[modelPerformanceSeries.Count - 1].GainLossDOD;
|
||||
sb.Append("$ GainLoss");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatCurrency(latestGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatCurrency(change));
|
||||
}
|
||||
else
|
||||
{
|
||||
double latestCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne;
|
||||
double prevCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 2].CumProdMinusOne;
|
||||
double change = latestCumGainLoss - prevCumGainLoss;
|
||||
sb.Append("% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatPercent(latestCumGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatPercent(change));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// *********************************************************** R E L A Y ***************************************************************
|
||||
|
||||
[RelayCommand]
|
||||
public async Task LoadFile()
|
||||
{
|
||||
await LoadTradeFile();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task Reload()
|
||||
{
|
||||
await ReloadTradeFile();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanClosePosition))]
|
||||
public async Task Close()
|
||||
{
|
||||
await OpenCloseDialog();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanEdit))]
|
||||
public async Task Edit()
|
||||
{
|
||||
await OpenEditDialog();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ToggleReturnOrPercent()
|
||||
{
|
||||
HandleToggleReturnOrPercent();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanAddToWatchList))]
|
||||
public async Task AddToWatchList()
|
||||
{
|
||||
WatchListDA.AddToWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Added {selectedPosition.Symbol} to WatchList", "Add To WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanRemoveFromWatchList))]
|
||||
public async Task RemoveFromWatchList()
|
||||
{
|
||||
WatchListDA.RemoveFromWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Removed {selectedPosition.Symbol} from WatchList", "Remove From WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
// ***************************************************************************************************************************************
|
||||
|
||||
public async Task OpenCloseDialog()
|
||||
{
|
||||
IPurePosition clonedPosition = Position.Clone(selectedPosition.Position);
|
||||
ClosePositionDialog dialog = new ClosePositionDialog();
|
||||
ClosePositionDialogViewModel closePositionViewModel = new ClosePositionDialogViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = closePositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
if (!closePositionViewModel.IsSuccess) return;
|
||||
CMMomentumBacktest momentumModel = new CMMomentumBacktest();
|
||||
if (!momentumModel.ClosePosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.SellDate, clonedPosition.CurrentPrice, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to close the position, check log for details.", "Close Position");
|
||||
return;
|
||||
}
|
||||
String strMessage = String.Format("Closed position for {0}, Purchase Date:{1}, Sell Date{2}, Current Price:{3}. Saved to {4}. A backup was created.",
|
||||
clonedPosition.Symbol,
|
||||
clonedPosition.PurchaseDate.ToShortDateString(),
|
||||
clonedPosition.SellDate.ToShortDateString(),
|
||||
Utility.FormatCurrency(clonedPosition.CurrentPrice),
|
||||
pathFileName);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Close Position");
|
||||
LoadSessionFile();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task OpenEditDialog()
|
||||
{
|
||||
EditPositionDialogNoStop dialog = new EditPositionDialogNoStop();
|
||||
Position clonedPosition = Position.Clone(selectedPosition.Position);
|
||||
EditPositionDialogNoStopViewModel editPositionViewModel = new EditPositionDialogNoStopViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = editPositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
GetTopLevelWindow().BringIntoView();
|
||||
if (!editPositionViewModel.IsSuccess) return;
|
||||
CMMomentumBacktest momentumBacktest = new CMMomentumBacktest();
|
||||
if (!momentumBacktest.EditPosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.PurchasePrice, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to edit the position, check log for details.", "Edit Position");
|
||||
return;
|
||||
}
|
||||
selectedPosition.PurchaseDate = clonedPosition.PurchaseDate;
|
||||
selectedPosition.PurchasePrice = clonedPosition.PurchasePrice;
|
||||
String strMessage = String.Format("Edited Position for {0} Purchase Date:{1} Purchase Price:{2}. A backup was created.",
|
||||
selectedPosition.Symbol, selectedPosition.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(selectedPosition.PurchasePrice));
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Edit Position");
|
||||
LoadSessionFile();
|
||||
}
|
||||
|
||||
private void HandleToggleReturnOrPercent()
|
||||
{
|
||||
showAsGainLoss = !showAsGainLoss;
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("PercentButtonText");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
public bool CanAddToWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
return WatchListDA.IsInWatchList(selectedPosition.Symbol) ? false : true;
|
||||
}
|
||||
|
||||
public bool CanClosePosition()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanEdit()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol || !Utility.IsEpoch(selectedPosition.SellDate)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanRemoveFromWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
if (!WatchListDA.IsInWatchList(selectedPosition.Symbol)) return false;
|
||||
PortfolioTrades portfolioTrades = PortfolioDA.GetOpenTrades();
|
||||
if (portfolioTrades.Any(x => x.Symbol.Equals(selectedPosition.Symbol))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task ReloadTradeFile()
|
||||
{
|
||||
LoadSessionFile();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task LoadTradeFile()
|
||||
{
|
||||
TopLevel topLevel = TopLevel.GetTopLevel(GetTopLevelWindow());
|
||||
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Open Trade File",
|
||||
AllowMultiple = false,
|
||||
});
|
||||
IStorageFile storageFile = files.FirstOrDefault();
|
||||
if (null == storageFile) return;
|
||||
Uri uri = storageFile.Path;
|
||||
pathFileName = uri.LocalPath;
|
||||
if (null == pathFileName) return;
|
||||
if (!CMSessionManager.IsValidSessionFile(pathFileName))
|
||||
{
|
||||
pathFileName = null;
|
||||
}
|
||||
else LoadSessionFile();
|
||||
}
|
||||
|
||||
public bool LoadSessionFile()
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!CMSessionManager.IsValidSessionFile(pathFileName)) return false;
|
||||
initialPath = Path.GetDirectoryName(pathFileName);
|
||||
sessionParams = CMSessionManager.RestoreSession(pathFileName);
|
||||
if (null == sessionParams) { MxMessageBox.Show(GetTopLevelWindow(), String.Format("Unable to open {0}", pathFileName)); pathFileName = null; return false; }
|
||||
modelStatistics = CMMomentumBacktest.GetModelStatistics(sessionParams);
|
||||
modelPerformanceSeries = CMMomentumBacktest.GetModelPerformance(sessionParams);
|
||||
cmParams = sessionParams.CMParams;
|
||||
NVPCollection nvpCollection = sessionParams.CMParams.ToNVPCollection();
|
||||
nvpDictionary = nvpCollection.ToDictionary();
|
||||
List<String> dictionaryKeys = new List<String>(nvpDictionary.Keys);
|
||||
dictionaryKeys.Sort();
|
||||
nvpDictionaryKeys = new ObservableCollection<String>(dictionaryKeys);
|
||||
selectedParameter = nvpDictionaryKeys[0];
|
||||
positions = new CMPositionModelCollection();
|
||||
positions.Add(sessionParams.ActivePositions);
|
||||
positions.Add(sessionParams.AllPositions);
|
||||
UpdatePositionPrices(false);
|
||||
UpdatePositionRSI3(true);
|
||||
RunPerformance();
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), String.Format("Exception {0}", exception.ToString()), "Error");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
workerTask.ContinueWith(continuation =>
|
||||
{
|
||||
IsBusy = false;
|
||||
base.OnPropertyChanged("Parameters");
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
base.OnPropertyChanged("Title");
|
||||
base.OnPropertyChanged("DisplayName");
|
||||
base.OnPropertyChanged("AllPositions");
|
||||
// base.OnPropertyChanged("CanMonitor");
|
||||
base.OnPropertyChanged("CashBalance");
|
||||
base.OnPropertyChanged("NonTradeableCash");
|
||||
base.OnPropertyChanged("ModelExpectation");
|
||||
base.OnPropertyChanged("ExpectationColor");
|
||||
base.OnPropertyChanged("ExpectationDescription");
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdatePositionPrices(bool change = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
DateTime today = DateTime.Now;
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from CMPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from CMPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (CMPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
Price price = PricingDA.GetPrice(symbol);
|
||||
if (null == price) continue;
|
||||
selectedPosition.CurrentPrice = price.Close;
|
||||
selectedPosition.LastUpdated = price.Date.Date < today.Date ? price.Date : today;
|
||||
}
|
||||
}
|
||||
if (change) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePositionRSI3(bool change = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from CMPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from CMPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (CMPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
RSICollection rsiCollection = RSIGenerator.GenerateRSI(symbol, 30, 3);
|
||||
if (null == rsiCollection || 0 == rsiCollection.Count) continue;
|
||||
selectedPosition.RSI3 = rsiCollection[rsiCollection.Count - 1].RSI;
|
||||
}
|
||||
}
|
||||
if (change) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void RunPerformance()
|
||||
{
|
||||
if (null == sessionParams) return;
|
||||
modelPerformanceSeries = CMMomentumBacktest.GetModelPerformance(sessionParams);
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
// ********************************************************** P E R S I S T E N C E ******************************************************
|
||||
|
||||
public override bool CanPersist()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override SaveParameters GetSaveParameters()
|
||||
{
|
||||
SaveParameters saveParams = new SaveParameters();
|
||||
if (null == pathFileName) return null;
|
||||
saveParams.Add(new KeyValuePair<String, String>("Type", GetType().Namespace + "." + GetType().Name));
|
||||
saveParams.Add(new KeyValuePair<String, String>("PathFileName", pathFileName));
|
||||
return saveParams;
|
||||
}
|
||||
|
||||
public override void SetSaveParameters(SaveParameters saveParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
pathFileName = (from KeyValuePair<String, String> item in saveParameters where item.Key.Equals("PathFileName") select item).FirstOrDefault().Value;
|
||||
if (!LoadSessionFile()) pathFileName = null;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Exception:{0}", exception.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************************** T O O L T I P S *************************************************
|
||||
|
||||
public String ExpectationDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if(null==modelStatistics) return "";
|
||||
StringBuilder sb=new StringBuilder();
|
||||
sb.Append("Expectancy is (percentage of winning trades * average gain) / (percentage of losing trades * average loss).").Append("\n");
|
||||
sb.Append("Total Trades : ").Append(modelStatistics.TotalTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(modelStatistics.WinningTrades).Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(modelStatistics.LosingTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(Utility.FormatNumber(modelStatistics.WinningTradesPercent,2)).Append("%").Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(Utility.FormatNumber(modelStatistics.LosingTradesPercent,2)).Append("%").Append("\n");
|
||||
sb.Append("Average Winning Trade Gain : ").Append(Utility.FormatNumber(modelStatistics.AverageWinningTradePercentGain,2)).Append("%").Append("\n");
|
||||
sb.Append("Average Losing Trade Loss : ").Append(Utility.FormatNumber(modelStatistics.AverageLosingTradePercentLoss,2)).Append("%").Append("\n");
|
||||
sb.Append("Expectancy : ").Append(Utility.FormatNumber(modelStatistics.Expectancy,2)).Append("\n");
|
||||
sb.Append("\n");
|
||||
sb.Append("Maintain a positive Expectancy and you're a winner.");
|
||||
sb.Append("\n");
|
||||
sb.Append("The calculations are based on closed positions.");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
621
PortfolioManager/ViewModels/CMTrendViewModel.cs
Normal file
621
PortfolioManager/ViewModels/CMTrendViewModel.cs
Normal file
@@ -0,0 +1,621 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Eremex.AvaloniaUI.Controls;
|
||||
using MarketData;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Generator.CMTrend;
|
||||
using MarketData.Generator.Interface;
|
||||
using MarketData.Generator.Model;
|
||||
using MarketData.Generator.ModelGenerators;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.DataSeriesViewModels;
|
||||
using PortfolioManager.Dialogs;
|
||||
using PortfolioManager.Models;
|
||||
using PortfolioManager.UIUtils;
|
||||
using Position=MarketData.Generator.CMTrend.Position;
|
||||
using StopLimit = MarketData.MarketDataModel.StopLimit;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public partial class CMTrendViewModel : WorkspaceViewModel
|
||||
{
|
||||
private bool isBusy = false;
|
||||
private bool showMarkers = false;
|
||||
private ModelPerformanceSeries modelPerformanceSeries = null;
|
||||
private ModelStatistics modelStatistics = null;
|
||||
private bool showAsGainLoss = true;
|
||||
private CMTPositionModelCollection positions = null;
|
||||
private CMTPositionModel selectedPosition = null;
|
||||
private CMTParams configuration = null;
|
||||
private String pathFileName = null;
|
||||
private String initialPath = null;
|
||||
private CMTSessionParams sessionParams;
|
||||
private NVPDictionary nvpDictionary = null;
|
||||
private ObservableCollection<String> nvpDictionaryKeys = null;
|
||||
private String selectedParameter = null;
|
||||
|
||||
public CMTrendViewModel()
|
||||
{
|
||||
DisplayName = "CMTrend Model";
|
||||
PropertyChanged += OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
protected override void OnDispose()
|
||||
{
|
||||
base.OnDispose();
|
||||
}
|
||||
|
||||
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get
|
||||
{
|
||||
return isBusy;
|
||||
}
|
||||
set
|
||||
{
|
||||
isBusy = value;
|
||||
base.OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool ShowMarkers
|
||||
{
|
||||
get
|
||||
{
|
||||
return showMarkers;
|
||||
}
|
||||
set
|
||||
{
|
||||
showMarkers = value;
|
||||
base.OnPropertyChanged("ShowMarkers");
|
||||
}
|
||||
}
|
||||
|
||||
public CompositeDataSource Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelPerformanceSeries) return GainLossModel.Empty();
|
||||
CompositeDataSource compositeDataSource = null;
|
||||
compositeDataSource = GainLossModel.GainLoss(modelPerformanceSeries, showAsGainLoss);
|
||||
return compositeDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
public String PercentButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!showAsGainLoss) return "Show $";
|
||||
else return "Show %";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ObservableCollection<String> Parameters
|
||||
{
|
||||
get { return nvpDictionaryKeys; }
|
||||
}
|
||||
|
||||
public String SelectedParameter
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedParameter;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedParameter = value;
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
}
|
||||
}
|
||||
|
||||
public String ParameterValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == nvpDictionary || null == nvpDictionaryKeys || null == selectedParameter) return null;
|
||||
return nvpDictionary[selectedParameter].Value;
|
||||
}
|
||||
}
|
||||
|
||||
public String TradeDate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
return Utility.DateTimeToStringMMSDDSYYYY(sessionParams.TradeDate);
|
||||
}
|
||||
}
|
||||
|
||||
public String CashBalance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.CashBalance);
|
||||
}
|
||||
}
|
||||
|
||||
public String NonTradeableCash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.NonTradeableCash);
|
||||
}
|
||||
}
|
||||
|
||||
public String ModelExpectation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return "";
|
||||
return Utility.FormatNumber(modelStatistics.Expectancy, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush ExpectationColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
if (modelStatistics.Expectancy > 0.00) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public String GraphTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams || null == modelPerformanceSeries) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Positions allPositions = sessionParams.GetCombinedPositions();
|
||||
DateTime minDate = allPositions.Min(x => x.PurchaseDate);
|
||||
DateTime maxDate = PricingDA.GetLatestDate();
|
||||
|
||||
if (modelPerformanceSeries.Count < 2)
|
||||
{
|
||||
sb.Append(showAsGainLoss ? "$ GainLoss" : "% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(showAsGainLoss ? Utility.FormatCurrency(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss) : Utility.FormatPercent(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne));
|
||||
return sb.ToString();
|
||||
}
|
||||
if (showAsGainLoss)
|
||||
{
|
||||
double latestGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss;
|
||||
double change = modelPerformanceSeries[modelPerformanceSeries.Count - 1].GainLossDOD;
|
||||
sb.Append("$ GainLoss");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatCurrency(latestGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatCurrency(change));
|
||||
}
|
||||
else
|
||||
{
|
||||
double latestCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne;
|
||||
double prevCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 2].CumProdMinusOne;
|
||||
double change = latestCumGainLoss - prevCumGainLoss;
|
||||
sb.Append("% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatPercent(latestCumGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatPercent(change));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************************************************
|
||||
public ObservableCollection<MenuItem> PositionsMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
ObservableCollection<MenuItem> collection = new ObservableCollection<MenuItem>();
|
||||
collection.Add(new MenuItem() { Header = "Close Position...", Command = CloseCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Edit Position...", Command = EditCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Add To WatchList", Command = AddToWatchListCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Remove From WatchList", Command = RemoveFromWatchListCommand, StaysOpenOnClick = false });
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ********************************************************************* R E L A Y S *****************************************************************
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanClosePosition))]
|
||||
public async Task Close()
|
||||
{
|
||||
await OpenCloseDialog();
|
||||
}
|
||||
|
||||
public bool CanClosePosition()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanEdit))]
|
||||
public async Task Edit()
|
||||
{
|
||||
await OpenEditDialog();
|
||||
}
|
||||
|
||||
public bool CanEdit()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol || !Utility.IsEpoch(selectedPosition.SellDate)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ToggleReturnOrPercent()
|
||||
{
|
||||
HandleToggleReturnOrPercent();
|
||||
}
|
||||
|
||||
// This is not currently being displayed
|
||||
// [RelayCommand]
|
||||
// public void Run()
|
||||
// {
|
||||
// RunCandidateGenerator();
|
||||
// }
|
||||
|
||||
[RelayCommand]
|
||||
public async Task LoadFile()
|
||||
{
|
||||
await LoadTradeFile();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task Reload()
|
||||
{
|
||||
await ReloadTradeFile();
|
||||
}
|
||||
|
||||
public async Task ReloadTradeFile()
|
||||
{
|
||||
LoadSessionFile();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanAddToWatchList))]
|
||||
public async Task AddToWatchList()
|
||||
{
|
||||
WatchListDA.AddToWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Added {selectedPosition.Symbol} to WatchList", "Add To WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public bool CanAddToWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
return WatchListDA.IsInWatchList(selectedPosition.Symbol) ? false : true;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanRemoveFromWatchList))]
|
||||
public async Task RemoveFromWatchList()
|
||||
{
|
||||
WatchListDA.RemoveFromWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Removed {selectedPosition.Symbol} from WatchList", "Remove From WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public bool CanRemoveFromWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
if (!WatchListDA.IsInWatchList(selectedPosition.Symbol)) return false;
|
||||
PortfolioTrades portfolioTrades = PortfolioDA.GetOpenTrades();
|
||||
if (portfolioTrades.Any(x => x.Symbol.Equals(selectedPosition.Symbol))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void HandleToggleReturnOrPercent()
|
||||
{
|
||||
showAsGainLoss = !showAsGainLoss;
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("PercentButtonText");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
public bool ReloadEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return !String.IsNullOrEmpty(pathFileName);
|
||||
}
|
||||
}
|
||||
|
||||
public CMTPositionModelCollection AllPositions
|
||||
{
|
||||
get { return positions; }
|
||||
}
|
||||
|
||||
public override String DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == pathFileName) return "CMTrend Model";
|
||||
String pureFileName = Utility.GetFileNameNoExtension(pathFileName);
|
||||
return "CMTrend Model (" + pureFileName + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public override String Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ***********************************************************************************************************************************
|
||||
#region Operations
|
||||
|
||||
public async Task OpenCloseDialog()
|
||||
{
|
||||
bool deleteStop = false;
|
||||
IPosition clonedPosition = Position.Clone(selectedPosition.Position);
|
||||
// bool hasStopLimit = PortfolioDA.HasStopLimit(clonedPosition.Symbol);
|
||||
|
||||
ClosePositionDialog dialog = new ClosePositionDialog();
|
||||
ClosePositionDialogViewModel closePositionViewModel = new ClosePositionDialogViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = closePositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
if (!closePositionViewModel.IsSuccess) return;
|
||||
CMTTrendModel mmTrendModel = new CMTTrendModel();
|
||||
if (!mmTrendModel.ClosePosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.SellDate, clonedPosition.CurrentPrice, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to close the position, check log for details.", "Close Position");
|
||||
return;
|
||||
}
|
||||
// if (deleteStop) PortfolioDA.DeleteStopLimit(clonedPosition.Symbol);
|
||||
String strMessage = String.Format("Closed position for {0}, Purchase Date:{1}, Sell Date{2}, Current Price:{3}, Delete Stop:{4} to {5}. A backup was created.",
|
||||
clonedPosition.Symbol, clonedPosition.PurchaseDate.ToShortDateString(), clonedPosition.SellDate.ToShortDateString(), Utility.FormatCurrency(clonedPosition.CurrentPrice), deleteStop, pathFileName);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Close Position");
|
||||
LoadSessionFile();
|
||||
}
|
||||
|
||||
public async Task OpenEditDialog()
|
||||
{
|
||||
EditPositionDialog dialog = new EditPositionDialog();
|
||||
IPosition clonedPosition = Position.Clone(selectedPosition.Position);
|
||||
EditPositionDialogViewModel editPositionViewModel = new EditPositionDialogViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = editPositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
GetTopLevelWindow().BringIntoView();
|
||||
if (!editPositionViewModel.IsSuccess) return;
|
||||
CMTTrendModel mmTrendModel = new CMTTrendModel();
|
||||
if (!mmTrendModel.EditPosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.PurchasePrice, clonedPosition.InitialStopLimit, clonedPosition.TrailingStopLimit, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to edit the position, check log for details.", "Edit Position");
|
||||
return;
|
||||
}
|
||||
if (!selectedPosition.TrailingStopLimit.Equals(clonedPosition.TrailingStopLimit))
|
||||
{
|
||||
selectedPosition.TrailingStopLimit = clonedPosition.TrailingStopLimit;
|
||||
selectedPosition.LastStopAdjustment = DateTime.Now.Date;
|
||||
}
|
||||
selectedPosition.PurchaseDate = clonedPosition.PurchaseDate;
|
||||
selectedPosition.PurchasePrice = clonedPosition.PurchasePrice;
|
||||
selectedPosition.TrailingStopLimit = clonedPosition.TrailingStopLimit;
|
||||
selectedPosition.InitialStopLimit = clonedPosition.InitialStopLimit;
|
||||
|
||||
String strMessage = String.Format("Edited Position for {0} Purchase Date:{1} Purchase Price:{2}, Trailing Stop:{3}. A backup was created.",
|
||||
selectedPosition.Symbol, selectedPosition.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(selectedPosition.PurchasePrice), Utility.FormatCurrency(selectedPosition.TrailingStopLimit));
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Edit Position");
|
||||
LoadSessionFile();
|
||||
}
|
||||
|
||||
public async Task LoadTradeFile()
|
||||
{
|
||||
TopLevel topLevel = TopLevel.GetTopLevel(GetTopLevelWindow());
|
||||
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Open Trade File",
|
||||
AllowMultiple = false,
|
||||
});
|
||||
IStorageFile storageFile = files.FirstOrDefault();
|
||||
if (null == storageFile) return;
|
||||
Uri uri = storageFile.Path;
|
||||
pathFileName = uri.LocalPath;
|
||||
|
||||
if (null == pathFileName) return;
|
||||
if (!CMTSessionManager.IsValidSessionFile(pathFileName))
|
||||
{
|
||||
pathFileName = null;
|
||||
}
|
||||
else LoadSessionFile();
|
||||
}
|
||||
|
||||
public bool LoadSessionFile()
|
||||
{
|
||||
IsBusy = true;
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!CMTSessionManager.IsValidSessionFile(pathFileName)) return false;
|
||||
initialPath = Path.GetDirectoryName(pathFileName);
|
||||
sessionParams = CMTSessionManager.RestoreSession(pathFileName);
|
||||
if (null == sessionParams)
|
||||
{
|
||||
pathFileName = null;
|
||||
return false;
|
||||
}
|
||||
modelStatistics = CMTTrendModel.GetModelStatistics(sessionParams);
|
||||
modelPerformanceSeries = CMTTrendModel.GetModelPerformance(sessionParams);
|
||||
configuration = sessionParams.CMTParams;
|
||||
NVPCollection nvpCollection = sessionParams.CMTParams.ToNVPCollection();
|
||||
nvpDictionary = nvpCollection.ToDictionary();
|
||||
List<String> dictionaryKeys = new List<String>(nvpDictionary.Keys);
|
||||
dictionaryKeys.Sort();
|
||||
nvpDictionaryKeys = new ObservableCollection<String>(dictionaryKeys);
|
||||
selectedParameter = nvpDictionaryKeys[0];
|
||||
positions = new CMTPositionModelCollection();
|
||||
positions.Add(sessionParams.ActivePositions);
|
||||
positions.Add(sessionParams.AllPositions);
|
||||
UpdatePositionPrices(false);
|
||||
RunPerformance();
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
IsBusy = false;
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
pathFileName = null;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
workerTask.ContinueWith(continuation =>
|
||||
{
|
||||
IsBusy = false;
|
||||
base.OnPropertyChanged("Parameters");
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
base.OnPropertyChanged("Title");
|
||||
base.OnPropertyChanged("DisplayName");
|
||||
base.OnPropertyChanged("AllPositions");
|
||||
base.OnPropertyChanged("CashBalance");
|
||||
base.OnPropertyChanged("NonTradeableCash");
|
||||
base.OnPropertyChanged("ModelExpectation");
|
||||
base.OnPropertyChanged("ExpectationColor");
|
||||
base.OnPropertyChanged("ExpectationDescription");
|
||||
base.OnPropertyChanged("ReloadEnabled");
|
||||
base.OnPropertyChanged("TradeDate");
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RunPerformance()
|
||||
{
|
||||
if (null == sessionParams) return;
|
||||
modelPerformanceSeries = CMTTrendModel.GetModelPerformance(sessionParams);
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
private void UpdatePositionPrices(bool signalChangeEvent = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
DateTime today = DateTime.Now;
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from CMTPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from CMTPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (CMTPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
Price price = PricingDA.GetPrice(symbol);
|
||||
if (null == price) continue;
|
||||
selectedPosition.CurrentPrice = price.Close;
|
||||
selectedPosition.CurrentPriceLow = price.Low;
|
||||
selectedPosition.LastUpdated = price.Date.Date < today.Date ? price.Date : today;
|
||||
selectedPosition.EdgeRatio = EdgeRatioGenerator.CalculateEdgeRatio(symbol, selectedPosition.PurchaseDate, selectedPosition.PurchasePrice, price.Date).EdgeRatio;
|
||||
}
|
||||
}
|
||||
if (signalChangeEvent) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public CMTPositionModel SelectedPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedPosition;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedPosition = value;
|
||||
base.OnPropertyChanged("SelectedPosition");
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************************************** P E R S I S T E N C E ********************************************************
|
||||
public override bool CanPersist()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override SaveParameters GetSaveParameters()
|
||||
{
|
||||
SaveParameters saveParams = new SaveParameters();
|
||||
if (null == pathFileName) return null;
|
||||
saveParams.Add(new KeyValuePair<String, String>("Type", GetType().Namespace + "." + GetType().Name));
|
||||
saveParams.Add(new KeyValuePair<String, String>("PathFileName", pathFileName));
|
||||
return saveParams;
|
||||
}
|
||||
|
||||
public override void SetSaveParameters(SaveParameters saveParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
pathFileName = (from KeyValuePair<String, String> item in saveParameters where item.Key.Equals("PathFileName") select item).FirstOrDefault().Value;
|
||||
if (!LoadSessionFile()) pathFileName = null;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Exception:{0}", exception.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************************** T O O L T I P S *************************************************
|
||||
|
||||
public String ExpectationDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if(null==modelStatistics) return "";
|
||||
StringBuilder sb=new StringBuilder();
|
||||
sb.Append("Expectancy is (percentage of winning trades * average gain) / (percentage of losing trades * average loss).").Append("\n");
|
||||
sb.Append("Total Trades : ").Append(modelStatistics.TotalTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(modelStatistics.WinningTrades).Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(modelStatistics.LosingTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(Utility.FormatNumber(modelStatistics.WinningTradesPercent,2)).Append("%").Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(Utility.FormatNumber(modelStatistics.LosingTradesPercent,2)).Append("%").Append("\n");
|
||||
sb.Append("Average Winning Trade Gain : ").Append(Utility.FormatNumber(modelStatistics.AverageWinningTradePercentGain,2)).Append("%").Append("\n");
|
||||
sb.Append("Average Losing Trade Loss : ").Append(Utility.FormatNumber(modelStatistics.AverageLosingTradePercentLoss,2)).Append("%").Append("\n");
|
||||
sb.Append("Expectancy : ").Append(Utility.FormatNumber(modelStatistics.Expectancy,2)).Append("\n");
|
||||
sb.Append("\n");
|
||||
sb.Append("Maintain a positive Expectancy and you're a winner.");
|
||||
sb.Append("\n");
|
||||
sb.Append("The calculations are based on closed positions.");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
PortfolioManager/ViewModels/CommandViewModel.cs
Normal file
37
PortfolioManager/ViewModels/CommandViewModel.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Input;
|
||||
using System.Text;
|
||||
using PortfolioManager.ViewModels;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public class CommandViewModel : ViewModelBase
|
||||
{
|
||||
public CommandViewModel(string displayName, ICommand command)
|
||||
{
|
||||
if (command == null) throw new ArgumentNullException("command");
|
||||
base.DisplayName = displayName;
|
||||
this.Command = command;
|
||||
}
|
||||
|
||||
public ICommand Command
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
public override SaveParameters GetSaveParameters()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void SetSaveParameters(SaveParameters saveParameters)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool CanPersist()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
PortfolioManager/ViewModels/DialogViewModelBase.cs
Normal file
45
PortfolioManager/ViewModels/DialogViewModelBase.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.ComponentModel;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Input;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public interface ICloseableDialog
|
||||
{
|
||||
public Task Close();
|
||||
}
|
||||
|
||||
public abstract class DialogViewModelBase : ModelBase, ICloseableDialog, IDisposable
|
||||
{
|
||||
public DialogViewModelBase(Window dialogWindow)
|
||||
{
|
||||
DialogWindow = dialogWindow;
|
||||
}
|
||||
|
||||
public virtual async Task Close()
|
||||
{
|
||||
Dispose();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (null == DialogWindow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
DialogWindow.Hide(); // !!Important ARM64
|
||||
DialogWindow.Close();
|
||||
DialogWindow = null;
|
||||
}
|
||||
|
||||
public bool IsSuccess { get; set; } = false;
|
||||
|
||||
protected Window DialogWindow { get; private set; }
|
||||
}
|
||||
}
|
||||
822
PortfolioManager/ViewModels/GainLossViewModel.cs
Normal file
822
PortfolioManager/ViewModels/GainLossViewModel.cs
Normal file
@@ -0,0 +1,822 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using DynamicData;
|
||||
using MarketData;
|
||||
using MarketData.Cache;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Generator;
|
||||
using MarketData.Generator.GainLoss;
|
||||
using MarketData.Generator.MovingAverage;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.MarketDataModel.GainLoss;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.DataSeriesViewModels;
|
||||
using PortfolioManager.Models;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public partial class GainLossViewModel : WorkspaceViewModel
|
||||
{
|
||||
private const String ALL = "{ALL}";
|
||||
private enum Tasks { Accounts, SelectedSymbol, SelectedAccounts, LeastSquaresFit, UseDividends };
|
||||
private Dictionary<Tasks, Semaphore> semaphorePool = new Dictionary<Tasks, Semaphore>();
|
||||
private PortfolioTrades portfolioTrades = null;
|
||||
private ObservableCollection<String> symbols = new ObservableCollection<String>(new String[]{ALL}.ToList());
|
||||
private ObservableCollection<String> accounts = new ObservableCollection<String>(new String[]{ALL}.ToList());
|
||||
private String selectedSymbol = ALL;
|
||||
private String selectedAccount = ALL;
|
||||
private DateTime latestMarketDate;
|
||||
private bool isBusy = false;
|
||||
private bool suspendUpdate = false;
|
||||
private bool includeDividends = false;
|
||||
private String selectedCompanyName = String.Empty;
|
||||
private ITotalGainLossGenerator gainLossGenerator = null;
|
||||
private GainLossCompoundModelCollection gainLossModelCollection = null;
|
||||
private bool showAsGainLoss = true;
|
||||
private bool showActiveGainLoss = false;
|
||||
private ObservableCollection<GainLossCompoundModel> gainLossCompoundModelCollection = new ObservableCollection<GainLossCompoundModel>();
|
||||
private ObservableCollection<GainLossSummaryItem> gainLossSummaryItemCollection = new ObservableCollection<GainLossSummaryItem>();
|
||||
private IActiveGainLossGenerator activeGainLossGenerator = null;
|
||||
private TotalGainLossCollection totalGainLoss = null; // total gain/loss
|
||||
private bool useCumulativeGainLoss = true;
|
||||
private bool showMarkers = false;
|
||||
private bool useLeastSquaresFit = true;
|
||||
private GainLossCompoundModel selectedGainLossCompoundItem = null;
|
||||
private GainLossSummaryItem selectedGainLossSummaryItem = null;
|
||||
|
||||
public GainLossViewModel()
|
||||
{
|
||||
|
||||
DisplayName = "GainLossView";
|
||||
semaphorePool.Add(Tasks.SelectedSymbol, new Semaphore(1, 1));
|
||||
semaphorePool.Add(Tasks.SelectedAccounts, new Semaphore(1, 1));
|
||||
semaphorePool.Add(Tasks.LeastSquaresFit, new Semaphore(1, 1));
|
||||
semaphorePool.Add(Tasks.Accounts, new Semaphore(1, 1));
|
||||
semaphorePool.Add(Tasks.UseDividends, new Semaphore(1, 1));
|
||||
PropertyChanged += OnViewModelPropertyChanged;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
protected override void OnDispose()
|
||||
{
|
||||
base.OnDispose();
|
||||
}
|
||||
|
||||
public override String DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
return "GainLossView";
|
||||
}
|
||||
}
|
||||
|
||||
public override String Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
public String PercentButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!showAsGainLoss) return "Show $";
|
||||
else return "Show %";
|
||||
}
|
||||
}
|
||||
|
||||
public String ActiveTotalButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!showActiveGainLoss) return "Show Active G/L";
|
||||
else return "Show Total G/L";
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowMarkers
|
||||
{
|
||||
get
|
||||
{
|
||||
return showMarkers;
|
||||
}
|
||||
set
|
||||
{
|
||||
showMarkers = value;
|
||||
base.OnPropertyChanged("ShowMarkers");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.PropertyName.Equals("SelectedSymbol"))HandleSelectedSymbol();
|
||||
else if (eventArgs.PropertyName.Equals("Symbols"))HandleSymbols();
|
||||
else if (eventArgs.PropertyName.Equals("SelectedAccount"))HandleSelectedAccount();
|
||||
else if (eventArgs.PropertyName.Equals("LeastSquaresFit"))HandleLeastSquaresFit();
|
||||
else if (eventArgs.PropertyName.Equals("SelectedGainLossCompoundItem"))HandleSelectedGainLossCompoundItem();
|
||||
else if (eventArgs.PropertyName.Equals("CheckBoxUseCumulativeReturns"))HandleCheckBoxUseCumulativeReturns();
|
||||
}
|
||||
|
||||
private void HandleSymbols()
|
||||
{
|
||||
SelectedSymbol = symbols[0];
|
||||
}
|
||||
|
||||
private void HandleSelectedSymbol()
|
||||
{
|
||||
try
|
||||
{
|
||||
semaphorePool[Tasks.SelectedSymbol].WaitOne();
|
||||
gainLossCompoundModelCollection = null;
|
||||
gainLossSummaryItemCollection = null;
|
||||
if (suspendUpdate) return;
|
||||
suspendUpdate = true;
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, $"HandleSelectedSymbol:{selectedSymbol}");
|
||||
if (String.IsNullOrEmpty(selectedSymbol) || String.IsNullOrEmpty(selectedAccount))
|
||||
{
|
||||
UpdateProperties();
|
||||
return;
|
||||
}
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("[GainLossViewModel::OnGainLossViewModelPropertyChanged]SelectedSymbol '{0}'", selectedSymbol));
|
||||
IsBusy = true;
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
DividendPayments dividendPayments = null;
|
||||
if (ALL.Equals(selectedSymbol))
|
||||
{
|
||||
portfolioTrades = PortfolioDA.GetTrades();
|
||||
if (includeDividends) dividendPayments = DividendPaymentDA.GetDividendPayments();
|
||||
selectedCompanyName = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
portfolioTrades = PortfolioDA.GetTrades(selectedSymbol);
|
||||
if (includeDividends) dividendPayments = DividendPaymentDA.GetDividendPaymentsForSymbol(selectedSymbol);
|
||||
selectedCompanyName = PricingDA.GetNameForSymbol(selectedSymbol);
|
||||
}
|
||||
if (!ALL.Equals(selectedAccount))
|
||||
{
|
||||
portfolioTrades = portfolioTrades.FilterAccount(selectedAccount);
|
||||
}
|
||||
if (null != dividendPayments && !ALL.Equals(selectedAccount))
|
||||
{
|
||||
dividendPayments = dividendPayments.FilterAccounts(selectedAccount);
|
||||
}
|
||||
|
||||
// check the TotalGainLoss generator and build appropriately
|
||||
InstantiateGenerators();
|
||||
LocalPriceCache.GetInstance().Refresh();
|
||||
GainLossCollection gainLoss = null;
|
||||
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, "GeneratingActiveGainLoss");
|
||||
|
||||
// gainLoss will contain the gain/loss from active positions. Never includes dividends .. just positions
|
||||
gainLoss = activeGainLossGenerator.GenerateGainLoss(portfolioTrades);
|
||||
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, "GeneratingTotalGainLoss)");
|
||||
// Call the appropriate TotalGainLoss method depending on whether we should include dividends or not.
|
||||
if (includeDividends) totalGainLoss = gainLossGenerator.GenerateTotalGainLossWithDividends(portfolioTrades, dividendPayments);
|
||||
else totalGainLoss = gainLossGenerator.GenerateTotalGainLoss(portfolioTrades);
|
||||
|
||||
TotalGainLossItem lastItem = totalGainLoss[totalGainLoss.Count - 1];
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, $"Date:{lastItem.Date.ToShortDateString()} TotalGainLoss:{Utility.FormatCurrency(lastItem.TotalGainLoss, 2)}");
|
||||
|
||||
gainLossModelCollection = null;
|
||||
gainLossModelCollection = new GainLossCompoundModelCollection(gainLoss, totalGainLoss);
|
||||
if (null != gainLossModelCollection)
|
||||
{
|
||||
gainLossModelCollection.Sort(new SortGainLossCompoundModelYearDescendingOrder());
|
||||
gainLossCompoundModelCollection = new ObservableCollection<GainLossCompoundModel>(gainLossModelCollection);
|
||||
}
|
||||
GainLossSummaryItemCollection gainLossSummaryItems = new GainLossSummaryItemCollection(portfolioTrades, gainLossGenerator, activeGainLossGenerator);
|
||||
gainLossSummaryItemCollection = new ObservableCollection<GainLossSummaryItem>(gainLossSummaryItems);
|
||||
});
|
||||
workerTask.ContinueWith((continuation) =>
|
||||
{
|
||||
IsBusy = false;
|
||||
UpdateProperties();
|
||||
suspendUpdate = false;
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphorePool[Tasks.SelectedSymbol].Release();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSelectedAccount()
|
||||
{
|
||||
try
|
||||
{
|
||||
semaphorePool[Tasks.SelectedAccounts].WaitOne();
|
||||
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
if (String.IsNullOrEmpty(selectedAccount)) return;
|
||||
if (ALL.Equals(selectedAccount))
|
||||
{
|
||||
portfolioTrades = PortfolioDA.GetTrades();
|
||||
}
|
||||
else
|
||||
{
|
||||
portfolioTrades = PortfolioDA.GetTradesForAccounts(new List<String>() { selectedAccount });
|
||||
}
|
||||
});
|
||||
workerTask.ContinueWith((continuation) =>
|
||||
{
|
||||
SetSymbols();
|
||||
SelectedSymbol = Symbols[0];
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphorePool[Tasks.SelectedAccounts].Release();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleLeastSquaresFit()
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, $"HandleLeastSquaresFit");
|
||||
}
|
||||
|
||||
private void HandleSelectedGainLossCompoundItem()
|
||||
{
|
||||
if (null == selectedGainLossCompoundItem) return;
|
||||
IsBusy = true;
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
DateTime selectedDate = selectedGainLossCompoundItem.Date;
|
||||
PortfolioTrades tradesOnOrBefore = portfolioTrades.GetTradesOnOrBefore(selectedDate);
|
||||
GainLossSummaryItemCollection gainLossSummaryItems = new GainLossSummaryItemCollection(tradesOnOrBefore, gainLossGenerator, activeGainLossGenerator, selectedDate);
|
||||
gainLossSummaryItemCollection = new ObservableCollection<GainLossSummaryItem>(gainLossSummaryItems);
|
||||
});
|
||||
workerTask.ContinueWith((continuation) =>
|
||||
{
|
||||
IsBusy = false;
|
||||
base.OnPropertyChanged("GainLossSummaryItemCollection");
|
||||
UpdateSummaryProperties();
|
||||
});
|
||||
}
|
||||
|
||||
private void HandleCheckBoxUseCumulativeReturns()
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, $"HandleCheckBoxUseCumulativeReturns");
|
||||
}
|
||||
|
||||
// ********************************************************************************************************************************
|
||||
|
||||
public void InstantiateGenerators()
|
||||
{
|
||||
if (null == gainLossGenerator)
|
||||
{
|
||||
if (useCumulativeGainLoss) gainLossGenerator = new GainLossGeneratorCum();
|
||||
else gainLossGenerator = new GainLossGenerator();
|
||||
}
|
||||
if (null == activeGainLossGenerator) activeGainLossGenerator = new ActiveGainLossGenerator();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
IsBusy = true;
|
||||
suspendUpdate = true;
|
||||
semaphorePool[Tasks.Accounts].WaitOne();
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
latestMarketDate = PremarketDA.GetLatestMarketDate();
|
||||
portfolioTrades = PortfolioDA.GetTrades();
|
||||
});
|
||||
workerTask.ContinueWith((continuation) =>
|
||||
{
|
||||
SetAccounts();
|
||||
SetSymbols();
|
||||
selectedSymbol = symbols[0];
|
||||
selectedAccount = accounts[0];
|
||||
IsBusy = false;
|
||||
suspendUpdate = false;
|
||||
base.OnPropertyChanged("SelectedSymbol");
|
||||
semaphorePool[Tasks.Accounts].Release();
|
||||
});
|
||||
}
|
||||
|
||||
public void SetAccounts()
|
||||
{
|
||||
if (null == portfolioTrades || 0 == portfolioTrades.Count) return;
|
||||
if (default != accounts.Where(x => x.Equals(ALL)).FirstOrDefault())
|
||||
{
|
||||
selectedAccount = accounts[0];
|
||||
IEnumerable<String> collectionToRemove = accounts.Where(x => !x.Equals(ALL)).ToList();
|
||||
accounts.Remove(collectionToRemove);
|
||||
accounts.AddRange(portfolioTrades.Accounts);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<String> portfolioTradeAccounts = new List<String>(portfolioTrades.Accounts);
|
||||
portfolioTradeAccounts.Insert(0, ALL);
|
||||
accounts.AddRange(portfolioTradeAccounts);
|
||||
selectedAccount = accounts[0];
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSymbols()
|
||||
{
|
||||
if (null == portfolioTrades || 0 == portfolioTrades.Count) return;
|
||||
if (default != symbols.Where(x => x.Equals(ALL)).FirstOrDefault())
|
||||
{
|
||||
selectedSymbol = symbols[0];
|
||||
IEnumerable<String> collectionToRemove = symbols.Where(x => !x.Equals(ALL)).ToList();
|
||||
symbols.Remove(collectionToRemove);
|
||||
symbols.AddRange(portfolioTrades.Symbols);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<String> tradedSymbols = new List<String>(portfolioTrades.Symbols);
|
||||
tradedSymbols.Insert(0, ALL);
|
||||
symbols.AddRange(tradedSymbols);
|
||||
selectedSymbol = symbols[0];
|
||||
}
|
||||
}
|
||||
|
||||
public String SelectedSymbol
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedSymbol;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (null == value) return;
|
||||
selectedSymbol = value;
|
||||
base.OnPropertyChanged("SelectedSymbol");
|
||||
}
|
||||
}
|
||||
|
||||
public String SelectedAccount
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedAccount;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (null == value) return;
|
||||
selectedAccount = value;
|
||||
base.OnPropertyChanged("SelectedAccount");
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<String> Accounts
|
||||
{
|
||||
get
|
||||
{
|
||||
return accounts;
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<String> Symbols
|
||||
{
|
||||
get
|
||||
{
|
||||
return symbols;
|
||||
}
|
||||
}
|
||||
|
||||
public String TotalGainLoss
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == totalGainLoss || 0 == totalGainLoss.Count)
|
||||
{
|
||||
return Constants.CONST_DASHES;
|
||||
}
|
||||
return Utility.FormatCurrency(totalGainLoss[totalGainLoss.Count - 1].TotalGainLoss, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************************************************************************************************************************
|
||||
|
||||
public String SummaryDate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == gainLossSummaryItemCollection || 0 == gainLossSummaryItemCollection.Count)
|
||||
{
|
||||
return Constants.CONST_DASHES;
|
||||
}
|
||||
return gainLossSummaryItemCollection[0].Date.ToShortDateString();
|
||||
}
|
||||
}
|
||||
|
||||
public String SummaryChange
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == gainLossSummaryItemCollection)
|
||||
{
|
||||
return Constants.CONST_DASHES;
|
||||
}
|
||||
return Utility.FormatCurrency(gainLossSummaryItemCollection.Sum(x => x.Change), 2);
|
||||
}
|
||||
}
|
||||
|
||||
public String SummaryGainLoss
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == gainLossSummaryItemCollection)
|
||||
{
|
||||
return Constants.CONST_DASHES;
|
||||
}
|
||||
return Utility.FormatCurrency(gainLossSummaryItemCollection.Sum(x => x.CurrentGainLoss), 2);
|
||||
}
|
||||
}
|
||||
|
||||
public String SummaryPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == gainLossSummaryItemCollection)
|
||||
{
|
||||
return Constants.CONST_DASHES;
|
||||
}
|
||||
return Utility.FormatNumber(gainLossSummaryItemCollection.Count(), 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckBoxIncludeDividends
|
||||
{
|
||||
get
|
||||
{
|
||||
return includeDividends;
|
||||
}
|
||||
set
|
||||
{
|
||||
includeDividends = value;
|
||||
base.OnPropertyChanged("CheckBoxIncludeDividends");
|
||||
base.OnPropertyChanged("SelectedSymbol");
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************************************************************************************************************************
|
||||
|
||||
private void UpdateProperties()
|
||||
{
|
||||
base.OnPropertyChanged("TotalGainLoss");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
base.OnPropertyChanged("Title");
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("MA21");
|
||||
base.OnPropertyChanged("MA55");
|
||||
base.OnPropertyChanged("MA90");
|
||||
base.OnPropertyChanged("LeastSquares");
|
||||
base.OnPropertyChanged("GainLossCompoundModelCollection"); // The compound model collection is the data behind the views left-hand grid.
|
||||
base.OnPropertyChanged("GainLossSummaryItemCollection"); // The summary item collection is the data behind the views right-hand grid.
|
||||
base.OnPropertyChanged("Parity");
|
||||
base.OnPropertyChanged("DollarChangePercent");
|
||||
UpdateSummaryProperties();
|
||||
}
|
||||
|
||||
private void UpdateSummaryProperties()
|
||||
{
|
||||
base.OnPropertyChanged("SummaryGainLoss");
|
||||
base.OnPropertyChanged("SummaryDate");
|
||||
base.OnPropertyChanged("SummaryChange");
|
||||
base.OnPropertyChanged("SummaryDate");
|
||||
base.OnPropertyChanged("SummaryPositions");
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************
|
||||
public bool IsBusy
|
||||
{
|
||||
get
|
||||
{
|
||||
return isBusy;
|
||||
}
|
||||
set
|
||||
{
|
||||
isBusy = value;
|
||||
base.OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************* P E R S I S T E N C E *********************************************
|
||||
public override bool CanPersist()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override SaveParameters GetSaveParameters()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void SetSaveParameters(SaveParameters saveParameters)
|
||||
{
|
||||
}
|
||||
|
||||
// ************************************************** C H A R T D A T A ********************************************
|
||||
public CompositeDataSource Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty();
|
||||
CompositeDataSource compositeDataSource = null;
|
||||
if (showActiveGainLoss) compositeDataSource = GainLossModel.GainLoss(gainLossModelCollection, showAsGainLoss);
|
||||
else compositeDataSource = GainLossModel.TotalGainLoss(gainLossModelCollection, showAsGainLoss);
|
||||
return compositeDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
public CompositeDataSource MA21
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty();
|
||||
CompositeDataSource compositeDataSource = null;
|
||||
DMAValues dmaValues = null;
|
||||
if (showActiveGainLoss)
|
||||
{
|
||||
if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLoss, 21);
|
||||
else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLossPercent, 21);
|
||||
compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLoss, 21);
|
||||
else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLossPercent, 21);
|
||||
compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues);
|
||||
}
|
||||
return compositeDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
public CompositeDataSource MA55
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty();
|
||||
CompositeDataSource compositeDataSource = null;
|
||||
DMAValues dmaValues = null;
|
||||
if (showActiveGainLoss)
|
||||
{
|
||||
if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLoss, 55);
|
||||
else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLossPercent, 55);
|
||||
compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLoss, 55);
|
||||
else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLossPercent, 55);
|
||||
compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues);
|
||||
}
|
||||
return compositeDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
public CompositeDataSource MA90
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty();
|
||||
CompositeDataSource compositeDataSource = null;
|
||||
DMAValues dmaValues = null;
|
||||
if (showActiveGainLoss)
|
||||
{
|
||||
if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLoss, 90);
|
||||
else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesActiveGainLossPercent, 90);
|
||||
compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (showAsGainLoss) dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLoss, 90);
|
||||
else dmaValues = MovingAverageGenerator.GenerateMovingAverage(gainLossModelCollection.DMAValuesTotalGainLossPercent, 90);
|
||||
compositeDataSource = MovingAverageModel.CreateCompositeDataSource(dmaValues);
|
||||
}
|
||||
return compositeDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
public CompositeDataSource LeastSquares
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!useLeastSquaresFit || null == gainLossModelCollection || 0 == gainLossModelCollection.Count) return GainLossModel.Empty();
|
||||
if (showActiveGainLoss) return GainLossModel.LeastSquares(gainLossModelCollection, showAsGainLoss);
|
||||
else return GainLossModel.TotalLeastSquares(gainLossModelCollection, showAsGainLoss);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ********************************************* C H A R T T I T L E ****************************************
|
||||
public String GraphTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == gainLossModelCollection || 0 == gainLossModelCollection.Count)
|
||||
{
|
||||
return "GainLossView";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append(showActiveGainLoss ? "Active" : "Total").Append(" ");
|
||||
sb.Append("Gain/Loss ");
|
||||
if (showAsGainLoss) sb.Append("($)");
|
||||
else sb.Append("(%)");
|
||||
String accounts = selectedAccount.Equals(ALL) ? Utility.ListToString(portfolioTrades.Accounts) : selectedAccount;
|
||||
if (!ALL.Equals(selectedSymbol)) sb.Append(" (").Append(accounts).Append(") ").Append(" - ").Append(selectedCompanyName).Append(" (").Append(selectedSymbol).Append(") ");
|
||||
else sb.Append(" (").Append(accounts).Append(") ").Append(" - ").Append(selectedCompanyName);
|
||||
|
||||
String part = sb.ToString();
|
||||
if (part.Length >= 75)
|
||||
{
|
||||
sb = new StringBuilder();
|
||||
sb.Append(part.Substring(0, 75));
|
||||
sb.Append("...");
|
||||
}
|
||||
|
||||
DateTime fromDate = gainLossModelCollection.Select(x => x.Date).Min();
|
||||
DateTime toDate = gainLossModelCollection.Select(x => x.Date).Max();
|
||||
sb.Append(" from ").Append(Utility.DateTimeToStringMMHDDHYYYY(fromDate));
|
||||
sb.Append(" Thru ").Append(Utility.DateTimeToStringMMHDDHYYYY(toDate));
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************************************************************************************
|
||||
// ***************************************************************************** T A B L E D A T A ***********************************************************************
|
||||
// *************************************************************************************************************************************************************************
|
||||
// The compound model collection is the data behind the views left-hand grid. This view shows the compound gain/loss since inception
|
||||
public ObservableCollection<GainLossCompoundModel> GainLossCompoundModelCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
return gainLossCompoundModelCollection;
|
||||
}
|
||||
}
|
||||
|
||||
// The summary model collection is the data behind the views right-hand grid. This view shows the gain/loss for the selected date
|
||||
public ObservableCollection<GainLossSummaryItem> GainLossSummaryItemCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
return gainLossSummaryItemCollection;
|
||||
}
|
||||
}
|
||||
|
||||
// ****************************************************************************** G R I D S E L E C T I O N S ***********************************************
|
||||
// The selected item in the compound model
|
||||
public GainLossCompoundModel SelectedGainLossCompoundItem
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedGainLossCompoundItem;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedGainLossCompoundItem = value;
|
||||
base.OnPropertyChanged("SelectedGainLossCompoundItem");
|
||||
}
|
||||
}
|
||||
|
||||
// The selected item in the summary model
|
||||
public GainLossSummaryItem SelectedGainLossSummaryItem
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedGainLossSummaryItem;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedGainLossSummaryItem = value;
|
||||
base.OnPropertyChanged("SelectedGainLossSummaryItem");
|
||||
base.OnPropertyChanged("Parity");
|
||||
base.OnPropertyChanged("DollarChangePercent");
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************* R E L A Y S *********************************************
|
||||
|
||||
[RelayCommand]
|
||||
public async Task ToggleReturnOrPercent()
|
||||
{
|
||||
showAsGainLoss = !showAsGainLoss;
|
||||
base.OnPropertyChanged("SelectedSymbol");
|
||||
base.OnPropertyChanged("PercentButtonText");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task ToggleActiveOrTotal()
|
||||
{
|
||||
showActiveGainLoss = !showActiveGainLoss;
|
||||
base.OnPropertyChanged("SelectedSymbol");
|
||||
base.OnPropertyChanged("ActiveTotalButtonText");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task PerformRefresh()
|
||||
{
|
||||
latestMarketDate = PremarketDA.GetLatestMarketDate();
|
||||
base.OnPropertyChanged("SelectedSymbol");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task PerformReset()
|
||||
{
|
||||
SelectedAccount = ALL;
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
// ****************************************************** T O O L T I P S *********************************************************
|
||||
public String DollarChangePercent
|
||||
{
|
||||
get
|
||||
{
|
||||
return "'$ Change(%)' is calculated as a percentage of dollar change in market value from the previous period to the current period.\nThis number may appear skewed if share count has changed day over day.";
|
||||
}
|
||||
}
|
||||
|
||||
public String Parity
|
||||
{
|
||||
get
|
||||
{
|
||||
Profiler profiler = new Profiler();
|
||||
try
|
||||
{
|
||||
if (null == selectedGainLossSummaryItem || null == selectedGainLossSummaryItem.Symbol)
|
||||
{
|
||||
return "No row selected.";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
PortfolioTrades portfolioTrades = PortfolioDA.GetOpenTradesSymbol(selectedGainLossSummaryItem.Symbol);
|
||||
DateTime currentDate = PricingDA.GetLatestDate(selectedGainLossSummaryItem.Symbol);
|
||||
|
||||
String companyName = PricingDA.GetNameForSymbol(selectedGainLossSummaryItem.Symbol);
|
||||
if (null != companyName) sb.Append(companyName).Append("\n");
|
||||
|
||||
CompanyProfile companyProfile = CompanyProfileDA.GetCompanyProfile(selectedGainLossSummaryItem.Symbol);
|
||||
if (null != companyProfile)
|
||||
{
|
||||
sb.Append(companyProfile.Sector ?? Constants.CONST_QUESTION).Append("/").Append(companyProfile.Industry ?? Constants.CONST_QUESTION).Append("\n");
|
||||
}
|
||||
|
||||
if (null != portfolioTrades && 0 != portfolioTrades.Count)
|
||||
{
|
||||
double shares = (from PortfolioTrade portfolioTrade in portfolioTrades select portfolioTrade.Shares).Sum();
|
||||
double exposure = portfolioTrades.Sum(x => x.Exposure());
|
||||
// Calculate the gain loss so that we can show the difference between our all time high percentage and where we are right now
|
||||
InstantiateGenerators();
|
||||
GainLossCollection gainLoss = activeGainLossGenerator.GenerateGainLoss(portfolioTrades); // gainLoss contains the gain/loss from active positions. Never includes dividends .. just positions
|
||||
GainLossItem maxGainLossItem = gainLoss.OrderByDescending(x => x.GainLossPercent).FirstOrDefault();
|
||||
GainLossItem minGainLossItem = gainLoss.OrderBy(x => x.GainLossPercent).FirstOrDefault();
|
||||
|
||||
String accounts = Utility.ListToString(portfolioTrades.Accounts);
|
||||
sb.Append("You own '").Append(selectedGainLossSummaryItem.Symbol).Append("' in ").Append(portfolioTrades.Count).Append(" lot(s) (").Append(Utility.FormatNumber(shares, 0, true)).Append(" shares) ").Append("\n");
|
||||
sb.Append("Exposure: ").Append(Utility.FormatCurrency(exposure));
|
||||
sb.Append("\n").Append("Accounts: ").Append(accounts);
|
||||
|
||||
ParityElement parityElement = ParityGenerator.GenerateBreakEven(selectedGainLossSummaryItem.Symbol);
|
||||
if (null != parityElement)
|
||||
{
|
||||
sb.Append("\n").Append(parityElement.ToString());
|
||||
sb.Append("\n").Append("All Time Gain/Loss: ").Append(maxGainLossItem.GainLossPercent < 0 ? "" : "+").Append(Utility.FormatPercent(maxGainLossItem.GainLossPercent / 100));
|
||||
sb.Append(" (").Append(minGainLossItem.GainLossPercent < 0 ? "" : "+").Append(Utility.FormatPercent(minGainLossItem.GainLossPercent / 100)).Append(")");
|
||||
}
|
||||
}
|
||||
else sb.Append("You don't hold any shares of '").Append(selectedGainLossSummaryItem.Symbol).Append("'");
|
||||
sb.Append(".");
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
DateTime priorDate = dateGenerator.FindPrevBusinessDay(currentDate);
|
||||
Price p1 = PricingDA.GetPrice(selectedGainLossSummaryItem.Symbol, currentDate);
|
||||
Price p2 = PricingDA.GetPrice(selectedGainLossSummaryItem.Symbol, priorDate);
|
||||
if (null == p2 && null != p1)
|
||||
{
|
||||
priorDate = dateGenerator.FindPrevBusinessDay(priorDate);
|
||||
p2 = PricingDA.GetPrice(selectedGainLossSummaryItem.Symbol, priorDate);
|
||||
}
|
||||
if (null == p1 || null == p2) return sb.ToString();
|
||||
sb.Append("\n");
|
||||
double change = (p1.Close - p2.Close) / p2.Close;
|
||||
sb.Append(String.Format("Latest Price {0} {1} ({2}{3})", Utility.DateTimeToStringMMSDDSYYYY(p1.Date), Utility.FormatCurrency(p1.Close), change < 0 ? "-" : "+", Utility.FormatPercent(Math.Abs(change))));
|
||||
if (companyProfile.FreezePricing) sb.Append(" ").Append("**Frozen**");
|
||||
sb.Append("\n").Append(String.Format("Source: {0}", p1.SourceAsString()));
|
||||
return sb.ToString();
|
||||
}
|
||||
finally
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, $"[Parity]Done, total took {profiler.End()}(ms)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
PortfolioManager/ViewModels/IPersistentViewModel.cs
Normal file
9
PortfolioManager/ViewModels/IPersistentViewModel.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public interface IPersistentViewModel
|
||||
{
|
||||
SaveParameters GetSaveParameters();
|
||||
void SetSaveParameters(SaveParameters saveParameters);
|
||||
bool CanPersist();
|
||||
}
|
||||
}
|
||||
712
PortfolioManager/ViewModels/MGSHMomentumViewModel.cs
Normal file
712
PortfolioManager/ViewModels/MGSHMomentumViewModel.cs
Normal file
@@ -0,0 +1,712 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Labs.Input;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Eremex.AvaloniaUI.Controls;
|
||||
using MarketData;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Generator;
|
||||
using MarketData.Generator.MGSHMomentum;
|
||||
using MarketData.Generator.Model;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.DataSeriesViewModels;
|
||||
using PortfolioManager.Dialogs;
|
||||
using PortfolioManager.Models;
|
||||
using PortfolioManager.UIUtils;
|
||||
using StopLimit = MarketData.MarketDataModel.StopLimit;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public partial class MGSHMomentumViewModel : WorkspaceViewModel
|
||||
{
|
||||
private ObservableCollection<MGSHMomentumCandidate> momentumCandidates = new ObservableCollection<MGSHMomentumCandidate>();
|
||||
private MGSHSessionParams sessionParams;
|
||||
private String initialPath = null;
|
||||
private MGSHPositionModelCollection positions = null;
|
||||
private ModelPerformanceSeries modelPerformanceSeries = null;
|
||||
private ModelStatistics modelStatistics = null;
|
||||
private bool showAsGainLoss = true;
|
||||
private String pathFileName = null;
|
||||
private ObservableCollection<String> nvpDictionaryKeys = default;
|
||||
private NVPDictionary nvpDictionary = null;
|
||||
private String selectedParameter = null;
|
||||
private MGSHConfiguration configuration = null;
|
||||
private MGSHPositionModel selectedPosition = null;
|
||||
private bool isBusy = false;
|
||||
private bool showMarkers = false;
|
||||
|
||||
public MGSHMomentumViewModel()
|
||||
{
|
||||
DisplayName = "MGSHMomentum Model";
|
||||
PropertyChanged += OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
protected override void OnDispose()
|
||||
{
|
||||
base.OnDispose();
|
||||
}
|
||||
|
||||
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get
|
||||
{
|
||||
return isBusy;
|
||||
}
|
||||
set
|
||||
{
|
||||
isBusy = value;
|
||||
base.OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowMarkers
|
||||
{
|
||||
get
|
||||
{
|
||||
return showMarkers;
|
||||
}
|
||||
set
|
||||
{
|
||||
showMarkers = value;
|
||||
base.OnPropertyChanged("ShowMarkers");
|
||||
}
|
||||
}
|
||||
|
||||
public CompositeDataSource Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelPerformanceSeries) return GainLossModel.Empty();
|
||||
CompositeDataSource compositeDataSource = null;
|
||||
compositeDataSource = GainLossModel.GainLoss(modelPerformanceSeries, showAsGainLoss);
|
||||
return compositeDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
public String PercentButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!showAsGainLoss) return "Show $";
|
||||
else return "Show %";
|
||||
}
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************************************************
|
||||
public ObservableCollection<MenuItem> PositionsMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
ObservableCollection<MenuItem> collection = new ObservableCollection<MenuItem>();
|
||||
collection.Add(new MenuItem() { Header = "Close Position...", Command = CloseCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Edit Position...", Command = EditCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Add To WatchList", Command = AddToWatchListCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Remove From WatchList", Command = RemoveFromWatchListCommand, StaysOpenOnClick = false });
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
// ********************************************************************* R E L A Y S *****************************************************************
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanClosePosition))]
|
||||
public async Task Close()
|
||||
{
|
||||
await OpenCloseDialog();
|
||||
}
|
||||
|
||||
public bool CanClosePosition()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanEdit))]
|
||||
public async Task Edit()
|
||||
{
|
||||
await OpenEditDialog();
|
||||
}
|
||||
|
||||
public bool CanEdit()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol || !Utility.IsEpoch(selectedPosition.SellDate)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ToggleReturnOrPercent()
|
||||
{
|
||||
HandleToggleReturnOrPercent();
|
||||
}
|
||||
|
||||
// This is not currently being displayed
|
||||
[RelayCommand]
|
||||
public void Run()
|
||||
{
|
||||
RunCandidateGenerator();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task LoadFile()
|
||||
{
|
||||
await LoadTradeFile();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task Reload()
|
||||
{
|
||||
await ReloadTradeFile();
|
||||
}
|
||||
|
||||
public async Task ReloadTradeFile()
|
||||
{
|
||||
LoadSessionFile();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanAddToWatchList))]
|
||||
public async Task AddToWatchList()
|
||||
{
|
||||
WatchListDA.AddToWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Added {selectedPosition.Symbol} to WatchList", "Add To WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public bool CanAddToWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
return WatchListDA.IsInWatchList(selectedPosition.Symbol) ? false : true;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanRemoveFromWatchList))]
|
||||
public async Task RemoveFromWatchList()
|
||||
{
|
||||
WatchListDA.RemoveFromWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Removed {selectedPosition.Symbol} from WatchList", "Remove From WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public bool CanRemoveFromWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
if (!WatchListDA.IsInWatchList(selectedPosition.Symbol)) return false;
|
||||
PortfolioTrades portfolioTrades = PortfolioDA.GetOpenTrades();
|
||||
if (portfolioTrades.Any(x => x.Symbol.Equals(selectedPosition.Symbol))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ****************************************************************************************************************************************************
|
||||
#region Operations
|
||||
|
||||
public async Task OpenEditDialog()
|
||||
{
|
||||
EditPositionDialog dialog = new EditPositionDialog();
|
||||
MGSHPosition clonedPosition = MGSHPosition.Clone(selectedPosition.Position);
|
||||
EditPositionDialogViewModel editPositionViewModel = new EditPositionDialogViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = editPositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
GetTopLevelWindow().BringIntoView();
|
||||
if (!editPositionViewModel.IsSuccess) return;
|
||||
MGSHMomentumBacktest mgshMomentumBacktest = new MGSHMomentumBacktest();
|
||||
if (!mgshMomentumBacktest.EditPosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.PurchasePrice, clonedPosition.InitialStopLimit, clonedPosition.TrailingStopLimit, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to edit the position, check log for details.", "Edit Position");
|
||||
return;
|
||||
}
|
||||
if (!selectedPosition.TrailingStopLimit.Equals(clonedPosition.TrailingStopLimit))
|
||||
{
|
||||
selectedPosition.TrailingStopLimit = clonedPosition.TrailingStopLimit;
|
||||
selectedPosition.LastStopAdjustment = DateTime.Now.Date;
|
||||
// StopLimit stopLimit = PortfolioDA.GetStopLimit(clonedPosition.Symbol);
|
||||
// if (null == stopLimit)
|
||||
// {
|
||||
// stopLimit = new StopLimit();
|
||||
// stopLimit.Symbol = clonedPosition.Symbol;
|
||||
// stopLimit.StopType = StopLimitConstants.STOP_QUOTE;
|
||||
// stopLimit.Shares = clonedPosition.Shares;
|
||||
// }
|
||||
// stopLimit.StopPrice = clonedPosition.TrailingStopLimit;
|
||||
// PortfolioDA.InsertUpdateStopLimit(stopLimit);
|
||||
}
|
||||
selectedPosition.PurchaseDate = clonedPosition.PurchaseDate;
|
||||
selectedPosition.PurchasePrice = clonedPosition.PurchasePrice;
|
||||
selectedPosition.TrailingStopLimit = clonedPosition.TrailingStopLimit;
|
||||
selectedPosition.InitialStopLimit = clonedPosition.InitialStopLimit;
|
||||
|
||||
String strMessage = String.Format("Edited Position for {0} Purchase Date:{1} Purchase Price:{2}, Trailing Stop:{3}. A backup was created.",
|
||||
selectedPosition.Symbol, selectedPosition.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(selectedPosition.PurchasePrice), Utility.FormatCurrency(selectedPosition.TrailingStopLimit));
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Edit Position");
|
||||
LoadSessionFile();
|
||||
}
|
||||
|
||||
public async Task OpenCloseDialog()
|
||||
{
|
||||
bool deleteStop = false;
|
||||
MGSHPosition clonedPosition = MGSHPosition.Clone(selectedPosition.Position);
|
||||
// bool hasStopLimit = PortfolioDA.HasStopLimit(clonedPosition.Symbol);
|
||||
|
||||
ClosePositionDialog dialog = new ClosePositionDialog();
|
||||
ClosePositionDialogViewModel closePositionViewModel = new ClosePositionDialogViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = closePositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
if (!closePositionViewModel.IsSuccess) return;
|
||||
MGSHMomentumBacktest mgshMomentumBacktest = new MGSHMomentumBacktest();
|
||||
if (!mgshMomentumBacktest.ClosePosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.SellDate, clonedPosition.CurrentPrice, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to close the position, check log for details.", "Close Position");
|
||||
return;
|
||||
}
|
||||
// if (deleteStop) PortfolioDA.DeleteStopLimit(clonedPosition.Symbol);
|
||||
String strMessage = String.Format("Closed position for {0}, Purchase Date:{1}, Sell Date{2}, Current Price:{3}, Delete Stop:{4} to {5}. A backup was created.",
|
||||
clonedPosition.Symbol, clonedPosition.PurchaseDate.ToShortDateString(), clonedPosition.SellDate.ToShortDateString(), Utility.FormatCurrency(clonedPosition.CurrentPrice), deleteStop, pathFileName);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Close Position");
|
||||
LoadSessionFile();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void HandleToggleReturnOrPercent()
|
||||
{
|
||||
showAsGainLoss = !showAsGainLoss;
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("PercentButtonText");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
// This is not currently being dispplayed
|
||||
private void RunCandidateGenerator()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (null == sessionParams) return;
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
DateTime selectedDate = dateGenerator.FindPrevBusinessDay(sessionParams.TradeDate);
|
||||
IsBusy = true;
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
MGSHConfiguration localConfiguration = new MGSHConfiguration();
|
||||
localConfiguration.MaxPositions = int.Parse(nvpDictionary["MaxPositions"].Value);
|
||||
localConfiguration.HoldingPeriod = int.Parse(nvpDictionary["HoldingPeriod"].Value);
|
||||
MGSHMomentumCandidates candidates = MGSHMomentumGenerator.GenerateMomentumWithFallback(selectedDate, configuration == null ? localConfiguration : configuration);
|
||||
momentumCandidates = new ObservableCollection<MGSHMomentumCandidate>();
|
||||
foreach (MGSHMomentumCandidate momentumCandidate in candidates) momentumCandidates.Add(momentumCandidate);
|
||||
});
|
||||
workerTask.ContinueWith((continuation) =>
|
||||
{
|
||||
IsBusy = false;
|
||||
base.OnPropertyChanged("AllItems");
|
||||
base.OnPropertyChanged("Title");
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LoadTradeFile()
|
||||
{
|
||||
TopLevel topLevel = TopLevel.GetTopLevel(GetTopLevelWindow());
|
||||
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Open Trade File",
|
||||
AllowMultiple = false,
|
||||
});
|
||||
IStorageFile storageFile = files.FirstOrDefault();
|
||||
if (null == storageFile) return;
|
||||
Uri uri = storageFile.Path;
|
||||
pathFileName = uri.LocalPath;
|
||||
|
||||
if (null == pathFileName) return;
|
||||
if (!MGSHSessionManager.IsValidSessionFile(pathFileName))
|
||||
{
|
||||
pathFileName = null;
|
||||
}
|
||||
else LoadSessionFile();
|
||||
}
|
||||
|
||||
public bool LoadSessionFile()
|
||||
{
|
||||
IsBusy = true;
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!MGSHSessionManager.IsValidSessionFile(pathFileName)) return false;
|
||||
initialPath = Path.GetDirectoryName(pathFileName);
|
||||
sessionParams = MGSHSessionManager.RestoreSession(pathFileName);
|
||||
if (null == sessionParams)
|
||||
{
|
||||
pathFileName = null;
|
||||
return false;
|
||||
}
|
||||
modelStatistics = MGSHMomentumBacktest.GetModelStatistics(sessionParams);
|
||||
modelPerformanceSeries = MGSHMomentumBacktest.GetModelPerformance(sessionParams);
|
||||
configuration = sessionParams.Configuration;
|
||||
NVPCollection nvpCollection = sessionParams.Configuration.ToNVPCollection();
|
||||
nvpDictionary = nvpCollection.ToDictionary();
|
||||
List<String> dictionaryKeys = new List<String>(nvpDictionary.Keys);
|
||||
dictionaryKeys.Sort();
|
||||
nvpDictionaryKeys = new ObservableCollection<String>(dictionaryKeys);
|
||||
selectedParameter = nvpDictionaryKeys[0];
|
||||
positions = new MGSHPositionModelCollection();
|
||||
positions.Add(sessionParams.ActivePositions); // active positions will go into their assigned slot
|
||||
positions.Add(sessionParams.HedgePositions, sessionParams.ActivePositions.GetMaxSlotNumber() + 1); // -1 is a special slot so active hedge positions will always appear in the slot position 1 past the max
|
||||
positions.Add(sessionParams.AllPositions);
|
||||
UpdatePositionPrices(false);
|
||||
UpdatePositionRSI3(true);
|
||||
RunPerformance();
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Console.WriteLine(exception.ToString());
|
||||
pathFileName = null;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
workerTask.ContinueWith(continuation =>
|
||||
{
|
||||
IsBusy = false;
|
||||
base.OnPropertyChanged("Parameters");
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
base.OnPropertyChanged("Title");
|
||||
base.OnPropertyChanged("DisplayName");
|
||||
base.OnPropertyChanged("AllPositions");
|
||||
base.OnPropertyChanged("CashBalance");
|
||||
base.OnPropertyChanged("NonTradeableCash");
|
||||
base.OnPropertyChanged("HedgeCash");
|
||||
base.OnPropertyChanged("ModelExpectation");
|
||||
base.OnPropertyChanged("ExpectationColor");
|
||||
base.OnPropertyChanged("ExpectationDescription");
|
||||
base.OnPropertyChanged("ReloadEnabled");
|
||||
base.OnPropertyChanged("LastTradeDate");
|
||||
base.OnPropertyChanged("NextTradeDate");
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdatePositionPrices(bool change = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
DateTime today = DateTime.Now;
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from MGSHPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from MGSHPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (MGSHPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
Price price = PricingDA.GetPrice(symbol);
|
||||
if (null == price) continue;
|
||||
selectedPosition.CurrentPrice = price.Close;
|
||||
selectedPosition.CurrentPriceLow = price.Low;
|
||||
selectedPosition.Volume = price.Volume;
|
||||
selectedPosition.LastUpdated = price.Date.Date < today.Date ? price.Date : today;
|
||||
}
|
||||
}
|
||||
if (change) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePositionRSI3(bool change = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from MGSHPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from MGSHPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (MGSHPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
RSICollection rsiCollection = RSIGenerator.GenerateRSI(symbol, 30, 3);
|
||||
if (null == rsiCollection || 0 == rsiCollection.Count) continue;
|
||||
selectedPosition.RSI3 = rsiCollection[rsiCollection.Count - 1].RSI;
|
||||
}
|
||||
}
|
||||
if (change) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void RunPerformance()
|
||||
{
|
||||
if (null == sessionParams) return;
|
||||
modelPerformanceSeries = MGSHMomentumBacktest.GetModelPerformance(sessionParams);
|
||||
base.OnPropertyChanged("Data"); // The CompositeDataSource for the ChartPlotters
|
||||
base.OnPropertyChanged("GraphTitle"); // The Title for the ChartPlotter
|
||||
}
|
||||
|
||||
// ************************************************************************************************************************
|
||||
|
||||
|
||||
public override bool CanPersist()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override SaveParameters GetSaveParameters()
|
||||
{
|
||||
SaveParameters saveParams = new SaveParameters();
|
||||
if (null == pathFileName) return null;
|
||||
saveParams.Add(new KeyValuePair<String, String>("Type", GetType().Namespace + "." + GetType().Name));
|
||||
saveParams.Add(new KeyValuePair<String, String>("PathFileName", pathFileName));
|
||||
return saveParams;
|
||||
}
|
||||
|
||||
public override void SetSaveParameters(SaveParameters saveParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
pathFileName = (from KeyValuePair<String, String> item in saveParameters where item.Key.Equals("PathFileName") select item).FirstOrDefault().Value;
|
||||
if (!LoadSessionFile()) pathFileName = null;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Exception:{0}", exception.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// **************************************************** P R O P E R T I E S ********************************************************
|
||||
// *****************************************************************************************************************************
|
||||
|
||||
public bool ReloadEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return !String.IsNullOrEmpty(pathFileName);
|
||||
}
|
||||
}
|
||||
|
||||
public MGSHPositionModelCollection AllPositions
|
||||
{
|
||||
get { return positions; }
|
||||
}
|
||||
|
||||
public override String DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == pathFileName) return "MGSHMomentum Model";
|
||||
String pureFileName = Utility.GetFileNameNoExtension(pathFileName);
|
||||
return "MGSHMomentum Model (" + pureFileName + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public override String Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<String> Parameters
|
||||
{
|
||||
get { return nvpDictionaryKeys; }
|
||||
}
|
||||
|
||||
public String SelectedParameter
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedParameter;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedParameter = value;
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
}
|
||||
}
|
||||
|
||||
public String ParameterValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == nvpDictionary || null == nvpDictionaryKeys || null == selectedParameter) return null;
|
||||
return nvpDictionary[selectedParameter].Value;
|
||||
}
|
||||
}
|
||||
|
||||
public String LastTradeDate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
return Utility.DateTimeToStringMMSDDSYYYY(dateGenerator.FindPrevBusinessDay(sessionParams.TradeDate));
|
||||
}
|
||||
}
|
||||
|
||||
public String NextTradeDate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.DateTimeToStringMMSDDSYYYY(sessionParams.TradeDate);
|
||||
}
|
||||
}
|
||||
|
||||
public String CashBalance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.CashBalance);
|
||||
}
|
||||
}
|
||||
|
||||
public String NonTradeableCash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.NonTradeableCash);
|
||||
}
|
||||
}
|
||||
|
||||
public String HedgeCash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return "";
|
||||
return Utility.FormatCurrency(sessionParams.HedgeCashBalance);
|
||||
}
|
||||
}
|
||||
|
||||
public String GraphTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams || null == modelPerformanceSeries) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
MGSHPositions allPositions = sessionParams.GetCombinedPositions();
|
||||
DateTime minDate = allPositions.Min(x => x.PurchaseDate);
|
||||
DateTime maxDate = PricingDA.GetLatestDate();
|
||||
|
||||
if (modelPerformanceSeries.Count < 2)
|
||||
{
|
||||
sb.Append(showAsGainLoss ? "$ GainLoss" : "% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(showAsGainLoss ? Utility.FormatCurrency(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss) : Utility.FormatPercent(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne));
|
||||
return sb.ToString();
|
||||
}
|
||||
if (showAsGainLoss)
|
||||
{
|
||||
double latestGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss;
|
||||
double change = modelPerformanceSeries[modelPerformanceSeries.Count - 1].GainLossDOD;
|
||||
sb.Append("$ GainLoss");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatCurrency(latestGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatCurrency(change));
|
||||
}
|
||||
else
|
||||
{
|
||||
double latestCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne;
|
||||
double prevCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 2].CumProdMinusOne;
|
||||
double change = latestCumGainLoss - prevCumGainLoss;
|
||||
sb.Append("% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatPercent(latestCumGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatPercent(change));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public String ModelExpectation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return "";
|
||||
return Utility.FormatNumber(modelStatistics.Expectancy, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush ExpectationColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
if (modelStatistics.Expectancy > 0.00) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public MGSHPositionModel SelectedPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedPosition;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedPosition = value;
|
||||
base.OnPropertyChanged("SelectedPosition");
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************************** T O O L T I P S *************************************************
|
||||
|
||||
public String ExpectationDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if(null==modelStatistics) return "";
|
||||
StringBuilder sb=new StringBuilder();
|
||||
sb.Append("Expectancy is (percentage of winning trades * average gain) / (percentage of losing trades * average loss).").Append("\n");
|
||||
sb.Append("Total Trades : ").Append(modelStatistics.TotalTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(modelStatistics.WinningTrades).Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(modelStatistics.LosingTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(Utility.FormatNumber(modelStatistics.WinningTradesPercent,2)).Append("%").Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(Utility.FormatNumber(modelStatistics.LosingTradesPercent,2)).Append("%").Append("\n");
|
||||
sb.Append("Average Winning Trade Gain : ").Append(Utility.FormatNumber(modelStatistics.AverageWinningTradePercentGain,2)).Append("%").Append("\n");
|
||||
sb.Append("Average Losing Trade Loss : ").Append(Utility.FormatNumber(modelStatistics.AverageLosingTradePercentLoss,2)).Append("%").Append("\n");
|
||||
sb.Append("Expectancy : ").Append(Utility.FormatNumber(modelStatistics.Expectancy,2)).Append("\n");
|
||||
sb.Append("\n");
|
||||
sb.Append("Maintain a positive Expectancy and you're a winner.");
|
||||
sb.Append("\n");
|
||||
sb.Append("The calculations are based on closed positions.");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
247
PortfolioManager/ViewModels/MainWindowViewModel.cs
Normal file
247
PortfolioManager/ViewModels/MainWindowViewModel.cs
Normal file
@@ -0,0 +1,247 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Threading;
|
||||
using MarketData.Cache;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.Command;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public class TabIndexArgs : EventArgs
|
||||
{
|
||||
public int Index { get; set; }
|
||||
}
|
||||
|
||||
public partial class MainWindowViewModel : WorkspaceViewModel
|
||||
{
|
||||
private ReadOnlyCollection<CommandViewModel> commands;
|
||||
private ObservableCollection<WorkspaceViewModel> workspaces;
|
||||
private int selectedIndex = -1;
|
||||
|
||||
public EventHandler<TabIndexArgs> OnIndexChangeEventHandler;
|
||||
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
base.DisplayName = GetTitle();
|
||||
Dispatcher.UIThread.InvokeAsync(() => LoadViewStateThreadProc());
|
||||
}
|
||||
|
||||
private static String GetTitle()
|
||||
{
|
||||
DataSourceEx dataSource = MainDataSource.Instance.LocateDataSource("market_data");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("eNavigator").Append(" ").Append("(").Append(Environment.UserName).Append(")").Append(" ");
|
||||
sb.Append("[").Append(Environment.OSVersion.VersionString).Append("]").Append(" ");
|
||||
sb.Append("[").Append("DataSource->").Append(dataSource.Datasource).Append("]");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private void LoadViewStateThreadProc()
|
||||
{
|
||||
WorkspacePersistenceHelper.Load(WorkspacePersistenceHelper.PARAMS_FILE, this.Workspaces, InstantiateWorkspace);
|
||||
}
|
||||
|
||||
protected override void OnDispose()
|
||||
{
|
||||
WorkspacePersistenceHelper.Save(WorkspacePersistenceHelper.PARAMS_FILE, this.Workspaces);
|
||||
|
||||
foreach (WorkspaceViewModel workspaceViewModel in this.Workspaces)
|
||||
{
|
||||
workspaceViewModel.Dispose();
|
||||
}
|
||||
|
||||
try { LocalPriceCache.GetInstance().Dispose(); } catch (Exception) {; }
|
||||
|
||||
try { GBPriceCache.GetInstance().Dispose(); } catch (Exception) {; }
|
||||
|
||||
// try{PriceCache.GetInstance().Dispose();}catch(Exception){;}
|
||||
// try{SymbolCache.GetInstance().Dispose();}catch(Exception){;}
|
||||
base.OnDispose();
|
||||
}
|
||||
|
||||
public override bool CanPersist()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override SaveParameters GetSaveParameters()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void SetSaveParameters(SaveParameters saveParameters)
|
||||
{
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<CommandViewModel> Commands
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == commands)
|
||||
{
|
||||
List<CommandViewModel> commandList = this.CreateCommands();
|
||||
commands = new ReadOnlyCollection<CommandViewModel>(commandList);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
|
||||
private List<CommandViewModel> CreateCommands()
|
||||
{
|
||||
return new List<CommandViewModel>()
|
||||
{
|
||||
new CommandViewModel("Gain/Loss", new MyRelayCommand(ParamArrayAttribute => this.ViewGainLoss())),
|
||||
new CommandViewModel("Momentum Model", new MyRelayCommand(ParamArrayAttribute => this.ViewMomentum())),
|
||||
new CommandViewModel("MGSHMomentum Model", new MyRelayCommand(ParamArrayAttribute => this.ViewMGSHMomentum())),
|
||||
new CommandViewModel("CMMomentum Model", new MyRelayCommand(ParamArrayAttribute => this.ViewCMMomentum())),
|
||||
new CommandViewModel("CMTrend Model", new MyRelayCommand(ParamArrayAttribute => this.ViewCMTrend()))
|
||||
};
|
||||
}
|
||||
private void ViewCMTrend()
|
||||
{
|
||||
CMTrendViewModel workspace = null;
|
||||
if (null == workspace)
|
||||
{
|
||||
workspace = new CMTrendViewModel();
|
||||
workspace.WorkspaceInstantiator = InstantiateWorkspace;
|
||||
// AddMenuItem(workspace);
|
||||
this.Workspaces.Add(workspace);
|
||||
}
|
||||
this.SetActiveWorkspace(workspace);
|
||||
}
|
||||
|
||||
private void ViewGainLoss()
|
||||
{
|
||||
GainLossViewModel workspace = null;
|
||||
if (null == workspace)
|
||||
{
|
||||
workspace = new GainLossViewModel();
|
||||
workspace.WorkspaceInstantiator = InstantiateWorkspace;
|
||||
// AddMenuItem(workspace);
|
||||
this.Workspaces.Add(workspace);
|
||||
}
|
||||
this.SetActiveWorkspace(workspace);
|
||||
}
|
||||
|
||||
private void ViewMGSHMomentum()
|
||||
{
|
||||
MGSHMomentumViewModel workspace = null;
|
||||
if (null == workspace)
|
||||
{
|
||||
workspace = new MGSHMomentumViewModel();
|
||||
workspace.WorkspaceInstantiator = InstantiateWorkspace;
|
||||
// AddMenuItem(workspace);
|
||||
this.Workspaces.Add(workspace);
|
||||
}
|
||||
this.SetActiveWorkspace(workspace);
|
||||
}
|
||||
|
||||
private void ViewMomentum()
|
||||
{
|
||||
MomentumViewModel workspace = null;
|
||||
if (null == workspace)
|
||||
{
|
||||
workspace = new MomentumViewModel();
|
||||
workspace.WorkspaceInstantiator = InstantiateWorkspace;
|
||||
// AddMenuItem(workspace);
|
||||
this.Workspaces.Add(workspace);
|
||||
}
|
||||
this.SetActiveWorkspace(workspace);
|
||||
}
|
||||
|
||||
private void ViewCMMomentum()
|
||||
{
|
||||
CMMomentumViewModel workspace = null;
|
||||
if (null == workspace)
|
||||
{
|
||||
workspace = new CMMomentumViewModel();
|
||||
workspace.WorkspaceInstantiator = InstantiateWorkspace;
|
||||
// AddMenuItem(workspace);
|
||||
this.Workspaces.Add(workspace);
|
||||
}
|
||||
this.SetActiveWorkspace(workspace);
|
||||
}
|
||||
|
||||
public ObservableCollection<WorkspaceViewModel> Workspaces
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == workspaces)
|
||||
{
|
||||
workspaces = new ObservableCollection<WorkspaceViewModel>();
|
||||
workspaces.CollectionChanged += this.OnWorkspacesChanged;
|
||||
}
|
||||
return workspaces;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWorkspacesChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.NewItems != null && e.NewItems.Count != 0)
|
||||
{
|
||||
foreach (WorkspaceViewModel workspace in e.NewItems)
|
||||
{
|
||||
workspace.RequestClose += this.OnWorkspaceRequestClose;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.OldItems != null && e.OldItems.Count != 0)
|
||||
{
|
||||
foreach (WorkspaceViewModel workspace in e.OldItems)
|
||||
{
|
||||
workspace.RequestClose -= this.OnWorkspaceRequestClose;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWorkspaceRequestClose(object sender, EventArgs e)
|
||||
{
|
||||
WorkspaceViewModel workspace = sender as WorkspaceViewModel;
|
||||
workspace.Dispose();
|
||||
this.Workspaces.Remove(workspace);
|
||||
RemoveMenuItem(workspace);
|
||||
if (null != workspace.Referer) this.SetActiveWorkspace(workspace.Referer);
|
||||
}
|
||||
|
||||
public void RemoveMenuItem(WorkspaceViewModel viewModel)
|
||||
{
|
||||
// if(!menuCollectionDictionary.ContainsKey(viewModel))return;
|
||||
// menuCollection.Remove(menuCollectionDictionary[viewModel]);
|
||||
// menuCollectionDictionary.Remove(viewModel);
|
||||
}
|
||||
|
||||
private void SetActiveWorkspace(WorkspaceViewModel workspace)
|
||||
{
|
||||
int itemIndex = workspaces.IndexOf(workspace);
|
||||
SelectedIndex = itemIndex;
|
||||
}
|
||||
|
||||
public int SelectedIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedIndex;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedIndex = value;
|
||||
TabIndexArgs args = new TabIndexArgs() { Index = selectedIndex };
|
||||
OnIndexChangeEventHandler.Invoke(this, args);
|
||||
base.OnPropertyChanged("SelectedIndex");
|
||||
}
|
||||
}
|
||||
|
||||
public void InstantiateWorkspace(SaveParameters saveParameters)
|
||||
{
|
||||
WorkspaceViewModel workspaceViewModel = WorkspacePersistenceHelper.Load(saveParameters, workspaces, InstantiateWorkspace);
|
||||
// AddMenuItem(workspaceViewModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
PortfolioManager/ViewModels/ModelBase.cs
Normal file
60
PortfolioManager/ViewModels/ModelBase.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Axiom.Utils;
|
||||
using Eremex.AvaloniaUI.Controls;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
|
||||
public abstract class ModelBase : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
|
||||
public Window GetTopLevelWindow()
|
||||
{
|
||||
Window currentWindow = default;
|
||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
currentWindow = desktop.MainWindow;
|
||||
}
|
||||
return currentWindow;
|
||||
}
|
||||
|
||||
public virtual String DisplayName
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
protected virtual bool ThrowOnInvalidPropertyName
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
protected virtual void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
this.VerifyPropertyName(propertyName);
|
||||
PropertyChangedEventHandler handler = PropertyChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
var e = new PropertyChangedEventArgs(propertyName);
|
||||
handler(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void VerifyPropertyName(string propertyName)
|
||||
{
|
||||
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
|
||||
{
|
||||
string message = "Invalid property name: " + propertyName;
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, message);
|
||||
if (this.ThrowOnInvalidPropertyName) throw new Exception(message); else Debug.Fail(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
622
PortfolioManager/ViewModels/MomentumViewModel.cs
Normal file
622
PortfolioManager/ViewModels/MomentumViewModel.cs
Normal file
@@ -0,0 +1,622 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Platform.Storage;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Eremex.AvaloniaUI.Controls;
|
||||
using MarketData;
|
||||
using MarketData.DataAccess;
|
||||
using MarketData.Generator;
|
||||
using MarketData.Generator.Interface;
|
||||
using MarketData.Generator.Model;
|
||||
using MarketData.Generator.Momentum;
|
||||
using MarketData.MarketDataModel;
|
||||
using MarketData.Utils;
|
||||
using PortfolioManager.DataSeriesViewModels;
|
||||
using PortfolioManager.Dialogs;
|
||||
using PortfolioManager.Models;
|
||||
using PortfolioManager.UIUtils;
|
||||
using Position = MarketData.Generator.Momentum.Position;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public partial class MomentumViewModel : WorkspaceViewModel
|
||||
{
|
||||
private bool isBusy = false;
|
||||
private MGSessionParams sessionParams;
|
||||
private ObservableCollection<String> nvpDictionaryKeys = default;
|
||||
private NVPDictionary nvpDictionary = null;
|
||||
private String selectedParameter = null;
|
||||
private String pathFileName;
|
||||
private String initialPath;
|
||||
private ModelPerformanceSeries modelPerformanceSeries = null;
|
||||
private ModelStatistics modelStatistics = null;
|
||||
private MGConfiguration configuration = null;
|
||||
private MGPositionModelCollection positions = null;
|
||||
private bool showAsGainLoss = true;
|
||||
private MGPositionModel selectedPosition = null;
|
||||
private bool showMarkers = false;
|
||||
|
||||
public MomentumViewModel()
|
||||
{
|
||||
DisplayName = "MGMomentum Model";
|
||||
PropertyChanged += OnViewModelPropertyChanged;
|
||||
}
|
||||
|
||||
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public override String Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
public override String DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == pathFileName) return "MGMomentum Model";
|
||||
String pureFileName = Utility.GetFileNameNoExtension(pathFileName);
|
||||
return "MGMomentum Model (" + pureFileName + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get
|
||||
{
|
||||
return isBusy;
|
||||
}
|
||||
set
|
||||
{
|
||||
isBusy = value;
|
||||
base.OnPropertyChanged("IsBusy");
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowMarkers
|
||||
{
|
||||
get
|
||||
{
|
||||
return showMarkers;
|
||||
}
|
||||
set
|
||||
{
|
||||
showMarkers = value;
|
||||
base.OnPropertyChanged("ShowMarkers");
|
||||
}
|
||||
}
|
||||
|
||||
public String TradeDate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
DateGenerator dateGenerator = new DateGenerator();
|
||||
return Utility.DateTimeToStringMMSDDSYYYY(dateGenerator.FindPrevBusinessDay(sessionParams.TradeDate));
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<String> Parameters
|
||||
{
|
||||
get { return nvpDictionaryKeys; }
|
||||
}
|
||||
|
||||
public String SelectedParameter
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedParameter;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedParameter = value;
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
}
|
||||
}
|
||||
|
||||
public String ParameterValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == nvpDictionary || null == nvpDictionaryKeys || null == selectedParameter) return null;
|
||||
return nvpDictionary[selectedParameter].Value;
|
||||
}
|
||||
}
|
||||
|
||||
public String CashBalance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.CashBalance);
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<MenuItem> PositionsMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
ObservableCollection<MenuItem> collection = new ObservableCollection<MenuItem>();
|
||||
collection.Add(new MenuItem() { Header = "Close Position...", Command = CloseCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Edit Position...", Command = EditCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Add To WatchList", Command = AddToWatchListCommand, StaysOpenOnClick = false });
|
||||
collection.Add(new MenuItem() { Header = "Remove From WatchList", Command = RemoveFromWatchListCommand, StaysOpenOnClick = false });
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ReloadEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return !String.IsNullOrEmpty(pathFileName);
|
||||
}
|
||||
}
|
||||
|
||||
public MGPositionModelCollection AllPositions
|
||||
{
|
||||
get { return positions; }
|
||||
}
|
||||
|
||||
public String NonTradeableCash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams) return Constants.CONST_DASHES;
|
||||
return Utility.FormatCurrency(sessionParams.NonTradeableCash);
|
||||
}
|
||||
}
|
||||
|
||||
public String PercentButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!showAsGainLoss) return "Show $";
|
||||
else return "Show %";
|
||||
}
|
||||
}
|
||||
|
||||
public String ModelExpectation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return "";
|
||||
return Utility.FormatNumber(modelStatistics.Expectancy, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public IBrush ExpectationColor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
if (modelStatistics.Expectancy > 0.00) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
|
||||
return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public MGPositionModel SelectedPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return selectedPosition;
|
||||
}
|
||||
set
|
||||
{
|
||||
selectedPosition = value;
|
||||
base.OnPropertyChanged("SelectedPosition");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public CompositeDataSource Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelPerformanceSeries) return GainLossModel.Empty();
|
||||
CompositeDataSource compositeDataSource = null;
|
||||
compositeDataSource = GainLossModel.GainLoss(modelPerformanceSeries, showAsGainLoss);
|
||||
return compositeDataSource;
|
||||
}
|
||||
}
|
||||
|
||||
public String GraphTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == sessionParams || null == modelPerformanceSeries) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Positions allPositions = sessionParams.GetCombinedPositions();
|
||||
DateTime minDate = allPositions.Min(x => x.PurchaseDate);
|
||||
DateTime maxDate = PricingDA.GetLatestDate();
|
||||
|
||||
if (modelPerformanceSeries.Count < 2)
|
||||
{
|
||||
sb.Append(showAsGainLoss ? "$ GainLoss" : "% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(showAsGainLoss ? Utility.FormatCurrency(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss) : Utility.FormatPercent(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne));
|
||||
return sb.ToString();
|
||||
}
|
||||
if (showAsGainLoss)
|
||||
{
|
||||
double latestGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss;
|
||||
double change = modelPerformanceSeries[modelPerformanceSeries.Count - 1].GainLossDOD;
|
||||
sb.Append("$ GainLoss");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatCurrency(latestGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatCurrency(change));
|
||||
}
|
||||
else
|
||||
{
|
||||
double latestCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne;
|
||||
double prevCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 2].CumProdMinusOne;
|
||||
double change = latestCumGainLoss - prevCumGainLoss;
|
||||
sb.Append("% Return");
|
||||
sb.Append(" ");
|
||||
sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
|
||||
sb.Append(" ");
|
||||
sb.Append(Utility.FormatPercent(latestCumGainLoss));
|
||||
sb.Append(",");
|
||||
sb.Append(" ");
|
||||
sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatPercent(change));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// *********************************************************** R E L A Y ***************************************************************
|
||||
|
||||
[RelayCommand]
|
||||
public async Task LoadFile()
|
||||
{
|
||||
await LoadTradeFile();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task Reload()
|
||||
{
|
||||
await ReloadTradeFile();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanClosePosition))]
|
||||
public async Task Close()
|
||||
{
|
||||
await OpenCloseDialog();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanEdit))]
|
||||
public async Task Edit()
|
||||
{
|
||||
await OpenEditDialog();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void ToggleReturnOrPercent()
|
||||
{
|
||||
HandleToggleReturnOrPercent();
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanAddToWatchList))]
|
||||
public async Task AddToWatchList()
|
||||
{
|
||||
WatchListDA.AddToWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Added {selectedPosition.Symbol} to WatchList", "Add To WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanRemoveFromWatchList))]
|
||||
public async Task RemoveFromWatchList()
|
||||
{
|
||||
WatchListDA.RemoveFromWatchList(selectedPosition.Symbol);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), $"Removed {selectedPosition.Symbol} from WatchList", "Remove From WatchList");
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
// ****************************************************************************************************************************************
|
||||
|
||||
public async Task OpenCloseDialog()
|
||||
{
|
||||
IPurePosition clonedPosition = Position.Clone(selectedPosition.Position);
|
||||
ClosePositionDialog dialog = new ClosePositionDialog();
|
||||
ClosePositionDialogViewModel closePositionViewModel = new ClosePositionDialogViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = closePositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
if (!closePositionViewModel.IsSuccess) return;
|
||||
MomentumBacktest momentumModel = new MomentumBacktest();
|
||||
if (!momentumModel.ClosePosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.SellDate, clonedPosition.CurrentPrice, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to close the position, check log for details.", "Close Position");
|
||||
return;
|
||||
}
|
||||
String strMessage = String.Format("Closed position for {0}, Purchase Date:{1}, Sell Date{2}, Current Price:{3}. Saved to {4}. A backup was created.",
|
||||
clonedPosition.Symbol,
|
||||
clonedPosition.PurchaseDate.ToShortDateString(),
|
||||
clonedPosition.SellDate.ToShortDateString(),
|
||||
Utility.FormatCurrency(clonedPosition.CurrentPrice),
|
||||
pathFileName);
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Close Position");
|
||||
LoadSessionFile();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task OpenEditDialog()
|
||||
{
|
||||
EditPositionDialogNoStop dialog = new EditPositionDialogNoStop();
|
||||
Position clonedPosition = Position.Clone(selectedPosition.Position);
|
||||
EditPositionDialogNoStopViewModel editPositionViewModel = new EditPositionDialogNoStopViewModel(dialog, clonedPosition);
|
||||
dialog.DataContext = editPositionViewModel;
|
||||
await dialog.ShowDialog(GetTopLevelWindow());
|
||||
GetTopLevelWindow().BringIntoView();
|
||||
if (!editPositionViewModel.IsSuccess) return;
|
||||
MomentumBacktest momentumBacktest = new MomentumBacktest();
|
||||
if (!momentumBacktest.EditPosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.PurchasePrice, pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), "Failed to edit the position, check log for details.", "Edit Position");
|
||||
return;
|
||||
}
|
||||
selectedPosition.PurchaseDate = clonedPosition.PurchaseDate;
|
||||
selectedPosition.PurchasePrice = clonedPosition.PurchasePrice;
|
||||
String strMessage = String.Format("Edited Position for {0} Purchase Date:{1} Purchase Price:{2}. A backup was created.",
|
||||
selectedPosition.Symbol, selectedPosition.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(selectedPosition.PurchasePrice));
|
||||
MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Edit Position");
|
||||
LoadSessionFile();
|
||||
}
|
||||
|
||||
private void HandleToggleReturnOrPercent()
|
||||
{
|
||||
showAsGainLoss = !showAsGainLoss;
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("PercentButtonText");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
public bool CanAddToWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
return WatchListDA.IsInWatchList(selectedPosition.Symbol) ? false : true;
|
||||
}
|
||||
|
||||
public bool CanClosePosition()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanEdit()
|
||||
{
|
||||
if (null == selectedPosition || null == selectedPosition.Symbol || !Utility.IsEpoch(selectedPosition.SellDate)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CanRemoveFromWatchList()
|
||||
{
|
||||
if (null == selectedPosition) return false;
|
||||
if (!WatchListDA.IsInWatchList(selectedPosition.Symbol)) return false;
|
||||
PortfolioTrades portfolioTrades = PortfolioDA.GetOpenTrades();
|
||||
if (portfolioTrades.Any(x => x.Symbol.Equals(selectedPosition.Symbol))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task ReloadTradeFile()
|
||||
{
|
||||
LoadSessionFile();
|
||||
await Task.FromResult(true);
|
||||
}
|
||||
|
||||
public async Task LoadTradeFile()
|
||||
{
|
||||
TopLevel topLevel = TopLevel.GetTopLevel(GetTopLevelWindow());
|
||||
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Open Trade File",
|
||||
AllowMultiple = false,
|
||||
});
|
||||
IStorageFile storageFile = files.FirstOrDefault();
|
||||
if (null == storageFile) return;
|
||||
Uri uri = storageFile.Path;
|
||||
pathFileName = uri.LocalPath;
|
||||
if (null == pathFileName) return;
|
||||
if (!MGSessionManager.IsValidSessionFile(pathFileName))
|
||||
{
|
||||
pathFileName = null;
|
||||
}
|
||||
else LoadSessionFile();
|
||||
}
|
||||
|
||||
public bool LoadSessionFile()
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
Task workerTask = Task.Factory.StartNew(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!MGSessionManager.IsValidSessionFile(pathFileName))
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), String.Format("'{0}' is not a valid model. IsValidSessionFile returned false.", pathFileName));
|
||||
pathFileName = null;
|
||||
return false;
|
||||
}
|
||||
initialPath = Path.GetDirectoryName(pathFileName);
|
||||
sessionParams = MGSessionManager.RestoreSession(pathFileName);
|
||||
if (null == sessionParams)
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), String.Format("Unable to open '{0}'. Restore session failed.", pathFileName));
|
||||
pathFileName = null;
|
||||
return false;
|
||||
}
|
||||
modelStatistics = MomentumBacktest.GetModelStatistics(sessionParams);
|
||||
modelPerformanceSeries = MomentumBacktest.GetModelPerformance(sessionParams);
|
||||
configuration = sessionParams.Configuration;
|
||||
NVPCollection nvpCollection = sessionParams.Configuration.ToNVPCollection();
|
||||
nvpDictionary = nvpCollection.ToDictionary();
|
||||
List<String> dictionaryKeys = new List<String>(nvpDictionary.Keys);
|
||||
dictionaryKeys.Sort();
|
||||
nvpDictionaryKeys = new ObservableCollection<String>(dictionaryKeys);
|
||||
selectedParameter = nvpDictionaryKeys[0];
|
||||
positions = new MGPositionModelCollection();
|
||||
positions.Add(sessionParams.ActivePositions);
|
||||
positions.Add(sessionParams.AllPositions);
|
||||
UpdatePositionPrices(false);
|
||||
UpdatePositionRSI3(true);
|
||||
RunPerformance();
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MxMessageBox.Show(GetTopLevelWindow(), String.Format("Exception {0}", exception.ToString()), "Error");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
workerTask.ContinueWith(continuation =>
|
||||
{
|
||||
IsBusy = false;
|
||||
base.OnPropertyChanged("Parameters");
|
||||
base.OnPropertyChanged("SelectedParameter");
|
||||
base.OnPropertyChanged("ParameterValue");
|
||||
base.OnPropertyChanged("Title");
|
||||
base.OnPropertyChanged("DisplayName");
|
||||
base.OnPropertyChanged("AllPositions");
|
||||
// base.OnPropertyChanged("CanMonitor");
|
||||
base.OnPropertyChanged("CashBalance");
|
||||
base.OnPropertyChanged("NonTradeableCash");
|
||||
base.OnPropertyChanged("ModelExpectation");
|
||||
base.OnPropertyChanged("ExpectationColor");
|
||||
base.OnPropertyChanged("ReloadEnabled");
|
||||
base.OnPropertyChanged("ExpectationDescription");
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdatePositionPrices(bool change = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
DateTime today = DateTime.Now;
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from MGPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from MGPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (MGPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
Price price = PricingDA.GetPrice(symbol);
|
||||
if (null == price) continue;
|
||||
selectedPosition.CurrentPrice = price.Close;
|
||||
selectedPosition.Volume = price.Volume;
|
||||
selectedPosition.LastUpdated = price.Date.Date < today.Date ? price.Date : today;
|
||||
}
|
||||
}
|
||||
if (change) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePositionRSI3(bool change = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (null == positions || 0 == positions.Count) return;
|
||||
List<String> symbols = (from MGPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
|
||||
foreach (String symbol in symbols)
|
||||
{
|
||||
var selectedPositions = (from MGPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
|
||||
foreach (MGPositionModel selectedPosition in selectedPositions)
|
||||
{
|
||||
RSICollection rsiCollection = RSIGenerator.GenerateRSI(symbol, 30, 3);
|
||||
if (null == rsiCollection || 0 == rsiCollection.Count) continue;
|
||||
selectedPosition.RSI3 = rsiCollection[rsiCollection.Count - 1].RSI;
|
||||
}
|
||||
}
|
||||
if (change) positions.OnCollectionChanged();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void RunPerformance()
|
||||
{
|
||||
if (null == sessionParams) return;
|
||||
modelPerformanceSeries = MomentumBacktest.GetModelPerformance(sessionParams);
|
||||
base.OnPropertyChanged("Data");
|
||||
base.OnPropertyChanged("GraphTitle");
|
||||
}
|
||||
|
||||
// ********************************************************** P E R S I S T E N C E ******************************************************
|
||||
|
||||
public override bool CanPersist()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override SaveParameters GetSaveParameters()
|
||||
{
|
||||
SaveParameters saveParams = new SaveParameters();
|
||||
if (null == pathFileName) return null;
|
||||
saveParams.Add(new KeyValuePair<String, String>("Type", GetType().Namespace + "." + GetType().Name));
|
||||
saveParams.Add(new KeyValuePair<String, String>("PathFileName", pathFileName));
|
||||
return saveParams;
|
||||
}
|
||||
|
||||
public override void SetSaveParameters(SaveParameters saveParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
pathFileName = (from KeyValuePair<String, String> item in saveParameters where item.Key.Equals("PathFileName") select item).FirstOrDefault().Value;
|
||||
if (!LoadSessionFile()) pathFileName = null;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Exception:{0}", exception.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
// ************************************************** T O O L T I P S *************************************************
|
||||
|
||||
public String ExpectationDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == modelStatistics) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("Expectancy is (percentage of winning trades * average gain) / (percentage of losing trades * average loss).").Append("\n");
|
||||
sb.Append("Total Trades : ").Append(modelStatistics.TotalTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(modelStatistics.WinningTrades).Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(modelStatistics.LosingTrades).Append("\n");
|
||||
sb.Append("Winning Trades : ").Append(Utility.FormatNumber(modelStatistics.WinningTradesPercent, 2)).Append("%").Append("\n");
|
||||
sb.Append("Losing Trades : ").Append(Utility.FormatNumber(modelStatistics.LosingTradesPercent, 2)).Append("%").Append("\n");
|
||||
sb.Append("Average Winning Trade Gain : ").Append(Utility.FormatNumber(modelStatistics.AverageWinningTradePercentGain, 2)).Append("%").Append("\n");
|
||||
sb.Append("Average Losing Trade Loss : ").Append(Utility.FormatNumber(modelStatistics.AverageLosingTradePercentLoss, 2)).Append("%").Append("\n");
|
||||
sb.Append("Expectancy : ").Append(Utility.FormatNumber(modelStatistics.Expectancy, 2)).Append("\n");
|
||||
sb.Append("\n");
|
||||
sb.Append("Maintain a positive Expectancy and you're a winner.");
|
||||
sb.Append("\n");
|
||||
sb.Append("The calculations are based on closed positions.");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
PortfolioManager/ViewModels/ViewModelBase.cs
Normal file
36
PortfolioManager/ViewModels/ViewModelBase.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.ComponentModel;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public abstract class ViewModelBase : ModelBase, IDisposable, IPersistentViewModel
|
||||
{
|
||||
protected ViewModelBase()
|
||||
{
|
||||
}
|
||||
public abstract SaveParameters GetSaveParameters();
|
||||
|
||||
public abstract void SetSaveParameters(SaveParameters saveParameters);
|
||||
|
||||
public abstract bool CanPersist();
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
this.OnDispose();
|
||||
}
|
||||
|
||||
protected virtual void OnDispose()
|
||||
{
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
~ViewModelBase()
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
110
PortfolioManager/ViewModels/WorkspaceViewModel.cs
Normal file
110
PortfolioManager/ViewModels/WorkspaceViewModel.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia.Controls;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public delegate void InstantiateWorkspace(SaveParameters saveParameters);
|
||||
|
||||
public abstract class WorkspaceViewModel : ViewModelBase
|
||||
{
|
||||
// Relay Command
|
||||
private RelayCommand closeCommand;
|
||||
|
||||
// Events
|
||||
public event EventHandler RequestClose;
|
||||
|
||||
private InstantiateWorkspace workspaceInstantiator;
|
||||
private bool canClose = true;
|
||||
private bool isClosed = false;
|
||||
private String title = "WorkspaceViewModel";
|
||||
|
||||
public WorkspaceViewModel Referer { get; set; }
|
||||
|
||||
protected WorkspaceViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
public InstantiateWorkspace WorkspaceInstantiator
|
||||
{
|
||||
get
|
||||
{
|
||||
return workspaceInstantiator;
|
||||
}
|
||||
set
|
||||
{
|
||||
workspaceInstantiator = value;
|
||||
}
|
||||
}
|
||||
|
||||
public IRelayCommand CloseCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == closeCommand)
|
||||
{
|
||||
Action closeAction = delegate ()
|
||||
{
|
||||
this.OnRequestClose();
|
||||
};
|
||||
closeCommand = new RelayCommand(delegate () { this.OnRequestClose(); });
|
||||
}
|
||||
return closeCommand;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsClosed
|
||||
{
|
||||
get { return isClosed; }
|
||||
set
|
||||
{
|
||||
if (isClosed != value)
|
||||
{
|
||||
isClosed = value;
|
||||
base.OnPropertyChanged("IsClosed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanClose
|
||||
{
|
||||
get { return canClose; }
|
||||
set
|
||||
{
|
||||
if (canClose != value)
|
||||
{
|
||||
canClose = value;
|
||||
base.OnPropertyChanged("CanClose");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual String Title
|
||||
{
|
||||
get
|
||||
{
|
||||
return title;
|
||||
}
|
||||
set
|
||||
{
|
||||
title = value;
|
||||
base.OnPropertyChanged("Title");
|
||||
}
|
||||
}
|
||||
|
||||
public virtual String Header
|
||||
{
|
||||
get
|
||||
{
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRequestClose()
|
||||
{
|
||||
EventHandler handler = this.RequestClose;
|
||||
if (null != handler) handler(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
//using System.Windows.Data;
|
||||
//using System.Windows.Forms;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
//using MarketData;
|
||||
using System.Reflection;
|
||||
using MarketData;
|
||||
|
||||
namespace PortfolioManager.ViewModels
|
||||
{
|
||||
public class WorkspaceViewModelPersistenceHolder
|
||||
{
|
||||
public WorkspaceViewModel Workspace { get; set; }
|
||||
public SaveParameters WorkspaceParameters { get; set; }
|
||||
}
|
||||
public class WorkspaceViewModelPersistenceCollection : List<WorkspaceViewModelPersistenceHolder>
|
||||
{
|
||||
}
|
||||
public class WorkspacePersistenceHelper
|
||||
{
|
||||
public static String PARAMS_FILE="saveparams.config";
|
||||
|
||||
private WorkspacePersistenceHelper()
|
||||
{
|
||||
}
|
||||
|
||||
public static void Save(String pathFileName, ObservableCollection<WorkspaceViewModel> workspaces)
|
||||
{
|
||||
try
|
||||
{
|
||||
StreamWriter streamWriter = null;
|
||||
FileStream outStream = null;
|
||||
if (null == workspaces || 0 == workspaces.Count) return;
|
||||
// MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Writing {0} to {1}", pathFileName, Directory.GetCurrentDirectory()));
|
||||
if (!File.Exists(pathFileName)) streamWriter = File.CreateText(pathFileName);
|
||||
else
|
||||
{
|
||||
outStream = new FileStream(pathFileName, FileMode.Truncate);
|
||||
streamWriter = new StreamWriter(outStream);
|
||||
}
|
||||
foreach (WorkspaceViewModel workspaceViewModel in workspaces)
|
||||
{
|
||||
if (workspaceViewModel.CanPersist())
|
||||
{
|
||||
SaveParameters workspaceViewModelParams = workspaceViewModel.GetSaveParameters();
|
||||
if (null == workspaceViewModelParams) continue;
|
||||
streamWriter.WriteLine(workspaceViewModelParams.ToString());
|
||||
}
|
||||
}
|
||||
streamWriter.Flush();
|
||||
streamWriter.Close();
|
||||
streamWriter.Dispose();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.Write(exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void Load(String pathFileName, ObservableCollection<WorkspaceViewModel> workspaces, InstantiateWorkspace workspaceInstantiator)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dictionary<int, SaveParameters> loadedItems = new Dictionary<int, SaveParameters>();
|
||||
WorkspaceViewModelPersistenceCollection workspaceViewModelPersistenceCollection = new WorkspaceViewModelPersistenceCollection();
|
||||
if (!File.Exists(PARAMS_FILE))
|
||||
{
|
||||
// MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Unable to load params file : {0}",PARAMS_FILE));
|
||||
return;
|
||||
}
|
||||
FileStream inStream = new FileStream(PARAMS_FILE, FileMode.Open);
|
||||
StreamReader streamReader = new StreamReader(inStream);
|
||||
String strLine = null;
|
||||
while (null != (strLine = streamReader.ReadLine()))
|
||||
{
|
||||
SaveParameters workspaceViewModelParams = null;
|
||||
try { workspaceViewModelParams = SaveParameters.Parse(strLine); }
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Error parsing line from configuration file. The line was : {0}", strLine));
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, String.Format("The exception was : {0}", exception.ToString()));
|
||||
continue;
|
||||
}
|
||||
if (loadedItems.ContainsKey(workspaceViewModelParams.Key))
|
||||
{
|
||||
// MDTrace.WriteLine(LogLevel.DEBUG,"Item already loaded, details follow...");
|
||||
foreach (KeyValuePair<String, String> kvp in workspaceViewModelParams)
|
||||
{
|
||||
// MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Key:{0} Value:{1}",kvp.Key,kvp.Value));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else loadedItems.Add(workspaceViewModelParams.Key, workspaceViewModelParams);
|
||||
var itemType = from KeyValuePair<String, String> item in workspaceViewModelParams where item.Key.Equals("Type") select item;
|
||||
KeyValuePair<String, String> selectedItem = itemType.FirstOrDefault();
|
||||
WorkspaceViewModel workspaceViewModel = null;
|
||||
Type[] booleanTypes = new Type[1];
|
||||
booleanTypes[0] = typeof(bool);
|
||||
ConstructorInfo constructorInfo = Type.GetType(selectedItem.Value).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, CallingConventions.HasThis, booleanTypes, null);
|
||||
if (null != constructorInfo) workspaceViewModel = (WorkspaceViewModel)Activator.CreateInstance(Type.GetType(selectedItem.Value), new object[] { true });
|
||||
else workspaceViewModel = (WorkspaceViewModel)Activator.CreateInstance(Type.GetType(selectedItem.Value));
|
||||
workspaceViewModel.WorkspaceInstantiator = workspaceInstantiator;
|
||||
WorkspaceViewModelPersistenceHolder workspaceViewModelPersistenceHolder = new WorkspaceViewModelPersistenceHolder();
|
||||
workspaceViewModelPersistenceHolder.Workspace = workspaceViewModel;
|
||||
workspaceViewModelPersistenceHolder.WorkspaceParameters = workspaceViewModelParams;
|
||||
workspaceViewModelPersistenceCollection.Add(workspaceViewModelPersistenceHolder);
|
||||
}
|
||||
streamReader.Close();
|
||||
for (int index = 0; index < workspaceViewModelPersistenceCollection.Count; index++)
|
||||
{
|
||||
WorkspaceViewModelPersistenceHolder workspaceViewModelPersistenceHolder = workspaceViewModelPersistenceCollection[index];
|
||||
workspaces.Add(workspaceViewModelPersistenceHolder.Workspace);
|
||||
workspaceViewModelPersistenceHolder.Workspace.SetSaveParameters(workspaceViewModelPersistenceHolder.WorkspaceParameters);
|
||||
if (index == workspaceViewModelPersistenceCollection.Count - 1)
|
||||
{
|
||||
// ICollectionView collectionView = CollectionViewSource.GetDefaultView(workspaces);
|
||||
// if (null != collectionView) collectionView.MoveCurrentTo(workspaceViewModelPersistenceHolder.Workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MDTrace.WriteLine(LogLevel.DEBUG, exception);
|
||||
}
|
||||
}
|
||||
|
||||
public static WorkspaceViewModel Load(SaveParameters saveParams, ObservableCollection<WorkspaceViewModel> workspaces, InstantiateWorkspace workspaceInstantiator)
|
||||
{
|
||||
var itemType = from KeyValuePair<String, String> item in saveParams where item.Key.Equals("Type") select item;
|
||||
KeyValuePair<String, String> selectedItem = itemType.FirstOrDefault();
|
||||
WorkspaceViewModel workspaceViewModel = null;
|
||||
Type[] booleanTypes = new Type[1];
|
||||
booleanTypes[0] = typeof(bool);
|
||||
ConstructorInfo constructorInfo = Type.GetType(selectedItem.Value).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, CallingConventions.HasThis, booleanTypes, null);
|
||||
if (null != constructorInfo) workspaceViewModel = (WorkspaceViewModel)Activator.CreateInstance(Type.GetType(selectedItem.Value), new object[] { true });
|
||||
else workspaceViewModel = (WorkspaceViewModel)Activator.CreateInstance(Type.GetType(selectedItem.Value));
|
||||
workspaceViewModel.WorkspaceInstantiator = workspaceInstantiator;
|
||||
WorkspaceViewModelPersistenceHolder workspaceViewModelPersistenceHolder = new WorkspaceViewModelPersistenceHolder();
|
||||
workspaceViewModelPersistenceHolder.Workspace = workspaceViewModel;
|
||||
workspaceViewModelPersistenceHolder.WorkspaceParameters = saveParams;
|
||||
workspaces.Add(workspaceViewModelPersistenceHolder.Workspace);
|
||||
workspaceViewModelPersistenceHolder.Workspace.SetSaveParameters(workspaceViewModelPersistenceHolder.WorkspaceParameters);
|
||||
// ICollectionView collectionView = CollectionViewSource.GetDefaultView(workspaces);
|
||||
// if (null != collectionView) collectionView.MoveCurrentTo(workspaceViewModelPersistenceHolder.Workspace);
|
||||
return workspaceViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
public class SaveParametersList : List<KeyValuePair<String, String>>
|
||||
{
|
||||
public int Key
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (KeyValuePair<String, String> kvp in this)
|
||||
{
|
||||
sb.Append(kvp.Key).Append(",").Append(kvp.Value);
|
||||
}
|
||||
return sb.ToString().GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SaveParameters : SaveParametersList
|
||||
{
|
||||
public WorkspaceViewModel Referer { get; set; }
|
||||
|
||||
public bool ContainsKey(String key)
|
||||
{
|
||||
foreach (KeyValuePair<String, String> item in this)
|
||||
{
|
||||
if (item.Key.Equals(key)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override String ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int index = 0; index < Count; index++)
|
||||
{
|
||||
KeyValuePair<String, String> item = this[index];
|
||||
sb.Append(item.Key).Append(",").Append(item.Value);
|
||||
if (index < Count - 1) sb.Append(",");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static SaveParameters Parse(String strLine)
|
||||
{
|
||||
if (null == strLine) return null;
|
||||
String[] strArray = strLine.Split(',');
|
||||
if (null == strArray || 0 == strArray.Length) return null;
|
||||
SaveParameters saveParameters = new SaveParameters();
|
||||
for (int index = 0; index < strArray.Length; index += 2)
|
||||
{
|
||||
KeyValuePair<String, String> savedParameter = new KeyValuePair<String, String>(strArray[index], strArray[index + 1]);
|
||||
saveParameters.Add(savedParameter);
|
||||
}
|
||||
return saveParameters;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
252
PortfolioManager/Views/CMMomentumView.axaml
Normal file
252
PortfolioManager/Views/CMMomentumView.axaml
Normal file
@@ -0,0 +1,252 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:PortfolioManager.ViewModels"
|
||||
xmlns:vw="using:PortfolioManager.Views"
|
||||
xmlns:md="using:PortfolioManager.Models"
|
||||
xmlns:local="using:PortfolioManager.UIUtils"
|
||||
xmlns:li="using:LoadingIndicators.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:mxc="https://schemas.eremexcontrols.net/avalonia/charts"
|
||||
x:DataType="vm:CMMomentumViewModel"
|
||||
x:Class="PortfolioManager.Views.CMMomentumView"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<local:CurrencyValueConverter x:Key="CurrencyFormat"/>
|
||||
<local:DoubleValueConverter x:Key="DoubleFormat"/>
|
||||
<local:IntValueConverter x:Key="IntFormat"/>
|
||||
<local:DateValueConverter x:Key="DateFormat"/>
|
||||
<local:RMultipleValueConverter x:Key="RMultipleFormat"/>
|
||||
<local:BoolValueConverter x:Key="BoolFormat"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Background="LightGray">
|
||||
<li:LoadingIndicator ZIndex="1" IsActive="{Binding IsBusy}" Mode="Arcs" SpeedRatio="1.2" Width="200" Height="200"/>
|
||||
<DockPanel>
|
||||
<Grid DockPanel.Dock="Left" Margin="0,2,4,2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="3" />
|
||||
<ColumnDefinition Width="11*" />
|
||||
<ColumnDefinition Width="3" />
|
||||
<ColumnDefinition Width="11*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="35" />
|
||||
<RowDefinition Height="3" />
|
||||
<RowDefinition Height="1*" />
|
||||
<RowDefinition Height="3" />
|
||||
<RowDefinition Height="2*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="5" FontSize="16" Text="{Binding Path=Title, Mode=OneWay}" HorizontalAlignment="Center"></TextBlock>
|
||||
|
||||
<StackPanel>
|
||||
<Label Content="Trade Date" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox MinWidth="80" Height="24" IsReadOnly="true" Text="{Binding Path=TradeDate, Mode=OneWay}" />
|
||||
|
||||
<Label Content="Parameters" HorizontalAlignment="Center" ></Label>
|
||||
<ComboBox ItemsSource="{Binding Path=Parameters, Mode=OneWay}" SelectedItem="{Binding Path=SelectedParameter}"></ComboBox>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=ParameterValue, Mode=OneWay}" />
|
||||
|
||||
<Label Content="Tradeable Cash" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=CashBalance, Mode=OneWay}" />
|
||||
|
||||
<Label Content="Non-Tradeable Cash" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=NonTradeableCash, Mode=OneWay}" />
|
||||
|
||||
<Button Content="Load Trade File" HorizontalAlignment="Stretch" Command="{Binding Path=LoadFileCommand}"></Button>
|
||||
|
||||
<Button Content="Reload" HorizontalAlignment="Stretch" Command="{Binding Path=ReloadCommand}" IsEnabled="{Binding Path=ReloadEnabled}"></Button>
|
||||
|
||||
<Button Content="{Binding Path=PercentButtonText}" HorizontalAlignment="Stretch" Command="{Binding Path=ToggleReturnOrPercentCommand}"></Button>
|
||||
|
||||
<Label Content="Expectancy" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Background="WhiteSmoke" Focusable="false" Height="24" MinWidth="80" HorizontalAlignment="Stretch"
|
||||
IsReadOnly="true" Text="{Binding Path=ModelExpectation, Mode=OneWay}" Foreground="{Binding Path=ExpectationColor}"
|
||||
ToolTip.Tip="{Binding $parent[vw:CMMomentumView].((vm:CMMomentumViewModel)DataContext).ExpectationDescription}, Mode=OneWay"/>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<DockPanel x:Name="DockPanelA" Grid.Row="2" Grid.Column="2" Width="NaN" Height="NaN" >
|
||||
</DockPanel>
|
||||
|
||||
<DockPanel x:Name="DockPanelB" Grid.Row="2" Grid.Column="4" Width="NaN" Height="NaN" LastChildFill="False">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="24" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" FontSize="16" Text="{Binding Path=GraphTitle}" HorizontalAlignment="Center"></TextBlock>
|
||||
<mxc:CartesianChart Grid.Row="1">
|
||||
<mxc:CartesianChart.Series>
|
||||
<mxc:CartesianSeries Name="PerformanceSeries" DataAdapter="{Binding Data.DataAdapter}" >
|
||||
<mxc:CartesianLineSeriesView Color="MidnightBlue" MarkerSize="4" ShowMarkers="{Binding Path=ShowMarkers, Mode=TwoWay}" Thickness="2"/>
|
||||
</mxc:CartesianSeries>
|
||||
</mxc:CartesianChart.Series>
|
||||
</mxc:CartesianChart>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
|
||||
<DockPanel x:Name="DockPanel2" Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" Width="NaN" Height="NaN">
|
||||
<Grid>
|
||||
<Grid x:Name="PositionsView" >
|
||||
<DataGrid IsEnabled="true" Focusable="true" Margin="20" SelectedItem="{Binding Path=SelectedPosition, Mode=TwoWay}" ItemsSource="{Binding Path=AllPositions}"
|
||||
IsReadOnly="True"
|
||||
CanUserReorderColumns="True"
|
||||
CanUserResizeColumns="True"
|
||||
CanUserSortColumns="False"
|
||||
GridLinesVisibility="All"
|
||||
BorderThickness="1" BorderBrush="Gray">
|
||||
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu ItemsSource="{Binding PositionsMenuItems}">
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow:selected">
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Foreground" Value="Black" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="Opacity" Value=".75" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Symbol" Binding="{Binding Symbol}"/>
|
||||
|
||||
<DataGridTextColumn Header="Slot" Binding="{Binding SlotAsString}" />
|
||||
|
||||
<DataGridTemplateColumn Header="Purchased">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding PurchaseDate,StringFormat='{}{0:MM/dd/yyyy}'}" Foreground="{Binding PurchaseDateColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Sold">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding SellDate, StringFormat='{}{0:MM/dd/yyyy}'}" Foreground="{Binding SellDateColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Shares" Binding="{Binding Shares, StringFormat='{}{0:N0}'}" />
|
||||
|
||||
<DataGridTemplateColumn Header="Exposure" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ActiveExposure, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" Foreground="{Binding ActiveExposureColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Market Value" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ActiveMarketValue, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" Foreground="{Binding ActiveMarketValueColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="GainLoss" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding GainLoss,Converter={StaticResource CurrencyFormat},ConverterParameter=2}" Foreground="{Binding GainLossColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="GainLoss(%)" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding GainLossPcnt,StringFormat='{}{0:P3}'}" Foreground="{Binding GainLossPcntColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Days" Binding="{Binding DaysHeld}" />
|
||||
|
||||
<DataGridTextColumn Header="Purch.Price" Binding="{Binding PurchasePrice, Converter={StaticResource CurrencyFormat},ConverterParameter=3}" />
|
||||
|
||||
<DataGridTemplateColumn Header="CurrentPrice" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding CurrentPrice, Converter={StaticResource CurrencyFormat},ConverterParameter=3}" Foreground="{Binding CurrentPriceColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="RSI3" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding RSI3, StringFormat='{}{0:N2}',Converter={StaticResource DoubleFormat},ConverterParameter=2}" Foreground="{Binding RSI3Color}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Updated" Binding="{Binding LastUpdated, StringFormat='{}{0:MM/dd/yyyy HH:mm:ss}'}" />
|
||||
|
||||
|
||||
<DataGridTemplateColumn Header="Beta" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=Beta,StringFormat='{}{0:N2}'}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="BetaMonths" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=BetaMonths,StringFormat='{}{0:N0}'}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="SharpeRatio" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=SharpeRatio,Converter={StaticResource DoubleFormat}}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="CNNPrediction" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=CNNPrediction,Converter={StaticResource BoolFormat},ConverterParameter=2}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</DockPanel>
|
||||
|
||||
<GridSplitter Background="LightBlue" ResizeDirection="Rows" Grid.Column="2" Grid.ColumnSpan="5" Grid.Row="3" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
|
||||
<GridSplitter Grid.Row="1" Grid.Column="1" ResizeDirection="Columns" Grid.RowSpan="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="LightBlue" Width="3"/>
|
||||
<GridSplitter Grid.Row="1" Grid.Column="3" ResizeDirection="Columns" Grid.RowSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="LightBlue" Width="3"/>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
13
PortfolioManager/Views/CMMomentumView.axaml.cs
Normal file
13
PortfolioManager/Views/CMMomentumView.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace PortfolioManager.Views;
|
||||
|
||||
public partial class CMMomentumView : UserControl
|
||||
{
|
||||
public CMMomentumView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
272
PortfolioManager/Views/CMTrendView.axaml
Normal file
272
PortfolioManager/Views/CMTrendView.axaml
Normal file
@@ -0,0 +1,272 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:PortfolioManager.ViewModels"
|
||||
xmlns:vw="using:PortfolioManager.Views"
|
||||
xmlns:md="using:PortfolioManager.Models"
|
||||
xmlns:local="using:PortfolioManager.UIUtils"
|
||||
xmlns:li="using:LoadingIndicators.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:mxc="https://schemas.eremexcontrols.net/avalonia/charts"
|
||||
x:DataType="vm:CMTrendViewModel"
|
||||
x:Class="PortfolioManager.Views.CMTrendView"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<local:CurrencyValueConverter x:Key="CurrencyFormat"/>
|
||||
<local:DoubleValueConverter x:Key="DoubleFormat"/>
|
||||
<local:IntValueConverter x:Key="IntFormat"/>
|
||||
<local:DateValueConverter x:Key="DateFormat"/>
|
||||
<local:RMultipleValueConverter x:Key="RMultipleFormat"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Background="LightGray">
|
||||
<li:LoadingIndicator ZIndex="1" IsActive="{Binding IsBusy}" Mode="Arcs" SpeedRatio="1.2" Width="200" Height="200"/>
|
||||
<DockPanel>
|
||||
<Grid DockPanel.Dock="Left" Margin="0,2,4,2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="3" />
|
||||
<ColumnDefinition Width="11*" />
|
||||
<ColumnDefinition Width="3" />
|
||||
<ColumnDefinition Width="11*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="35" />
|
||||
<RowDefinition Height="3" />
|
||||
<RowDefinition Height="1*" />
|
||||
<RowDefinition Height="3" />
|
||||
<RowDefinition Height="2*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="5" FontSize="16" Text="{Binding Path=Title, Mode=OneWay}" HorizontalAlignment="Center"></TextBlock>
|
||||
|
||||
<StackPanel Orientation="Vertical" Grid.Row="2" Grid.RowSpan="3" Grid.Column="0" Margin="0,5,0,-5">
|
||||
<Label Content="Trade Date" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox MinWidth="80" Height="24" IsReadOnly="true" Text="{Binding Path=TradeDate, Mode=OneWay}" />
|
||||
|
||||
<Label Content="Parameters" HorizontalAlignment="Center" ></Label>
|
||||
<ComboBox ItemsSource="{Binding Path=Parameters, Mode=OneWay}" SelectedItem="{Binding Path=SelectedParameter}"></ComboBox>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=ParameterValue, Mode=OneWay}" />
|
||||
|
||||
<Label Content="Tradeable Cash" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=CashBalance, Mode=OneWay}" />
|
||||
|
||||
<Label Content="Non-Tradeable Cash" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=NonTradeableCash, Mode=OneWay}" />
|
||||
|
||||
<Button Content="Load Trade File" HorizontalAlignment="Stretch" Command="{Binding Path=LoadFileCommand}"></Button>
|
||||
|
||||
<Button Content="Reload" HorizontalAlignment="Stretch" Command="{Binding Path=ReloadCommand}" IsEnabled="{Binding Path=ReloadEnabled}"></Button>
|
||||
|
||||
<Button Content="{Binding Path=PercentButtonText}" HorizontalAlignment="Stretch" Command="{Binding Path=ToggleReturnOrPercentCommand}"></Button>
|
||||
|
||||
<Label Content="Expectancy" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Background="WhiteSmoke" Focusable="false" Height="24" MinWidth="80" HorizontalAlignment="Stretch"
|
||||
IsReadOnly="true" Text="{Binding Path=ModelExpectation, Mode=OneWay}" Foreground="{Binding Path=ExpectationColor}"
|
||||
ToolTip.Tip="{Binding $parent[vw:CMTrendView].((vm:CMTrendViewModel)DataContext).ExpectationDescription}, Mode=OneWay"/>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<DockPanel x:Name="DockPanelA" Grid.Row="2" Grid.Column="2" Width="NaN" Height="NaN" >
|
||||
<!-- RadGrid for Candidates -->
|
||||
|
||||
</DockPanel>
|
||||
<DockPanel x:Name="DockPanelB" Grid.Row="2" Grid.Column="4" Width="NaN" Height="NaN" LastChildFill="False">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="24" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" FontSize="16" Text="{Binding Path=GraphTitle}" HorizontalAlignment="Center"></TextBlock>
|
||||
<mxc:CartesianChart Grid.Row="1">
|
||||
<mxc:CartesianChart.Series>
|
||||
<mxc:CartesianSeries Name="PerformanceSeries" DataAdapter="{Binding Data.DataAdapter}" >
|
||||
<mxc:CartesianLineSeriesView Color="MidnightBlue" MarkerSize="4" ShowMarkers="{Binding Path=ShowMarkers, Mode=TwoWay}" Thickness="2"/>
|
||||
</mxc:CartesianSeries>
|
||||
</mxc:CartesianChart.Series>
|
||||
</mxc:CartesianChart>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
|
||||
<DockPanel x:Name="DockPanel2" Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" Width="NaN" Height="NaN">
|
||||
<Grid>
|
||||
<Grid x:Name="PositionsView" >
|
||||
<DataGrid IsEnabled="true" Focusable="true" Margin="20" SelectedItem="{Binding Path=SelectedPosition, Mode=TwoWay}" ItemsSource="{Binding Path=AllPositions}"
|
||||
IsReadOnly="True"
|
||||
CanUserReorderColumns="True"
|
||||
CanUserResizeColumns="True"
|
||||
CanUserSortColumns="False"
|
||||
GridLinesVisibility="All"
|
||||
BorderThickness="1" BorderBrush="Gray">
|
||||
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu ItemsSource="{Binding PositionsMenuItems}">
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow:selected">
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Foreground" Value="Black" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="Opacity" Value=".50" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Symbol" Binding="{Binding Symbol}"/>
|
||||
|
||||
<DataGridTemplateColumn Header="Purchased">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding PurchaseDate,StringFormat='{}{0:MM/dd/yyyy}'}" Foreground="{Binding PurchaseDateColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Sold">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding SellDate, StringFormat='{}{0:MM/dd/yyyy}'}" Foreground="{Binding SellDateColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Shares" Binding="{Binding Shares, StringFormat='{}{0:N0}'}" />
|
||||
|
||||
<DataGridTemplateColumn Header="Exposure" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ActiveExposure, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" Foreground="{Binding ActiveExposureColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Market Value" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ActiveMarketValue, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" Foreground="{Binding ActiveMarketValueColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="GainLoss" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding GainLoss, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" Foreground="{Binding GainLossColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="GainLoss(%)" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding GainLossPcnt,StringFormat='{}{0:P3}'}" Foreground="{Binding GainLossPcntColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Days" Binding="{Binding DaysHeld}" />
|
||||
|
||||
<DataGridTextColumn Header="Purch.Price" Binding="{Binding PurchasePrice, Converter={StaticResource CurrencyFormat},ConverterParameter=3}" />
|
||||
|
||||
<DataGridTemplateColumn Header="CurrentPrice" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding CurrentPrice, Converter={StaticResource CurrencyFormat},ConverterParameter=3}" Foreground="{Binding CurrentPriceColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
|
||||
|
||||
<DataGridTemplateColumn Header="Price Low" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=CurrentPriceLowAsString,StringFormat='{}{0:S}'}" Foreground="{Binding CurrentPriceLowAsStringColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Initial Stop" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding InitialStopLimit, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" Foreground="{Binding InitialStopLimitColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Trailing Stop">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding TrailingStopLimit, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" Foreground="{Binding TrailingStopLimitColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="R/Share" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding R, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" Foreground="{Binding RColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Risk" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding TotalRiskExposure, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" Foreground="{Binding TotalRiskExposureColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="RMultiple" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding RMultipleAsString,StringFormat='{}{0:S}'}" Foreground="{Binding RMultipleColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="E-Ratio" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=EdgeRatioAsString,StringFormat='{}{0:S}'}" Foreground="{Binding EdgeRatioAsStringColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Last Stop Adj." Binding="{Binding LastStopAdjustment, Converter={StaticResource DateFormat}}" />
|
||||
|
||||
<DataGridTextColumn Header="Days" Binding="{Binding DaysSinceLastStopAdjustment, Converter={StaticResource IntFormat},ConverterParameter=0}}" />
|
||||
|
||||
<DataGridTextColumn Header="Updated" Binding="{Binding LastUpdated, StringFormat='{}{0:MM/dd/yyyy HH:mm:ss}'}" />
|
||||
|
||||
</DataGrid.Columns>
|
||||
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
<GridSplitter Background="LightBlue" ResizeDirection="Rows" Grid.Column="2" Grid.ColumnSpan="5" Grid.Row="3" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
|
||||
<GridSplitter Grid.Row="1" Grid.Column="1" ResizeDirection="Columns" Grid.RowSpan="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="LightBlue" Width="3"/>
|
||||
<GridSplitter Grid.Row="1" Grid.Column="3" ResizeDirection="Columns" Grid.RowSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="LightBlue" Width="3"/>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
13
PortfolioManager/Views/CMTrendView.axaml.cs
Normal file
13
PortfolioManager/Views/CMTrendView.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace PortfolioManager.Views;
|
||||
|
||||
public partial class CMTrendView : UserControl
|
||||
{
|
||||
public CMTrendView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
327
PortfolioManager/Views/GainLossView.axaml
Normal file
327
PortfolioManager/Views/GainLossView.axaml
Normal file
@@ -0,0 +1,327 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:PortfolioManager.ViewModels"
|
||||
xmlns:vw="using:PortfolioManager.Views"
|
||||
xmlns:md="using:PortfolioManager.Models"
|
||||
xmlns:local="using:PortfolioManager.UIUtils"
|
||||
xmlns:li="using:LoadingIndicators.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:mxc="https://schemas.eremexcontrols.net/avalonia/charts"
|
||||
x:DataType="vm:GainLossViewModel"
|
||||
x:Class="PortfolioManager.Views.GainLossView"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<local:CurrencyValueConverter x:Key="CurrencyFormat"/>
|
||||
<local:DoubleValueConverter x:Key="DoubleFormat"/>
|
||||
<local:IntValueConverter x:Key="IntFormat"/>
|
||||
<local:DateValueConverter x:Key="DateFormat"/>
|
||||
<local:RMultipleValueConverter x:Key="RMultipleFormat"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Background="LightGray">
|
||||
<li:LoadingIndicator ZIndex="1" IsActive="{Binding IsBusy}" Mode="Arcs" SpeedRatio="1.2" Width="200" Height="200"/>
|
||||
<DockPanel>
|
||||
<Grid Margin="4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="10*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Vertical">
|
||||
<Label Content="Account" HorizontalAlignment="Center" ></Label>
|
||||
<ComboBox ItemsSource="{Binding Path=Accounts, Mode=OneTime}" SelectedItem="{Binding Path=SelectedAccount, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" >
|
||||
<!-- <ComboBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
</ComboBox.ItemsPanel> -->
|
||||
</ComboBox>
|
||||
<Label Content="Symbols" HorizontalAlignment="Center" ></Label>
|
||||
<ComboBox ItemsSource="{Binding Path=Symbols, Mode=OneTime}" SelectedItem="{Binding Path=SelectedSymbol, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}">
|
||||
<!-- <ComboBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
</ComboBox.ItemsPanel> -->
|
||||
</ComboBox>
|
||||
<Label Content="Total Gain/Loss" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=TotalGainLoss, Mode=OneWay}" />
|
||||
<CheckBox Content="Include Dividends" IsChecked="{Binding Mode=TwoWay,Path=CheckBoxIncludeDividends}" HorizontalAlignment="Stretch" />
|
||||
|
||||
<Label Content="Summary" HorizontalAlignment="Center" ></Label>
|
||||
<Border Margin="2,1,2,1" Background="LightGray" BorderBrush="Black" BorderThickness="1" CornerRadius="5" Padding="5">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Label Content="Selected Date" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=SummaryDate, Mode=OneWay}" />
|
||||
<Label Content="$ Change" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=SummaryChange, Mode=OneWay}" />
|
||||
<Label Content="Current G/L" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=SummaryGainLoss, Mode=OneWay}" />
|
||||
<Label Content="Positions" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=SummaryPositions, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Button Content="{Binding Path=PercentButtonText}" HorizontalAlignment="Stretch" Command="{Binding Path=ToggleReturnOrPercentCommand}"></Button>
|
||||
<Button Content="{Binding Path=ActiveTotalButtonText}" HorizontalAlignment="Stretch" Command="{Binding Path=ToggleActiveOrTotalCommand}"></Button>
|
||||
<Button Content="Refresh" HorizontalAlignment="Stretch" Command="{Binding Path=PerformRefreshCommand}"/>
|
||||
<Button Content="Reset" HorizontalAlignment="Stretch" Command="{Binding Path=PerformResetCommand}"/>
|
||||
</StackPanel>
|
||||
<Grid Grid.Column="1" Grid.RowSpan="2" >
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height=".7*" />
|
||||
<RowDefinition Height="2" />
|
||||
<RowDefinition Height=".5*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<GridSplitter Background="LightBlue" ResizeDirection="Rows" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Width="NaN" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
|
||||
|
||||
<Border CornerRadius="6" BorderBrush="WhiteSmoke" Background="LightGray" BorderThickness="2" Padding="8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="24" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" FontSize="16" Text="{Binding Path=GraphTitle}" HorizontalAlignment="Center"></TextBlock>
|
||||
<mxc:CartesianChart Grid.Row="1">
|
||||
<mxc:CartesianChart.Series>
|
||||
<mxc:CartesianSeries Name="GainLossSeries" DataAdapter="{Binding Data.DataAdapter}" >
|
||||
<mxc:CartesianLineSeriesView Color="MidnightBlue" MarkerSize="4" ShowMarkers="{Binding Path=ShowMarkers, Mode=TwoWay}" Thickness="2"/>
|
||||
</mxc:CartesianSeries>
|
||||
</mxc:CartesianChart.Series>
|
||||
|
||||
<mxc:CartesianChart.Series>
|
||||
<mxc:CartesianSeries Name="MA21" DataAdapter="{Binding MA21.DataAdapter}" >
|
||||
<mxc:CartesianLineSeriesView Color="Green" MarkerSize="4" ShowMarkers="{Binding Path=ShowMarkers, Mode=TwoWay}" Thickness="2"/>
|
||||
</mxc:CartesianSeries>
|
||||
</mxc:CartesianChart.Series>
|
||||
|
||||
<mxc:CartesianChart.Series>
|
||||
<mxc:CartesianSeries Name="MA55" DataAdapter="{Binding MA55.DataAdapter}" >
|
||||
<mxc:CartesianLineSeriesView Color="LimeGreen" MarkerSize="4" ShowMarkers="{Binding Path=ShowMarkers, Mode=TwoWay}" Thickness="2"/>
|
||||
</mxc:CartesianSeries>
|
||||
</mxc:CartesianChart.Series>
|
||||
|
||||
<mxc:CartesianChart.Series>
|
||||
<mxc:CartesianSeries Name="LeastSquares" DataAdapter="{Binding LeastSquares.DataAdapter}" >
|
||||
<mxc:CartesianLineSeriesView Color="Orange" MarkerSize="4" ShowMarkers="{Binding Path=ShowMarkers, Mode=TwoWay}" Thickness="2"/>
|
||||
</mxc:CartesianSeries>
|
||||
</mxc:CartesianChart.Series>
|
||||
|
||||
</mxc:CartesianChart>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="2" Grid.Column="1" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="7*" />
|
||||
<ColumnDefinition Width="3" />
|
||||
<ColumnDefinition Width="7*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Grid GainLossSummary (Left) Goes Here Column 0-->
|
||||
<Grid Grid.Row="0" Grid.Column="0" Background="WhiteSmoke">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<DataGrid IsEnabled="true" Focusable="true" Margin="20" Background="WhiteSmoke"
|
||||
ItemsSource="{Binding Path=GainLossCompoundModelCollection,Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedGainLossCompoundItem, Mode=TwoWay}"
|
||||
IsReadOnly="True"
|
||||
CanUserReorderColumns="True"
|
||||
CanUserResizeColumns="True"
|
||||
CanUserSortColumns="False"
|
||||
GridLinesVisibility="All"
|
||||
BorderThickness="1" BorderBrush="Gray">
|
||||
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow:selected">
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Foreground" Value="Black" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="Opacity" Value=".50" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
|
||||
<DataGridTemplateColumn Header="Date" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Date,StringFormat='{}{0:MM/dd/yyyy}'}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Exposure" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ActiveExposure, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<!-- <DataGridTemplateColumn Header="Dividends" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=TotalDividendsPaid,StringFormat='{}{0:C}'}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn> -->
|
||||
|
||||
<DataGridTemplateColumn Header="Active G/L" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ActiveGainLoss, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Active G/L(%)" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=ActiveGainLossPercent,StringFormat='{}{0:N3}'}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Total G/L" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=TotalGainLoss, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Total G/L(%)">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=TotalGainLossPercent,StringFormat='{}{0:N3}'}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
<!-- Grid GainLossSummary (Right) Goes Here Column 2-->
|
||||
<Grid Grid.Row="0" Grid.Column="2" Background="WhiteSmoke">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<DataGrid IsEnabled="true" Focusable="true" Margin="20" Background="WhiteSmoke"
|
||||
ItemsSource="{Binding Path=GainLossSummaryItemCollection,Mode=OneWay}"
|
||||
SelectedItem="{Binding SelectedGainLossSummaryItem, Mode=TwoWay}"
|
||||
IsReadOnly="True"
|
||||
CanUserReorderColumns="True"
|
||||
CanUserResizeColumns="True"
|
||||
CanUserSortColumns="False"
|
||||
GridLinesVisibility="All"
|
||||
BorderThickness="1" BorderBrush="Gray">
|
||||
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow:selected">
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Foreground" Value="Black" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="Opacity" Value=".50" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
|
||||
<DataGridTemplateColumn Header="Date" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Date,StringFormat='{}{0:MM/dd/yyyy}'}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Symbol">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate >
|
||||
<TextBlock Text="{Binding Symbol}">
|
||||
<ToolTip.Tip>
|
||||
<Border Margin="2,1,2,1" Background="AntiqueWhite" BorderBrush="Black" BorderThickness="1" CornerRadius="15" Padding="5">
|
||||
<TextBlock FontWeight="Normal" FontSize="12" Text="{Binding $parent[vw:GainLossView].((vm:GainLossViewModel)DataContext).Parity}, Mode=OneWay"/>
|
||||
</Border>
|
||||
</ToolTip.Tip>
|
||||
<ToolTip.ShowDelay>125</ToolTip.ShowDelay>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Current G/L" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding CurrentGainLoss, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Previous G/L" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding PreviousGainLoss, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="$ Change" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=Change, Converter={StaticResource CurrencyFormat},ConverterParameter=2}"/>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="$ Change(%)" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=ChangePercent,StringFormat='{}{0:N3}'}">
|
||||
<ToolTip.Tip>
|
||||
<Border Margin="2,1,2,1" Background="AntiqueWhite" BorderBrush="Black" BorderThickness="1" CornerRadius="15" Padding="5">
|
||||
<TextBlock FontWeight="Normal" FontSize="12" Text="{Binding $parent[vw:GainLossView].((vm:GainLossViewModel)DataContext).DollarChangePercent}, Mode=OneWay"/>
|
||||
</Border>
|
||||
</ToolTip.Tip>
|
||||
<ToolTip.ShowDelay>125</ToolTip.ShowDelay>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
|
||||
<GridSplitter Background="LightBlue" ResizeDirection="Columns" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
13
PortfolioManager/Views/GainLossView.axaml.cs
Normal file
13
PortfolioManager/Views/GainLossView.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace PortfolioManager.Views;
|
||||
|
||||
public partial class GainLossView : UserControl
|
||||
{
|
||||
public GainLossView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
282
PortfolioManager/Views/MGSHMomentumView.axaml
Normal file
282
PortfolioManager/Views/MGSHMomentumView.axaml
Normal file
@@ -0,0 +1,282 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:PortfolioManager.ViewModels"
|
||||
xmlns:vw="using:PortfolioManager.Views"
|
||||
xmlns:md="using:PortfolioManager.Models"
|
||||
xmlns:local="using:PortfolioManager.UIUtils"
|
||||
xmlns:li="using:LoadingIndicators.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:mxc="https://schemas.eremexcontrols.net/avalonia/charts"
|
||||
x:DataType="vm:MGSHMomentumViewModel"
|
||||
x:Class="PortfolioManager.Views.MGSHMomentumView"
|
||||
>
|
||||
|
||||
<UserControl.Resources>
|
||||
<local:CurrencyValueConverter x:Key="CurrencyFormat"/>
|
||||
<local:DoubleValueConverter x:Key="DoubleFormat"/>
|
||||
<local:IntValueConverter x:Key="IntFormat"/>
|
||||
<local:DateValueConverter x:Key="DateFormat"/>
|
||||
<local:RMultipleValueConverter x:Key="RMultipleFormat"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="DataGridRow.isfocused">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
|
||||
<Grid Background="LightGray">
|
||||
<li:LoadingIndicator ZIndex="1" IsActive="{Binding IsBusy}" Mode="Arcs" SpeedRatio="1.2" Width="200" Height="200"/>
|
||||
<DockPanel>
|
||||
<Grid DockPanel.Dock="Left" Margin="0,2,4,2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="3" />
|
||||
<ColumnDefinition Width="11*" />
|
||||
<ColumnDefinition Width="3" />
|
||||
<ColumnDefinition Width="11*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="35" />
|
||||
<RowDefinition Height="3" />
|
||||
<RowDefinition Height="1*" />
|
||||
<RowDefinition Height="3" />
|
||||
<RowDefinition Height="2*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="5" FontSize="16" Text="{Binding Path=Title, Mode=OneWay, DataType={x:Type vm:MGSHMomentumViewModel}}" HorizontalAlignment="Center"></TextBlock>
|
||||
|
||||
<StackPanel Orientation="Vertical" Grid.Row="2" Grid.RowSpan="3" Grid.Column="0" Margin="0,5,0,-5">
|
||||
<Label Content="Last Trade Date" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox MinWidth="80" Height="24" IsReadOnly="true" Text="{Binding Path=LastTradeDate, Mode=OneWay, DataType={x:Type vm:MGSHMomentumViewModel}}" />
|
||||
<Label Content="Next Trade Date" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" IsReadOnly="true" Text="{Binding Path=NextTradeDate, Mode=OneWay, DataType={x:Type vm:MGSHMomentumViewModel}}" />
|
||||
|
||||
<Label Content="Parameters" HorizontalAlignment="Center" ></Label>
|
||||
<ComboBox ItemsSource="{Binding Path=Parameters, DataType={x:Type vm:MGSHMomentumViewModel}, Mode=OneWay}" SelectedItem="{Binding Path=SelectedParameter, DataType={x:Type vm:MGSHMomentumViewModel}}"></ComboBox>
|
||||
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=ParameterValue, Mode=OneWay, DataType={x:Type vm:MGSHMomentumViewModel}}" />
|
||||
<Label Content="Tradeable Cash" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=CashBalance, Mode=OneWay, DataType={x:Type vm:MGSHMomentumViewModel}}" />
|
||||
|
||||
<Label Content="Non-Tradeable Cash" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=NonTradeableCash, Mode=OneWay, DataType={x:Type vm:MGSHMomentumViewModel}}" />
|
||||
|
||||
<Label Content="Hedge Cash" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=HedgeCash, Mode=OneWay, DataType={x:Type vm:MGSHMomentumViewModel}}" />
|
||||
|
||||
<Button Content="Find Candidates" HorizontalAlignment="Stretch" Command="{Binding Path=RunCommand, DataType={x:Type vm:MGSHMomentumViewModel}}"></Button>
|
||||
|
||||
<Button Content="Load Trade File" HorizontalAlignment="Stretch" Command="{Binding Path=LoadFileCommand}"></Button>
|
||||
|
||||
<Button Content="Reload" HorizontalAlignment="Stretch" Command="{Binding Path=ReloadCommand}" IsEnabled="{Binding Path=ReloadEnabled}"></Button>
|
||||
|
||||
<Button Content="{Binding Path=PercentButtonText}" HorizontalAlignment="Stretch" Command="{Binding Path=ToggleReturnOrPercentCommand}"></Button>
|
||||
|
||||
<Label Content="Expectancy" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Background="WhiteSmoke" Focusable="false" Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true"
|
||||
Text="{Binding Path=ModelExpectation, Mode=OneWay}" Foreground="{Binding Path=ExpectationColor}"
|
||||
ToolTip.Tip="{Binding $parent[vw:MGSHMomentumView].((vm:MGSHMomentumViewModel)DataContext).ExpectationDescription}, Mode=OneWay"/>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<DockPanel x:Name="DockPanelA" Grid.Row="2" Grid.Column="2" Width="NaN" Height="NaN" >
|
||||
<!-- RadGrid for Candidates -->
|
||||
|
||||
</DockPanel>
|
||||
<DockPanel x:Name="DockPanelB" Grid.Row="2" Grid.Column="4" Width="NaN" Height="NaN" LastChildFill="False">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="24" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" FontSize="16" Text="{Binding Path=GraphTitle}" HorizontalAlignment="Center"></TextBlock>
|
||||
<mxc:CartesianChart Grid.Row="1">
|
||||
<mxc:CartesianChart.Series>
|
||||
<mxc:CartesianSeries Name="PerformanceSeries" DataAdapter="{Binding Data.DataAdapter}" >
|
||||
<mxc:CartesianLineSeriesView Color="MidnightBlue" MarkerSize="4" ShowMarkers="{Binding Path=ShowMarkers, Mode=TwoWay}" Thickness="2"/>
|
||||
</mxc:CartesianSeries>
|
||||
</mxc:CartesianChart.Series>
|
||||
</mxc:CartesianChart>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
|
||||
<DockPanel x:Name="DockPanel2" Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" Width="NaN" Height="NaN">
|
||||
<Grid>
|
||||
<Grid x:Name="PositionsView" >
|
||||
<DataGrid IsEnabled="true" Focusable="true" Margin="20" SelectedItem="{Binding Path=SelectedPosition, Mode=TwoWay}" ItemsSource="{Binding Path=AllPositions, DataType={x:Type vm:MGSHMomentumViewModel}}"
|
||||
IsReadOnly="True"
|
||||
CanUserReorderColumns="True"
|
||||
CanUserResizeColumns="True"
|
||||
CanUserSortColumns="False"
|
||||
GridLinesVisibility="All"
|
||||
BorderThickness="1" BorderBrush="Gray">
|
||||
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu ItemsSource="{Binding PositionsMenuItems}">
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow:selected">
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Foreground" Value="Black" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="Opacity" Value=".50" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Symbol" Binding="{Binding Symbol, DataType={x:Type md:MGSHPositionModel}}"/>
|
||||
|
||||
<DataGridTextColumn Header="Slot" Binding="{Binding SlotAsString, DataType={x:Type md:MGSHPositionModel}}" />
|
||||
|
||||
<DataGridTemplateColumn Header="Purchased">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding PurchaseDate,StringFormat='{}{0:MM/dd/yyyy}', DataType={x:Type md:MGSHPositionModel}}" Foreground="{Binding PurchaseDateColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
|
||||
<DataGridTemplateColumn Header="Sold">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding SellDate, StringFormat='{}{0:MM/dd/yyyy}', DataType={x:Type md:MGSHPositionModel}}" Foreground="{Binding SellDateColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Shares" Binding="{Binding Shares, StringFormat='{}{0:N0}', DataType={x:Type md:MGSHPositionModel}}" />
|
||||
|
||||
<DataGridTemplateColumn Header="Exposure" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ActiveExposure, Converter={StaticResource CurrencyFormat},ConverterParameter=2, DataType={x:Type md:MGSHPositionModel}}" Foreground="{Binding ActiveExposureColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Market Value" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ActiveMarketValue, Converter={StaticResource CurrencyFormat},ConverterParameter=2, DataType={x:Type md:MGSHPositionModel}}" Foreground="{Binding ActiveMarketValueColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="GainLoss" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding GainLoss, Converter={StaticResource CurrencyFormat},ConverterParameter=2, DataType={x:Type md:MGSHPositionModel}}" Foreground="{Binding GainLossColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="GainLoss(%)" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding GainLossPcnt,StringFormat='{}{0:P3}', DataType={x:Type md:MGSHPositionModel}}" Foreground="{Binding GainLossPcntColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Purch.Price" Binding="{Binding PurchasePrice, Converter={StaticResource CurrencyFormat},ConverterParameter=3, DataType={x:Type md:MGSHPositionModel}}" />
|
||||
|
||||
<DataGridTemplateColumn Header="CurrentPrice" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding CurrentPrice, Converter={StaticResource CurrencyFormat},ConverterParameter=2, DataType={x:Type md:MGSHPositionModel}}" Foreground="{Binding CurrentPriceColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Days" Binding="{Binding DaysHeld, DataType={x:Type md:MGSHPositionModel}}" />
|
||||
|
||||
<DataGridTemplateColumn Header="RSI3" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding RSI3, StringFormat='{}{0:N2}', DataType={x:Type md:MGSHPositionModel},Converter={StaticResource DoubleFormat},ConverterParameter=2 }" Foreground="{Binding RSI3Color}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Updated" Binding="{Binding LastUpdated, StringFormat='{}{0:MM/dd/yyyy HH:mm:ss}', DataType={x:Type md:MGSHPositionModel}}" />
|
||||
|
||||
<DataGridTemplateColumn Header="Initial Stop" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding InitialStopLimit,StringFormat='{}{0:C}', DataType={x:Type md:MGSHPositionModel}}" Foreground="{Binding InitialStopLimitColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Trailing Stop">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding TrailingStopLimit, Converter={StaticResource CurrencyFormat},ConverterParameter=2, DataType={x:Type md:MGSHPositionModel}}" Foreground="{Binding TrailingStopLimitColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="R/Share" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding R, Converter={StaticResource CurrencyFormat},ConverterParameter=2, DataType={x:Type md:MGSHPositionModel}}" Foreground="{Binding RColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Risk" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding TotalRiskExposure, Converter={StaticResource CurrencyFormat},ConverterParameter=2, DataType={x:Type md:MGSHPositionModel}}" Foreground="{Binding TotalRiskExposureColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="RMultiple" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding RMultipleAsString,StringFormat='{}{0:S}', DataType={x:Type md:MGSHPositionModel}}" Foreground="{Binding RMultipleColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Last Stop Adj." Binding="{Binding LastStopAdjustment, Converter={StaticResource DateFormat}, DataType={x:Type md:MGSHPositionModel}}" />
|
||||
<DataGridTextColumn Header="Days" Binding="{Binding DaysSinceLastStopAdjustment, DataType={x:Type md:MGSHPositionModel}, Converter={StaticResource IntFormat},ConverterParameter=0}}" />
|
||||
<DataGridTextColumn Header="PE" Binding="{Binding PE, StringFormat='{}{0:N2}', DataType={x:Type md:MGSHPositionModel}}" />
|
||||
<DataGridTextColumn Header="Beta" Binding="{Binding Beta, StringFormat='{}{0:N2}', DataType={x:Type md:MGSHPositionModel}}" />
|
||||
<DataGridTextColumn Header="IDIndicator" Binding="{Binding IDIndicator, StringFormat='{}{0:N2}', DataType={x:Type md:MGSHPositionModel}}" />
|
||||
<DataGridTextColumn Header="Score" Binding="{Binding Score, DataType={x:Type md:MGSHPositionModel}, Converter={StaticResource DoubleFormat},ConverterParameter=2}}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
<GridSplitter Background="LightBlue" ResizeDirection="Rows" Grid.Column="2" Grid.ColumnSpan="5" Grid.Row="3" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
|
||||
<GridSplitter Grid.Row="1" Grid.Column="1" ResizeDirection="Columns" Grid.RowSpan="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="LightBlue" Width="3"/>
|
||||
<GridSplitter Grid.Row="1" Grid.Column="3" ResizeDirection="Columns" Grid.RowSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="LightBlue" Width="3"/>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
13
PortfolioManager/Views/MGSHMomentumView.axaml.cs
Normal file
13
PortfolioManager/Views/MGSHMomentumView.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace PortfolioManager.Views;
|
||||
|
||||
public partial class MGSHMomentumView : UserControl
|
||||
{
|
||||
public MGSHMomentumView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
161
PortfolioManager/Views/MainWindow.axaml
Normal file
161
PortfolioManager/Views/MainWindow.axaml
Normal file
@@ -0,0 +1,161 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:PortfolioManager.ViewModels"
|
||||
xmlns:vw="using:PortfolioManager.Views"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:HyperText.Avalonia.Controls;assembly=HyperText.Avalonia"
|
||||
xmlns:mxc="https://schemas.eremexcontrols.net/avalonia/charts"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="PortfolioManager.Views.MainWindow"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
Icon="/Assets/HighSeas.jpg"
|
||||
Title="{Binding Path=DisplayName}" >
|
||||
|
||||
<Design.DataContext>
|
||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||
<vm:MainWindowViewModel/>
|
||||
</Design.DataContext>
|
||||
|
||||
<Window.DataTemplates>
|
||||
<DataTemplate DataType="vm:MGSHMomentumViewModel">
|
||||
<vw:MGSHMomentumView />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="vm:MomentumViewModel">
|
||||
<vw:MomentumView />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="vm:CMMomentumViewModel">
|
||||
<vw:CMMomentumView />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="vm:CMTrendViewModel">
|
||||
<vw:CMTrendView />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="vm:GainLossViewModel">
|
||||
<vw:GainLossView />
|
||||
</DataTemplate>
|
||||
</Window.DataTemplates>
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="Foreground" Value="#000000"/>
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="Margin" Value="2,1,2,1"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style Selector="ComboBox">
|
||||
<Setter Property="Foreground" Value="#000000"/>
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="Margin" Value="2,1,2,1"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style Selector="Label">
|
||||
<Setter Property="Foreground" Value="#000000"/>
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="Margin" Value="2,1,2,1"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="FontWeight" Value="DemiBold"/>
|
||||
</Style>
|
||||
<Style Selector="Button">
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
</Style>
|
||||
<Style Selector="Button.hyperlink">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<TextBlock Text="{TemplateBinding Content}" Foreground="#2B2B2B" TextDecorations="Underline">
|
||||
<TextBlock.Styles>
|
||||
<Style Selector="TextBlock:pointerover">
|
||||
<Setter Property="Foreground" Value="#D0D0D0"/>
|
||||
</Style>
|
||||
</TextBlock.Styles>
|
||||
</TextBlock>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<!-- <DataGrid.Styles>
|
||||
<Style Selector="DataGridRow">
|
||||
<Setter Property="ToolTip.Tip" Value="{Binding ToolTipText}" />
|
||||
</Style>
|
||||
</DataGrid.Styles> -->
|
||||
|
||||
<Grid >
|
||||
<DockPanel>
|
||||
<DockPanel DockPanel.Dock="Top" KeyboardNavigation.TabNavigation="None">
|
||||
<Menu KeyboardNavigation.TabNavigation="Cycle" Name="mainMenu">
|
||||
<MenuItem Header="_File" >
|
||||
<MenuItem Header="E_xit" Command="{Binding Path=CloseCommand}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Edit"/>
|
||||
<MenuItem Header="_Options"/>
|
||||
<!-- <MenuItem Header="_Windows" ItemsSource="{Binding MenuItems,Mode=OneWay}"/> -->
|
||||
<MenuItem Header="_Help"/>
|
||||
</Menu>
|
||||
</DockPanel>
|
||||
<Grid Margin="4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="4" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Background="#8b8378" BorderBrush="LightGray" BorderThickness="1" CornerRadius="5" Width="185" >
|
||||
<HeaderedContentControl Content="{Binding Path=Commands}" Header="View Panel" >
|
||||
<HeaderedContentControl.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl IsTabStop="False" ItemsSource="{Binding}" Margin="6,2">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="vm:CommandViewModel">
|
||||
<Button Margin="2,6" Command="{Binding Path=Command}" Classes="hyperlink" Content="{Binding Path=DisplayName}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</HeaderedContentControl.ContentTemplate>
|
||||
</HeaderedContentControl>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Column="2" Background="#8b8378" BorderBrush="LightGray" BorderThickness="1" CornerRadius="5" >
|
||||
<HeaderedContentControl x:Name="HeaderedContentControl" Content="{Binding Path=Workspaces}" Header="Views">
|
||||
<HeaderedContentControl.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<!-- <TabControl Margin="4" ItemsSource="{Binding}" SelectedIndex="{Binding Path=SelectedIndex,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type vm:WorkspaceViewModel}}}, Mode=TwoWay"> -->
|
||||
<TabControl x:Name="TabViews" Margin="4" ItemsSource="{Binding}" SelectionChanged="tabControl_SelectionChanged">
|
||||
<TabControl.ItemTemplate>
|
||||
<DataTemplate DataType="vm:WorkspaceViewModel">
|
||||
<DockPanel>
|
||||
<TextBlock Text="{Binding Title}"></TextBlock>
|
||||
<Button
|
||||
Command="{Binding Path=CloseCommand}"
|
||||
Content="X"
|
||||
Cursor="Hand"
|
||||
DockPanel.Dock="Right"
|
||||
Focusable="False"
|
||||
FontFamily="Arial"
|
||||
FontSize="10"
|
||||
FontWeight="Bold"
|
||||
Margin="1,1,1,1"
|
||||
Padding="0"
|
||||
VerticalContentAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Width="16" Height="16">
|
||||
</Button>
|
||||
</DockPanel>
|
||||
</DataTemplate>
|
||||
</TabControl.ItemTemplate>
|
||||
</TabControl>
|
||||
</DataTemplate>
|
||||
</HeaderedContentControl.ContentTemplate>
|
||||
</HeaderedContentControl>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
33
PortfolioManager/Views/MainWindow.axaml.cs
Normal file
33
PortfolioManager/Views/MainWindow.axaml.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.ComponentModel;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Templates;
|
||||
using PortfolioManager.ViewModels;
|
||||
|
||||
namespace PortfolioManager.Views;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private TabControl tabControl = null;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void tabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (null == tabControl)
|
||||
{
|
||||
tabControl = (TabControl)sender;
|
||||
MainWindowViewModel viewModel = (MainWindowViewModel)DataContext;
|
||||
viewModel.OnIndexChangeEventHandler += OnIndexChangeEvent;
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnIndexChangeEvent(object sender, TabIndexArgs args)
|
||||
{
|
||||
if (null == tabControl) return;
|
||||
tabControl.SelectedIndex = args.Index;
|
||||
}
|
||||
}
|
||||
296
PortfolioManager/Views/MomentumView.axaml
Normal file
296
PortfolioManager/Views/MomentumView.axaml
Normal file
@@ -0,0 +1,296 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:PortfolioManager.ViewModels"
|
||||
xmlns:vw="using:PortfolioManager.Views"
|
||||
xmlns:md="using:PortfolioManager.Models"
|
||||
xmlns:local="using:PortfolioManager.UIUtils"
|
||||
xmlns:li="using:LoadingIndicators.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
xmlns:mxc="https://schemas.eremexcontrols.net/avalonia/charts"
|
||||
x:DataType="vm:MomentumViewModel"
|
||||
x:Class="PortfolioManager.Views.MomentumView"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<local:CurrencyValueConverter x:Key="CurrencyFormat"/>
|
||||
<local:DoubleValueConverter x:Key="DoubleFormat"/>
|
||||
<local:IntValueConverter x:Key="IntFormat"/>
|
||||
<local:DateValueConverter x:Key="DateFormat"/>
|
||||
<local:RMultipleValueConverter x:Key="RMultipleFormat"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Background="LightGray">
|
||||
<li:LoadingIndicator ZIndex="1" IsActive="{Binding IsBusy}" Mode="Arcs" SpeedRatio="1.2" Width="200" Height="200"/>
|
||||
<DockPanel>
|
||||
<Grid DockPanel.Dock="Left" Margin="0,2,4,2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="3" />
|
||||
<ColumnDefinition Width="11*" />
|
||||
<ColumnDefinition Width="3" />
|
||||
<ColumnDefinition Width="11*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="35" />
|
||||
<RowDefinition Height="3" />
|
||||
<RowDefinition Height="1*" />
|
||||
<RowDefinition Height="3" />
|
||||
<RowDefinition Height="2*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="5" FontSize="16" Text="{Binding Path=Title, Mode=OneWay}" HorizontalAlignment="Center"></TextBlock>
|
||||
|
||||
<StackPanel>
|
||||
<Label Content="Trade Date" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox MinWidth="80" Height="24" IsReadOnly="true" Text="{Binding Path=TradeDate, Mode=OneWay}" />
|
||||
|
||||
<Label Content="Parameters" HorizontalAlignment="Center" ></Label>
|
||||
<ComboBox ItemsSource="{Binding Path=Parameters, Mode=OneWay}" SelectedItem="{Binding Path=SelectedParameter}"></ComboBox>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=ParameterValue, Mode=OneWay}" />
|
||||
|
||||
<Label Content="Tradeable Cash" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=CashBalance, Mode=OneWay}" />
|
||||
|
||||
<Label Content="Non-Tradeable Cash" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true" Text="{Binding Path=NonTradeableCash, Mode=OneWay}" />
|
||||
|
||||
<Button Content="Load Trade File" HorizontalAlignment="Stretch" Command="{Binding Path=LoadFileCommand}"></Button>
|
||||
|
||||
<Button Content="Reload" HorizontalAlignment="Stretch" Command="{Binding Path=ReloadCommand}" IsEnabled="{Binding Path=ReloadEnabled}"></Button>
|
||||
|
||||
<Button Content="{Binding Path=PercentButtonText}" HorizontalAlignment="Stretch" Command="{Binding Path=ToggleReturnOrPercentCommand}"></Button>
|
||||
|
||||
<Label Content="Expectancy" HorizontalAlignment="Center" ></Label>
|
||||
<TextBox Background="WhiteSmoke" Focusable="false" Height="24" MinWidth="80" HorizontalAlignment="Stretch" IsReadOnly="true"
|
||||
Text="{Binding Path=ModelExpectation, Mode=OneWay}" Foreground="{Binding Path=ExpectationColor}"
|
||||
ToolTip.Tip="{Binding $parent[vw:MomentumView].((vm:MomentumViewModel)DataContext).ExpectationDescription}, Mode=OneWay"/>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<DockPanel x:Name="DockPanelA" Grid.Row="2" Grid.Column="2" Width="NaN" Height="NaN" >
|
||||
</DockPanel>
|
||||
|
||||
<DockPanel x:Name="DockPanelB" Grid.Row="2" Grid.Column="4" Width="NaN" Height="NaN" LastChildFill="False">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="24" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" FontSize="16" Text="{Binding Path=GraphTitle}" HorizontalAlignment="Center"></TextBlock>
|
||||
<mxc:CartesianChart Grid.Row="1">
|
||||
<mxc:CartesianChart.Series>
|
||||
<mxc:CartesianSeries Name="PerformanceSeries" DataAdapter="{Binding Data.DataAdapter}" >
|
||||
<mxc:CartesianLineSeriesView Color="MidnightBlue" MarkerSize="4" ShowMarkers="{Binding Path=ShowMarkers, Mode=TwoWay}" Thickness="2"/>
|
||||
</mxc:CartesianSeries>
|
||||
</mxc:CartesianChart.Series>
|
||||
</mxc:CartesianChart>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
|
||||
<DockPanel x:Name="DockPanel2" Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" Width="NaN" Height="NaN">
|
||||
<Grid>
|
||||
<Grid x:Name="PositionsView" >
|
||||
<DataGrid IsEnabled="true" Focusable="true" Margin="20" SelectedItem="{Binding Path=SelectedPosition, Mode=TwoWay}" ItemsSource="{Binding Path=AllPositions}"
|
||||
IsReadOnly="True"
|
||||
CanUserReorderColumns="True"
|
||||
CanUserResizeColumns="True"
|
||||
CanUserSortColumns="False"
|
||||
GridLinesVisibility="All"
|
||||
BorderThickness="1" BorderBrush="Gray">
|
||||
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu ItemsSource="{Binding PositionsMenuItems}">
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
|
||||
<DataGrid.Styles>
|
||||
<Style Selector="DataGridRow:selected">
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
<Setter Property="Foreground" Value="Black" />
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="Opacity" Value=".50" />
|
||||
</Style>
|
||||
</DataGrid.Styles>
|
||||
|
||||
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Symbol" Binding="{Binding Symbol}"/>
|
||||
|
||||
<DataGridTextColumn Header="Slot" Binding="{Binding SlotAsString}" />
|
||||
|
||||
<DataGridTemplateColumn Header="Purchased">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding PurchaseDate,StringFormat='{}{0:MM/dd/yyyy}'}" Foreground="{Binding PurchaseDateColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Sold">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding SellDate, StringFormat='{}{0:MM/dd/yyyy}'}" Foreground="{Binding SellDateColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Shares" Binding="{Binding Shares, StringFormat='{}{0:N0}'}" />
|
||||
|
||||
<DataGridTemplateColumn Header="Exposure" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ActiveExposure, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" Foreground="{Binding ActiveExposureColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Market Value" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding ActiveMarketValue, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" Foreground="{Binding ActiveMarketValueColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="GainLoss" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding GainLoss, Converter={StaticResource CurrencyFormat},ConverterParameter=2}" Foreground="{Binding GainLossColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="GainLoss(%)" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding GainLossPcnt,StringFormat='{}{0:P3}'}" Foreground="{Binding GainLossPcntColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Days" Binding="{Binding DaysHeld}" />
|
||||
|
||||
<DataGridTextColumn Header="Purch.Price" Binding="{Binding PurchasePrice, StringFormat='{}{0:C3}'}" />
|
||||
|
||||
<DataGridTemplateColumn Header="CurrentPrice" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding CurrentPrice, Converter={StaticResource CurrencyFormat},ConverterParameter=3}" Foreground="{Binding CurrentPriceColor}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="RSI3" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding RSI3, StringFormat='{}{0:N2}',Converter={StaticResource DoubleFormat},ConverterParameter=2}" Foreground="{Binding RSI3Color}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTextColumn Header="Updated" Binding="{Binding LastUpdated, StringFormat='{}{0:MM/dd/yyyy HH:mm:ss}'}" />
|
||||
|
||||
<DataGridTemplateColumn Header="PE" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=PE,StringFormat='{}{0:N2}'}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Beta" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=Beta,StringFormat='{}{0:N2}'}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="SharpeRatio" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=SharpeRatio,Converter={StaticResource DoubleFormat}}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="IDIndicator" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=IDIndicator,StringFormat='{}{0:N2}'}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Score" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=Score,Converter={StaticResource DoubleFormat},ConverterParameter=2}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="MaxDrawdown" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=MaxDrawdown,StringFormat='{}{0:P2}'}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="MaxUpside" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=MaxUpside,StringFormat='{}{0:P2}'}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="ZacksRank" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=ZacksRank}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
<DataGridTemplateColumn Header="Volume" >
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Path=Volume,StringFormat='{}{0:N0}'}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellEditingTemplate/>
|
||||
</DataGridTemplateColumn>
|
||||
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</DockPanel>
|
||||
|
||||
<GridSplitter Background="LightBlue" ResizeDirection="Rows" Grid.Column="2" Grid.ColumnSpan="5" Grid.Row="3" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0"/>
|
||||
<GridSplitter Grid.Row="1" Grid.Column="1" ResizeDirection="Columns" Grid.RowSpan="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="LightBlue" Width="3"/>
|
||||
<GridSplitter Grid.Row="1" Grid.Column="3" ResizeDirection="Columns" Grid.RowSpan="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="LightBlue" Width="3"/>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
13
PortfolioManager/Views/MomentumView.axaml.cs
Normal file
13
PortfolioManager/Views/MomentumView.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace PortfolioManager.Views;
|
||||
|
||||
public partial class MomentumView : UserControl
|
||||
{
|
||||
public MomentumView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
18
PortfolioManager/app.manifest
Normal file
18
PortfolioManager/app.manifest
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<!-- This manifest is used on Windows only.
|
||||
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||
<assemblyIdentity version="1.0.0.0" name="PortfolioManager.Desktop"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- A list of the Windows versions that this application has been tested on
|
||||
and is designed to work with. Uncomment the appropriate elements
|
||||
and Windows will automatically select the most compatible environment. -->
|
||||
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
17
PortfolioManager/appsettings.json
Normal file
17
PortfolioManager/appsettings.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"market_data" : "Database=market_data;Datasource=10.0.0.199;Username=guest;Password=guest",
|
||||
"portfolio_data" : "Database=portfolio_data;Datasource=10.0.0.199;Username=guest;Password=guest",
|
||||
"user_data" : "Database=user_data;Datasource=10.0.0.199;Username=guest;Password=guest",
|
||||
"sms_smtpaddress" : "smtp.gmail.com",
|
||||
"sms_smsusername" : "skessler1964@gmail.com",
|
||||
"sms_smspassword" : "xjfo isnf gmyi zovr",
|
||||
"sms_smsrecipients" : "skessler1964@gmail.com",
|
||||
"proxy_address" : "http://euporie:8182",
|
||||
"proxy_GetLatestPriceYahoo" : "false",
|
||||
"proxy_GetLatestPriceBigCharts" : "false",
|
||||
"proxy_GetETFHoldings" : "false",
|
||||
"proxy_GetDailyPrices" : "false",
|
||||
"proxy_GetFundamentalEx" : "false",
|
||||
"proxy_GetDividendHistory" : "false",
|
||||
"proxy_GetAnalystPriceTargetMarketBeat" : "false"
|
||||
}
|
||||
BIN
PortfolioManager/libSkiaSharp.so
Normal file
BIN
PortfolioManager/libSkiaSharp.so
Normal file
Binary file not shown.
40034
PortfolioManager/portfolio_manager.log
Normal file
40034
PortfolioManager/portfolio_manager.log
Normal file
File diff suppressed because it is too large
Load Diff
4
PortfolioManager/saveparams.config
Normal file
4
PortfolioManager/saveparams.config
Normal file
@@ -0,0 +1,4 @@
|
||||
Type,PortfolioManager.ViewModels.MGSHMomentumViewModel,PathFileName,C:\boneyard\marketdata\Sessions\MGSH20250331.TXT
|
||||
Type,PortfolioManager.ViewModels.CMTrendViewModel,PathFileName,C:\boneyard\marketdata\Sessions\CMT20200817.TXT
|
||||
Type,PortfolioManager.ViewModels.MomentumViewModel,PathFileName,C:\boneyard\marketdata\Sessions\MG20180131.TXT
|
||||
Type,PortfolioManager.ViewModels.CMMomentumViewModel,PathFileName,C:\boneyard\marketdata\Sessions\CM20191031.TXT
|
||||
Reference in New Issue
Block a user